Search code examples
c#asp.netasp.net-mvc-4asp.net-web-apimediatypeformatter

Change Response Headers on Media Type Formatter for ASP.NET Web API


I have created an ASP.NET web API controller that is returning a strongly typed object on an action, as follows:

// GET api/iosdevices/5
public iOSDevice Get(string id) {
  return new iOSDevice();
}

I have created a BufferedMediaTypeFormatter to handle the type iOSDevice:

public class iOSDeviceXmlFormatter : BufferedMediaTypeFormatter
{
    public iOSDeviceXmlFormatter() {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml"));
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml"));
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
    }

    public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content) {
        content.Headers.ContentType = new MediaTypeHeaderValue("application/xml");
        iOSDevice device = (iOSDevice)value;
        using (XmlWriter writer = XmlWriter.Create(writeStream)) {
            writer.WriteStartElement("iOSDevice");
            if (device.identifierForVendor != Guid.Empty) {
                writer.WriteElementString("identifierForVendor", device.identifierForVendor.ToString());
                writer.WriteElementString("userInterfaceIdiom", device.userInterfaceIdiom);
                writer.WriteElementString("systemName", device.systemName);
                writer.WriteElementString("systemVersion", device.systemVersion);
                writer.WriteElementString("model", device.model);
            }
            writer.WriteEndElement();
        }
        writeStream.Close();
    }
}

My problem is when I catch type "text/html" (e.g. someone attempts to access the API from his or her web browser), the response type is "text/html" instead of "application/xml". I want to override the response type so that the user gets a response that is "application/xml" instead of "text/html".

I cannot in the ApiController type get access to the "Response" property that is on regular MVC controllers and I am at a loss. How do I override the response type for this action that is being handled by a media type formatter?

EDIT: HELPFUL NOTE

I was trying this previously:

var response = Request.CreateResponse<iOSDevice>(HttpStatusCode.Accepted, device);
response.Headers.Remove("Content-Type");
response.Headers.Add("Content-Type", "application/xml; charset=utf-8");
return response;

And it claimed I was "misusing" the headers.

But when I used Filip's example below of setting Content directly, it worked!

var response = Request.CreateResponse();
response.Content = new ObjectContent<iOSDevice>(device, new iOSDeviceXmlFormatter());
return response;

Solution

  • When you write to stream in the formatter, headers have been already sent.

    You can do this:

        public HttpResponseMessage Get(string id) {
        {
            var value = new iOSDevice();
            var response = Request.CreateResponse();
            response.Content = new ObjectContent(typeof(iOSDevice), value, new iOSDeviceXmlFormatter());
            //set headers on the "response"
            return response;
        }
    

    or you can do this (add this method to your formatter):

        public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, string mediaType)
        {
            base.SetDefaultContentHeaders(type, headers, mediaType);
            headers.ContentType = new MediaTypeHeaderValue("application/xml");
        }
    

    Here is an example on how I used the SetDefaultContentHeaders with a custom formatter: http://www.strathweb.com/2012/09/generate-kindle-mobi-ebooks-with-your-asp-net-web-api/

       public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
       {
          if (CanWriteType(type) && mediaType.MediaType == supportedMediaType)
          {
             headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
             headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
             headers.ContentDisposition.FileName = "ebook.mobi";
          }
          else
          {
             base.SetDefaultContentHeaders(type, headers, mediaType);
          }
       }