Есть ли способ отключить JSON ModelBinder в ASP.NET MVC 3 RC2?

В ASP.NET MVC 3 RC2 по умолчанию ModelBinder автоматически анализирует тело запроса, если для параметра Content-Type установлено значение application/json. Проблема в том, что в конце потока остается Request.InputStream. Это означает, что если вы попытаетесь прочитать входной поток, используя свой собственный код, сначала reset верните его в начало:

// 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();

Поскольку я использую Json.NET для выполнения сериализации/десериализации, я бы хотел отключить стандартный ModelBinder по умолчанию для этого дополнительного анализа. Есть ли способ сделать это?

Ответ 1

В приложении Global_asax можно добавить следующее:

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

Это предполагает, что существует только один из этого типа (который по умолчанию есть), но его можно легко изменить, чтобы работать, если его больше одного. Я не верю, что есть более чистый способ, если это то, что вы ищете.

Ответ 2

Я, очевидно, довольно поздно отвечаю на это, но я разработал способ изменить IValueProvider для определенного действия в MVC5. Я не пытался понять, возможно ли это в MVC3, поскольку этот вопрос старый, но я предполагаю, что он несколько схож.

Отказ от ответственности: это не очень.

Сначала мы создаем новый интерфейс, который мы можем реализовать в атрибуте для создания конкретных действий:

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

Затем мы создаем пользовательский ControllerActionInvoker (или AsyncControllerActionInvoker, если вы используете async), чтобы подключить наш новый интерфейс:

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;
    }
}

Теперь мы должны реализовать пользовательский DefaultControllerFactory для установки 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;
    }
}

Наконец, мы устанавливаем наш пользовательский контроллер factory как значение по умолчанию в код запуска:

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

и реализуем наш интерфейс IActionConfigurator в пользовательском атрибуте:

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);
    }
}

Поскольку для каждого запроса создается новый экземпляр Controller, мы можем установить определенные для конкретного действия значения на контроллере, чтобы изменить способ обработки MVC действием.

[AcceptVerbs(HttpVerbs.Post)]
[IgnoreJsonActionConfigurator]
public async Task<ActionResult> Foo() { ... }