On an .NET MVC controller, what does the mapping? - c#

So I have a action method that takes a fancy Cart object:
[HttpPost]
public JsonResult BuildTransaction(Cart cart) { }
The Cart model:
public class Cart
{
public Guid UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
}
I throw some JSON at the route, that looks like this:
object cart = new {
UserId = uid,
FirstName = "John",
LastName = "Travlota",
Address = new {
Line1 = "Ramsdean Grange",
Town = "boom town",
PostCode = "dd7 7sx"
}
};
var request = client.PostAsJsonAsync("BuildTransaction", cart);
The result, I have a cart of type Cart to play with in my controller. Fantastic!
My question is, how does .NET do this mapping? I imagine its someplace in the OnActionExecute but what/how does it do this.
If I wanted to mimic this functionality, what would I do? Do I really need an external tool like AutoMapper if MVC seems perfectly capable of doing it without it?

This is done by the Model Binder. (System.Web.Mvc.DefaultModelBinder)
You can implement a custom model binder like so:
Controller:
public ActionResult Create([ModelBinder(typeof(CreateModelBinder))] CreateViewModel vModel)
{
}
Model Binder:
public class CreateModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
//assign request parameters here, and return a CreateViewModel
//for example
CreateViewModel cVM = new CreateViewModel();
cVM.Name = controllerContext.HttpContext.Request.Params["Name"];
return cVM;
}
}
More info: http://www.dotnetcurry.com/ShowArticle.aspx?ID=584
https://stackoverflow.com/a/1249602/1324019

First MVC receives a post (for example) which contains your html input values and the name associated with each of them.
Then it checks the properties in the expected object (the model type) and tries to find a name which matches in the form received, and so on.
It all happens in the ModelBinder, you can even change the way it is done since this is an extension point.
So this is for the how, and about using AutoMapper, you don't need it here.

This is Mvc Model Binding. MVC has a set of a default model binders. It uses the DefaultModelBinder object as default for your post requests, but it also have the 3 following objects registered by default.
HttpPostedFiledModelBinder
ByteArrayModelBinder
LinqBinaryModelBinder
You can also create your own binders and associate them to a parameter with an attribute on a parameter as #Mansfield pointed out. But you can also register them globally for a specific type as follow (In Application_Start for example)
//Register FooModelBinder for all posted objects that are of type Foo
ModelBinders.Binders.Add(typeof(Foo),new FooModelBinder());
And if for some reason you want to reinvent the wheel you can also change de DefaultModelBinder
ModelBinders.Binders.DefaultBinder = new CustomDefaultModelBinder();
Here's a very simple implemention of a ModelBinder for Foo type
public class FooModelBinder: IModelBinder
{
#region IModelBinder Members
public object BindModel(ControllerContext controllerContext,ModelBindingContext bindingContext) {
IUnvalidatedValueProvider provider = (IUnvalidatedValueProvider)bindingContext.ValueProvider;
return new Foo {
Bar = provider.GetValue("Bar", true).AttemptedValue,
Banana= provider.GetValue("Banana", true).AttemptedValue
};
}
#endregion
}

The ASP.NET MVC model binder is what does this translation of request parameter to class instances. This great piece of functionality operates based on conventions, so as long as you follow default conventions (which means that your request parameters need to have the same name as the names of the properties of your class). So yes, in most cases the default model binder behavior is fine and you don't need a library like AutoMapper.
If you want to know more about when model binding occurs, this article goes into the MVC pipeline in great detail. If you just want to know more about model binding, I found this two page tutorial a great help: part 1 and part 2.

Related

Make MVC not post back one of the members in the model which is an interface

