ASP.NET MVC 3: Defaultmodelbinder with Inheritance/Polymorphism

ASP.NET MVC 3: DefaultModelBinder with inheritance/polymorphism

I've tried to do something similar before and I came to the conclusion that there's nothing built in which will handle this.

The option I went with was to create my own model binder (though inherited from the default so its not too much code). It looked for a post back value with the name of the type called xxxConcreteType where xxx was another type it was binding to. This means that a field must be posted back with the value of the type you're trying to bind; in this case OrderConcreteType with a value of either OrderBottling or OrderFinishing.

Your other alternative is to use UpdateModel or TryUpdateModel and ommit the parameter from your method. You will need to determine which kind of model you're updating before calling this (either by a parameter or otherwise) and instantiate the class beforehand, then you can use either method to popuplate it

Edit:

Here is the code..

public class AbstractBindAttribute : CustomModelBinderAttribute
{
public string ConcreteTypeParameter { get; set; }

public override IModelBinder GetBinder()
{
return new AbstractModelBinder(ConcreteTypeParameter);
}

private class AbstractModelBinder : DefaultModelBinder
{
private readonly string concreteTypeParameterName;

public AbstractModelBinder(string concreteTypeParameterName)
{
this.concreteTypeParameterName = concreteTypeParameterName;
}

protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var concreteTypeValue = bindingContext.ValueProvider.GetValue(concreteTypeParameterName);

if (concreteTypeValue == null)
throw new Exception("Concrete type value not specified for abstract class binding");

var concreteType = Assembly.GetExecutingAssembly().GetType(concreteTypeValue.AttemptedValue);

if (concreteType == null)
throw new Exception("Cannot create abstract model");

if (!concreteType.IsSubclassOf(modelType))
throw new Exception("Incorrect model type specified");

var concreteInstance = Activator.CreateInstance(concreteType);

bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => concreteInstance, concreteType);

return concreteInstance;
}
}
}

Change your action method to look like this:

public ActionResult Create([AbstractBind(ConcreteTypeParameter = "orderType")] Order order) { /* implementation ommited */ }

You would need to put the following in your view:

@Html.Hidden("orderType, "Namespace.xxx.OrderBottling")

ASP.net MVC problems when model binding with sub-class

I think you will need to create your own ModelBinder. Take a look at ASP.NET MVC 3: DefaultModelBinder with inheritance/polymorphism.

How to update a property of an abstract with an inheriting/using a subblass in MVC

Not in EF. You have to instantiate an object to work with EF, and you can't instantiate an abstract class.

You could make the class not be abstract. Or you could use a stored proc to update the field, or some direct sql.

Polymorphic model binding / Complex Models

Here is a demo:

Index.cshtml(when select SmartPhone,use example.cshtml,when select Laptop,use example1.cshtml):

@model MainCont
@{
ViewData["Title"] = "Home Page";
}

<form asp-action="create" asp-controller="home" method="post">
<select id="select" name="select">
<option value="SmartPhone">SmartPhone </option>
<option value="Laptop">Laptop </option>
</select>
<div id="sample"></div>
<button type="submit">გაგზავნა</button>
</form>
@section scripts{
<script>
$(function () {
GetPartialView();
})
$("#select").change(function () {
GetPartialView();
})
function GetPartialView() {
$.ajax({
url: "/Test1/ReturnExample",
type: "POST",
data: {
select: $("#select").val()
},
success: function (data) {
$('#sample').html(data);
},
error: function (reponse) {
alert("error : " + reponse);
}
});
}
</script>
}

example.cshtml:

@model SmartPhone
@Html.TextBoxFor(model => model.imei)
@Html.TextBoxFor(model => model.screensize)

example1.cshtml:

@model Laptop
@Html.TextBoxFor(model => model.CPU)
@Html.TextBoxFor(model => model.GPu)

Controller:

public IActionResult Index()
{
return View(new MainCont());
}
public IActionResult ReturnExample(string select)
{
if (select == "SmartPhone")
{
return PartialView("~/Views/Test1/example.cshtml", new SmartPhone());
}
else {
return PartialView("~/Views/Test1/example1.cshtml", new Laptop());
}

}

Create Action in Home Controller:

[HttpPost]
public IActionResult Create([ModelBinder(typeof(DataBinder))]MainCont mainCont) {
return Ok();
}

DataBinder:

public class DataBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var model1 = new MainCont();

var select = bindingContext.ValueProvider.GetValue("select").FirstValue;
if (select == "SmartPhone")
{
var model2 = new SmartPhone();
model2.screensize = bindingContext.ValueProvider.GetValue("screensize").FirstValue;
model2.imei = bindingContext.ValueProvider.GetValue("imei").FirstValue;
model1.Device = model2;
}
else if (select == "Laptop")
{
var model2 = new Laptop();
model2.CPU = bindingContext.ValueProvider.GetValue("CPU").FirstValue;
model2.GPu = bindingContext.ValueProvider.GetValue("GPu").FirstValue;
model1.Device = model2;
}

bindingContext.Result = ModelBindingResult.Success(model1);

return Task.CompletedTask;
}
}

