MVC UpdateModel and Sub Classes vs Base Class - asp.net-mvc

I'm looking to use the UpdateModel method to a Sub Class that retrieved at runtime, would be great if someone could shed the light on whether I'm making a total hash of it and/or whether or not what I'm trying to do is possible.
I'm using a generic action to control the validation of a bunch of partial views; I'm trying to get away from having a specific action per partial view.
Each partial view has a unique Model that derives from a Base Model:
public class ModelA : ModelBase{
[Required]
public string SomeStringProperty{get;set;}
...
}
public class ModelB : ModelBase{
[Required]
public DateTime? SomeDateProperty{get;set;}
...
}
public class ModelBase{
public Guid InstanceId{get;set;}
}
I'm using the FormCollection on the Action to get the submitted form elements and their values, this includes the type of model that the View should be using to validate its request. Ignore the security implications of this for this example, I'm aware of them and this is an internal only proof of concept
[HttpPost]
public ActionResult ChangeCaseState(int id, FormCollection formCollection)
{
Guid instanceId = new Guid(formCollection["instanceId"]);
string modelType = formCollection["modelType"];
//Return a specific Model class based on the event/modelType
var args = GetStateModelClass(modelType, instanceId);
try
{
UpdateModel(args);
if(Model.IsValid){
...
}
catch (Exception)
{
return View("~/Views/Shared/StateForms/" + modelType + ".ascx", args);
}...
And here is the code I'm using to return a Sub Class based on the modelType passed to the controller.
private static ModelBase StateModelClassFactory(string stateModelTypeName, Guid instanceId)
{
switch (stateModelTypeName)
{
case "modelTypeA":
return new ModelA(workflowInstanceId);
case "modelTypeB":
return new ModelB(workflowInstanceId);
...
}
Because the return type of the StateModelClassFactory method is of the Base Class, even though I'm actually returning a Sub Class, the Model Binder used by the UpdateModel method only binds against the values within the Base Class.
Any ideas on how I can solve this problem?
UPDATE:
I created a Customer Model Binder:
public class CustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
And Assigned the new Model Binder to the correct Base Class to see what was going on a little more under the hood:
ModelBinders.Binders.Add(typeof(ModelBase), new CaseController.CustomModelBinder());
When I debug the model binder and inspect the bindingContext, the Model property represets the correct Sub Class, but the ModelType property is that of the Base Class. Should I be looking at changing the ModelType within the BindModel method? If so any pointers on how to do this, the setter on the ModelType seems to have been made redundant. I also noticed that the SomeDateProperty from the Sub Class is actaully in the PropertyMetadata property....Seems so close to behaving as I'd like.

I just ran into this particular issue and found that a better general approach would be just to cast your model to dynamic while passing it into UpdateModel:
[HttpPost]
public ActionResult ChangeCaseState(int id, FormCollection formCollection)
{
...try
{
UpdateModel((dynamic)args);//!!notice cast to dynamic here
if(Model.IsValid){
...
}
catch...
This appears to set all available properties of my type, regardless of whether my variable is delcared with the base type.
There's a work item filed in CodePlex for this issue: http://aspnet.codeplex.com/workitem/8277?ProjectName=aspnet

So I think I've solved my problem. Basically because of the way that I'm retrieving the Model class before calling the UpdateModel, the Model Binder is binding the BaseClass even though the Model was that of the SubClass - this is the code I used to solve my particular problem:
public class SubClassModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = bindingContext.Model;
var metaDataType = ModelMetadataProviders.Current.GetMetadataForType(null, model.GetType());
bindingContext.ModelMetadata = metaDataType;
bindingContext.ModelMetadata.Model = model;
return base.BindModel(controllerContext, bindingContext);
}
}
And in the Global.asax:
ModelBinders.Binders.Add(typeof(ModelBase), new SubClassModelBinder ());
Thanks to Darin for his inital pointer.

To solve this problem you could write a custom model binder for the base type which based on the value of the string property will return the correct child instance.

Related

ModelBindingContext.ValueProvider.GetValue(bindingContext.ModelName) returns null

I have bindingContext.ValueProvider.GetValue(bindingContext.ModelName) for ModelBinding and it returns null, but if I use bindingContext.ValueProvider.GetValue("id") is returns the correct record. Any Idea what's missing? Am I supposed to register the model class somehow?
public class EntityModelBinder<TEntity>: IModelBinder where TEntity : Entity
{
private readonly IUnitOfWork unitOfWork;
public EntityModelBinder(IUnitOfWork unitOfWork)
{
this.unitOfWork = unitOfWork;
}
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
ValueProviderResult value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var id = Guid.Parse(value.AttemptedValue);
var entity = ((IGenericRepository<TEntity>)unitOfWork.GetRepository(typeof(TEntity))).GetByID(id);
return entity;
}
}
And Controller Call is "Bill" is one of my Entity Classes, and it's part of the UnitOfWork:
public ActionResult Edit(Bill bill)
{
var model = Mapper.Map<Bill, BillEditModel>(bill);
return View("Edit",model);
}
I am not an expert on mvc, but I have an idea about your issue. I am assuming that you are trying to get a Bill entity from your unit of work. Your action method defines the parameter Bill bill. This means that MVC will set bindingContext.ModelName to "bill", not "id".
Instead of trying to get Bill entity through model binding, I suggest using your unit of work within the controller. So the Edit action could be like
public ActionResult Edit(Guid id)
{
Bill bill = _unitOfWork.GetByID(id);
}
and your Controller constructor might be like:
public MyController(IUnitOfWork uow) {
_unitOfWork = uow;
}
This is assuming that you are using DI.
I think, using the model binder for getting entities from repository could be dangerous. Unless your GetByID method throws an exception, MVC will continue searching for Bill entity in request data. Imagine a scenario, where user posts a new entity that does not exist in your repository. Your controller will have to do some extra work to check whether this entity really exists in your repository.
You are better off using your unit of work within the controller.

Polymorphic model binding

This question has been asked before in earlier versions of MVC. There is also this blog entry about a way to work around the problem. I'm wondering if MVC3 has introduced anything that might help, or if there are any other options.
In a nutshell. Here's the situation. I have an abstract base model, and 2 concrete subclasses. I have a strongly typed view that renders the models with EditorForModel(). Then I have custom templates to render each concrete type.
The problem comes at post time. If I make the post action method take the base class as the parameter, then MVC can't create an abstract version of it (which i would not want anyways, i'd want it to create the actual concrete type). If I create multiple post action methods that vary only by parameter signature, then MVC complains that it's ambiguous.
So as far as I can tell, I have a few choices on how to solve this proble. I don't like any of them for various reasons, but i will list them here:
Create a custom model binder as Darin suggests in the first post I linked to.
Create a discriminator attribute as the second post I linked to suggests.
Post to different action methods based on type
???
I don't like 1, because it is basically configuration that is hidden. Some other developer working on the code may not know about it and waste a lot of time trying to figure out why things break when changes things.
I don't like 2, because it seems kind of hacky. But, i'm leaning towards this approach.
I don't like 3, because that means violating DRY.
Any other suggestions?
Edit:
I decided to go with Darin's method, but made a slight change. I added this to my abstract model:
[HiddenInput(DisplayValue = false)]
public string ConcreteModelType { get { return this.GetType().ToString(); }}
Then a hidden automatically gets generated in my DisplayForModel(). The only thing you have to remember is that if you're not using DisplayForModel(), you'll have to add it yourself.
Since I obviously opt for option 1 (:-)) let me try to elaborate it a little more so that it is less breakable and avoid hardcoding concrete instances into the model binder. The idea is to pass the concrete type into a hidden field and use reflection to instantiate the concrete type.
Suppose that you have the following view models:
public abstract class BaseViewModel
{
public int Id { get; set; }
}
public class FooViewModel : BaseViewModel
{
public string Foo { get; set; }
}
the following controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new FooViewModel { Id = 1, Foo = "foo" };
return View(model);
}
[HttpPost]
public ActionResult Index(BaseViewModel model)
{
return View(model);
}
}
the corresponding Index view:
#model BaseViewModel
#using (Html.BeginForm())
{
#Html.Hidden("ModelType", Model.GetType())
#Html.EditorForModel()
<input type="submit" value="OK" />
}
and the ~/Views/Home/EditorTemplates/FooViewModel.cshtml editor template:
#model FooViewModel
#Html.EditorFor(x => x.Id)
#Html.EditorFor(x => x.Foo)
Now we could have the following custom model binder:
public class BaseViewModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
var type = Type.GetType(
(string)typeValue.ConvertTo(typeof(string)),
true
);
if (!typeof(BaseViewModel).IsAssignableFrom(type))
{
throw new InvalidOperationException("Bad Type");
}
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
}
}
The actual type is inferred from the value of the ModelType hidden field. It is not hardcoded, meaning that you could add other child types later without having to ever touch this model binder.
This same technique could be easily be applied to collections of base view models.
I have just thought of an intersting solution to this problem. Instead of using Parameter bsed model binding like this:
[HttpPost]
public ActionResult Index(MyModel model) {...}
I can instead use TryUpdateModel() to allow me to determine what kind of model to bind to in code. For example I do something like this:
[HttpPost]
public ActionResult Index() {...}
{
MyModel model;
if (ViewData.SomeData == Something) {
model = new MyDerivedModel();
} else {
model = new MyOtherDerivedModel();
}
TryUpdateModel(model);
if (Model.IsValid) {...}
return View(model);
}
This actually works a lot better anyways, because if i'm doing any processing, then I would have to cast the model to whatever it actually is anyways, or use is to to figure out the correct Map to call with AutoMapper.
I guess those of us who haven't been using MVC since day 1 forget about UpdateModel and TryUpdateModel, but it still has its uses.
It took me a good day to come up with an answer to a closely related problem - although I'm not sure it's precisely the same issue, I'm posting it here in case others are looking for a solution to the same exact problem.
In my case, I have an abstract base-type for a number of different view-model types. So in the main view-model, I have a property of an abstract base-type:
class View
{
public AbstractBaseItemView ItemView { get; set; }
}
I have a number of sub-types of AbstractBaseItemView, many of which define their own exclusive properties.
My problem is, the model-binder does not look at the type of object attached to View.ItemView, but instead looks only at the declared property-type, which is AbstractBaseItemView - and decides to bind only the properties defined in the abstract type, ignoring properties specific to the concrete type of AbstractBaseItemView that happens to be in use.
The work-around for this isn't pretty:
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
// ...
public class ModelBinder : DefaultModelBinder
{
// ...
override protected ICustomTypeDescriptor GetTypeDescriptor(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType.IsAbstract && bindingContext.Model != null)
{
var concreteType = bindingContext.Model.GetType();
if (Nullable.GetUnderlyingType(concreteType) == null)
{
return new AssociatedMetadataTypeTypeDescriptionProvider(concreteType).GetTypeDescriptor(concreteType);
}
}
return base.GetTypeDescriptor(controllerContext, bindingContext);
}
// ...
}
Although this change feels hacky and is very "systemic", it seems to work - and does not, as far as I can figure, pose a considerable security-risk, since it does not tie into CreateModel() and thus does not allow you to post whatever and trick the model-binder into creating just any object.
It also works only when the declared property-type is an abstract type, e.g. an abstract class or an interface.
On a related note, it occurs to me that other implementations I've seen here that override CreateModel() probably will only work when you're posting entirely new objects - and will suffer from the same problem I ran into, when the declared property-type is of an abstract type. So you most likely won't be able to edit specific properties of concrete types on existing model objects, but only create new ones.
So in other words, you will probably need to integrate this work-around into your binder to also be able to properly edit objects that were added to the view-model prior to binding... Personally, I feel that's a safer approach, since I control what concrete type gets added - so the controller/action can, indirectly, specify the concrete type that may be bound, by simply populating the property with an empty instance.
Using Darin's method to discriminate your model types via a hidden field in your view, I would recommend that you use a custom RouteHandler to distinguish your model types, and direct each one to a uniquely named action on your controller. For example, if you have two concrete models, Foo and Bar, for your Create action in your controller, make a CreateFoo(Foo model) action and a CreateBar(Bar model) action. Then, make a custom RouteHandler, as follows:
public class MyRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var httpContext = requestContext.HttpContext;
var modelType = httpContext.Request.Form["ModelType"];
var routeData = requestContext.RouteData;
if (!String.IsNullOrEmpty(modelType))
{
var action = routeData.Values["action"];
routeData.Values["action"] = action + modelType;
}
var handler = new MvcHandler(requestContext);
return handler;
}
}
Then, in Global.asax.cs, change RegisterRoutes() as follows:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
AreaRegistration.RegisterAllAreas();
routes.Add("Default", new Route("{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Home",
action = "Index",
id = UrlParameter.Optional }),
new MyRouteHandler()));
}
Then, when a Create request comes in, if a ModelType is defined in the returned form, the RouteHandler will append the ModelType to the action name, allowing a unique action to be defined for each concrete model.

