Windows Phone select and upload image to a website over HTTP POST

You have often seen photo sharing websites where you can attach images and after clicking the submit button the local images will be uploaded to the server. It is implemented by using HTML forms which send HTTP requests automatically. But if your application is not a web application written in HTML, you should write HTTP requests by yourself. You should do that in Windows Phone too.

So the full sequence consists of 2 actions: 1 – select an image, 2 – upload it to the server.
ChoosePhotoPage

At first, you should select an image from the gallery.
The code is simple, just use the built-in PhotoChooserTask class:

public void AttachFile()
{
    var chooseTask = new PhotoChooserTask();
    chooseTask.ShowCamera = true;
    chooseTask.Completed += (s, e) => this.OnChoosePhotoCompleted(e);
    chooseTask.Show();
}

private void OnChoosePhotoCompleted(PhotoResult result)
{
    if (result.TaskResult == TaskResult.OK)
    {
        this.HasPhoto = true;
        this.PhotoPath = new Uri(result.OriginalFileName);
        this.PhotoName = Path.GetFileName(result.OriginalFileName);

        using (var fileBytes = new MemoryStream())
        {
            result.ChosenPhoto.CopyTo(fileBytes);
            this._photoBytes = fileBytes.GetBuffer();
        }
    }
}

Then you should use the HttpWebRequest class to post the selected image to the server. This class alone is not enough, so you should write lots of extra code to make it work. I have implemented a special class for this purpose, the code is based on this answer on stackoverflow.com, you can just copy it to your project.

public class HttpPostTask
{
    private readonly string _boundary = "----------" + DateTime.Now.Ticks.ToString();

    private bool _isCancelled;
    private bool _isExecuted;

    public HttpPostTask(string url, Dictionary<string, object> parameters, Action<string> onCompleted)
    {
        this.Url = url;
        this.Parameters = parameters;
        this.OnCompleted = onCompleted;
    }

    public string Url { get; set; }

    public Dictionary<string, object> Parameters { get; set; }

    public Action<string> OnCompleted { get; private set; }

    public Action<string> OnError { get; set; }

    public void Execute()
    {
        if (this._isExecuted)
        {
            throw new NotSupportedException();
        }

        this._isExecuted = true;

        HttpWebRequest httpWebRequest = WebRequest.CreateHttp(this.Url);
        httpWebRequest.Method = "POST";
        httpWebRequest.ContentType = string.Format("multipart/form-data; boundary={0}", this._boundary);

        httpWebRequest.BeginGetRequestStream(new AsyncCallback(this.GetRequestStreamCallback), httpWebRequest);
    }
    
    public void Cancel()
    {
        this._isCancelled = true;
    }

    protected void InvokeOnErrorHandler(string message)
    {
        if (this.OnError != null)
        {
            this.InvokeInUiThread(() => this.OnError(message));
        }
    }

    protected void InvokeInUiThread(Action action)
    {
        if (!this._isCancelled)
        {
            Deployment.Current.Dispatcher.BeginInvoke(action);
        }
    }


    private void GetRequestStreamCallback(IAsyncResult asynchronousResult)
    {
        HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;
        Stream postStream = request.EndGetRequestStream(asynchronousResult);

        this.WriteMultipartObject(postStream, this.Parameters);
        postStream.Close();

        request.BeginGetResponse(new AsyncCallback(this.GetResponseCallback), request);
    }

    private void GetResponseCallback(IAsyncResult asynchronousResult)
    {
        HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;

        // get the response
        HttpWebResponse response;
        try
        {
            response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
        }
        catch (Exception e)
        {
            if (e.InnerException != null && e.InnerException.Message.StartsWith("[net_WebHeaderInvalidControlChars]"))
            {
                // not an exception, everything is ok
                this.InvokeInUiThread(() => this.OnCompleted(null));
            }
            else
            {
                this.InvokeOnErrorHandler("Unable to post data.");
            }

            return;
        }

        if (response.StatusCode != HttpStatusCode.OK)
        {
            this.InvokeOnErrorHandler((int)response.StatusCode + " " + response.StatusDescription);
            return;
        }

        // response stream
        using (Stream stream = response.GetResponseStream())
        {
            using (StreamReader reader = new StreamReader(stream))
            {
                string str = reader.ReadToEnd();
                this.InvokeInUiThread(() => this.OnCompleted(str));
            }
        }
    }

    private void WriteMultipartObject(Stream stream, Dictionary<string, object> data)
    {
        StreamWriter writer = new StreamWriter(stream);
        if (data != null)
        {
            foreach (var entry in data)
            {
                this.WriteEntry(writer, entry.Key, entry.Value);
            }
        }

        writer.Write("--");
        writer.Write(this._boundary);
        writer.WriteLine("--");
        writer.Flush();
    }

    private void WriteEntry(StreamWriter writer, string key, object value)
    {
        if (value != null)
        {
            writer.Write("--");
            writer.WriteLine(this._boundary);
            if (value is byte[])
            {
                byte[] ba = value as byte[];

                writer.WriteLine(@"Content-Disposition: form-data; name=""{0}""; filename=""{1}""", key, "image.png");
                writer.WriteLine(@"Content-Type: application/octet-stream");
                writer.WriteLine(@"Content-Length: " + ba.Length);
                writer.WriteLine();
                writer.Flush();
                Stream output = writer.BaseStream;

                output.Write(ba, 0, ba.Length);
                output.Flush();
                writer.WriteLine();
            }
            else
            {
                writer.WriteLine(@"Content-Disposition: form-data; name=""{0}""", key);
                writer.WriteLine();
                writer.WriteLine(value.ToString());
            }
        }
    }
}

The usage of this class is simple. I use the http://posttestserver.com website to which I post images, but you can use a more appropriate site like Google Picasa or Imgur.

string url = "http://posttestserver.com/post.php?dir=wp7posttest";
var parameters = new Dictionary<string, object>();
parameters.Add("name", this.PhotoName);
parameters.Add("photo", this._photoBytes);

this._currentPostTask = new HttpPostTask(url, parameters, this.OnPostCompleted);
this._currentPostTask.OnError = this.OnPostError;

this._currentPostTask.Execute();

UploadedPhotoPage

The website http://posttestserver.com returns a string with a link to the posted data, other website will return different responses.

The working sample application which I used in this post you can download here: HttpPostSample.zip

Advertisements

7 Responses to Windows Phone select and upload image to a website over HTTP POST

  1. Son says:

    Hello this is kind of of off topic but I was wanting to know if blogs use WYSIWYG editors or if you have to manually code with HTML. I’m starting a blog soon but have no coding knowledge so I wanted to get advice from someone with experience. Any help would be enormously appreciated!

  2. suhail says:

    what are the Naming conventions for choose photo and upload button i e UI design

  3. Please let me know if you’re looking for a article author for your blog.
    You have some really great articles and I feel I would be a good asset.
    If you ever want to take some of the load off, I’d absolutely love to write some content for
    your blog in exchange for a link back to mine. Please shoot
    me an e-mail if interested. Many thanks!

  4. Mano says:

    Amazing article, Suggest me how to POST log-in details like username,first name,last name, email to web-service. I followed this article but still i cant understand it.

  5. very helpful article. could you share php side code to save the image details to database and save image on server.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: