I'm attempting to write a Protobuf ValueProviderFactory for ASP MVC3. I've managed to work out how to add the factories, but now I've stumbled on a more pressing problem.
Here is where the current serialization takes place in JsonValueProviderFactory.cs
JavaScriptSerializer serializer = new JavaScriptSerializer();
object jsonData = serializer.DeserializeObject(bodyText);
return jsonData;
So the deserialization is accomplished without any type information? What sort of object does DeserializeObject
return? A Dynamic? How does it know the type of data? I was hoping to slot protobuf-net in here but it obviously needs a type to do its magic!
I haven't looked through all the MVC3 source, but I'm guessing the mapping to types occurs at the final stage, and there is no way to know of the types in the ValueProviderFactories?
Will I have to give up and do the conversion in the actions?
There are a few questions here.
For how JavaScriptSerializer
works you should read the documentation. The class tries to infer the type for basic types (int, bool, date, etc) and returns Dictionary<string, object>
for more complex cases. In addition if the JSON blob contains a special property called "__type" then the deserializer will try to create an object of that type.
Now for how this works in MVC. The process of mapping values from a request to an object instance used in your controller is called model binding. This is split into two components: ModelBinder and ValueProviders. The model binder knows the target type (e.g. Product), tries to create an instance of it, and then populate its properties with values from the request. It does so by asking the ValueProviders. For example, to set the Name property on the Product instance it asks the value providers for the value of "Name". The value providers get queried in sequence and return a match (from query string, post data, JSON request body, etc).
There's lots of resources about this on the web, but in short value providers shouldn't really be concerned with types.
Here is a quick stab at a solution using a ModelBinder, as suggested by marcind. It's untested, but it's a start. In this case FromProtobuf<T>
is a simple byte[]
to object extension method.
public class ProtobufModelBinder<T> : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/x-protobuf", StringComparison.OrdinalIgnoreCase))
return null;
using (MemoryStream ms = new MemoryStream())
{
controllerContext.HttpContext.Request.InputStream.CopyTo(ms);
return ms.ToArray().FromProtobuf<T>();
}
}
}
This could be setup as follows:
ModelBinders.Binders.Add(typeof(MyClass), new ProtobufModelBinder<MyClass>());