uadrive's answer took me part of the way, but there were still some gaps. Without any data in the input to new NameValueCollectionValueProvider()
, the model binder will bind the controller to an empty model, not to the model
object.
That's fine -- just serialise your model as a NameValueCollection
, and then pass that into the NameValueCollectionValueProvider
constructor. Well, not quite. Unfortunately, it didn't work in my case because my model contains a collection, and the NameValueCollectionValueProvider
does not play nicely with collections.
The JsonValueProviderFactory
comes to the rescue here, though. It can be used by the DefaultModelBinder
as long as you specify a content type of "application/json
" and pass your serialised JSON object into your request's input stream (Please note, because this input stream is a memory stream, it's OK to leave it undisposed, as a memory stream doesn't hold on to any external resources):
protected void BindModel<TModel>(Controller controller, TModel viewModel)
{
var controllerContext = SetUpControllerContext(controller, viewModel);
var bindingContext = new ModelBindingContext
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => viewModel, typeof(TModel)),
ValueProvider = new JsonValueProviderFactory().GetValueProvider(controllerContext)
};
new DefaultModelBinder().BindModel(controller.ControllerContext, bindingContext);
controller.ModelState.Clear();
controller.ModelState.Merge(bindingContext.ModelState);
}
private static ControllerContext SetUpControllerContext<TModel>(Controller controller, TModel viewModel)
{
var controllerContext = A.Fake<ControllerContext>();
controller.ControllerContext = controllerContext;
var json = new JavaScriptSerializer().Serialize(viewModel);
A.CallTo(() => controllerContext.Controller).Returns(controller);
A.CallTo(() => controllerContext.HttpContext.Request.InputStream).Returns(new MemoryStream(Encoding.UTF8.GetBytes(json)));
A.CallTo(() => controllerContext.HttpContext.Request.ContentType).Returns("application/json");
return controllerContext;
}