result:
Sample Image

ASP.NET MVC Generic List of Different SubClasses

You can't do that with the DefaultModelBinder. You'll have to create your own custom model binder in order to do what you want to do.

These might be helpful:

https://gist.github.com/joelpurra/2415633

ASP.NET MVC3 bind to subclass

ASP.NET MVC 3: DefaultModelBinder with inheritance/polymorphism

How do I deserialize from JSON to an abstract parameter on an ASP.NET MVC 5 controller

Turns out I made a bad assumption that MVC uses Json.NET for deserialization (shame on me). MVC has its own ModelBinder, which is DefaultModelBinder by default. Bear with me on the solution, it was a little rushed and needs testing. I was able to extend the DefaultModelBinder,

public class AbstractModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
if (modelType.IsAbstract)
{
var typeName = bindingContext.ValueProvider.GetValue("$type");
if (typeName == null)
throw new Exception("Cannot create abstract model");

var type = Type.GetType(typeName);
if (type == null)
throw new Exception("Cannot create abstract model");

if (!type.IsSubclassOf(modelType))
throw new Exception("Incorrect model type specified");

var model = Activator.CreateInstance(type);

// this line is very important.
// It updates the metadata (and type) of the model on the binding context,
// which allows MVC to properly reflect over the properties
// and assign their values. Without this,
// it will keep the type as specified in the parameter list,
// and only the properties of the base class will be populated
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);

return model;
}

return base.CreateModel(controllerContext, bindingContext, modelType);
}
}

and registered it as my default binder on startup.

// in Global.asax.cs on application start
ModelBinders.Binders.DefaultBinder = new AbstractModelBinder();

I only change the behavior when the model type is abstract, and default otherwise. If it's abstract, but I can't create an instance of the type, or the type name is unavailable, etc., then for now I'm throwing an exception, but that's a detail that I can figure out later. This way, I don't need to use any attributes on my Actions' parameters, and the Json.NET type specifiers are being used as best as I can figure out at the moment. It would be nice to just have Json.NET deserialize the model for me, but this is the best solution I've found thus far.

I'd like to reference posts that helped me find my answer. Change the default model binder in asp.net MVC, ASP.NET MVC 3: DefaultModelBinder with inheritance/polymorphism

ASP.NET MVC RegisterModel with Interface

I would look at using Knockout.js for the front end to allow the user to construct their list of objects. It is an MVVM framework that lets you manage client side events and manipulate the DOM.

You could send an ajax request back to the server for each new shape they add (on the onclick event). You would need to then persist the list the user is building up in the database or session, but when the user hits confirm/submit you would already have access to the full list so it will be easy to perform validation on all the items at once.

passing the child model to a base model view

Try an interface.

public ActionResult ChildAction(ChildClass model)
{
return PartialView("Partials/BaseClassView", model);
}
public class ChildClass : BaseClass
{

}
public class BaseClass : IBaseClass
{

}
public interface IBaseClass
{

}

View

@model IBaseClass


Related Topics



Leave a reply



Submit