Adding multiple Prefixes to DefaultModelBinder MVC2

I've looked at most of the ModelBinding examples but can't seem to glean what I'm looking for.
I'd like:
<%= Html.TextBox("User.FirstName") %>
<%= Html.TextBox("User.LastName") %>
to bind to this method on post
public ActionResult Index(UserInputModel input) {}
where UserInputModel is
public class UserInputModel {
public string FirstName {get; set;}
public string LastName {get; set;}
}
The convention is to use the class name sans "InputModel", but I'd like to not have to specify this each time with the BindAttribute, ie:
public ActionResult Index([Bind(Prefix="User")]UserInputModel input) {}
I've tried overriding the DefaultModelBinder but can't seem to find the proper place to inject this tiny bit of functionality.
The ModelName property in the ModelBindingContext object passed to the BindModel function is what you want to set. Here's a model binder that does this:
public class PrefixedModelBinder : DefaultModelBinder
{
public string ModelPrefix
{
get;
set;
}
public PrefixedModelBinder(string modelPrefix)
{
ModelPrefix = modelPrefix;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
bindingContext.ModelName = ModelPrefix;
return base.BindModel(controllerContext, bindingContext);
}
}
Register it in your Application_Start like so:
ModelBinders.Binders.Add(typeof(MyType), new PrefixedModelBinder("Content"));
Now you will no longer to need to add the Bind attribute for types you specify use this model binder!
The BindAttribute can be used at the class level to avoid duplicating it for each instance of the UserInputModel parameter.
======EDIT======
Just dropping the prefix from your form or using the BindAttribute on the view model would be the easiest option, but an alternative would be to register a custom model binder for the UserInputModel type and explicitly looking for the prefix you want.

