Confusing how to replace built-in formatters

Topics: Web Api
May 1, 2011 at 2:23 PM

I've tried a number of things:

  • HttpHostConfiguration.Create().AddFormatters: adds but doesn't replace built-in ones (i.e. Json)
  • HttpHostConfiguration.Create().SetOperationHandlerFactory<JsonNetOperationHandlerFactory> and a custom operation handler factory that passes a specific formatter to the base class

When returning an HttpResponseMessage<T>, it looks like the ObjectContent has its own collection of formatters :S (with default built-in formatters not taken neither from the config neither from the operation handler!).

 

So, how can we do the previously trivial task of providing our own set of formatters and neither of the built-in ones? (in particular, I'm interested in getting rid of the crappy Json formatter in favor of a Json.NET based one).

 

May 1, 2011 at 2:44 PM

Tried also to pass the media formatters to the response message, didn't work either:

  • return new HttpResponseMessage<User>(user, new[] { new JsonNetFormatter() });
May 1, 2011 at 2:50 PM

When ObjectContent.WriteToStreamInternal is called, all built-in formatters have been added somehow (?!) and they are all BEFORE my formatters specified in the response constructor. Clearly it should be the other way around?

May 1, 2011 at 4:02 PM

Here's another attempt:

- When the ObjectContent is constructed inside the HttpReponseMessage<T> with additional formatters, these are added to the Formatters collection of the ObjectContent, but the collection is created with the built-in ones. Clearing the formatters and adding our own doesn't work either:

var response = new HttpResponseMessage<User>(user);
response.Content.Formatters.Clear();
response.Content.Formatters.Add(new EntityContractFormatter(new JsonNetFormatter()));

return response;

There's a ResponseContentHandler that is doing:

                    objectContent.Formatters.ReplaceAllWith(formatters);

 

Still investigating. This is all WAY confusing. Too many APIs for doing the same thing with non-evident behavior... :(

May 1, 2011 at 4:20 PM

Timeout.

My solution for now? Invent my own media type that is NOT any of the built-in supported ones and so my formatter gets picked up :(((

Please fix this!

May 1, 2011 at 5:22 PM
Edited May 1, 2011 at 5:25 PM

Hey Daniel,

The way to remove the standard formatters is to create your own OperationHandlerFactory and in the OnCreateResponseHandlers override, don't call the base class.  It's the base HttpOperationHandlerFactory that adds the default formatters.

You will need to new up a Collection<HttpOperationHandller> and put your formatters in and return that.

I have expressed my concern with the team that there is a bit too much magic going on inside ObjectContent<T> but I was not really able to give any concrete examples.  I believe your difficulty here is one example of the side-effects.  Understanding how the ObjectContent<T> gets its formatters and how you can manipulate those formatters is not obvious.

May 1, 2011 at 10:00 PM

My handler factory is never called :(

/kzu from galaxy tab

On May 1, 2011 1:22 PM, "DarrelMiller" <notifications@codeplex.com> wrote:
> From: DarrelMiller
>
> Hey Daniel,The way to remove the standard formatters is to create your own OperationHandlerFactory and in the OnCreateResponseHandlers override, don't call the base class. It's the base HttpOperationHandlerFactory that adds the default formatters.You will need to new up a Collection<HttpOperationHandller> and put your formatters in and return that.
>
>
May 2, 2011 at 3:34 AM

I'm looking at the HttpOperationHandlerFactory.OnCreateResponseHandlers (the base method you're saying I shouldn't be calling) and it's doing a whole bunch of stuff!

Am I supposed to copy all that source? Ignore it?

May 2, 2011 at 4:01 AM

I think it's kinda a must to show as part of the examples, how to replace a built-in formatter ;) (Json.NET ideally since it's what most people will do as soon as they have object graphs to return)

May 2, 2011 at 4:06 AM

From what I recall of a conversation about it, you simply just don't call the base class at all.  It worked for me.  Having said that, I haven't looked at what is in that base method.

I'm curious, what's the problem with the built in JSON formatter.  I hardly use JSON so, I'm not aware of the issues.

May 2, 2011 at 4:22 AM

Ok, I just looked into the base HttpOperationHandlerFactory class and you are right that by doing what I am suggesting will also remove other functionality like the URITemplateHandler and the RequestContentHandler.  It also appears that by providing a formatter collection with a new Json formatter will not actually cause your new Json formatter to be used.

I suspect we are going to need to get Glenn's input on this.

Coordinator
May 2, 2011 at 4:35 AM
What you do is add your formatter to the beginning of the list using the insert method on the Collection. You do that after calling the base. By putting the formatter first and as long as the SupportedMediaTypes are properly set, it will work. Once we find a matching formatter we stop looking further.

I demonstrated this in my talk last week where I showed how to plug in json.net. Also a nuget pack is coming. I will post the code of the factory/formatter here once I am a bit more awake as I just returned from overseas.

Sent from my Windows Phone

From: DarrelMiller
Sent: Sunday, May 01, 2011 8:22 PM
To: Glenn Block
Subject: Re: Confusing how to replace built-in formatters [wcf:255873]

From: DarrelMiller

Ok, I just looked into the base HttpOperationHandlerFactory class and you are right that by doing what I am suggesting will also remove other functionality like the URITemplateHandler and the RequestContentHandler. It also appears that by providing a formatter collection with a new Json formatter will not actually cause your new Json formatter to be used.

I suspect we are going to need to get Glenn's input on this.

May 2, 2011 at 5:07 AM

Thanks Glenn.  I also just got it working a different way.  If you return a HttpResponseMessage<T> instead of just T it does not set seem to set the default serializers. Not sure why, but I just tested it and it works.

The IsHttp() function here returns true for HttpResponseMessage<T>

           if (contentType != typeof(JsonValue) && !HttpTypeHelper.IsHttp(contentType))
            {
                SetSerializerForXmlFormatter(operation, contentType, formatters);
                SetSerializerForJsonFormatter(operation, contentType, httpParameter.Name, formatters);
            }

May 2, 2011 at 1:32 PM

That sounds a bit unintuitive ;). The code looks like this:

protected override Collection<HttpOperationHandler> OnCreateRequestHandlers(ServiceEndpoint endpoint, HttpOperationDescription operation)
{
  var handlers = base.OnCreateRequestHandlers(endpoint, operation);
  base.Formatters.Insert(0, new JsonNetFormatter());
  return handlers;
}

It's not clear how many times the method is called, if I should be checking if my formatter is already in the base class list, why do I have to do this after calling the base (side effects of a method call and overriding the side-effect with a property change? :|), etc.

I'm confident this will become way easier, right?

Also, it's a bit confusing that the ObjectContent exposes its own list of formatters but modifying it (like clearing it and replacing the formatters) has no effect, for example.

Coordinator
May 2, 2011 at 4:13 PM
Actually no you don't have to do that.

In the ctor you have access to formatters. Just add the insert call in the ctor.

Sent from my Windows Phone

From: dcazzulino
Sent: Monday, May 02, 2011 5:32 AM
To: Glenn Block
Subject: Re: Confusing how to replace built-in formatters [wcf:255873]

From: dcazzulino

That sounds a bit unintuitive ;). The code looks like this:

protected override Collection<HttpOperationHandler> OnCreateRequestHandlers(ServiceEndpoint endpoint, HttpOperationDescription operation)
{
var handlers = base.OnCreateRequestHandlers(endpoint, operation);
base.Formatters.Insert(0, new JsonNetFormatter());
return handlers;
}

It's not clear how many times the method is called, if I should be checking if my formatter is already in the base class list, why do I have to do this after calling the base (side effects of a method call and overriding the side-effect with a property change? :|), etc.

I'm confident this will become way easier, right?

Also, it's a bit confusing that the ObjectContent exposes its own list of formatters but modifying it (like clearing it and replacing the formatters) has no effect, for example.

Coordinator
May 4, 2011 at 3:10 AM

Here is the simplest way to do it without requiring deriving or creating a custom handler. Given that you have a config instance (config below) just use these two lines of code.

            var hostConfig = (HttpHostConfiguration) config;
            hostConfig.OperationHandlerFactory.Formatters.Insert(0, new NewtonsoftJsonFormatter());

Just for completion, the formatter is below (which is based on Christian Weyer's old formatter)

public class NewtonsoftJsonFormatter : MediaTypeFormatter
{
    public NewtonsoftJsonFormatter()
    {
        this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
        this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json"));
    }

    public override object OnReadFromStream(Type type, System.IO.Stream stream, HttpContentHeaders contentHeaders)
    {
        var serializer = new JsonSerializer();
        using (var sr = new StreamReader(stream))
        using (var reader = new JsonTextReader(sr))
        {
            var result = serializer.Deserialize(reader, type);
            return result;
        }
    }

    public override void OnWriteToStream(Type type, object value, System.IO.Stream stream, HttpContentHeaders contentHeaders, System.Net.TransportContext context)
    {
        var serializer = new JsonSerializer();

        using (var sw = new StreamWriter(stream))
        using (var writer = new JsonTextWriter(sw))
        {
            serializer.Serialize(writer, value);
        }
    }
}
Coordinator
May 4, 2011 at 3:13 AM

BTW, this could easily be wrapped in an extension method on HttpHostConfiguration.

public static class HttpHostConfigurationExtensions {
  public static void UseNewtonsoftJson(this HttpHostConfiguration config) {
    config.OperationHandlerFactory.Formatters.Insert(0, new NewtonsoftJsonFormatter());
  }
}

Now is that so hard? :-)

May 4, 2011 at 4:31 AM

It's not hard when you know how :-)

May 4, 2011 at 9:17 AM

Precisely. ;)

/kzu from Android

Coordinator
May 4, 2011 at 11:36 AM
Touché.

I think what would make it easier is if the AddFormatters method on the FI gave you the collection so you can insert.

An alternative option would be to add our defaut formatters at the very end of the list at the last possible moment.

This way you just add your formatter normally and it automatically takes precedence over our defaults.

That removes needing to know to insert.

Thoughts?
>
May 4, 2011 at 12:25 PM

How about ReplaceFormatters in addition to AddFormatters?

/kzu from Android

May 4, 2011 at 1:27 PM

kzu,

The tricky issue with ReplaceFormatters is Formatters can support multiple media types, 'application/json', 'text/json'.  

How would you handle scenarios where a new Formatter only supported a subset of the media types of the existing formatter?

 

Glenn,

I think adding the default formatters at the end would produce a more intuitive behaviour.  I don't see a valid scenario where someone

is going to add a custom formatter but want the default one to override.

May 4, 2011 at 2:33 PM

I would explicitly add built in ones I am interested in?

"built-in" should only mean "as a convenience", never "hardcoded"

/kzu from Android