I have a view model that looks like this:
class MyViewModel
{
public string Stuff { get; set; }
public IFoo Foo { get; set; }
}
The GET requests all work fine because I supply both, Stuff and IFoo to the view.
However, when posting back, MVC says it can't make an object of an interface.
Now, I have two options:
a) Change the type of the Foo property to a concrete implementation, which is no big deal; or
b) Do the model binding myself, which is an overkill for what I want. I really don't care about posting back the IFoo member.
How do I tell MVC not to post back IFoo? I don't have any HTML controls also representing the IFoo. Properties from the IFoo, I use as hidden thingies in the view.
You can exclude properties from binding.
Via the model
[Bind(Exclude="Foo")]
class MyViewModel
{
public string Stuff { get; set; }
public IFoo Foo { get; set; }
}
Via the action
[HttpPost]
public ActionResult MyGreatAction([Bind(Exclude="Foo")]MyViewModel model)
{
}
I had a similar problem and found this link to be of super help to me.. Custom Model Binder But just in case the link goes dead sometime in the future here is the thought process behind it.
Posting a list of interfaces
If you try to post this form, MVC will throw this error: Cannot create an instance of an interface. This is because the default model binder works by creating an instance of your model (and any properties it has) and mapping the posted field names to it; Section.Title maps to the Title property on the Section object, for example. However, Section.SectionFields[0] presents a problem – you cannot create an instance of an interface (or an abstract class).
The solution is to create and register a custom model binder for the IField class...
First create a model binder and inherit from the DefaultModelBinder class:
public class IFieldModelBinder : DefaultModelBinder {
protected override object CreateModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
Type modelType) {
// Our work here
}
}
Secondly, register it with your application – this is usually done in Application_Start in Global.asax.
protected void Application_Start(Object sender, EventArgs e) {
ModelBinders.Binders.Add(typeof(IField), new IFieldModelBinder());
}
Next cast each IField into its concrete type. I think she used reflection so she added to properties to her Interface FieldClassName, and FieldAssemblyName. Then she put in 2 hidden fields for Class name and Assembly name in her editor template.
#model SectionSummary
#Html.HiddenFor(x => x.FieldClassName)
#Html.HiddenFor(x => x.FieldAssemblyName)
Now, whenever this particular field is posted, the custom model binder will have the information about the actual type available – which means we can use it to cast the IField object and return a model that can be instantiated.
public class IFieldModelBinder : DefaultModelBinder
{
protected override object CreateModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
Type modelType)
{
// Get the submitted type - should be IField
var type = bindingContext.ModelType;
// Get the posted 'class name' key - bindingContext.ModelName will return something like Section.FieldSections[0] in our particular context, and 'FieldClassName' is the property we're looking for
var fieldClassName = bindingContext.ModelName + ".FieldClassName";
// Do the same for the assembly name
var fieldAssemblyName = bindingContext.ModelName + ".FieldAssemblyName";
// Check that the values aren't empty/null, and use the bindingContext.ValueProvider.GetValue method to get the actual posted values
if (!String.IsNullOrEmpty(fieldClassName) && !String.IsNullOrEmpty(fieldAssemblyName))
{
// The value provider returns a string[], so get the first ([0]) item
var className = ((string[])bindingContext.ValueProvider.GetValue(fieldClassName).RawValue)[0];
// Do the same for the assembly name
var assemblyName =
((string[])bindingContext.ValueProvider.GetValue(fieldAssemblyName).RawValue)[0];
// Once you have the assembly and the class name, get the type - I am overwriting the IField object that came in, but I do not think you have to do that
modelType = Type.GetType(className + ", " + assemblyName);
// Finally, create an instance of this type
var instance = Activator.CreateInstance(modelType);
// Update the binding context's meta data
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instance, modelType);
// Return the instance - which will now be a SummaryField or CommentField - rather than an IField
return instance;
}
return null;
}
}
You will now be able to post your List object – for each IField, the custom model binder will be called to figure out what the concrete type of each object is.

Object as a parameter for an action controller?

