Home > ASP.NET, C#, WepApi > ASP.NET WebApi upload image with custom MediaTypeFormatter

ASP.NET WebApi upload image with custom MediaTypeFormatter

UPDATE: changes to WebApi mean this code no longer works, see here for update.

I’ve been reading a few tutorials on www.asp.net and come across a good article demonstrating the basics of MediaTypeFormatters.

I’m currently working on a personal project utilising WebApi to post an image for OCR. I don’t need to save this file, I just need a byte array of it to perform some character recognition using Tesseract. One of the tutorials on http://www.asp.net, Forms and Multi-part MIME, describes how to upload an image by reading the multipart data directly inside the controller, and saving it to appdata folder.

This doesn’t seem ideal for my scenario, firstly, I don’t need to store the images and, more importantly, it makes it difficult to test.

Ideally, I needed my actions signature to be like this:

[HttpPost]
public Task<string> Upload(ImageMedia media)

Where ImageMedia looks like this:

    public class ImageMedia
    {
        public string FileName { get; set; }
        public string MediaType { get; set; }
        public byte[] Buffer { get; set; }

        public ImageMedia(string fileName, string mediaType, byte[] imagebuffer)
        {
            FileName = fileName;
            MediaType = mediaType;
            Buffer = imagebuffer;
        }
    }

Now, implementing a new MediaTypeFormatter I can extract the image and populate my ImageMedia model.

Firstly, we need to to tell the framework what media types the MediaFormatter applies to, we can do this by adding MediaTypeHeaderValues to the list of SupportedMediaTypes inside the constructor, something like this does the job:

        public ImageMediaFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpg"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
        }

We also need to specifiy what model types the formatter can write/read, because I’m only reading from the stream to my ImageMedia, I’ll need to override CanReadType comparing the Type passed in to typeof(ImageMedia):

        protected override bool CanReadType(Type type)
        {
            return type == typeof(ImageMedia);
        }

And I’ll simply return false from CanWriteType:

        protected override bool CanWriteType(Type type)
        {
            return false;
        }

Now for the actual work:

1. First check that the request is IsMimeMultipartContent
2. Then read the content of the request as Multipart.
3. We the find the part that that matches one of our SupportedMediaTypes, this will be the image we’re after.
4. We extract te fileName and mediaType from the headers of the content we retrieved.
5. then on content call ReadAsStreamAsync()
6. as ImageMedia wants a byte[] I call a handy helper “ReadFully” which is an extension method on Stream, (written by Jon Skeet)

       protected async override Task OnReadFromStreamAsync(
            Type type, Stream stream, HttpContentHeaders contentHeaders,
            FormatterContext formatterContext)
        {
            HttpRequestMessage request = formatterContext.Request;
            if (!request.Content.IsMimeMultipartContent())
            {
                throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
            }

            var parts = await request.Content.ReadAsMultipartAsync();
            var content = parts.First(x =>
                SupportedMediaTypes.Contains(x.Headers.ContentType));

            string fileName = content.Headers.ContentDisposition.FileName;
            string mediaType = content.Headers.ContentType.MediaType;

            using (var imgstream = await content.ReadAsStreamAsync())
            {
                byte[] imagebuffer = imgstream.ReadFully();
                return new ImageMedia(fileName, mediaType, imagebuffer);
            }
        }

And Jon Skeets read fully:

        public static byte[] ReadFully(this Stream input)
        {
            byte[] buffer = new byte[16 * 1024];
            using (MemoryStream ms = new MemoryStream())
            {
                int read;
                while ((read = input.Read(buffer, 0, buffer.Length)) &gt; 0)
                {
                    ms.Write(buffer, 0, read);
                }
                return ms.ToArray();
            }
        }

And thats it!!! So now the controller is no longer responsible for extracting the content of the request, and it can be left with simply coordinating it. Our ImageMediaFormatter will kick in any time an action on our WebApi requires an ImageMedia passed in.

For clarity, here’s the ImageMediaFormatter in full:


 public class ImageMediaFormatter : MediaTypeFormatter
    {
        public ImageMediaFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpg"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
        }

        protected override bool CanReadType(Type type)
        {
            return type == typeof(ImageMedia);
        }

        protected override bool CanWriteType(Type type)
        {
            return false;
        }

        protected async override Task OnReadFromStreamAsync(
            Type type, Stream stream, HttpContentHeaders contentHeaders,
            FormatterContext formatterContext)
        {
            HttpRequestMessage request = formatterContext.Request;
            if (!request.Content.IsMimeMultipartContent())
            {
                throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
            }

            var parts = await request.Content.ReadAsMultipartAsync();
            var content = parts.First(x =>
                SupportedMediaTypes.Contains(x.Headers.ContentType));

            string fileName = content.Headers.ContentDisposition.FileName;
            string mediaType = content.Headers.ContentType.MediaType;

            using (var imgstream = await content.ReadAsStreamAsync())
            {
                byte[] imagebuffer = imgstream.ReadFully();
                return new ImageMedia(fileName, mediaType, imagebuffer);
            }
        }
    }
Categories: ASP.NET, C#, WepApi Tags: ,
  1. Amol Gathale
    August 9, 2012 at 11:31 am | #1

    Nice Post But You Have Developed these in mvc 4.0 beta can u say how to developed it in mvc 4.0 RC. can you give the Request body for posting image.

    • August 13, 2012 at 5:40 am | #2

      Thanks Amol, you’re right. I have been meaning to update this post, but haven’t got around to it.

  2. December 5, 2013 at 10:22 am | #3

    FormatterContext do not exist anymore in last version of webapi

    • December 5, 2013 at 10:29 am | #4

      Yep, a later post has corrected this, however knowing Microsoft it may have changed again :)

      • December 5, 2013 at 10:31 am | #5

        Can you please add a link to the new post in this. Will be more easy for your guests to know for the new version and just go there :) Thanks

  1. September 24, 2012 at 1:29 am | #1
  2. September 24, 2012 at 3:36 am | #2

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

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: