ASP.NET MVC - How To Dynamically Bind Models At Run-time
What Will I Learn?
- You will learn dynamically bind models at run-time.
Requirements
- ASP.NET MVC
Difficulty
- Intermediate
Tutorial Contents
- Using Case for Dynamic Model Binding
- Dynamic Model Binding
How To Dynamically Bind Models At Run-time
Dynamic model binding enables a single action to handle multiple forms.
ASP.NET MVC model binding automatically converts forms or query strings to action parameters. It binds not only primitive data types, but also complex types. Behind the scene, it is DefaultModelBidner doing all the heavy lifting. Out of the box, DefaultModelBidner supports only static model binding. The bound models must be concrete types spelled out at compile time. It cannot bind to interfaces or abstract classes, whose actual types will not be determined until run-time. In this tutorial, I will show you how to dynamically bind models at run-time. But, the first question is, why do we need dynamic binding in the first place?
Use Case for Dynamic Model Binding
Suppose we have the following hierarchical data types to model different types of pets. All pets have a few common properties, and many type-specific properties. Because of the type-specific properties, it makes sense for each type of pets to have its own form (view or partial view) to handle CRUD tasks. How should we process these forms in the controller?
public interface IPet {
string Name { get; set; }
}
public abstract class Pet : IPet {
public string Name { get; set; }
}
public class Dog : Pet {
// Dog specific properties
}
public class Cat : Pet {
// Cat specific properties
}
public class Fish : Pet {
// Fish specific properties
}
The most obvious solution is to post each form to its own action, as shown below. Since the concrete data model is specified in the action, the DefaultModelBidner will happily bind the form to the model for us. However, if there were 20 types of pets, we would have to write 20 almost identical actions. This is hardly an elegant solution.
[HttpPost]
public ActionResult CreateDog(Dog dog) {
// BusinessLogic.Save(dog)
return View();
}
[HttpPost]
public ActionResult CreateCat(Cat cat) {
// BusinessLogic.Save(cat)
return View();
}
[HttpPost]
public ActionResult CreateFish(Fish fish) {
// BusinessLogic.Save(fish)
return View();
}
Another option is to use a single action to process raw forms, without the benefit of automatic model binding. Although the number of actions is reduced from 20 to 1, we could end up writing more lines of code to do data binding by ourselves. This approach is not attractive either.
[HttpPost]
public ActionResult Create(FormCollection collection) {
IPet pet = null;
string petType = collection["PetType"];
switch (petType) {
case "Dog":
pet = new Dog();
// BusinessLogic.DataBind(pet, collection);
break;
case "Cat":
pet = new Cat();
// BusinessLogic.DataBind(pet, collection);
break;
case "Fish":
pet = new Fish();
// BusinessLogic.DataBind(pet, collection);
break;
default:
throw new Exception("Unknown pet type");
}
// BusinessLogic.Save(pet);
return View();
}
It would be nice if we can write a single action bound to IPet interface, which covers all the sub types.
[HttpPost]
public ActionResult Create(IPet pet) {
// BusinessLogic.Save(pet)
return View();
}
The above code actually compiles. At run-time, however, the DefaultModelBidner complains that it cannot bind to an interface. In order to make the action to work, we need to specify which concrete model to bind dynamically at run-time.
Dynamic Model Binding
First, we need a custom ModelBinder, which extends DefaultModelBinder. In the overridden BindModel() method, before relaying control to the base class, we find out the concrete model type from a (hidden) form field ("PetType"), and pass it to the binding context. That is all the information the base class (DefaultModelBinder) needs to perform the magic.
public class PetModelBinder : DefaultModelBinder {
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext) {
var type = controllerContext.HttpContext.Request.Form["PetType"];
bindingContext.ModelName = type;
bindingContext.ModelMetadata = ModelMetadataProviders
.Current.GetMetadataForType(null, petMap[type]);
return base.BindModel(controllerContext, bindingContext);
}
static Dictionary<string, type> petMap = new Dictionary<string, type>{
{"Dog", typeof(Dog)},
{"Cat", typeof(Cat)},
{"Fish", typeof(Fish)}
};
}
Next, we need to add an annotation above the action method.to tell MVC framework to use our custom ModelBinder instead of DefaultModelBinder.
[HttpPost]
[ModelBinder(typeof(PetModelBinder))]
public ActionResult Create(IPet pet) {
// BusinessLogic.Save(pet)
return View();
}
That's it. Now we have a single action capable of processing multiple forms with the benefit of automatic model binding.
Summary
It is surprisingly simple to implement dynamic model binding, which identifies the actual data model at run-time, enabling a single action to handle multiple forms.
Posted on Utopian.io - Rewarding Open Source Contributors
Thank you for the contribution. It has been approved.
You can contact us on Discord.
[utopian-moderator]
Hey @dorodor I am @utopian-io. I have just upvoted you!
Achievements
Community-Driven Witness!
I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!
Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x