@Shazwazza

Shannon Deminick's blog all about web development

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.