Model Binding

There are a plethora of examples for model binding and, like most examples, they are run through the complicator. So here is the short version.

Model binding is the idea of taking the data that is passed into an API call and massaging it into a form that is expected by the API handler. This is usually done by default. If you have a handler function like this one:

[HttpPost]
[Route ("someroute")]
public IActionResult some_function (SomeType parameters) {
	...
}

And let’s assume SomeType takes the form:

class SomeType {
	public String some_string { get; set; }
	public int some_int { get; set; }
}

And you pass in the following query string:

?some_string=mystringthing&some_int=57

Then the default model binder will take that query string, break it into its component parts, create a SomeType instance and assign those values to the various properties which it then passes to the handler.

Now, let’s assume that there’s a third property in SomeType:

class SomeType {
	public String some_string { get; set; }
	public int some_int { get; set; }
	public String another_string { get; set; }
}

And, whenever this isn’t provided, you want to add:

some_type.another_string = "default another string"

You could simply add this line in your API handler function, but what if you want to do that for every API handler function? That’s where the custom binder comes into play.

There are three steps:

  1. Create the binder class/function.
  2. Create a “provider”.
  3. Register the provider.

The binder class/function is the one that everyone seems to overcomplicate. It’s really very simple. It implements the IModelBinder interface and defines a BindModelAsync function. In the BindModelAsync function it passes a new object to ModelBindingResult.Success function, assigns the output to ModelBindingContext.Result and returns Task.CompletedTask (saying everything is okay). It looks like this:

class [CustomBinder - chose your own name]: IModelBinder {

	public Task BindModelAsync (ModelBindingContext context) {
		SomeType new_object = [Create your new object here, for example:];
		new_object.another_string = "Yay!";
		context.Result = ModelBindingResult.Success (new_object);
		return Task.CompletedTask;
	}// BindModelAsync;
}// CustomBinder;

Easy, no? Here you can do whatever you need to shape your SomeType instance into whatever you like.

Step 2: Create a Provider.

This is a class that implements the IModelBinderProvider interface, and simply says: hey, I have a new binder. This is what it is. And it looks like this:

public class BinderProvider: IModelBinderProvider {

	public IModelBinder? GetBinder (ModelBinderProviderContext context) {
		ArgumentNullException.ThrowIfNull (context);
		return new BinderTypeModelBinder (typeof (CustomBinder));
	}// GetBinder;

}// BinderProvider;

The first line of the GetBinder method checks to make sure that a context (the nuts and bolts of the binder) actually exists and throws an exception if it doesn’t. The second line simply returns a new instance of our custom binder.

Step 3: Register the provider

In your program.cs file (this is for .Net Core – MVC has its own arrangement, which I don’t plan on going into), add the provider when you add controllers. You might have something like:

WebApplicationBuilder builder = WebApplication.CreateBuilder (args);

builder.Services.AddControllers ();

Just modify the AddControllers method to add an arrow function to insert the custom binder into the binder chain (yes, you can have multiple custom binders), like this:

builder.Services.AddControllers (options => options.ModelBinderProviders.Insert (0, new BinderProvider ())).AddNewtonsoftJson ();

This will insert our binder at the root (position 0) of the providers list.

What you see here is the bare minimum to create a custom binder. Also, in the binder method you can get the original query string by accessing:

context.HttpContext.Request.Body

And reading it with a stream reader. Google it. Again, plenty of examples.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *