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.
Related
I need some help with some fundamentals here...
I have this controller that serves up my view with an instance of a class (at least that's how I think it works). So since I am giving my view a new instance of the object, why does it have to create a NEWer one for the model binding for my post back? Please look at the below example.
[HttpGet]
public ActionResult Index(){
int hi = 5;
string temp = "yo";
MyModel foo = new MyModel(hi, temp);
return View(foo);
}
[HttpPost]
public ActionResult Index(MyModel foo){
MyModel poo = foo;
if(poo.someString == laaaa)
return RedirctToAction("End", "EndCntrl", poo);
else
throw new Exception();
}
View:
#model myApp.models.MyModel
#html.EditorFor(m => m.hi)
<input type="submit" value="hit"/>
Model:
public class MyModel {
public int hi {get; set;}
public string someString {get; set;}
public stuff(int number, string laaaa){
NumberforClass = number;
someString = laaaa;
}
}
Why do I need a blank constructor? Furthermore, if I had an parameterless constructor, why would poo.someString change by the time I got to RedirctToAction("End", "EndCntrl", poo)?
Why do I need a blank constructor?
because of
[HttpPost]
public ActionResult Index(MyModel foo){ ... }
You asked the binder to give you a concrete instance on Post, so the binder needs to create that object for you. Your original object does not persist between the GET and POST actions, only (some of) its properties live on as HTML fields. Thats what "HTTP is stateless" means.
It becomes a little more obvious when you use the lower level
[HttpPost]
public ActionResult Index(FormCollection collection)
{
var Foo = new MyModel();
// load the properties from the FormCollection yourself
}
why would poo.someString change by the time I got to RedirctToAction("End", "EndCntrl", poo)?
Because someString isn't used in your View. So it will always be blank when you get the new model back. To change that:
#model myApp.models.MyModel
#html.HiddenFor(m => m.SomeString)
#html.EditorFor(m => m.hi)
<input type="submit" value="hit"/>
this will store the value as a hidden field in the HTML and it will be restored for you on POST.
There is no direct connection between model you pass to view and model that you receive in request. In ultimate case the code for initial request and response will run in different instance of IIS or even different machines.
So when request come back ASP.Net MVC need to recreate all objects (controller, model,...). Having default constructor allows run-time to create new object without knowledge of particular arguments to your custom constructor.
Side note: Similar reconstruction for constructor exist for generics where you can only specify where T:new() for default constructor.
I am a little confused by the question but instead have you tried this:
[HttpPost]
public ActionResult Index(MyModel foo){
if(foo.someString == "laaaa")
return RedirctToAction("End", "EndCntrl", foo);
else
throw new Exception();
}
You only need a parameterless constructor if you added a parameterized constructor.
Ex: MyObject item = new MyObject();
It doesn't need a parameter less constructor as long you do not define any constructors. If you define a constructor with parameters, you need then a parameter less constructor as it is the one used by the model binder..
when you postback the values, the binder will map your request in a typed object, it first creates the object, and then tries to map your posted values to some property.
If you can not have a parameter less constructor... if code is not Under your control, then you have to create a Custom binder.
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.
I have a model and it looks like this:
public class MyModel {
public List<SomeBaseClass> list {get; set;}
public string SomeProperty {get; set;}
}
where SomeBaseClass is in fact a base class and the list can contain items of different types, but all those types are inherited from SomeBaseClass.
To make sure my model binds properly, I had to implement a custom binder that fills out the model based on the form data.
public class MyModelBinder: DefaultModelBinder
{
public override object BindModel(ControllerContext cntxt, ModelBindingContext bindingContext)
{
var model = new MyModel {
list = new List<SomeBaseClass>(),
SomeProperty = ...
};
... // some data mangling and type twisting here
return model; // here the debugger shows that the model's list is populated properly based on the form data.
}
}
But when a view calls an action, my model is not complete:
public string SomeAction(MyModel model) { // <~~ It calls the custom binder before coming to here, which is correct
// As the result, the model variable is an instance of MyModel, but the list is null
return "somethhing";
}
In the action method, I receive the model object with its list property set to null. Which is strange, because the binder is called correctly and it populates the model and all its properties properly.
I can't figure out what I am doing wrong.
P.S. When I try to call UpdateModel<MyModel>(model); in the action method, it throws "The model of type MyModel could not be updated."
Ok, I got it. I had to add the binder attribute next to my model class declaration to make sure the binder is called when the model is passed to the action method.
[ModelBinder(typeof(MyModelBinder))]
public class MyModel
{
...
}
It is not obvious, because even when this attribute is not present, the custom binder is still called, but for some reason its output is not being sent to the action, the default binder's output is used instead.
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.
I have the following ViewModel:
public class EditViewModel
{
public int FooType { get; set; }
public IEnumerable<SelectListItem> FooTypes { get; set; }
}
I originally populated it in my Edit action like so:
public ActionResult Edit(int id)
{
EditViewModel model = new EditViewModel();
model.FooTypes = new SelectList(repository.GetFooTypes(), "Id", "Value");
return View(model);
}
When I created the action to POST the values I had to repeat the same code:
public ActionResult Edit(int id, EditViewModel model)
{
if( !ModelState.IsValid )
{
model.FooTypes = new SelectList(repository.GetFooTypes(), "Id", "Value");
return View(model);
}
return RedirectToAction("Index");
}
I don't like having this code in two separate locations. Is there any common practice for refactoring this into a single spot so I dont need to repeat this code?
Given that c# is an object oriented language, there are plenty of options available.
The simplest would be to just wrap it in a method within the controller:
private SelectList GetFooTypesList()
{
return new SelectList(repository.GetFooTypes(), "Id", "Value);
}
and call it when setting up your model
or if you're using it in multiple classes you could create a helper method in another class that accepts the repository or an IEnumerable as a parameter.
If you want to get really advanced, you could use a ModelFactory to create the FooType model for you, with a prepopulated FooType property so the controller doesn't need to worry about it at all.
There's plenty of options, you just need to pick the one that's best for you.
My personal preference is the simple helper method in the controller.
I've done it in the model before (when it was the coding practice for that project team), but it depends on your philosophy on what's "business logic" and what's "data access", and what belongs in the model vs controller. Different, and justifiable, opinions exist.
Model, where you need a nullable type for FooType:
public class EditViewModel
{
public int? FooType { get; set; }
public IEnumerable<SelectListItem> GetFooTypes(object selectedFooType = null)
{
return new SelectList(repository.GetFooTypes(), "Id", "Value", selectedFooType);
}
}
"Get" controller, where you need to create the model first to ensure the Model property is available in the view:
public ActionResult Edit(int id)
{
EditViewModel model = new EditViewModel();
return View(model);
}
The View (sans Barbara Wawa):
#Html.DropDownListFor(m => m.FooType, Model.GetFooTypes(Model.FooType))
An alternative that takes the "view stuff" out of the model might look like so:
Model:
public class EditViewModel
{
public int? FooType { get; set; }
public IEnumerable<int?> FooTypes
{
get
{
// declare/define repository in your model somewhere
return repository.GetFooTypes();
}
}
}
View:
#Html.DropDownListFor(m => m.FooType, new SelectList(Model.FooTypes, "Id", "Value", Model.FooType))
In the reply by "nekno" (answered Sep 30 at 22:19), there are two alternatives of a ViewModel which either returns a 'IEnumerable<SelectListItem>' or a 'IEnumerable<int?>'.
Both of these alternative uses a repository but without actually creating it, so I would like to extend the code example a little bit, and chooses the second alternative i.e. the class with the property typed 'IEnumerable<int?>' :
using Microsoft.Practices.ServiceLocation; // ServiceLocator , http://commonservicelocator.codeplex.com/
using MyOwnRepositoryNameSpace; // IRepository
public class EditViewModel
{
public int? FooType { get; set; }
public IEnumerable<int?> FooTypes
{
get
{
return Repository.GetFooTypes();
}
}
private IRepository Repository
{
get
{
return ServiceLocator.Current.GetInstance<IRepository>();
}
}
}
The above kind of code with a "Dependecy Lookup" is now using a dependency to a third-part library, in this case the Common Service Locator library.
My question is how can the above code be replaced with "Dependency Injection" ?
The ViewModel itself would indeed be very trivial to implement, just like this:
using MyOwnRepositoryNameSpace; // IRepository
public class EditViewModel
{
private readonly IRepository _repository;
public EditViewModel(IRepository repository)
{
_repository = repository;
}
public int? FooType { get; set; }
public IEnumerable<int?> FooTypes
{
get
{
return _repository.GetFooTypes();
}
}
}
The problem is how to make the ViewModel become injected with an implementation, when the ASP.NET MVC framework will instantiate the 'EditViewModel' and send it as a parameter into an Action method such as tihs method signature:
public ActionResult Edit(int id, EditViewModel model) {
// How do we make the framework instantiate the above 'EditViewModel' with an implementation of 'IRepository' when the Action method is invoked ???
The official MVC tutorial does not seem to provide any nice solution as far I can see.
In the section "Handling Edits" (methods 'public ActionResult Edit(...)' ) at the below pages they are duplicating the creation of the options in a similar way as in the poster of this stackoverflow question you are now reading.
http://www.asp.net/mvc/tutorials/mvc-music-store/mvc-music-store-part-5
http://mvcmusicstore.codeplex.com/SourceControl/changeset/view/d9f25c5263ed#MvcMusicStore%2fControllers%2fStoreManagerController.cs
If there is a solution about how to make the framework inject view model with your data retrievers (such as a repository) then I believe it may be to use some implementation of either 'IModelBinderProvider' or 'IModelBinder' but I have experimented with these without real success...
So, can anyone provide a link to a complete working example with ASP.NET MVC 3 code that enables injection of a data retriever into the constructor of a view model that the framework instantiates and will send as parameter into an action method ?
Update 2012-01-01:
For those intrested in a solution to this specific question about constructor injection of a ViewModel instance, when the framework instantiates it and sends it as parameter to an MVC Action Method parameter, I have created a new question with a more specific subject, and thus hopefully more likely that someone with a solution will find it and post a good answer:
Constructor injection of a View Model instance used as an Action method parameter