ASP.NET MVC UpdateModel with interface

I am trying to get UpdateModel to populate a model that is set as only an interface at compile-time. For example, I have:
// View Model
public class AccountViewModel {
public string Email { get; set; }
public IProfile Profile { get; set; }
}
// Interface
public interface IProfile {
// Empty
}
// Actual profile instance used
public class StandardProfile : IProfile {
public string FavoriteFood { get; set; }
public string FavoriteMusic { get; set; }
}
// Controller action
public ActionResult AddAccount(AccountViewModel viewModel) {
// viewModel is populated already
UpdateModel(viewModel.Profile, "Profile"); // This isn't working.
}
// Form
<form ... >
<input name='Email' />
<input name='Profile.FavoriteFood' />
<input name='Profile.FavoriteMusic' />
<button type='submit'></button>
</form>
Also note that I have a custom model binder that inherits from DefaultModelBinder being used that populates IProfile with an instance of StandardProfile in the overriden CreateModel method.
The problem is that FavoriteFood and FavoriteMusic are never populated. Any ideas? Ideally this would all be done in the model binder, but I'm not sure it is possible without writing a completely custom implementation.
Thanks, Brian
I would have to check the ASP.NET MVC code (DefaultModelBinder) but I'm guessing that its reflecting on the type IProfile, and not the instance, StandardProfile.
So it looks for any IProfile members it can try to bind, but its an empty interface, so it considers itself done.
You could try something like updating the BindingContext and changing the ModelType to StandardProfile and then calling
bindingContext.ModelType = typeof(StandardProfile);
IProfile profile = base.BindModel(controllerContext, bindingContext);
Anyways, having an empty Interface is weird~
Edit: just want to add that code above is just pseudo code, you would need to check DefaultModelBinder to see exactly what you want to write.
Edit#2:
Can you do:
public class ProfileModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
{
bindingContext.ModelType = typeof(StandardProfile);
return base.BindModel(controllerContext, bindingContext);
}
}
No need to make a model binder for AccountView, that one works fine.
Edit #3
Tested it out, the above binder works, just need to add:
ModelBinders.Binders[typeof(IProfile)] = new ProfileModelBinder();
Your action looks like:
public ActionResult AddAccount(AccountViewModel viewModel) {
// viewModel is fully populated, including profile, don't call UpdateModel
}
You can use IOC when setting the model binder (have the type constructor injected for instance).
Not inspecting the actual type behind the interface was discussed here: http://forums.asp.net/t/1348233.aspx
That said, I found a hackish way around the problem. Since I already had a custom model binder for this type, I was able to add some code to it to perform the binding for me. Here's what my model binder looks like now:
public class AccountViewModelModelBinder : DefaultModelBinder
{
private readonly IProfileViewModel profileViewModel;
private bool profileBound = false;
public AccountViewModelModelBinder(IProfileViewModel profileViewModel)
{
this.profileViewModel = profileViewModel;
}
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Bind the profile
if (profileBound)
return;
profileBound = true;
bindingContext.ModelType = profileViewModel.GetType();
bindingContext.Model = profileViewModel;
bindingContext.ModelName = "Profile";
BindModel(controllerContext, bindingContext);
}
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType)
{
var model = new AccountViewModel();
model.Profile = profileViewModel;
return model;
}
}
Basically, when the model binder is "done" binding the main AccountViewModel, I then alter the binding context (as suggested by eyston) and call BindModel once again. This then binds my profile. Note that I called GetType on the profileViewModel (which is supplied by the IOC container in the constructor). Also notice that I include a flag to indicate if the profile model has been bound already. Otherwise there would be an endless loop of OnModelUpdated being called.
I'm not saying this is pretty, but it does work well enough for my needs. I'd still love to hear about other suggestions.