Is it possible for an action controller to accept a literal object. For example, I have several views in which I would like to post various models from to a single controller that can then determine the incoming model object for further processing.
Model sample:
public class Model1
{
// properties, etc.
}
public class Model2
{
// properties, etc.
}
public class Model3
{
// properties, etc.
}
controller sample:
[HttpPost]
public ActionResult ProcessModel(Object anyModel)
{
// determine the model
if((anyModel as Model1) != null)
{
var model1 = anyModel as Model1;
// continue with code
}
else if((anyModel as Model2) != null)
{
var model2 = anyModel as Model2;
// continue with code
}
// continue with model check, etc.
}
I've tried, but my controller does not appear to be picking up the model as my object parameter remains empty. Is this possible?
Have a quick read about how model binding works... The model binder (which takes whatever is posted to your Action and turns it into the anyModel parameter uses the type of the parameter to determine what to do.
Since the type is Object it can't do anything.
My guess (depending on what you're trying to achieve) is that you can have several Action overloads each with a different type of Model as the parameter which then call common code.
[HttpPost]
public ActionResult ProcessModel(Model1 anyModel){}
[HttpPost]
public ActionResult ProcessModel(Model2 anyModel){}
[HttpPost]
public ActionResult ProcessModel(Model3 anyModel){}
That said it's a bit odd to have one action which takes lots of different models. There's a good chance you're better off doing something else.
Your question might gather a better answer if you say what you're trying to achieve
The Default Asp.NET ModelBinder cannot bind generic Objects this way. You should take a look here to understand how the model will be build back in the server by the DefaultModelBinder: Understanding ASP.NET MVC Model Binding.
Given that your form has many Models, you should encapsulate them into a ViewModel to do this kind of operation.
The ViewModel should looks like this:
public class MyViewModel
{
public Model1 Model1 {get; set;}
public Model1 Model2 {get; set;}
public Model1 Model3 {get; set;}
}
And the controller:
[HttpPost]
public ActionResult ProcessModel(MyViewModel myViewModel)
{
// determine the model
if(myViewModel.Model1 != null)
{
// continue with code
}
else if(myViewModel.Model2 != null)
{
// continue with code
}
// continue with model check, etc.
}
Recently I faced the same issue and resolved it as below:
Step 1: From javascript pass 2 parameter :
First, pass model name as String for identification which model is coming
Second, Pass data from javascript using JSON.stringify(data). where your data can be from Model1, Model2 , Model3 etc.
Step2: In your controller:
[HttpPost]
public ActionResult ProcessModel(string modelName, string anyModel)
{
switch(modelName) {
case "Model1":
var modelValue= JsonDeserialize<Model1>(anyModel);
// do something
break;
case "Model2":
var modelValue= JsonDeserialize<Model2>(anyModel);
// do something
break;
}
}
You Need One method like below:
public T JsonDeserialize<T>(string jsonModel){
return JsonConvert.DeserializeObject<T>(jsonModel, jsonSettings);
}
JsonConvert need namespace "Newtonsoft.Json".
You also need to declare jsonSettings as below
JsonSerializerSettings jsonSettings= new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
DefaultValueHandling = DefaultValueHandling.Ignore
};
This solution is kind of workaround. There is one more solution. you can check that also:
How can I make a Controller Action take a dynamic parameter?
Hope this helps.

Asp.Net MVC 2 - Bind a model's property to a different named value

Update (21st Sept 2016) - Thanks to Digbyswift for commenting that this solution still works in MVC5 also.
Update (30th April 2012) - Note to people stumbling across this question from searches etc - the accepted answer is not how I ended up doing this - but I left it accepted because it might have worked in some cases. My own answer contains the final solution I used, which is reusable and will apply to any project.
It's also confirmed to work in v3 and v4 of the MVC framework.
I have the following model type (the names of the class and its properties have been changed to protect their identities):
public class MyExampleModel
{
public string[] LongPropertyName { get; set; }
}
This property is then bound to a bunch (>150) of check boxes, where each one's input name is of course LongPropertyName.
The form submits to url with an HTTP GET, and say the user selects three of those checkboxes - the url will have the query string ?LongPropertyName=a&LongPropertyName=b&LongPropertyName=c
Big problem then is that if I select all (or even just over half!) the checkboxes, I exceed the maximum query string length enforced by the request filter on IIS!
I do not want to extend that - so I want a way to trim down this query string (I know I can just switch to a POST - but even so I still want to minimize the amount of fluff in the data sent by the client).
What I want to do is have the LongPropertyName bound to simply 'L' so the query string would become ?L=a&L=b&L=c but without changing the property name in code.
The type in question already has a custom model binder (deriving from DefaultModelBinder), but it's attached to its base class - so I don't want to put code in there for a derived class. All the property binding is currently performed by the standard DefaultModelBinder logic, which I know uses TypeDescriptors and Property Descriptors etc from System.ComponentModel.
I was kinda hoping that there might be an attribute I could apply to the property to make this work - is there? Or should I be looking at implementing ICustomTypeDescriptor?
In response to michaelalm's answer and request - here's what I've ended up doing. I've left the original answer ticked mainly out of courtesy since one of the solutions suggested by Nathan would have worked.
The output of this is a replacement for DefaultModelBinder class which you can either register globally (thereby allowing all model types to take advantage of aliasing) or selectively inherit for custom model binders.
It all starts, predictably with:
/// <summary>
/// Allows you to create aliases that can be used for model properties at
/// model binding time (i.e. when data comes in from a request).
///
/// The type needs to be using the DefaultModelBinderEx model binder in
/// order for this to work.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class BindAliasAttribute : Attribute
{
public BindAliasAttribute(string alias)
{
//ommitted: parameter checking
Alias = alias;
}
public string Alias { get; private set; }
}
And then we get this class:
internal sealed class AliasedPropertyDescriptor : PropertyDescriptor
{
public PropertyDescriptor Inner { get; private set; }
public AliasedPropertyDescriptor(string alias, PropertyDescriptor inner)
: base(alias, null)
{
Inner = inner;
}
public override bool CanResetValue(object component)
{
return Inner.CanResetValue(component);
}
public override Type ComponentType
{
get { return Inner.ComponentType; }
}
public override object GetValue(object component)
{
return Inner.GetValue(component);
}
public override bool IsReadOnly
{
get { return Inner.IsReadOnly; }
}
public override Type PropertyType
{
get { return Inner.PropertyType; }
}
public override void ResetValue(object component)
{
Inner.ResetValue(component);
}
public override void SetValue(object component, object value)
{
Inner.SetValue(component, value);
}
public override bool ShouldSerializeValue(object component)
{
return Inner.ShouldSerializeValue(component);
}
}
This proxies a 'proper' PropertyDescriptor that is normally found by the DefaultModelBinder but presents its name as the alias.
Next we have the new model binder class:
UPDATED WITH #jsabrooke's suggestion below
public class DefaultModelBinderEx : DefaultModelBinder
{
protected override System.ComponentModel.PropertyDescriptorCollection
GetModelProperties(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var toReturn = base.GetModelProperties(controllerContext, bindingContext);
List<PropertyDescriptor> additional = new List<PropertyDescriptor>();
//now look for any aliasable properties in here
foreach (var p in
this.GetTypeDescriptor(controllerContext, bindingContext)
.GetProperties().Cast<PropertyDescriptor>())
{
foreach (var attr in p.Attributes.OfType<BindAliasAttribute>())
{
additional.Add(new AliasedPropertyDescriptor(attr.Alias, p));
if (bindingContext.PropertyMetadata.ContainsKey(p.Name)
&& !string.Equals(p.Name, attr.Alias, StringComparison.OrdinalIgnoreCase)))
{
bindingContext.PropertyMetadata.Add(
attr.Alias,
bindingContext.PropertyMetadata[p.Name]);
}
}
}
return new PropertyDescriptorCollection
(toReturn.Cast<PropertyDescriptor>().Concat(additional).ToArray());
}
}
And, then technically, that's all there is to it. You can now register this DefaultModelBinderEx class as the default using the solution posted as the answer in this SO: Change the default model binder in asp.net MVC, or you can use it as a base for your own model binder.
Once you've selected your pattern for how you want the binder to kick in, you simply apply it to a model type as follows:
public class TestModelType
{
[BindAlias("LPN")]
//and you can add multiple aliases
[BindAlias("L")]
//.. ad infinitum
public string LongPropertyName { get; set; }
}
The reason I chose this code was because I wanted something that would work with custom type descriptors as well as being able to work with any type. Equally, I wanted the value provider system to be used still in sourcing the model property values. So I've changed the meta data that the DefaultModelBinder sees when it starts binding. It's a slightly more long-winded approach - but conceptually it's doing at the meta data level exactly what you want it to do.
One potentially interesting, and slightly annoying, side effect will be if the ValueProvider contains values for more than one alias, or an alias and the property by it's name. In this case, only one of the retrieved values will be used. Difficult to think of a way of merging them all in a type-safe way when you're just working with objects though. This is similar, though, to supplying a value in both a form post and query string - and I'm not sure exactly what MVC does in that scenario - but I don't think it's recommended practise.
Another problem is, of course, that you must not create an alias that equals another alias, or indeed the name of an actual property.
I like to apply my model binders, in general, using the CustomModelBinderAttribute class. The only problem with this can be if you need to derive from the model type and change it's binding behaviour - since the CustomModelBinderAttribute is inherited in the attribute search performed by MVC.
In my case this is okay, I'm developing a new site framework and am able to push new extensibility into my base binders using other mechanisms to satisfy these new types; but that won't be the case for everybody.
You can use the BindAttribute to accomplish this.
public ActionResult Submit([Bind(Prefix = "L")] string[] longPropertyName) {
}
Update
Since the 'longPropertyName' parameter is part of the model object, and not an independent parameter of the controller action, you have a couple of other choices.
You could keep the model and the property as independent parameters to your action and then manually merge the data together in the action method.
public ActionResult Submit(MyModel myModel, [Bind(Prefix = "L")] string[] longPropertyName) {
if(myModel != null) {
myModel.LongPropertyName = longPropertyName;
}
}
Another option would be implementing a custom Model Binder that performs the parameter value assignment (as above) manually, but that is most likely overkill. Here's an example of one, if you're interested: Flags Enumeration Model Binder.
would this be a solution similar to yours Andras? i hope you could post your answer as well.
controller method
public class MyPropertyBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
for (int i = 0; i < propertyDescriptor.Attributes.Count; i++)
{
if (propertyDescriptor.Attributes[i].GetType() == typeof(BindingNameAttribute))
{
// set property value.
propertyDescriptor.SetValue(bindingContext.Model, controllerContext.HttpContext.Request.Form[(propertyDescriptor.Attributes[i] as BindingNameAttribute).Name]);
break;
}
}
}
}
Attribute
public class BindingNameAttribute : Attribute
{
public string Name { get; set; }
public BindingNameAttribute()
{
}
}
ViewModel
public class EmployeeViewModel
{
[BindingName(Name = "txtName")]
public string TestProperty
{
get;
set;
}
}
then to use the Binder in the controller
[HttpPost]
public ActionResult SaveEmployee(int Id, [ModelBinder(typeof(MyPropertyBinder))] EmployeeViewModel viewModel)
{
// do stuff here
}
the txtName form value should be set to the TestProperty.
This should probably be a shorter comment on Andras Zoltan's answer but don't have enough reputation, sorry.
Thanks for the solution, I've just used it and it still works great! However, some of my properties have an alias with the same name, but different case e.g.
[BindAlias("signature")]
public string Signature { get; set; }
These throw an error when the custom model binder tries to add the aliases to the
PropertyMetadata dictionary, as their main property name versions have already been added by the base model binder, and the model binding is case-insensitive.
To solve this, just do a case insensitive check -
replace
if (bindingContext.PropertyMetadata.ContainsKey(p.Name))
with
if (bindingContext.PropertyMetadata.ContainsKey(p.Name)
&& !string.Equals(p.Name, attr.Alias, StringComparison.OrdinalIgnoreCase))
So I've spent most of the day trying to figure out why I couldn't get this to work. Since I'm making my calls from a System.Web.Http.ApiController turns out that you can't use the DefaultPropertyBinder solution as mentioned above but instead must us an IModelBinder class.
the class that I've wound up writing to replace #AndreasZoltan's foundational work as written above is as follows:
using System.Reflection;
using System.Web;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;
using QueryStringAlias.Attributes;
namespace QueryStringAlias.ModelBinders
{
public class AliasModelBinder : IModelBinder
{
private bool TryAdd(PropertyInfo pi, NameValueCollection nvc, string key, ref object model)
{
if (nvc[key] != null)
{
try
{
pi.SetValue(model, Convert.ChangeType(nvc[key], pi.PropertyType));
return true;
}
catch (Exception e)
{
Debug.WriteLine($"Skipped: {pi.Name}\nReason: {e.Message}");
}
}
return false;
}
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
Type bt = bindingContext.ModelType;
object model = Activator.CreateInstance(bt);
string QueryBody = actionContext.Request.Content.ReadAsStringAsync().Result;
NameValueCollection nvc = HttpUtility.ParseQueryString(QueryBody);
foreach (PropertyInfo pi in bt.GetProperties())
{
if (TryAdd(pi, nvc, pi.Name, ref model))
{
continue;
};
foreach (BindAliasAttribute cad in pi.GetCustomAttributes<BindAliasAttribute>())
{
if (TryAdd(pi, nvc, cad.Alias, ref model))
{
break;
}
}
}
bindingContext.Model = model;
return true;
}
}
}
In order to ensure that this runs as part of a WebAPI call you must also add config.BindParameter(typeof(TestModelType), new AliasModelBinder()); in the Regiser portion of your WebApiConfig.
If you are using this method, you also must remove [FromBody] from your method signature.
[HttpPost]
[Route("mytestendpoint")]
[System.Web.Mvc.ValidateAntiForgeryToken]
public async Task<MyApiCallResult> Signup(TestModelType tmt) // note that [FromBody] does not appear in the signature
{
// code happens here
}
Note that this work builds on the answer above, using the QueryStringAlias samples.
At the moment this would likely fail in the case where TestModelType had complex nested types. Ideally there are a few other things:
handle complex nested types robustly
enable an attribute on the class to activate the IModelBuilder as opposed to in the registration
enable the same IModelBuilder to work in both Controllers and ApiControllers
But for now I'm satisfied with this for my own needs. Hopefully someone finds this piece useful.

Passing an interface to an ASP.NET MVC Controller Action method

In my ASP.NET MVC app, I have an interface which acts as the template for several different view models:
public interface IMyViewModel
{
Client Client1 { get; set; }
Client Client2 { get; set; }
Validator Validate();
}
So, my view models are defined like this:
public interface MyViewModel1 : IMyViewModel
{
Client Client1 { get; set; }
Client Client2 { get; set; }
// Properties specific to MyViewModel1 here
public Validator Validate()
{
// Do ViewModel-specific validation here
}
}
public interface MyViewModel2 : IMyViewModel
{
Client Client1 { get; set; }
Client Client2 { get; set; }
// Properties specific to MyViewModel2 here
public Validator Validate()
{
// Do ViewModel-specific validation here
}
}
Then I currently have a separate controller action to do the validation for each different type, using model binding:
[HttpPost]
public ActionResult MyViewModel1Validator(MyViewModel1 model)
{
var validator = model.Validate();
var output = from Error e in validator.Errors
select new { Field = e.FieldName, Message = e.Message };
return Json(output);
}
[HttpPost]
public ActionResult MyViewModel2Validator(MyViewModel2 model)
{
var validator = model.Validate();
var output = from Error e in validator.Errors
select new { Field = e.FieldName, Message = e.Message };
return Json(output);
}
This works fine—but if I had 30 different view model types then there would have to be 30 separate controller actions, all with identical code apart from the method signature, which seems like bad practice.
My question is, how can I consolidate these validation actions so that I can pass any kind of view model in and call it's Validate() method, without caring about which type it is?
At first I tried using the interface itself as the action parameter:
public ActionResult MyViewModelValidator(IMyViewModel model)...
But this didn't work: I get a Cannot create an instance of an interface exception. I thought an instance of the model would be passed into the controller action, but apparently this is not the case.
I'm sure I'm missing something simple. Or perhaps I've just approached this all wrong. Can anyone help me out?
The reason why you cannot use the interface is because of serialization. When a request comes in it only contains string key/value pairs that represent the object:
"Client1.Name" = "John"
"Client2.Name" = "Susan"
When the action method gets invoked the MVC runtime tries to create values to populate the method's parameters (via a process called model binding). It uses the type of the parameter to infer how to create it. As you've noticed, the parameter cannot be an interface or any other abstract type because the runtime cannot create an instance of it. It needs a concrete type.
If you want to remove repeated code you could write a helper:
[HttpPost]
public ActionResult MyViewModel1Validator(MyViewModel1 model)
{
return ValidateHelper(model);
}
[HttpPost]
public ActionResult MyViewModel2Validator(MyViewModel2 model)
{
return ValidateHelper(model);
}
private ActionResult ValidateHelper(IMyViewModel model) {
var validator = model.Validate();
var output = from Error e in validator.Errors
select new { Field = e.FieldName, Message = e.Message };
return Json(output);
}
However, you will still need a different action method for each model type. Perhaps there are other ways you could refactor your code. It seems the only difference in your model classes is the validataion behavior. You could find a different way to encode the validation type in your model class.
You could check this: http://msdn.microsoft.com/en-us/magazine/hh781022.aspx.
This is caused because DefaultModelBinder has no way of knowing what concrete type of IMyViewModel should create.
For solution that, you create custom model binder and indicate how to create and bind an instance of interface.
I think I would create an abstract base class that implemented IMyViewModel. I would make Validate an abstract method and require overriding in my concrete view models that inherited from MyAbstractViewModel. Inside your controller, you can work with the IMyViewModel interface if you want, but binding and serialization really needs a concrete class to bind. My $.02.
You could consider using a base class instead of the interface.

