Model Binding in MVC refers to how HTTP Requests are intercepted by ASP.NET Core Middleware and translated to the signatures of MVC Controllers that you define and subsequently to represented model objects on those signatures. Consider the following example:
Model binding defines how the POST message body is translated to the “User” object, and similarly how the incoming route with a user ID is connected to the integer of the method signature titled “UserId”. Model binding conventions and attributes in ASP.NET Core MVC usually work very well. There is a plethora of flexibility and options that generally make defining API contracts in .NET Core a real pleasure. For an overview of model binding and to get started, have a look at:
As good as it is, there are definitely times when you may want to customize how certain signatures are translated to objects from incoming HTTP requests. Sometimes you want to make a really convenient way of mapping certain objects, or other times you want to validate incoming text for custom business rules. Either way, it is pretty flexible to implement a custom model binding object.
Create a class that extends IModelBinder:
Apply the new Model Binder to your Controller method Signature:
Creating custom model binding logic is very flexible since it is defined programmatically, outside of the aspect-oriented and declarative syntax. There ARE MANY more ways of applying custom model binding directly to model objects (POCO class objects, etc). For more information get started at: https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-2.1
*NOTE: ASP.NET Core has pretty flexible and complex model binding built in. Just when I think I may need a custom model binder, I find a way to use an existing syntax option to make it work the way I need. Once in a while, this custom flexibility may be useful. However, I’d highly encourage looking for out of the box ways to bind what you need first, and only when necessary build something custom. Writing and unit testing a good custom model binder can be very tedious but worth it for the right scenario.
*In this particular case, we struggled greatly looking for a resolution to how we wanted to bind the model, but could not find one.
Our organization standard is to use “snake_case” for JSON serialization. However, when describing known keys in an enumeration we use “kebab-case”. In the creation of a user preference service, the preference type is an enumeration that we wanted such behaviour. By default, .NET seems to serialize Enum values no problem into any associated value specified in an “EnumMember” attribute (allowing for complete customization of the value). But there seems to be no way in ASP.NET Core for it to deserialize according to the EnumMember attribute, out of the box, for root query parameters in MVC.
A look at the simple Enum:
The following is an example MVC controller signature where we want to accept an optional query value for the user preference type, and have it deserialize from the “kebab-case” values above into the Enum.
The following HTTP request will yield the preferenceTypeKey always being NULL using the “kebab-case”. I could pass in: “LandingPageSortOrder” instead and it would work just fine. But that is not the nicely fine-tuned contract I was hoping to expose.
Now, let’s create a custom model binder to handle this…
This custom model binder has a number of behaviours to be aware of:
– Validates that the Model being bound to is in fact a type of Enum.
– Safely pulls the value out of the query string that matches the same name as the field name that it is bound too (meaning we can use this model binder in the same signature multiple times).
– The string is dynamically converted to a Nullable Enum matching the bound type using reflection (meaning we can use for any Enum).
– If the provided value does not match an EnumMember in the enum or is not provided on the query string, a custom model error is added, and model binding triggers a failure on the context. But notice this only happens if “IsNullableValueType”. This allows us to use nullable Enum or not and have that enforce model validation failure if not provided or incorrect. The model validation message could easily be updated to include the possible enum options as well.
Notice the plethora of information available to you inside the binding context. I would encourage you to debug and take a look at it. The ModelMetadata specifically provides a great deal of information about the binding source, the property (reflection), the HTTP context, etc.
Finally, all we have to do is update the method signature to use the custom model binder:
Or similarly, if I wanted it to be a required I could use the following signature: