@Shazwazza

Shannon Deminick's blog all about web development

Controller Scoped Model Binding in ASP.NET Core

May 12, 2020 01:51
Controller Scoped Model Binding in ASP.NET Core

Want to avoid [FromBody] attributes everywhere? Don’t want to use [ApiController] strict conventions? Don’t want to apply IInputFormatter’s globally?

ASP.NET Core MVC is super flexible but it very much caters towards configuring everything at a global level. Perhaps you are building a framework or library or a CMS in .NET Core? In which case you generally want to be as unobtrusive as possible so mucking around with global MVC configuration isn’t really acceptable. The traditional way of dealing with this is by applying configuration directly to controllers which generally means using controller base classes and attributes. This isn’t super pretty but it works in almost all cases from applying authorization/resource/action/exception/result filters to api conventions. However this doesn’t work for model binding.

Model binding vs formatters

Model binding comes in 2 flavors: formatters for deserializing the request body (like JSON) into models  and value providers for getting data from other places like form body, query string, headers, etc… Both of these things internally in MVC use model binders though typically the language used for binding the request body are called formatters. The problem with formatters (which are of type IInputFormatter) is that they are only applied at the global level as part of MvcOptions which are in turn passed along to a special model binder called BodyModelBinder. Working with IInputFormatter at the controller level is almost impossible.

There seems to be a couple options that look like you might be able to apply a custom IInputFormatter to a specific controller:

  • Create a custom IModelBinderProvider – this unfortunately will not work because the ModelBinderProviderContext doesn’t provide the ControllerActionDescriptor executing so you cannot apply this provider to certain controllers/actions (though this should be possible).
  • Assign a custom IModelBinderFactory to the controller explicitly by assigning ControllerBase.ModelBinderFactory in the controllers constructor – this unfortunately doesn’t work because the ControllerBase.ModelBinderFactory isn’t used for body model binding

So how does [ApiController] attribute work?

The [ApiController] attribute does quite a lot of things and configures your controller in a very opinionated way. It almost does what I want and it somehow magically does this

[FromBody] is inferred for complex type parameters

That’s great! It’s what I want to do but I don’t want to use the [ApiController] attribute since it applies too many conventions and the only way to toggle these …. is again at the global level :/ This also still doesn’t solve the problem of applying a specific IInputFormatter to be used for the model binding but it’s a step in the right direction.

The way that the [ApiController] attribute works is by using MVC’s “application model” which is done by implementing IApplicationModelProvider.

A custom IApplicationModelProvider

Taking some inspiration from the way [ApiController] attribute works we can have a look at the source of the application model that makes this happen: ApiBehaviorApplicationModelProvider. This basically assigns a bunch of IActionModelConvention’s: ApiVisibilityConvention, ClientErrorResultFilterConvention, InvalidModelStateFilterConvention, ConsumesConstraintForFormFileParameterConvention, ApiConventionApplicationModelConvention, and InferParameterBindingInfoConvention. The last one InferParameterBindingInfoConvention is the important one that magically makes complex type parameters bind from the request body like JSON like good old WebApi used to do.

So we can make our own application model to target our own controllers and use a custom IActionModelConvention to apply a custom body model binder:


public class MyApplicationModelProvider : IApplicationModelProvider
{
    public MyApplicationModelProvider(IModelMetadataProvider modelMetadataProvider)
    {
        ActionModelConventions = new List<IActionModelConvention>()
        {
            // Ensure complex models are bound from request body
            new InferParameterBindingInfoConvention(modelMetadataProvider),
            // Apply custom IInputFormatter to the request body
            new MyModelBinderConvention()
        };
    }

    public List<IActionModelConvention> ActionModelConventions { get; }

    public int Order => 0;

    public void OnProvidersExecuted(ApplicationModelProviderContext context)
    {
    }

    public void OnProvidersExecuting(ApplicationModelProviderContext context)
    {
        foreach (var controller in context.Result.Controllers)
        {
            // apply conventions to all actions if attributed with [MyController]
            if (IsMyController(controller))
                foreach (var action in controller.Actions)
                    foreach (var convention in ActionModelConventions)
                        convention.Apply(action);
        }
    }

    // returns true if the controller is attributed with [MyController]
    private bool IsMyController(ControllerModel controller)
        => controller.Attributes.OfType<MyControllerAttribute>().Any();
}

And the custom convention:


public class MyModelBinderConvention : IActionModelConvention
{
    public void Apply(ActionModel action)
    {
        foreach (var p in action.Parameters
            // the InferParameterBindingInfoConvention must execute first,
            // which assigns this BindingSource, so if that is assigned
            // we can then assign a custom BinderType to be used.
            .Where(p => p.BindingInfo?.BindingSource == BindingSource.Body))
        {
            p.BindingInfo.BinderType = typeof(MyModelBinder);
        }
    }
}

Based on the above application model conventions, any controller attributed with our custom [MyController] attribute will have these conventions applied to all of it’s actions. With the above, any complex model that will be bound from the request body will use the IModelBinder type: MyModelBinder, so here’s how that implementation could look:


// inherit from BodyModelBinder - it does a bunch of magic like caching
// that we don't want to miss out on
public class MyModelBinder : BodyModelBinder
{
    // TODO: You can inject other dependencies to pass to GetInputFormatter
    public MyModelBinder(IHttpRequestStreamReaderFactory readerFactory)
        : base(GetInputFormatter(), readerFactory)
    {
    }

    private static IInputFormatter[] GetInputFormatter()
    {  
        return new IInputFormatter[]
        {
            // TODO: Return any IInputFormatter you want
            new MyInputFormatter()
        };
    }
}

The last thing to do is wire it up in DI:


services.TryAddSingleton<MyModelBinder>();            
services.TryAddEnumerable(
    ServiceDescriptor.Transient<IApplicationModelProvider,
    MyApplicationModelProvider>());

That’s a reasonable amount of plumbing!

It could certainly be simpler to configure a body model binder at the controller level but at least there’s actually a way to do it. For a single controller this is quite a lot of work but for a lot of controllers the MVC “application mode” is quite brilliant! … it just took a lot of source code reading to figure that out :)

Model binding with FromServices in ASP.Net 5

December 8, 2014 03:34

Here’s a new nifty feature I found in ASP.Net 5 – you can construct your model during model binding with IoC without any additional work. This is available on the dev branch on GitHub and is based on something called ServicesModelBinder. This is actually pretty cool because it means that you can have a model wired up with all of it’s dependencies based on IoC and then bound to your controller action’s parameters.

FromServices attribute

There’s a new attribute called FromServices which is what you use to enable this functionality. For example:

public async Task<ActionResult> GetProduct(
    [FromServices]ProductModel product)
{

}

What this attribute does is tell MVC to bind the model using IServiceActivatorBinderMetadata which is what the FromServices attribute implements. This in turn tells MVC to lookup the model binder that is aware of IServiceActivatorBinderMetadata which happens to be the ServicesModelBinder.

ServicesModelBinder

This model binder is pretty simple, it’s just going to resolve the model type from your IoC container. That could be pretty useful if you need to build up your model properties based on other services. I think some people might argue that this isn’t great practice because this is putting the binding logic in to the model itself instead of using a separate class to perform the binding logic. I suppose it’s up to the individual developer as to what their preference is. You can of course still create your own IModelBinder and use the ModelBinderAttribute to keep the model binding logic in the binder itself.

Incoming route values

Since the model is being created from IoC, how do you get the current route values to build up your model? To do that you’d put a constructor dependency on IContextAccessor<ActionContext>. This will give you all of the current route values, HttpContext, etc… basically everything you’d need to pull the data out of the current request to build your model.

Example

Given the above GetProduct example, the ProductModel class could look like:

public class ProductModel
{
    public ProductModel(IContextAccessor<ActionContext> action, IProductService prodService)
    {
        //TODO: Do some error checking...
        var productId = action.Value.RouteData.Values["product"];
        Value = prodService.Get(productId);
    }

    public IProduct Value { get; private set; }
}

This is pretty simple – the IProduct is looked up from the IProductService based on the incoming ‘product’ route value. Then in the controller you could just do: product.Value to get the value for IProduct.

You then need to ensure that both IProductService and ProductModel are registered as services in your container and it’s really important that the ProductModel is registered as a Transient object

MVC Attributes - ReadOnly vs Editable vs Bind & the DefaultModelBinder

December 6, 2010 22:13

