Search code examples
asp.net-web-apimediatypeformatter

MediaTypeFormatter WriteToStreamAsync not called unless I add to Accept headers


I have a MediaTypeFormatter that converts an internal rep of an image to a png/jpeg/etc. if someone asks for it. However, my WriteToStreamAsync never gets called unless I add an image/png or similar to the accept headers.

First, here is my webapi method, with some key bits removed for brevity:

public ImageFormatter.BinaryImage GetImage(int cId, int iId)
{
    ....

    using (var input = iFIO.OpenRead())
    {
        input.Read(b.data, 0, (int)iFIO.Length);
    }

    // With this next line my mediatypeformatter is correctly called.
    Request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("image/png"));

    return b;
}

And here is the write portion of my MediaTypeFormatter (there is also a read portion, and that works great, actually).

namespace PivotWebsite.MediaFormatters
{
    public class ImageFormatter : MediaTypeFormatter
    {
        public class BinaryImage
        { 
            public byte[] data;
            public string metaData;
        }

        public ImageFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpg"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png"));
        }

        public override bool CanWriteType(Type type)
        {
            return true;
        }

        public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
        {
            var b = value as BinaryImage;
            if (b == null)
                throw new InvalidOperationException("Can only work with BinaryImage types!");

            await writeStream.WriteAsync(b.data, 0, b.data.Length);
        }
    }
}

What I expected to be able to do was, in WriteToStreamAsync, to alter the outgoing headers to include Content-Type as "image/png" (or whatever, depending on the data type).

However, when I call this from a web browser with a URL like "http://my.testdomain.net:57441/api/Images?cID=1&iID=1", the WriteToStreamAsync never gets called (accepted headers are listed as {text/html, application/xhtml+xml, */*}). If I add the line above that adds the proper image type, then everything is called as I would expect.

What am I missing here? The accepted header of "*/*" should have triggered my media formatter, right? Or... am I missing something basic about the plumbing in Web API.


Solution

  • Do you want the image formatter to always get used if the Accept header is "/"? If that's the case, then you should insert your formatter first in the Formatters collection like this:

    config.Formatters.Insert(0, new ImageFormatter());
    

    What happens when there isn't an exact Accept header match like in your case is that the first formatter that can write the type gets selected to serialize the object. So if you register your formatter first, it would get used.

    This could have unintended side-effects because it would affect all your controllers though. I would suggest changing the CanWriteType implementation to only return true if it's a BinaryImage. That should make the formatter only get used when that's your return type.

    Another thing you could do is select the formatter directly in your action by returning an HttpResponseMessage:

    return Request.CreateResponse(HttpStatusCode.OK, image, new ImageFormatter());
    

    That's basically saying "this action should always use the image formatter, regardless of content-type, accept headers etc". That might be reasonable in your case if you're always just returning an image and you need it serialized with your formatter.