Handling images in WebApi

I am digging into WebApi (v1) and one of the issues I was facing was images – specifically how to handle images.  I spent a fair amount of time researching the possibilities over the last couple of days and this is what I think I think.

1) You can include your image in your JSON payload like this:

  1. public class Person
  2. {
  3.     public Int32 PersonId { get; set; }
  4.     public String FirstName { get; set; }
  5.     public byte[] Image { get; set; }
  6. }

 

or you can include the imageUri in your JSON payload like this:

  1. public class Person
  2. {
  3.     public Int32 PersonId { get; set; }
  4.     public String FirstName { get; set; }
  5.     public String ImageUri { get; set; }
  6. }

 

The advantage of the former is that you can make 1 POST or GET call and get all of the data.  The disadvantages are that the image gets serialized into a large size and your service can get bogged down processing images when the rest of the payload is very small.

The advantage of the later is that you can separate the image into its own location – in fact not even use a Controller, and the processing is snappy-pappy.  The downside is that you need to make 2 GETs and POSTs for any given person (in this example).  For the POST, there is the additional factor that you have to wait for the 1st post to complete so you can get some kind of association key returned so that both POSTs can be associated together  I was talking to fellow TRINUG member Ian Hoppes about problem and he came up with a great idea – have the client generate a GUID and then push both POSTs at the same time.

In any event, if you separate the payload into two POSTS, you need a way of handling the image alone.  In WebApi V1, there seems to be 3 different ways you can do this:

  • A Controller
  • A Handler
  • A Formatter

I wasn’t sure what was the <right> answer so I posted this question to stack overflow – and no one answered it (at the time of this writing).  Left to my own devices, I decided to use an image controller so that the API has consistent implementation.

I newed up an Image Controller and added 3 methods: GET, POST, and DELETE.  The GET was pretty straight foreward (I used a Int32 on the parameter because I haven’t implemented the GUID yet):

  1. public HttpResponseMessage Get(int id)
  2. {
  3.     var result = new HttpResponseMessage(HttpStatusCode.OK);
  4.     String filePath = HostingEnvironment.MapPath("~/Images/HT.jpg");
  5.     FileStream fileStream = new FileStream(filePath, FileMode.Open);
  6.     Image image = Image.FromStream(fileStream);
  7.     MemoryStream memoryStream = new MemoryStream();
  8.     image.Save(memoryStream, ImageFormat.Jpeg);
  9.     result.Content = new ByteArrayContent(memoryStream.ToArray());
  10.     result.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
  11.  
  12.     return result;
  13. }

 

The DELETE was event easier:

 

  1. public void Delete(int id)
  2. {
  3.     String filePath = HostingEnvironment.MapPath("~/Images/HT.jpg");
  4.     File.Delete(filePath);
  5. }

 

 

The POST, not so easy.  My first attempt was to read the steam and push it into an Image:

  1. public void Post()
  2. {
  3.     var result = new HttpResponseMessage(HttpStatusCode.OK);
  4.     if (Request.Content.IsMimeMultipartContent())
  5.     {
  6.         StreamContent content = (StreamContent)Request.Content;
  7.         Task<Stream> task = content.ReadAsStreamAsync();
  8.         Stream readOnlyStream = task.Result;
  9.         Byte[] buffer = new Byte[readOnlyStream.Length];
  10.         readOnlyStream.Read(buffer, 0, buffer.Length);
  11.         MemoryStream memoryStream = new MemoryStream(buffer);
  12.         Image image = Image.FromStream(memoryStream);
  13.     }
  14.     else
  15.     {
  16.         throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
  17.     }
  18. }

 

No dice, because the stream includes things other than the image binrary.  My second attempt was to use the MutlipartFormDataStreamProvider (which is the most popular way apparently) like so:

  1. public Task<HttpResponseMessage> Post(int id)
  2. {
  3.     if (!Request.Content.IsMimeMultipartContent())
  4.     {
  5.         throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
  6.     }
  7.  
  8.     string root = HttpContext.Current.Server.MapPath("~/Images");
  9.     var provider = new MultipartFormDataStreamProvider(root);
  10.  
  11.     var task = Request.Content.ReadAsMultipartAsync(provider).
  12.         ContinueWith<HttpResponseMessage>(t =>
  13.         {
  14.             if (t.IsFaulted || t.IsCanceled)
  15.             {
  16.                 Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
  17.             }
  18.  
  19.             foreach (MultipartFileData file in provider.FileData)
  20.             {
  21.                 //Trace.WriteLine(file.Headers.ContentDisposition.FileName);
  22.                 //Trace.WriteLine("Server file path: " + file.LocalFileName);
  23.             }
  24.             return Request.CreateResponse(HttpStatusCode.OK);
  25.         });
  26.  
  27.     return task;
  28. }

 

I don’t like this because I am writing to disk first – and I don’t need to use the file system – in fact, it might be locked down in some scenarios.  So finally, I cobbled together some posts and found a way to read the parsed stream and create an image without using disk:

  1. public HttpResponseMessage Post()
  2. {
  3.     var result = new HttpResponseMessage(HttpStatusCode.OK);
  4.     if (Request.Content.IsMimeMultipartContent())
  5.     {
  6.         Request.Content.ReadAsMultipartAsync<MultipartMemoryStreamProvider>(new MultipartMemoryStreamProvider()).ContinueWith((task) =>
  7.         {
  8.             MultipartMemoryStreamProvider provider = task.Result;
  9.             foreach (HttpContent content in provider.Contents)
  10.             {
  11.                 Stream stream = content.ReadAsStreamAsync().Result;
  12.                 Image image = Image.FromStream(stream);
  13.                 var testName = content.Headers.ContentDisposition.Name;
  14.                 String filePath = HostingEnvironment.MapPath("~/Images/");
  15.                 String[] headerValues = (String[])Request.Headers.GetValues("UniqueId");
  16.                 String fileName = headerValues[0] + ".jpg";
  17.                 String fullPath = Path.Combine(filePath, fileName);
  18.                 image.Save(fullPath);
  19.             }
  20.         });
  21.         return result;
  22.     }
  23.     else
  24.     {
  25.         throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
  26.     }
  27.  
  28.  
  29. }

 

Note that I am using a disk right now on the POST and GET but I am going to swap it out with Azure Blob Storage.  That way I can just create the image and push it over to Azure without my local disk being used at all.

Sure enough, this works like a champ. The GET

image

and the POST (using Fiddler – you have to use the most recent Fiddler so you get the [Upload File] link):

image

image

Another note is that I am pushing the UniqueID of the image in the request header – not in the content’s header.

This works:

  1. (String[])Request.Headers.GetValues("UniqueId");

This does not:

  1. var testName = content.Headers.GetValues("UniqueId");

Advertisements

14 Responses to Handling images in WebApi

  1. Adri says:

    Hi Jamie i’m using this documentation to implement a image upload in my asp.net web api.
    Basicalli I copie pasted the last post and edited some lines to change the path where the images have to be saved.

    the point of is this coment is that I’m having an error, in that lina :

    MultipartMemoryStreamProvider provider = task.Result;

    it throws a agregate exception, do you know where can I get information about it?

    thank you!

    Adri.

  2. Adri says:

    yes I am, will it be a problem?

  3. jamie dixon says:

    No, that is what you want.

  4. Adri says:

    Hi again, the problem was the image size, this works great whit a smaller image.
    Thank you!
    Adri.

  5. Adri says:

    Hi Jamie, it’s me again. I found a way to make it handle bigger images (http://stackoverflow.com/questions/12384544/multipart-form-post-using-asp-net-web-api)
    I just needed to to add this line:

    request.Content.LoadIntoBufferAsync().Wait();

    to buffer the request content before reading,

    the problem I’m facing now is that I don’t know where to add the image in a web request.

    My implementation works great whit fiddler but I need it to comunicate whit a visual basic program and I’m a bit lost.

    Could you guide me a little?

    excuse me about my inglish I’m doing my best.

    thank you, Adri.

  6. jamie dixon says:

    Use the WebClient class to make the request for you

  7. tim says:

    This is very interesting. I implemented a client that uses one of the angular file upload libraries on github. And I noticed it became quite complex dealing with multiple posts and syncing other database records with file urls, etc. I think it is way more OO and straight-forward to handle the images like you have done.

  8. Braim says:

    Works no problem. Saved time here

  9. Omar Zammit says:

    Hi,

    When attempting this twice in a row the application threw and IO exception due to both streams not being closed and tied to the same connection.

    In order to fix it make sure to dispose of both streams before sending the response back to the user

  10. kvnphllps says:

    This worked great for me. Thank you!

  11. Vinod says:

    Hi,

    This Works for small files but foe large files it throws error at “MultipartMemoryStreamProvider provider = task.Result;” (error: Error reading MIME multipart body part)

    Thanks

  12. Nils Schiwek says:

    Just one tiny thing to add:
    If you use urls to handle the images (json with urls) the client browsers are able to cache the images locally (they usually do this). So on subsequent calls on the same person, the browser will only once get the image from the url. The other times it will just get it from it’s own cache.

    If you send the image data within the json, the browser as no chance to cache this.

  13. Dave says:

    Just wanted to say thank you for this. I have yet to try out the code, but it has given me some great ideas going forward that wouldn’t have been possible without this article. You are a hero in my eyes 🙂

    Also, some of the comments have been fantastic. Thanks guys and gals. I totally agree that it should have been implemented using bufferedStream or similar for larger images, and Nils thoughts on caching is most interesting.

    Thanks all

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: