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.
I have two actions in my controller when a user call one i need to redirect it to another one and pass a complex object :
first action :
public virtual ActionResult Index(string Id) {
var input = new CustomInput();
input.PaymentTypeId = Id;
return RedirectToAction(MVC.Ops.SPS.Actions.Test(input));
}
second action :
public virtual ActionResult Test(CustomInput input) {
return View();
}
The probelm is that the input arrives null at the second action. how can i solve it?
You can solve this using temp data to temporarily hold a value from which the second method retrieves that value.
public virtual ActionResult Index(string Id)
{
var input = new CustomInput();
input.PaymentTypeId = Id;
TempData["TheCustomData"] = input; //temp data, this only sticks around for one "postback"
return RedirectToAction(MVC.Ops.SPS.Actions.Test());
}
public virtual ActionResult Test()
{
CustomInput = TempData["TheCustomData"] as CustomInput;
//now do what you want with Custom Input
return View();
}
You can keep your tempData going so long as it is never null using the .keep() method like this,
if (TempData["TheCustomData"] != null)
TempData.Keep("TheCustomData");
I want to make sure that you know that RedirectToAction creates new HTTP request so this create another GET and all you can pass is RouteValueDictionary object which is like having query string parameters which is list of key and value pairs of string. That said, You can't pass complex object with your way of code however the TempData solution mentioned by #kyleT will work.
My recommendation based on your code is to avoid having two actions and redirect from one another unless you are doing something more than what you have mentioned in your question. Or you can make your Test action accepting the id parameter the your Index action will contain only RedirectToAction passing the id as route parameter.
Edit:
Also, If you have no primitive properties you can pass your object like the following (Thanks to #Stephen Muecke)
return RedirectToAction("Test",(input);
I've written a base class and some classes which derive from it.
I want to use these classes in one ActionResult, but if I'm trying to cast PSBase to PS1 I'm getting a System.InvalidCastException that type PSBase can not be converted to PS1.
Classes:
public class PSBase
{
public int stationId { get; set; }
public string name { get; set; }
}
public class PS1 : PSBase
{
public string reference { get; set; }
}
public class PS2 : PSBase
{
}
ActionResult:
[HttpPost]
public ActionResult ProductionStep(PSBase ps)
{
if (ModelState.IsValid)
{
var product = db.Product.FirstOrDefault(.........);
switch (ps.stationId )
{
case 1:
{
product.Reference = ((PS1)ps).reference;
db.SaveChanges();
break;
}
}
}
return View();
}
As I don't want to have for each class a own ActionResult (repeating much of the same code many times) I wanted put all this to one ActionResult. Any Ideas how I could implement this?
What you are trying to do will never work without custom ModelBinder (and even then it will be a huge mess I'd not recommend to implement), sorry.
Only when you are passing a model from Controller to View it remembers what type it was originally (including inheritance, etc.) because at that point you are still on the server side of the page and you are merely passing an object.
Once you enter a view and submit a form all that does it creates some POST request with body containing list of values based on input names.
In your case if you have a form based on PS1 and used all the fields as inputs, you would get something like:
POST:
stationId = some value
name = some value
reference = some value
(there is no mention of the original type, controller, method, etc.)
Now, what MVC does is that it checks what argument you are using in the header of the method (in your case ProductionStep(PSBase ps)).
Based on the argument it calls a model binder. What the default model binder does is that it creates new instance of that class (in your case PSBase) and goes via reflection through all the properties of that class and tries to get them from the POST body.
If there are some extra values in the POST body those are forgotten.
Unless you write a custom model binder for this default MVC implementation can't help you there.
I'd recommend creating two separate methods, one of each accepting different implementation of PSBase.
If you want to read more on Model Binders check this out http://msdn.microsoft.com/en-us/magazine/hh781022.aspx
EDIT:
By creating two separate methods I mean something like this:
[HttpPost]
public ActionResult ProductionStepA(PS1 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
[HttpPost]
public ActionResult ProductionStepB(PS2 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
then you have to distinguish between them in the view via different form action.
EDIT:
Found the answer here: ASP.NET MVC 3 ValidateRequest(false) not working with FormCollection
Turns out I needed to add System.Web.Helpers so I could use the Unvalidated() extension method on the Request object. That gives you a request that won't throw exceptions on unsafe-looking inputs.
--
So here's the context in which my problem is occurring:
I have a model class which contains a collection of child objects
I've written a constructor which will parse FORM inputs so that I can post the model to an action method
I've set up a binder which grabs the Form object from the posted Request and passes it to my model's constructor
As some of the child objects can accept string inputs which may contain HTML, I need to disable MVC's input validation. I've set a [ValidateInput(false)] attribute on the action method, but HttpRequestValidationException is still being thrown in my model's constructor. On a whim I even tried putting a [ValidateInput] attribute on my model's binder and on the model itself, but that didn't solve the issue either.
I'm at a loss here. How do I go about handling these exceptions in such a way that I can still pull information from the form? Or, what is the appropriate way to go about disabling MVC's input validation in this situation?
Class sketch follows:
public class FooController : ControllerBase {
[HttpPost]
[ValidateInput(false)]
public ActionResult FooAction(FooModel model) { //do stuff; }
}
//tried [ValidateInput(false)] here as well, to no avail
public class FooBinder : BinderBase {
public override object BindModel(...) {
return new FooModel(controllerContext.HttpContext.Request.Form);
}
}
//tried [ValidateInput(false)] here, too....again, no success
public class FooModel {
public FooModel(NameValueCollection formData) {
//do some initialization stuff
var keys = formData.AllKeys; //exception thrown here when inputs contain '<' or '>'
//do some object construction stuff
}
public IEnumerable<FooChid> ChildCollection { get; set; }
}
Try putting the [ValidationInput(false)] on the Post method (where the exception is being thrown) and additionally adding [AcceptVerbs(HttpVerbs.Post)]. But if this is going to be a public website, you're opening yourself up to XXS, which is ill-advised.
I have web application based on ASP.Net MVC3. I have a need for a "Create" view which will not know the model type until the user picks a sub-type to create from a drop-down. To attempt to solve this problem, I have created an editor template under Shared/EditorTemplates for each derived model type. This allows me to create a single "Create.cs" which is strongly-typed to a view model. The view model only has two members, an enum and a complex type. The idea is that the view will initially show only a drop-down (editor for the enum member) then when the user initially submits the specified "model type" (drop-down selected value), the POST action can check the "model type" specified and instantiate the correct derived model type for the view model's single complex member who's type is the base type for all possible "model types".
The abstract + derived type model objects...
public abstract class MyModelBase
{
public MyModelType_e ModelType {get; set; }
[Required]
public string Name { get; set; }
}
public class DerivedType1 : MyModelBase
{ ... }
public class DerivedType2 : MyModelBase
{ ... }
public class DerivedType3 : MyModelBase
{ ... }
I have a complex view model as follows...
public enum MyModelType_e
{
DerivedType1 = 0,
DerivedType2 = 1,
DerivedType3 = 2
}
public class MyModelCreate
{
public MyModelType_e ModelTypeForSelectList { get; set; }
public MyModelBase ModelBase { get; set; }
}
My GET controller action instantiates the above view model for the view i.e., only a drop-down list displayed with items based on the MyModelType_e enum + the value of the model's "ModelBase" property is initially null. So the GET action method looks like this...
[HttpGet]
public ActionResult Create()
{
return View(new MyModelCreate());
}
Note the comment in caps bellow regarding the crux of my issue which is that TryUpdateModel fails (see below) even though it sets the properties of the ModelBase (derived-type) member to the corresponding form values as expected...
[HttpPost]
public ActionResult Create(MyModelCreate model)
{
if (model.ModelBase == null ||
(int)model.ModelTypeForSelectList != model.ModelBase.ModelType)
{
switch (model.ModelType)
{
case MyModelType_e.DerivedType1:
model.ModelBase = new DerivedType1();
break;
case MyModelType_e.DerivedType2:
model.ModelBase = new DerivedType2();
break;
case MyModelType_e.DerivedType3:
model.ModelBase = new DerivedType3();
break;
}
return View(model);
}
if (!TryUpdateModel(model.ModelBase))
return View(model); // <<< THIS HAPPENS EVEN THOUGH ModelBase APPEARS TO BE UPDATED PROPERLY...
// For instance, I can see right here with intellisense that model.ModelBase.Name
// is NOT null or empty but rather is truly updated with the correct form value(s)...
// TODO: Insert the record, etc... (currently we never get here...)
}
So the above section is where the problem stems from but here is my view to help understand...
#model MyNamespace.MyModelCreate
<h2>Create</h2>
...
#using (Html.BeginForm())
{
#Html.ValidationSummary(false)
<fieldset>
<legend>Input</legend>
<div class="editor-label">
#Html.Label("Select Model Type")
</div>
<div>
#Html.EnumDropDownListFor(model => model.ModelType)
#Html.ValidationMessageFor(model => model.ModelType)
</div>
#*
Conditionally show the correct editor template...
There is one existing under ../Shared/EditorTemplates for each
derived type (DerivedType1, DerivedType2, DerivedType3, etc...)
This much is working in the sense that the correct editor fields
are displayed based on what the user selects in the above drop-down.
*#
#if (Model.InputModel != null)
{
#Html.EditorFor(model => model.ModelBase);
}
<p>
<input type="submit" value="Continue" />
</p>
</fieldset>
}
So once with the initial POST (model type is selected), my POST action method falls through to the TryUpdateModel line by design but for some reason the validation fails. The part I really don't understand is that the validation summary reports "Name is required" even though I can clearly watch TryUpdateModel set the Name propery properly on the ModelBase member of the view model.
I'd greatly appreciate any help or guidance here... I am fairly new to ASP.NET MVC and I am open to doing things differently since I understand there are probably other ways I could design my requests/actions + views to accomplish this "multi-step" problem but I really am just going for the simplest possible thing which I why I like the EditorTemplate approach for handling the derived model types, etc.
Thanks in advance...
Check ModelState. There should be errors set after TryUpdateModel runs.
I had to remove a Required attribute from some properties.
My solution basically involved growing two additional controller actions (GET and POST). I created separate views under ../Shared/EditorTemplates which are strongly typed (one for each derived model type). This way the initial "Create" POST action only receives the selected type (model is just an enum value specifying the desired type) and then redirects to another controller action like "CreateByType" which gets a new instance of the requested concrete type and returns the correct edit view. The additional "CreateByType" controller actions (GET and POST) only needs to deal with the abstract base because it requests the instance from the service layer (passing the enum value). Then all I had to do was create a view under EditorTemplates for each concrete type.