Custom Model Binders are a super helpful tool in “binding” a view model in unconventinal ways. There are a few reasons you may find yourself using a custom model binder.
- You are taking in values in seperated out fields, but want to combine them into a single object/struct.
- The model the front end is sending is actually nothing like the backend model, and neither side can change for some reason. (We’ve all been there!)
- You need to “Supplement” your model with outside data that isn’t coming in the payload (e.g. a cookie, a value from a database/config etc).
We will be focussing on the first reason as it’s the easiest to demonstrate. However once you have the hang of a custom model binder the possibilities really open up and there is no limit to what you can bind.
In our example, we are requesting a URL like the following :
http://www.mydomain.com/date?day=1&month=2&year=1985
This might happen if you have separated out select boxes for your dates for whatever reason. You end up with each portion of the date coming through individually. On the backend this can be a pain because we want to deal with a single Datetime object. We could receive all of these individually and build them up in the action, or we could “bind” them before it even reaches the action. Let’s see how.
First, we build our ViewModel of what we want to receive. In our case it’s a simple class with a single property :
public class MyViewModel { public DateTime MyDate { get; set; } }
Next we build a “ModelBinder” that inherits from IModelBinder. This might look like the following :
public class MyViewModelBinder : IModelBinder { public Task BindModelAsync(ModelBindingContext bindingContext) { var day = 0; var month = 0; var year = 0; if(!int.TryParse(bindingContext.ActionContext.HttpContext.Request.Query["day"], out day) || !int.TryParse(bindingContext.ActionContext.HttpContext.Request.Query["month"], out month) || !int.TryParse(bindingContext.ActionContext.HttpContext.Request.Query["year"], out year)) { return Task.CompletedTask; } var result = new MyViewModel { MyDate = new DateTime(year, month, day) }; bindingContext.Result = ModelBindingResult.Success(result); return Task.CompletedTask; } }
Notice that in this case we reach into the query string to read our values, but this could be from anywhere.
In the latest version of ASP.NET core, we also need to use a provider. A provider tells ASP.NET core when to use our custom binder. For us it’s very simple. If the model we are binding to is of type “MyViewModel” then use our binder. Of course this can be much more complicated and can even return different binders based on code within the provider. Our provider looks like the following :
public class MyViewModelBinderProvider : IModelBinderProvider { public IModelBinder GetBinder(ModelBinderProviderContext context) { if (context.Metadata.ModelType == typeof(MyViewModel)) return new MyViewModelBinder(); return null; } }
Simple!
Next we just need to let ASP.NET Core know that we have a model binder ready for use. You do this in your startup.cs file in the ConfigureServices method. Note that we “insert” our model binder provider at the start of the list. This is because it’s first in first served, so we want to make sure that our modelbinder is always the first to run if possible.
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(config => config.ModelBinderProviders.Insert(0, new MyViewModelBinderProvider()) ); }
Finally, create your action in your controller that asks for “MyViewModel”. Something like the following :
[HttpGet("")] public string Get(MyViewModel viewModel) { if (viewModel == null) return "Woops. We received null!"; return $"The date you sent was {viewModel.MyDate}"; }
And all going well, when you call your URL you should see :
The date you sent was 1/02/1985 12:00:00 AM
As you can see, Model Binding really isn’t that hard. It may seem a bit overkill at first, but simply knowing it exists means that when that one time you actually need it comes up, you have it ready to use at your disposal.