asp.NET MVC 2 DataAnnotations UpdateModel<T> validation

I'm trying to use DataAnnotations to add validation to my models in asp.NET MVC 2 RC2, using TryUpdateModel
var user = UserManager.Find(id);
this.TryUpdateModel<IProvisioningObject>(user, form.ToValueProvider());
This updates the model, but the validation is never called. I tried using TryUpdateModel as well (which is the direct type of user), not using the form value provider, using ProvisioningObject directly (which has the validation metadata), to no avail.
Googling for examples only gives me ways to use DataAnnotations by binding through a parameter
public ActionResult Update(User user)
Which I dislike for update scenarios.
Any tips and/or solutions?
EDIT
My objects are auto-generated objects from a WCF service.
I made partials to be able to add DataAnnotations.
I call TryUpdateModel three times because it apparently doesn't support inheritance, which I think is also my problem with DataAnnotations. I specify the validation attributes for ProvisioningObject, and the binding doesn't look for inherited stuff like that.
[MetadataType(typeof(ProvisioningObjectMetadata))]
public partial class ProvisioningObject : IProvisioningObject
{
public string DisplayNameInvariant { get { return string.IsNullOrEmpty(this.DisplayName) ? this.Name : this.DisplayName; } }
}
[MetadataType(typeof(UserMetadata))]
public partial class User : IUser
{
}
public class ProvisioningObjectMetadata
{
[DisplayName("Country")]
public string CountryIsoCode { get; set; }
[Required(ErrorMessageResourceType = typeof(Properties.Validation), ErrorMessageResourceName = "DisplayNameIsRequired")]
[TempValidator]
public string DisplayName { get; set; }
}
public class UserMetadata
{
[DisplayName("Username")]
public string Name { get; set; }
}
// Controller action
public ActionResult Update(string id, FormCollection form)
{
var user = UserManager.Find(id);
this.TryUpdateModel<IUser>(user.User, form.ToValueProvider());
this.TryUpdateModel<IPerson>(user.User, form.ToValueProvider());
this.TryUpdateModel<IProvisioningObject>(user.User, form.ToValueProvider());
if (ModelState.IsValid) // always true
{
return Redirect;
}
else
{
return View();
}
}
If I add the metadata for DisplayName in UserMetadata, it works as expected, but that seems very redundant for nothing. And it would mean I would also have to copy/paste all my inherited interfaces so TryUpdateModel behaves appropriately.
I guess I'm looking for a way that doesn't require me to copy and paste my validation attributes to inherited classes.
New Answer:
"My objects are auto-generated objects from a WCF service."
Autogenerated objects won't have any attributes on them. Are you defining your objects and their attributes on the server side or on the client side?
Old Answer:
If your metadata is not on IProvisioningObject then no validation will be called. The MVC2 default model binder only knows how to find "extra" [MetadataType(buddyClass)] validation information.
For update scenarios bind against DTOs and then map the DTOs, if IsValid() to your main model classes.
Implement IDataErrorInfo interface in your partial class
You will have to write custom validation for each field(where you can use data annotation class to validate each required property)
If you need code example then let me know. I will write it for you!
source: http://www.asp.net/(S(pdfrohu0ajmwt445fanvj2r3))/learn/mvc/tutorial-37-cs.aspx
How do you know that the validation is not being called? Are you checking ModelState.IsValid in your update controller and finding that it is erroneously coming back true?
A typical update pattern is:
UpdateModel(model);
if(!ModelState.IsValid) return View(model);
return RedirectToAction("Index");
If you are expecting some "IsValid" on your model to automatically be called, that will not happen. The data annotations work behind the scenes with the ModelState dictionary on the Controller base class.

Categories

Resources