What if I Need the CultureInfo of the Request Thread in a Custom MediaTypeFormatter?

Topics: Web Api
Jan 26, 2012 at 3:35 PM
Edited Jan 26, 2012 at 3:37 PM

It's known that the thread that processes OnReadFromStream/OnWriteToStream isn't guaranteed to be the same thread that initially processes the request. Just as an example, given the following class:

 

  [DataContract]
  public class Sample
  {
    [DataMember]
    public string RequestCulture { get; set; }

    [DataMember]
    public string FormatterCulture { get; set; }
  }

 

...and say that I have a special JSON formatter:

 

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

    protected override object OnReadFromStream(Type type, System.IO.Stream stream, System.Net.Http.Headers.HttpContentHeaders contentHeaders)
    {
      using (var sr = new StreamReader(stream))
      {
        var posted = sr.ReadToEnd();
        var json = JsonValue.Parse(posted);
        return new Sample
                 {
                   RequestCulture = json["RequestCulture"].ReadAs<string>(),
                   FormatterCulture = CultureInfo.CurrentCulture.ToString(),
                 };
      }
    }

    protected override void OnWriteToStream(Type type, object value, System.IO.Stream stream, System.Net.Http.Headers.HttpContentHeaders contentHeaders, System.Net.TransportContext context)
    {
      var sample = (Sample) value;
      using (var sw = new StreamWriter(stream))
      {
        var sampleJson = new JsonObject
                           {
                            {"RequestCulture", sample.RequestCulture}, 
                            {"FormatterCulture", CultureInfo.CurrentCulture.ToString()}
                           };
        sampleJson.Save(sw);
      }
    }
  }

And I attach a new culture info to the request thread:

System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE");

And I hook up the custom formatter in the call to MapServiceRoute and all. When I 
make a request that maps to the following method:

    [WebGet(UriTemplate="")]
    public Sample Get()
    {
      return new Sample {RequestCulture = CultureInfo.CurrentCulture.ToString()};
    }

You are likely to get content in the response that looks a lot like this:

{
  "RequestCulture":"de-DE",
  "FormatterCulture":"en-US"
}

I understand why this happens. The two methods are not called on the same thread. 

What if I really need to know the culture info that was attached to the request 
thread? Headers? What if the CultureInfo is a derived class that's more than 
just something that could be handled by Accept-Language? Is there a way to "callback" 
to the request thread?

Thanks.
Jan 26, 2012 at 4:20 PM

Use the properties of the HttpMessage instead.

HttpRequestMessage.Properties

System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.Properties

What I'm guessing is the formatter needs to run before the requestmessage is built.

Why do you need the culture like this, inside the formatter? Why not inside a request operation?

Jan 26, 2012 at 7:04 PM

Thanks for the reply. Basically, in my real case I do have a derived CultureInfo class with some nifty pieces of information in it. It's attached to the thread's current culture. Putting the request aside for a minute, the formatting of the content in OnWriteToStream is what I'm really interested in.

I need the culture info to be correct (what was on the request thread), basically so that I can format some of the string properties appropriate to some of these culture settings.

It seemed like a good idea at the time, that creating a formatter was the place to put the code that essentially handles customizing the serialization/de-serialization of a given object graph(s) in the message content?

You're suggesting I move that code to an OperationHandler instead?

Thanks again.

Jan 26, 2012 at 9:46 PM

I'm curious as to why you need to write a formatter. If you are using JSON it already supports JSON.

Formatters are for when the current formatters cannot read the content you are sending/receiving. Like you want to return CSV's, or images, or something.

Could you explain the "business need", what you want to accomplish in broader terms and maybe I can let you know my ideas?

Jan 27, 2012 at 5:46 PM

Sure. The actual objects that I need to serialize/de-serialize have a different structure from the way that I want to represent them in JSON. Typical serializers make the wild (sarcasm) assumption that I'd want the two representations to be roughly the same. So I want to use a "special" handcrafted serializer that understands how to handle my particular representation.

The business case here is that the JSON spit out by default serializers isn't nice to deal with for non .net clients. We could change the way we represent the business entities, but there are reasons for the way that they are. It isn't so tough to make a custom JSON serializer/de-serializer where everyone is happy.

So I wanted to plug in a custom JSON serializer, like Mr. Block does here: http://wcf.codeplex.com/discussions/255873...and I would have gotten away with it too, but the formatter runs on a thread separate from the request thread, and the change in CurrentCulture is causing me grief.

Any thoughts you have are greatly appreciated.

Jan 28, 2012 at 12:09 AM

ideally, you should be able save the culture info in the request's property bag, and retrieve it in the Write method.

To work around this issue in the current drop, can you change the return object to be a wrapper class which contains both the cultureinfo and the original object you want to return?  

Jan 30, 2012 at 6:53 PM

Yes, I suppose that would be a work around. The sample code I have above basically does that. That's certainly not what I'd like to do (makes me change the object being returned by the service method).

Is there a way to get the current operation context in the formatter. It seems to be null there? That would be a good place to attach things like CurrentCulture and principals? Is that what you mean by "property bag"?

Jan 30, 2012 at 10:48 PM
Edited Jan 30, 2012 at 10:48 PM

He means what I stated earlier.

Use the properties of the HttpMessage instead.

HttpRequestMessage.Properties

System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.Properties

Requests have the same thing, HttpContext.Current.Properties

Jan 31, 2012 at 1:38 PM
We've done a lot of plumbing work with Tasks in Web API, preserving synchronization context and the like, so in the next drop the thread you run on should have all the correct information attached to it (like culture).
Feb 3, 2012 at 4:59 PM

Thanks! Looking forward to it.