I’ve been learning quite a lot about the DefaultModelBInder and it’s internal behaviour because of the complexities involved with some of the model binding in Umbraco v5 and wanted to point out the behaviour of the following attributes when it comes to how the DefaultModelBinder works:

Each of these attributes ‘seems’ like they serve the same purpose by making properties of your model read only, not-bindable, not-editable or vice versa. In my mind they all seem like they are the same but that’s just my opinion, as there could be some other hidden secret in the .Net framework which differentiates between these explicitly. The DefaultModelBinder definitely doesn’t treat each of these the same, in fact the DefaultModelBinder doesn’t check for the Editable attribute on properties. Before the DefaultModelBinder binds all of your model’s properties (If you have a complex model), it creates a new ModelBindingContext which contains a new PropertyFilter to exclude any properties that have been defined as ReadOnly or not bindable.

There’s a few internal/private methods of the DefaultModelBinder that do these lookups:

This one creates the new model binding context and checks if any BindAttributes have been declared for the model, if so, then it creates a new property filter which combines the BindAttribute filter and the standard model binder filter:

internal ModelBindingContext CreateComplexElementalModelBindingContext(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) { BindAttribute bindAttr = (BindAttribute)GetTypeDescriptor(controllerContext, bindingContext).GetAttributes()[typeof(BindAttribute)]; Predicate<string> newPropertyFilter = (bindAttr != null) ? propertyName => bindAttr.IsPropertyAllowed(propertyName) && bindingContext.PropertyFilter(propertyName) : bindingContext.PropertyFilter; ModelBindingContext newBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, bindingContext.ModelType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = newPropertyFilter, ValueProvider = bindingContext.ValueProvider }; return newBindingContext; }

This method is called to do the actual binding of each property, but as you can see it only binds properties that have been filtered:

private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) { IEnumerable<PropertyDescriptor> properties = GetFilteredModelProperties(controllerContext, bindingContext); foreach (PropertyDescriptor property in properties) { BindProperty(controllerContext, bindingContext, property); } }

The GetFilteredModelProperties method gets all of the unfiltered PropertyDescriptor’s of the model, and then calls ShouldUpdateProperty to perform the actual filtering (which passes in the new ModelBindingContext’s PropertyFilter which is now based on the Bind attribute if one was declared on the model):

protected IEnumerable<PropertyDescriptor> GetFilteredModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) { PropertyDescriptorCollection properties = GetModelProperties(controllerContext, bindingContext); Predicate<string> propertyFilter = bindingContext.PropertyFilter; return from PropertyDescriptor property in properties where ShouldUpdateProperty(property, propertyFilter) select property; }

The ShouldUpdateProperty method does the following:

  1. Check if the PropertyDescriptor is read only. This is determined by whether or not the ReadOnly attribute was specified on the model property, the Editable attribute doesn’t seem to affect this value. The first check also makes a call to CanUpdateReadonlyTypedReference which checks for value types such as guid, int, string, and ensures they are excluded if IsReadOnly is true.
  2. Check if the property filter allows the property to be bound which is based on the Bind attribute (if supplied on the model)
