Is there any way to disable the JSON ModelBinder i

2019-03-15 12:20发布

问题:

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?

回答1:

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.



回答2:

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() { ... }