Setting the default formatter

Topics: Web Api
May 12, 2011 at 3:59 PM

Hi,

Is there a way to set the default formatter that should be used if the client doesn't provide an Accept header in the request?

I cannot find a way to do it as part of the HttpHostConfiguration...

Thanks,

Mario

May 13, 2011 at 1:44 AM

Just to provide some more context... I'm trying to invoke a web service with a GET operation, and receive a response formatted in JSONP.

I created a custom formatter for JSONP. However, it does not get picked because of the code below in the ObjectContent class.

My client cannot set an Accept header, so the SelectWriteFormatter is return null. The system is then using the "DefaultFormatter"... which defaults the the Xml or Json formatter.

Is there any way that I can initialize it to my custom JSONP formatter? 

        private MediaTypeFormatter SelectAndValidateWriteFormatter()
        {
            MediaTypeHeaderValue mediaType = null;
            MediaTypeFormatter formatter = this.SelectWriteFormatter(out mediaType);

            if (formatter == null)
            {
                if (this.DefaultFormatter != null &&
                    this.DefaultFormatter.SupportedMediaTypes.Count > 0)
                {
                    formatter = this.DefaultFormatter;
                    mediaType = this.DefaultFormatter.SupportedMediaTypes[0];
                }
                else
                {
                    string errorMessage = this.MediaType == null
                                            ? SR.MediaTypeMustBeSetBeforeWrite(HeadersContentTypeName, ObjectContentType.Name)
                                            : SR.NoWriteSerializerAvailable(MediaTypeFormatterType.Name, this.Type.Name, this.MediaType.ToString());
                    throw Fx.Exception.AsError(new InvalidOperationException(errorMessage));
                }
            }

Thanks
Mario
Coordinator
May 13, 2011 at 1:56 AM

There's two options of what you can do there.

1. You can create a custom channel that will take a request with no accept header and add one. I show an example of this in the ContactManager here

2. Each formatter has a collection of MediaTypeMappings. You can write one that uses an alternative mechanism other than the accept header to determine whether or not to use that formatter. 


May 13, 2011 at 7:11 PM

I tweaked the UriFormatExtensionMesageChannel example to setup a default type.  Below is my code, and the comment is my tweak.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace WebAPIWebApp {

    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Linq;

    public static class UriFormatExtensions {
        public static void SetUriExtensionMappings(this HttpApplication application, IEnumerable<UriExtensionMapping> mappings) {
            UriFormatExtensionMessageChannel.SetUriExtensionMappings(mappings);
        }
    }

    public class UriFormatExtensionMessageChannel : DelegatingChannel {

        private string _defaultEncoding = "application/json";

        public UriFormatExtensionMessageChannel(HttpMessageChannel handler)
            : base(handler) {
        }

        private static Dictionary<string, MediaTypeWithQualityHeaderValue> extensionMappings = new Dictionary<string, MediaTypeWithQualityHeaderValue>();

        public static void SetUriExtensionMappings(IEnumerable<UriExtensionMapping> mappings) {
            foreach (var mapping in mappings) {
                extensionMappings[mapping.Extension] = mapping.MediaType;
            }
        }

        protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) {
            var segments = request.RequestUri.Segments;
            var lastSegment = segments.LastOrDefault();
            MediaTypeWithQualityHeaderValue mediaType;
            var found = extensionMappings.TryGetValue(lastSegment, out mediaType);

            if (found) {
                var newUri = request.RequestUri.OriginalString.Replace("/" + lastSegment, "");
                request.RequestUri = new Uri(newUri, UriKind.Absolute);
                request.Headers.Accept.Clear();
                request.Headers.Accept.Add(mediaType);
            } else if (request.Headers.Accept.Count == 0) {
                // My addition, to make json the default encoding
                request.Headers.Accept.Clear();
                request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(_defaultEncoding));
            }

            return base.SendAsync(request, cancellationToken);
        }

        protected override HttpResponseMessage Send(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) {
            throw new NotImplementedException();
        }
    }

    public class UriExtensionMapping {
        public string Extension { get; set; }

        public MediaTypeWithQualityHeaderValue MediaType { get; set; }

    }

    public static class UriExtensionMappingExtensions {
        public static void AddMapping(this IList<UriExtensionMapping> mappings, string extension, string mediaType) {
            mappings.Add(new UriExtensionMapping { Extension = extension, MediaType = new MediaTypeWithQualityHeaderValue(mediaType) });
        }
    }

}

Jun 3, 2011 at 7:09 AM

After implementation of DelegetingChannel similar to the one provided by INeedADip. I can not get OperationContext.Current inside a MediaTypeFormatter anymore. It is always NULL. If I remove this custom channel everything is working again. Any ideas?