.Net Core Custom Binding
By: Tom Chizek
Tags: Dotnet Core, How To, Csharp Programming, Software Development, Model View Controller

Image by <a href=”https://pixabay.com/users/RAEng_Publications-11384528/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=4690505">RAEng_Publications</a> from <a href=”https://pixabay.com/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=4690505">Pixabay</a>

Introduction

In .Net Core, there are two types of custom binding, binding to Entity objects in Entity Framework and binding to Model properties in an MVC application. Both are for translating objects to and from the raw data that is needed by the underlying model into something useable by the framework, be it a database or web-page. In general, Entity Framework binding is the more straightforward of the two, so I will cover that briefly in the first section. While the View to Model binding is more complex, having some pitfalls that are not obvious at first glance. Because this type of binding is inherently more complicated, I will be spending more time describing how to write and configure the supporting classes for using this binding.

Binding Entity Framework Entity Objects

Binding this is straightforward. The purpose is to provide some conversion from your object to some object that the underlying database can handle. The underlying object can be a string, an XML object, a binary, anything that you want to stuff into an entity in the database that you can convert reliably back to your object. For usability, I decided to write most of the Entities to the database as strings in a format that parseable by any of the classes. This was a good balance between human-readable data in the database and a clean interface in the code. For each entity, the block of code that does this looks like:

builder.Entity<Star>().Property(p => p.Mass)

.HasConversion(m => m.ToString(), s => Mass.Parse(s));

builder.Entity<Star>().Property(p => p.Radius)

.HasConversion(m => m.ToString(), s => Length.Parse(s));

builder.Entity<Star>().Property(p => p.OrbitalDistance)

.HasConversion(o => o.ToString(), s => Length.Parse(s));

builder.Entity<Star>().Property(ss => ss.Location)

.HasConversion(l => l.ToString(), s => Point3D.Parse(s));

builder.Entity<Star>().Property(p => p.Temperature)

.HasConversion(t => t.ToString(), s => Temperature.Parse(s));

To have this work, ToString() must produce an output string that Parse() can well parse. Also, Parse must create a correct value for the object it is parsing or throw an Invalid Operation Exception if the string is recognizably not able to be parsed. Failing that return null if there is some other exception while attempting to parse the string.

The only caveat with this particular choice is that string operations are time-consuming. So, I chose to trade off some database performance for having my data be human-readable in the database table. This is a tradeoff that needs to be decided by each database designer for their specific application.

This definition is needed for each property on each entity that is not mappable to a database type. These definitions are made in the Database Context OnModelCreateing() method, just like all of the other configurations for the Framework model. There is enough to configuring an Entity Model that discussing it will take its own article to cover in any depth.

For now, this gives a basic idea of how to configure conversions for Entity Framework object properties.

Image by <a href=”https://pixabay.com/users/qimono-1962238/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=2125180">Arek Socha</a> from <a href=”https://pixabay.com/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=2125180">Pixabay</a>

Custom binding MVC model properties

The first thing to realize about this custom binding is that it has significantly more moving parts in more places than its Entity Framework relative. The basic idea is the same, define a way to get the data from one form into another form where the underlying system and libraries do not have a direct conversion. However, due to the nature of web applications in general and MVC applications, specifically, the specific implementation is slightly less straightforward.

The first step somewhat similar to its simpler cousin. You need a method to turn the incoming data into the object you are working with. However, it can’t be as direct as calling Parse(), although that is what happens in the end. First, figure out what kind of binding object this is. Then decide what type of object you have bound to. Then decode the binding message, parse the binding message, and parse the value from the binding message, set the result in a response message, and tell the caller if the task has completed successfully or not.

So to do this, you need two classes. The first is a class that inherits from IModelBinder this class must implement the BindModelAsync method. This method receives a ModelBindingContext as a parameter, and the majority of this method involves interpreting this object to decide if this specific context is both carrying a type you know how to deal with and has a valid value.

I am going to break this method down into parts because it will be easier to explain. First, on entry, make sure that the context is both non-null and has an interface we know how to deal with:

public Task BindModelAsync(ModelBindingContext bindingContext)

{

if (bindingContext is null)

{

throw new ArgumentNullException(

$"{nameof(bindingContext)} must not be null");

}

if (!(bindingContext.Model is IQuantity q))

{

throw new ArgumentException(

$"{bindingContext.ModelMetadata.ModelType} is not an IQuantity");

}

QuantityType quantityType = q.Type;

The above code is directly from the template for this method except that I added the check for IQuantity — the base interface of all the types I am dealing with in this particular handler.

A couple of notes here, QuantityType is an Enum of the various types that IQuantity can take on and q.Type is overridden in each of the inherited objects to return. It’s QuantityType — which is part of the construction of the object, so I know it will be set in every object of this base interface.

Then comes the first thing that makes me think that whoever designed these objects and interfaces was laying traps for the poor suckers who followed behind. This block gets the property name in your model that you are looking for and short returns if there is no property.

// ModelName is a horrible name, this is the Property Name of the property in

// the Model you are looking up. If you use the Model name, or the Type name

// of the object you are looking for this will fail right here due to

// not finding the correct value provider with this lookup.

string propertyName = bindingContext.ModelName;

var valueProviderResult = bindingContext.ValueProvider.GetValue(propertyName); if (valueProviderResult == ValueProviderResult.None)

{

return Task.CompletedTask;

}

It should be fairly obvious since I put in a comment to make sure that no one who had to maintain this code later but the absolutely insanely named “ModelName” is actually the name of the property you are going to be processing. So, this block uses “GetValue(),” which you might assume “gets” the value, well you would be wrong. It retrieves a flag that tells you if there is actually a value available to get. If there isn’t, then return to the caller, that yes, we processed the nothing you sent to us. Then we move on to the next block of code that drives home how insane the designer was who designed this mess.

bindingContext.ModelState.SetModelValue(propertyName, valueProviderResult); var value = valueProviderResult.FirstValue; // Check if the argument value is null or empty

if (string.IsNullOrEmpty(value))

{

Your next step is to set the property value into that variable you created above with GetValue(), and grab the first value out of it. Now I suspect that there are cases where you might want something other than the first value — except I have never seen any cases where there is anything except a first value in this variable. So, the implication being, maybe sometimes you get a Second or Third Value, and you need to know when you need to grab that instead? Anyway, check to see if the value is null, i.e., again, the caller sent in emptiness. If it is null, tell the caller we successfully parsed the nothingness they sent us.

Then we can get to the actual parsing. Which is straightforward, use the QuantityType we got above to figure out which parser to use, use TryParse, so we know if the parse succeeded, then set the return value, and either give an error return or return success with the value we parsed. Either way, the caller gets success as a return value. A general note, I would rather have the caller expect some kind of failure response on failure, but trying to return anything except a completed task caused exceptions and crashes. So, nope just stick with the generic — sure everything is great in here we finished, nope nothing wrong here response.

object retValue;

bool success;

switch (quantityType)

{

case QuantityType.Acceleration:

{

success = Acceleration.TryParse(value, out Acceleration val);

retValue = val;

break;

}

case QuantityType.Area:

{

success = Area.TryParse(value, out Area val);

retValue = val;

break;

}

case QuantityType.Length:

{

success = Length.TryParse(value, out Length val);

retValue = val;

break;

}

case QuantityType.Mass:

{

success = Mass.TryParse(value, out Mass val);

retValue = val;

break;

}

case QuantityType.Speed:

{

success = Speed.TryParse(value, out Speed val);

retValue = val; break;

}

case QuantityType.Temperature:

{

success = Temperature.TryParse(value, out Temperature val);

retValue = val;

break;

} default:

{

throw new NotSupportedException(

$"{quantityType} binding not supported yet");

}

} if (!success)

{

// Non-integer arguments result in model state errors

bindingContext.ModelState.TryAddModelError(

propertyName, $"value must be parsable as a {quantityType}."); return Task.CompletedTask;

} bindingContext.Result = ModelBindingResult.Success(retValue);

return Task.CompletedTask;

So, that gets the parser side done. How do you set up to call this? Well, there are three parts to that, first is an attribute on each property that uses this specific class as it’s binder. This looks like:

[BindProperty(BinderType = typeof(QuantityValueEntityBinder), Name = "Temperature")]

public Temperature Temperature { get; set; }

You have to tell it what the type of the binder object that knows how to deal with this property type is and what the name of the property that you are binding to is. This may not be the best example because I have both type and property named Temperature. To be clear, what I am referring to is the property name that is set in the Name field of the Bind Property attribute.

If you stop here, nothing will happen, and you will spend time pulling your hair out, wondering why the hell this doesn’t work as advertised. The reason you might stop here is that I found several articles about this topic that got this far and stopped. But I am not going to do that. There are more steps, the next one is to write another class. This is a relatively simple class that does the binding between the model property and the class we wrote above. This class inherits from IModelBinderProvider and must implement the GetBinder() method. This method accepts a ModelBinderProviderContext as a parameter and returns an IModelBinder. See the connection? This is the factory class that knows how to create the IModelBinder class you created above. Mine looks like:

using Microsoft.AspNetCore.Mvc.ModelBinding;

using UnitsNet;

using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;

using TevandelSupportLibrary.Classes.Interfaces; public class QuantityValueBinderProvider : IModelBinderProvider

{

public IModelBinder GetBinder(ModelBinderProviderContext context)

{

if (context == null)

{

throw new ArgumentNullException(nameof(context));

} if (context.Metadata.ModelType is IQuantity)

{

return new BinderTypeModelBinder(typeof(QuantityValueEntityBinder));

}

else if(context.Metadata.ModelType is IUnit3D)

{

return new BinderTypeModelBinder(typeof(SupportEntityBinder));

}

return null;

}

}

Image by <a href=”https://pixabay.com/users/qimono-1962238/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=1804499">Arek Socha</a> from <a href=”https://pixabay.com/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=1804499">Pixabay</a>

However, you aren’t done yet. I found two or three articles that got this far, and I guess they assumed everyone knew the last step. Well, I didn’t know when I started doing this, and I am going to consider that some of you don’t either. So, the last step, you need to configure the framework to include this factory in its list of binding provider factories. If you miss this step, then nothing works, and you can sit with breakpoints in all of your classes, and they never happen. I’ll let you guess who that happened to, and the first two guesses don’t count.

The configuration happens in the Startup class in the ConfigureServices() method. When you add the Razor Pages service add additional MVC options like this:

services.AddRazorPages()

.AddMvcOptions( options =>

{

options.ModelBinderProviders.Add(new QuantityValueBinderProvider());

});

After you do the above steps then your binder will be called whenever a property of the type you have bound shows up in a binding list in your controllers, like this:

public async Task<IActionResult> Edit(Guid id, [Bind("Id,BarycenterId,Mass,

Radius,Location,StarSystemId,Temperature,

AbsoluteMagnitude,SpectralClass,OrbitalDistance,Name,Created,

LastUpdated")]Star star)

The significant bit in this line is that the Temperature object in the binding will be parsed, allowing for entry of values like “75 F” or “21 C” as valid temperatures that will be converted to the appropriate temperature value. This might seem like a minor detail, but by hiding this detail from the people who write the controller and the view, it simplifies the use of the Temperature object. Extend that across all of the other unit types such as Mass, Distance, Speed, etc. and you get greater functionality for less effort even if the same person writes both sets of code. Write it once, test it once, use it many times.

Conclusion

As sarcastic as I might have been in parts of the description of building the binding object structure, it is a tool that you should be aware of and use. It opens the possibility of using custom objects on your user interface without adding masses of code in your controller or middleware to support them. It lets you write and test code, put it into a package or library. Then to use it across multiple projects, enhancing your productivity and allowing you to write the exciting parts rather than rewriting the same code to do the same thing in every project. These types of tools and features are the things that make the difference between experienced software engineers who know their environment and novice’s who write the same block of code in every project. It may not be pleasant or fair, but when I see repeated code that doesn’t have some underlying purpose, I start looking for bugs related to inexperience.

Frankly, I even do this in my code. If I find myself repeating the same block of code more than two or three times, I will look hard at why I am repeating it and look for a way to factor it out into a base class, common method, or at least an interface. If two types have enough in common that I am writing the same method twice, then what else do they have in common? Do I really need two classes? Or would one do? Or is this a base class with two derived classes with the commonalities implemented in the base? These are all questions that experience makes me ask while I am architecting, designing, writing, testing, or even deploying projects. I may not be able to make the changes to that specific project, but it is something to keep in mind for the next project, or the one after that.

Good luck, and have fun.