-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Description
As a framework/library developer I find it difficult to apply 'MvcOptions' to only my controllers or areas since the documented and preferred way to do this is to apply MvcOptions globally. Ideally you could apply MvcOptions at an "Area" level (but that's a topic for another day). So in order to apply various MvcOptions to only my controllers I need to resort to using base classes and attributes which isn't very pretty. However I've found that ModelBinding and IInputFormatter's extensibility are particularly limited especially for binding the Body of a request.
For example, I don't want to apply this (AddNewtonsoftJson
) globally, I just want the NewtonsoftJsonInputFormatter
applied for my own controllers for body model binding.
services.AddMvc()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
But this is very difficult to make possible because:
It is not possible to create anIModelBinderProvider
to target specific controllers. This is because the currentControllerActionDescriptor
does not flow to theModelBinderProviderContext
even though aControllerActionDescriptor
is available when this whole process takes place withinControllerBinderDelegateProvider
. If a model binder had access to theControllerActionDescriptor
then it would be possible to create a model binder specifically for certain controllers/assemblies/etc...- It is not possible to assign a custom
IModelBinderFactory
to theControllerBase.ModelBinderFactory
in it's ctor to be used for the actual model binding process. This was my last resort and I assumed this would work but actually assigningControllerBase.ModelBinderFactory
doesn't actually do anything apart from affect howControllerBase.TryUpdateModelAsync
works which isn't used for the actual model binding process (also mentioned here TryUpdateModelAsync not working in ASP․NET Core 3.1 #17876 and TryUpdateModelAsync() doesn't work for ASP.NET Core MVC Web API aspnet/Mvc#5659 (comment))
I know that I can assign [ModelBinder] attributes to my action parameters but I don't want to do that in a variety of cases. I know I can create a custom IModelBinderProvider
and inspect the type being bound and only return a result for my own types, but that is also not what I need since I need controller specific model binders, not just model specific.
It seems like this area of MVC is very limiting but doesn't need to be since all of the info necessary to make this flexible is already there. I definitely would have thought that at least assigning an instance to ControllerBase.ModelBinderFactory
that it would actually be used in the model binding process.
I have figured out how to do this though but it's quite a process to do:
- Create a custom IModelBinder that you want to use to bind
- Create a custom
IApplicationModelProvider
very similar to ApiBehaviorApplicationModelProvider- In my case I use a custom attribute assigned to my controllers similar to how the
[ApiController]
is used inApiBehaviorApplicationModelProvider
- In my case I use a custom attribute assigned to my controllers similar to how the
- Ensure that the
InferParameterBindingInfoConvention
convention is applied like this which will assignBindingSource.Body
as theBindingSource
to any complex parameter - Create a new
IActionModelConvention
and apply that convention afterInferParameterBindingInfoConvention
. The source can look like:public class MyBodyModelBinderConvention : IActionModelConvention { public void Apply(ActionModel action) { foreach (var p in action.Parameters .Where(p => p.BindingInfo?.BindingSource == BindingSource.Body)) { p.BindingInfo.BinderType = typeof(MyBodyModelBinder); } } }
- Configure DI:
services.TryAddSingleton<MyBodyModelBinder>();
services.TryAddEnumerable(ServiceDescriptor.Transient<IApplicationModelProvider, MyApplicationModelProvider>());
That's quite a process :) It's fine now that I've got it but for simplicity, I would expect that ControllerBase.ModelBinderFactory
is just used to all the model binding including binding the body.
Further technical details
- ASP.NET Core version: 3.1
- The IDE: VS 2019 enterprise preview