private static bool ShouldUpdateProperty(PropertyDescriptor property, Predicate<string> propertyFilter) { if (property.IsReadOnly && !CanUpdateReadonlyTypedReference(property.PropertyType)) { return false; } // if this property is rejected by the filter, move on if (!propertyFilter(property.Name)) { return false; } // otherwise, allow return true; }

So the result of what can actually be bound in the DefaultModelBinder is:

  • Any property that doesn’t have [ReadOnly(true)] specified
  • Any property that is referenced as an Include using the Bind attribute on the model containing the property:
    • [Bind(Include = “MyProperty”)]
  • Any property that is NOT referenced as an Exclude using the Bind attribute on the model containing the property:
    • [Bind(Exclude = “NotThisProperty”)]

So it appears that even if you set the an editable attribute as [Editable(false)], this property will still be included for binding.

The DefaultModelBinder is quite a crazy beast and contains a lot of functionality under the hood. What I’ve mentioned above is based purely on experience and looking at the source code so if you have any feedback or feel that any of this is in error please comment!

Custom MVC ModelBinder with Complex Models/Objects/Interfaces using built in MVC Validation

September 29, 2010 00:49

I’ve been creating some cool stuff using ASP.Net MVC 3 lately and came across a situation where I’d like to have quite a complex model/object bound to an Action on my Controller based on a set of posted values from a form. In order to do this, a custom ModelBinder is necessary to collect the data from the posted values, turn it into my custom object, and bind that object to my Action’s parameter. The easy part is to write code to turn the posted values into my custom object and return it, the tricky part is trying to get the in-built back-end MVC validation working for my model… which is currently using DataAnnotations. I really didn’t feel like writing my own logic to validate my model against DataAnnotations and also didn’t want to write the logic to take into account that DataAnnotations might not be the current developers validation provider of choice. So after much digging through the source of MVC and using Reflector, I finally found the solution.  To better describe the concept, here’s an example of the issue:

Each IMyObject has many properties: IProperty. Each IProperty is of a type: IType and each IType has a model which is used to render out the editor in MVC (EditorFor)

public interface IMyObject { int Id { get; } string Name { get; } IEnumerable<IProperty> Properties { get; } } public interface IProperty { Guid Id { get; set; } string Alias { get; set; } string Name { get; set; } IType DataType { get; } } public interface IType { Guid Id { get; } string Name { get; } string Alias { get; } object EditorModel { get; } }

So my controller’s Action looks something like this:

public override ActionResult Edit(IMyObject obj) { if (!ModelState.IsValid) return View(obj); //Save some data and do other stuff... }

Initially my model binder looks like this which is the ‘easy’ part that converts the posted values into an IMyObject object with all of it’s values filled in:

[ModelBinderType(typeof(IMyObject))] public class EditorModelBinder : DefaultModelBinder { protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { if (modelType.Equals(typeof(IMyObject))) { //get the id from the posted values var id = (int)controllerContext .RouteData .Values .GetRequiredObject("id"); //get the object from my data repository by id var model = GetMyObjectFromMyDataRepository(id); foreach (var p in item.Properties) { //it's the editor model that created the editor //for each property in mvc which means that //it's this data that is being posted back var editorModel = p.DataType.EditorModel; // ... Go convert all of the posted values using the bindingContext // ValueProvider and build up the MyObject object created above // ... (A bunch of code goes here to do the conversion) // Except, now that it's created, how the heck do i run it through // MVC validation? } return model; } return base.CreateModel(controllerContext, bindingContext, modelType); } }

In order for the call in your controller to check if your ModelState.IsValid, something needs to do the validation of the model and put the validation results inside the ModelState object. This of course already exists in the MVC framework and is done with the DefaultModelBinder. The DefaultModelBinder is pretty smart and can figure out how to automagically parse and transform the posted values into the specified model and also run it through the MVC validators so long as the model is simple enough to figure out. When your model consists of interfaces, it generally can’t do much about it because it doesn’t know how to create your interface. It also has problems when the model is complex and contains sub objects of sub objects (like the IMyObject). So how do we tap in to this underlying functionality?

[ModelBinderType(typeof(IMyObject))] public class EditorModelBinder : DefaultModelBinder { protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { if (modelType.Equals(typeof(IMyObject))) { //get the id from the posted values var id = (int)controllerContext .RouteData .Values .GetRequiredObject("id"); //get the object from my data repository by id var model = GetMyObjectFromMyDataRepository(id); foreach (var p in item.Properties) { //it's the editor model that created the editor //for each property in mvc which means that //it's this data that is being posted back var editorModel = p.DataType.EditorModel; //get a binder for the EditorModel IModelBinder binder = this.Binders .GetBinder(model.GetType()); //create a new context for it ModelBindingContext customBindingContext = new ModelBindingContext(); //get the meta data for it customBindingContext.ModelMetadata = ModelMetadataProviders .Current .GetMetadataForType(() => model, model.GetType()); //ensure we use our correct field 'prefix' //(this is optional and depends on if you are using a custom prefix) customBindingContext.ModelName = p.Id.ToString(); //use our existing model state customBindingContext.ModelState = bindingContext.ModelState; //use our existing value provider customBindingContext.ValueProvider = bindingContext.ValueProvider; //do the binding! this will also validate and put the errors into the ModelState for us. model = (object)binder.BindModel(controllerContext, customBindingContext); } return model; } return base.CreateModel(controllerContext, bindingContext, modelType); } }

The concept above is pretty much how a Controller’s TryUpdateModel method works and how it does the underlying validation.