Is there any way to disable the JSON ModelBinder in ASP.NET MVC 3 RC2?
Asked Answered
C

2

11

In ASP.NET MVC 3 RC2, the default ModelBinder will automatically parse the request body if the Content-Type is set to application/json. Problem is, this leaves the Request.InputStream at the end of the stream. This means that if you try to read the input stream using your own code, you first have reset it back to the beginning:

// client sends HTTP request with Content-Type: application/json and a JSON
// string in the body

// requestBody is null because the stream is already at the end
var requestBody = new StreamReader(Request.InputStream).ReadToEnd();

// resets the position back to the beginning of the input stream
var reader = new StreamReader(Request.InputStream);
reader.BaseStream.Position = 0;
var requestBody = reader.ReadToEnd();

Since I'm using Json.NET to do my serialization/deserialization, I'd like to disable the default ModelBinder from doing this extra parsing. Is there any way to do that?

Combes answered 21/12, 2010 at 4:16 Comment(0)
H
17

You can put the following in Application_Start in your Global.asax:

ValueProviderFactories.Factories.Remove(
            ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().First());

This assumes there is only one of that type (which by default there is), but it can easily be changed to work if there is more than one. I don't believe there is a cleaner way if that is what you are looking for.

Handclasp answered 21/12, 2010 at 4:27 Comment(6)
Thanks, that did the trick! Is this an undocumented feature, or did I just miss something?Combes
MVC includes a set of value providers factories by default. With MVC3, they included the JsonValueProviderFactory as one of the defaults. All the above code does is find it and remove it when the application starts. Another way around it could have been not to have your ajax requests use the content type application/json, but I feel that removing the value provider factory is probably more correct.Handclasp
Thanks for the explanation. I wasn't even aware there were provider factories for things like querystrings and routes. I'd assumed that the ModelBinder handled all of that.Combes
Nope, my understanding is that the value providers essentially parse the request data, and the model binders then take the parsed data and fill the values of the models.Handclasp
It would still be nice to disable this binder for a specific action instead of disabling it completely.Mosqueda
@Mosqueda Almost 6 years later, but I've added an answer that should allow you to disable the value provider for a specific action.Grus
G
1

I'm obviously pretty late in answering this, but I've developed a way to change the IValueProvider for a specific action in MVC5. I haven't gone through the effort of seeing if this is possible in MVC3 since this question is old, but I assume it is somewhat similar.

Disclaimer: It's not pretty.

First, we create a new interface we can implement in an attribute to make action-specific configurations:

internal interface IActionConfigurator
{
    void Configure(ControllerContext controllerContext, ActionDescriptor actionDescriptor);
}

Then, we create a custom ControllerActionInvoker (or AsyncControllerActionInvoker if you use async) to hook up our new interface:

internal sealed class CustomControllerActionInvoker : AsyncControllerActionInvoker
{
    protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
    {
        var actionDescriptor = base.FindAction(controllerContext, controllerDescriptor, actionName);
        var configurators = actionDescriptor.GetCustomAttributes(typeof(IActionConfigurator), true).Cast<IActionConfigurator>();
        foreach (var configurator in configurators)
            configurator.Configure(controllerContext, actionDescriptor);
        return actionDescriptor;
    }
}

Now, we have to implement a custom DefaultControllerFactory to set Controller.ActionInvoker:

internal sealed class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        var instance = base.GetControllerInstance(requestContext, controllerType);
        var controller = instance as Controller;
        if (controller != null)
            controller.ActionInvoker = new CustomControllerActionInvoker();
        return instance;
    }
}

Finally, we set our custom controller factory as the default in the startup code:

ControllerBuilder.Current.SetControllerFactory(typeof(CustomControllerFactory));

and implement our IActionConfigurator interface in a custom attribute:

internal sealed class IgnoreJsonActionConfiguratorAttribute : Attribute, IActionConfigurator
{
    public void Configure(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        // Here we can configure action-specific stuff on the controller
        var factories = ValueProviderFactories.Factories.Where(f => !(f is JsonValueProviderFactory)).ToList();
        controllerContext.Controller.ValueProvider = new ValueProviderFactoryCollection(factories).GetValueProvider(controllerContext);
    }
}

Since a new Controller instance is created on each request, we are able to set action-specific values on the controller to modify how MVC processes the action.

[AcceptVerbs(HttpVerbs.Post)]
[IgnoreJsonActionConfigurator]
public async Task<ActionResult> Foo() { ... }
Grus answered 27/3, 2017 at 13:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.