I've got a page in an app I'm building. The page contains a few bits and pieces, then a partial view that loads a different view depending on what's selected from a dropdown. Each of the options from the dropdown has a different view associated with it, and each view has its own fields and model.
Whatever the view is that loads, I'm performing the same action - I'm serializing the model and storing the XML in a database. This is always the case, and there is no unique processing based on the views/models (other than the fact that the fields are different). All models inherit from the same base class for serialization purposes.
I wanted to be able to do something like:
public ActionResult SubmitPartialView<T>(T model)
{
BaseClass baseClassModel = (BaseClass)(object)model;
// serialize and save to database
}
But MVC doesn't allow this - "cannot call action on controller because the action is a generic method".
If I try passing the BaseClass in as a parameter itself, it only contains the properties of the base class and therefore none of the model's properties.
Is there no other option other than to create a separate action for every single view that can submit, and make each one call a separate method that handles the logic?
I see this question is a little old, but if it helps anyone - I was doing some reading with dynamic models and MVC, saw this and it led me to think of a possible solution. Not sure why you would want to have dynamic models. But the great thing with MVC is, you can!
So;
[HttpPost]
public ActionResult SubmitPartial([DynamicModelBinder] dynamic model)
{
// Our model.ToString() serialises it from the baseModel class
var serialisedString = model.ToString();
// do something .. echo it back for demo
return Content(serialisedString);
}
And the model binder is something like this;
public class DynamicModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var currentModel = controllerContext.HttpContext.Request.Form["CurrentModel"];
if (currentModel == "CompanyModel")
{
Type customModel = typeof(CompanyModel);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, customModel);
}
if (currentModel == "UserModel")
{
Type customModel = typeof(UserModel);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, customModel);
}
return base.BindModel(controllerContext, bindingContext);
}
}
hth
Related
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.
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.
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.
I'm trying to create a tagging system for my project. I need the pass a string (for ex: "test1, test2, test3") which will be binded to an entity as a list.
I'm using EF and my view inherits an entity, defined in EF. Without creating a view model, is it possible to do that?
Quite honestly view models is the way to go here.
But because you asked I will try to answer. IIRC EF models are partial classes, meaning that you could add properties to them, like this:
public partial class MyEFModel
{
public IEnumerable<string> List
{
get
{
return SomeStringProperty.Split(',');
}
set
{
SomeStringProperty = string.Join(",", value.ToArray());
}
}
}
Another way to achieve this is to write a custom model binder, like this:
public class MyBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (value != null)
{
return value.AttemptedValue.Split(',');
}
return base.BindModel(controllerContext, bindingContext);
}
}
public class HomeController : Controller
{
public ActionResult Index(
[ModelBinder(typeof(MyBinder))] IEnumerable<string> values
)
{
return View();
}
}
and then /home/index?values=val1,val2,val3 should bind correctly to the list.
There are couple of ways to achieve this:
Custom Action Filter
Custom Model Binder
These implementations can be found here:
Is it possible to change the querystring variable in ASP.NET MVC path before it hits the controller?
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.