Custom content negotiationhttp://forums.asp.net/t/1780356.aspx/1?Custom+content+negotiationThu, 15 Mar 2012 20:23:16 -040017803564880291http://forums.asp.net/p/1780356/4880291.aspx/1?Custom+content+negotiationCustom content negotiation <p style="background:#dddddd"><span style="font-family:'Verdana','sans-serif'">I have read <a href="http://www.tugberkugurlu.com/archive/asp-net-web-api-mediatypeformatters-with-mediatypemappings"> http://www.tugberkugurlu.com/archive/asp-net-web-api-mediatypeformatters-with-mediatypemappings</a>. And this gives a very good tutorial for content negotiation if the content is one of the supported types. But it doesn't give alot of detials about writing and integrating a custom formatter.</span></p> <p>I am exploring ASP.NET Web API and I have trouble with a Post method that has the signature</p> <p>HttpResponseMessage&lt;SubmitFeedResponse&gt; Post(SubmitFeed request)</p> <p>In order to take in an object as an argument it seems that it depends on the ContentType. If the ContentType is not &quot;application/xml&quot; (or if ai add JSON to the medatype mappers JSON will deserialize to an object) but I don't know how to tell Web API what to do if the ContentType is &quot;application/octet-stream.&nbsp;Rather than having the Post method not get called and getting an ambiguous 500 (InternalServerError) how can I intelligently handle a custom type like &quot;application/octet-stream&quot;?. The easiest fix would be to hard code the ContentType but how can I get the Post method to deserialize the content when the ContentType is &quot;application/octet-stream&quot;?</p> 2012-03-14T16:47:27-04:004880350http://forums.asp.net/p/1780356/4880350.aspx/1?Re+Custom+content+negotiationRe: Custom content negotiation <p>Did you create a custom media type formatter which can deserialize your data in &quot;application/octet-stream&quot; format?</p> <p>In media type formatters there is a collection called SupportedMediaTypes to which you would need to add this media type&nbsp;&quot;application/octet-stream&quot;. Once you have the custom formatter, you need to add it to the Formatters collection present on the Configuration object.</p> <p>When a request is sent to the service, WebAPI looks for the appropriate formatter to deserialize your request based on the incoming request's content-type header.</p> 2012-03-14T17:28:49-04:004880361http://forums.asp.net/p/1780356/4880361.aspx/1?Re+Custom+content+negotiationRe: Custom content negotiation <p>I am looking for an example of creating such a class. Basically it would read the content in. In my caswe it should be XML. If it is not XML then I would want to return some kind of unsupported media HTTP error. If it is (or looks like XML then I would use the XmlSerializer and turn the content into an object and return it. Unfortunately I don't know how to write such a class so that it hooks into MVC. I see MediaTypeMapping abbstract class so I could derive a mapping class from it to indicate that the content type is accepted but I don't know how to tie in the actual deserialization class. Do you know of an example?</p> <p>You indicate a formatters collection. Is that different from the media type mapping? Is the formatters collection on the server? I would want the content to go across the wire as it does now so if it is on the server that is best (for me).</p> <p>Thank you.</p> 2012-03-14T17:38:12-04:004880471http://forums.asp.net/p/1780356/4880471.aspx/1?Re+Custom+content+negotiationRe: Custom content negotiation <p>Just to clarify...MediaTypeMapping is <em>not</em> used during the process of reading a request. It is one of the criteria used by the default con-neg algorithm while deciding to <em>write the response</em>.&nbsp;</p> <p>The default con-neg algorithm depends on the incoming request's content-type header to decide about the formatter to read. So, in your case you could create a custom formatter which supports the mediatype &quot;application/octect-stream&quot; and add it to the Formatters collection on the config object.</p> <p><strong>NOTE</strong>: i have a crude example formatter here where i check for the body content to start with &quot;[&quot; and end with &quot;]&quot;. The following example is written &nbsp;as a test in XUnit. Hope this helps. <em>Here Test1 fails as its body is not in expected format, where as Test2 passes</em>.</p> <pre class="prettyprint">using System; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Formatting; using System.Net.Http.Headers; using System.Web.Http; using System.Web.Http.ModelBinding; using System.Web.Http.SelfHost; using Xunit; public class ConNegTests : IDisposable { [Fact] public void Test1() { HttpClient httpClient = new HttpClient(); HttpRequestMessage request = new HttpRequestMessage(); request.Content = new StringContent(&quot;Hello&quot;); request.Content.Headers.ContentType = new MediaTypeHeaderValue(&quot;application/abc&quot;); request.RequestUri = new Uri(this.BaseAddress &#43; &quot;/UpdateData&quot;); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(&quot;application/abc&quot;)); request.Method = HttpMethod.Post; HttpResponseMessage response = httpClient.SendAsync(request).Result; Assert.NotNull(response.Content); Assert.NotNull(response.Content.Headers.ContentType); Assert.Equal&lt;string&gt;(&quot;application/abc&quot;, response.Content.Headers.ContentType.MediaType); Assert.Equal&lt;string&gt;(&quot;[Hello-updated]&quot;, response.Content.ReadAsStringAsync().Result); } [Fact] public void Test2() { HttpClient httpClient = new HttpClient(); HttpRequestMessage request = new HttpRequestMessage(); request.Content = new StringContent(&quot;<strong>[</strong>Hello<strong>]</strong>&quot;); //NOTE request.Content.Headers.ContentType = new MediaTypeHeaderValue(&quot;application/abc&quot;); request.RequestUri = new Uri(this.BaseAddress &#43; &quot;/UpdateData&quot;); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(&quot;application/abc&quot;)); request.Method = HttpMethod.Post; HttpResponseMessage response = httpClient.SendAsync(request).Result; Assert.NotNull(response.Content); Assert.NotNull(response.Content.Headers.ContentType); Assert.Equal&lt;string&gt;(&quot;application/abc&quot;, response.Content.Headers.ContentType.MediaType); Assert.Equal&lt;string&gt;(&quot;[Hello-updated]&quot;, response.Content.ReadAsStringAsync().Result); } private HttpSelfHostServer server = null; public ConNegTests() { HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(this.BaseAddress); config.Routes.MapHttpRoute(&quot;Default&quot;, &quot;{action}&quot;, new { controller = &quot;ConNegTests&quot; }); //NOTE config.Formatters.Add(new AbcFormatter()); config.ServiceResolver.SetService(typeof(IRequestContentReadPolicy), new ReadAsSingleObjectPolicy()); server = new HttpSelfHostServer(config); server.OpenAsync().Wait(); } public void Dispose() { if (server != null) { server.CloseAsync().Wait(); } } protected virtual string BaseAddress { get { return string.Format(&quot;http://{0}:9090/ConNegTests&quot;, Environment.MachineName); } } } public class ConNegTestsController : ApiController { [HttpPost] public string UpdateData(string input) { return input &#43; &quot;-updated&quot;; } } public class AbcFormatter : BufferedMediaTypeFormatter { public AbcFormatter() { this.SupportedMediaTypes.Add(new MediaTypeHeaderValue(&quot;application/abc&quot;)); } protected override bool CanReadType(Type type) { if (type == typeof(IKeyValueModel)) { return false; } return true; } protected override bool CanWriteType(Type type) { return true; } protected override object OnReadFromStream(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext) { string content = null; using (var reader = new StreamReader(stream)) { content = reader.ReadToEnd(); } if (!content.StartsWith(&quot;[&quot;) &amp;&amp; !content.EndsWith(&quot;[&quot;)) { throw new HttpResponseException(&quot;Invalid data sent. Expected data to begin with '[' and end with ']'&quot;); } string trimmedContent = content.TrimStart('[').TrimEnd(']'); return trimmedContent; } protected override void OnWriteToStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext) { var output = &quot;[&quot; &#43; value.ToString() &#43; &quot;]&quot;; var writer = new StreamWriter(stream); writer.Write(output); writer.Flush(); } } public class ReadAsSingleObjectPolicy : IRequestContentReadPolicy { public RequestContentReadKind GetRequestContentReadKind(System.Web.Http.Controllers.HttpActionDescriptor actionDescriptor) { return RequestContentReadKind.AsSingleObject; } }</pre> <p></p> 2012-03-14T18:54:05-04:004880509http://forums.asp.net/p/1780356/4880509.aspx/1?Re+Custom+content+negotiationRe: Custom content negotiation <p>A couple of questions if you don't mind. The AbcFormatter class derives from BufferedMediaTypeFormatter. Is this required? Are there other options? I am assuming that this formatter is considered &quot;appropriate&quot; because of the 'SupportedMediaTypes'. I don't totally understand CanReadType. What is 'IKeyValueModel'? For 'OnReadFromStream' is the Type that is passed in is the type of object. You don't use the type in this method so I am guessing that you could further check that the type is typeof(string) for your class. Right? Since the class ReadAsSingleObjectPolicy is only used for the tests do I need it? What is its function?</p> <p>I know it is used as an illustration but in reading <a href="http://www.tugberkugurlu.com/archive/asp-net-web-api-mediatypeformatters-with-mediatypemappings"> http://www.tugberkugurlu.com/archive/asp-net-web-api-mediatypeformatters-with-mediatypemappings</a>&nbsp;it seems that the two cases of JSON and XML are already handled with the default negotiation. So the default will handle either JSON or XML without the additions mentioned in this blog. Right?</p> <p></p> <p>So with your help I came up with the below class. Does this look right (Assuming that the stream contains XML)?</p> <p>Thank you.</p> <pre class="prettyprint">public class StreamFormatter : BufferedMediaTypeFormatter { public StreamFormatter() { this.SupportedMediaTypes.Add(new MediaTypeHeaderValue(&quot;application/octet-stream&quot;)); } protected override bool CanReadType(Type type) { if (type == typeof(IKeyValueModel)) { return false; } return true; } protected override bool CanWriteType(Type type) { return true; } protected override object OnReadFromStream(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext) { XmlSerializer serializer = new XmlSerializer(type); return serializer.Deserialize(stream); } protected override void OnWriteToStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext) { XmlSerializer serializer = new XmlSerializer(type); serializer.Serialize(stream, value); } }</pre> 2012-03-14T19:36:31-04:004880808http://forums.asp.net/p/1780356/4880808.aspx/1?Re+Custom+content+negotiationRe: Custom content negotiation <p>No, its not required to derive from BufferedMediaTypeFormatter. It's bascially provides a synchronous way of reading or writing into a stream, whereas if you derive from MediaTypeFormatter, you get the 'Async' methods for asynchronous operations.</p> <p>SupportedMediaTypes is a collection that is exposed by the parent MediaTypeFormatter object.&nbsp;</p> <p>Yes, that right...the Type passed to the OnReadFromStream is the one to which you would deserialize the content.</p> <p>IKeyValueModel and ReadAsSingleObjectPolicy are related to ModelBinding.&nbsp;IKeyValueModel as its name indicates reads the content in the form of key-value pairs, which gives a greater control for validating the content. [Just FYI...these types are going away in our next release]</p> <p>Yes, Json and Xml formatters are provided by default. So, you shouldn't need to write your own custom xml formatter.&nbsp;</p> <p>What you could do is to add your supported media type &quot;application/octet-stream&quot; to the SupportedMediaTypes collection of the default xml formatter....example below:</p> <p><em>config.Formatters.XmlFormatter.SupportedMediaTypes.Add(new&nbsp;MediaTypeHeaderValue(&quot;application/octet-stream&quot;));</em></p> <p>Once you do the above, any requests with content-type &quot;application/octet-stream&quot; would be handled by the default XmlMediaTypeFormatter.</p> <p>By default, it uses XmlSerializer, but there is a property on it to change it to use DataContractSerializer.</p> 2012-03-15T01:53:03-04:004881915http://forums.asp.net/p/1780356/4881915.aspx/1?Re+Custom+content+negotiationRe: Custom content negotiation <p>I am still missing something. Since</p> <pre class="prettyprint">config.Formatters.XmlFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue(&quot;application/octet-stream&quot;));</pre> <p>didn't work I have more debug options by setting a custom configuration like:</p> <pre class="prettyprint"> GlobalConfiguration.Configuration.Formatters.Add(new StreamFormatter());</pre> <p>But the method&nbsp;OnReadFromStream in the custom formatter is never called (I attached the VS debugger to the w3wp process). What am I missing?</p> <p><strong>NOTE: For some reason adding a new formatter works now. But adding application/octet-stream to XmlFormatter does not work. At least I have something that works even if I don't understand why adding the type to existing XmlFormatter doesn't.</strong></p> <p></p> 2012-03-15T14:03:59-04:004882393http://forums.asp.net/p/1780356/4882393.aspx/1?Re+Custom+content+negotiationRe: Custom content negotiation <p>Hi Kevin,</p> <p>Can you clarify on the below statement?</p> <p>&quot;'<strong>But adding application/octet-stream to XmlFormatter does not work&quot;</strong></p> <p><b><br> </b>Are you seeing any exceptin when the deserialization is happening? This would be expected if the content sent in your request does not conform to the format that the serializers of out-of-box Xml formatter do not understand. In this case you need to write your own custom formatter as you seem to be doing now.</p> <p>if you are still seeing a problem, could you send me a repro to <em>kiranchalla at hotmail</em></p> 2012-03-15T18:44:36-04:004882536http://forums.asp.net/p/1780356/4882536.aspx/1?Re+Custom+content+negotiationRe: Custom content negotiation <p>I have been unable to set a breakpoint in the formatter. I just see the results so I know that the formatter is called. Is the formatter called in another process or thread such that a breakpoint cannot be set?</p> 2012-03-15T20:23:16-04:00