Ways to bind a parameter to a different name in ASP.NET MVC?

I keep running into scenarios where I would like to provide a slightly more intuitive or "well-formed" parameter name for action methods, but with the default behavior, this is turning out to be quite painful. For example, suppose that I have an action parameter like GetWidget(int id). If I want it to be GetWidget(int widgetId), I have to add a new route. It gets worse when you use a library like jqGrid which uses awful names for its querystring parameters: GetWidgets(int? nodeid, int? n_level). Instead, I'd like to have GetWidgets(int? parentId, int? level) or something similar.
So, is there something simple that I'm overlooking? It seems like it should be a very simple thing to tell MVC that my "parentId" parameter should be bound to the value of "nodeid" in the request. I thought about writing a custom action filter to do this, but it seems so obvious that I can't believe it's not supported out of the box.
As per Rony's answer use a custom model binder. Here is an example:
public class BindToAliasAttribute : CustomModelBinderAttribute
{
private readonly string parameterAlias;
public BindToAliasAttribute(string parameterAlias)
{
this.parameterAlias = parameterAlias;
}
public override IModelBinder GetBinder()
{
return new ParameterWithAliasModelBinder(parameterAlias);
}
}
public class ParameterWithAliasModelBinder : IModelBinder
{
private readonly string parameterAlias;
public ParameterWithAliasModelBinder(string parameterAlias)
{
this.parameterAlias = parameterAlias;
}
object IModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
return controllerContext.RouteData.Values[parameterAlias];
}
}
public class UserController : Controller
{
[HttpGet]
public ActionResult Show( [BindToAlias("id")] string username)
{
...
}
}
If you use named parameters on the URL, you can specify a specific name for the parameter into your controller method, like so:
http://mydomain.com/mycontroller/getwidget?parentid=1&level=2
...and you won't have to match routes on the parameters.
use you own custom model binder which implements IModelBinder

Resources