Object as a parameter for an action controller? - c#

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.

Related

In Umbraco, how can I bind a generic list of Objects to my View?

Something strange is happening in my umbraco project where I have a repository set up like so;
public class HireItemsRepo:BaseGenericRepository<YouHireItContext,HireItem>
{
public List<HireItemViewModel> PopulateHireItemViewModel(RenderModel model)
{ List<HireItemViewModel> HireItems = new List<HireItemViewModel>();
foreach (var Hireitem in base.GetAll())
{
HireItems.Add(
new HireItemViewModel(model.Content)
{
Title = Hireitem.Title,
Price = Hireitem.Price
}
);
}
return HireItems;
}
}
which I'm using in my controller like this
public class HiresController : RenderMvcController
{
// GET: Hire
public override ActionResult Index(RenderModel model)
{
HireItemsRepo repo = new HireItemsRepo();
var VM = repo.PopulateHireItemViewModel(model);
return View("Hires",VM.ToList());
}
}
And using that model in the view like this;
#model List<You_Hire_It.Models.HireItemViewModel>
/*HTML starts here*/
It's strange because if I try to use that model as a List, Umbraco will blow up with the following error;
Cannot bind source type System.Collections.Generic.List`1[[You_Hire_It.Models.HireItemViewModel, You_Hire_It, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] to model type Umbraco.Web.Models.RenderModel.
However, if I refactor all the code to use the model on it's own as if I only have one set of values to use, it has no problem with it!
Could anybody point me in the right direction with this please?
Many thanks in advance!
You can inherit from RenderModel as DZL suggests. However, I generally prefer to use route hijacking which would enable me to keep my models simple.
Instead of the Index method in your RenderMvcController, you can create a method with the same name as your view. I note your view is called Hires. So change your controller code to this:
public class HiresController : RenderMvcController
{
// GET: Hire
public ActionResult Hires(RenderModel model)
{
HireItemsRepo repo = new HireItemsRepo();
var VM = repo.PopulateHireItemViewModel(model);
return CurrentTemplate(VM)
}
}
You now need to have your view inherit from UmbracoViewPage. So at the top of your view replace the #model line with the following:
#inherits UmbracoViewPage<List<HireItemViewModel>>
Your model in the view is now of type List<HireItemViewModel> which I think is what you want.
So to iterate the items you would use:
#foreach(var item in Model){
{
// etc
}
Additionally, as this view now inherits from UmbracoViewPage, you have access to the UmbracoContext - just use #Umbraco
For example:
#Umbraco.TypedContentAtRoot().Where(x=>x.DocumentTypeAlias == "HomePage")
or
#Umbraco.AssignedContentItem etc
That is because the model you return from the action need to be of type RenderModel or inherit from it and in your case you are returning a List.
So your model should look something like this:
public class ViewModel : RenderModel
{
public ViewModel(IPublishedContent content) : base(content) { }
public List<HireItem> HireItems { get; set; }
}
public override ActionResult Index(RenderModel model)
{
var vm = new ViewModel(model);
vm.HireItems = new HireItemsRepo().GetHireItems();
return View("Hires", vm);
}

MVC - Generic Actions

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

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

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.

Bound object never be null

For instance, there is some object that will be model for strongly-typed view:
class SomeModel {
public string SomeUsualField { get; set; }
}
Further, some action exists in controller that will work with object specified above:
public ActionResult Index(SomeModel obj)
{
return View(obj);
}
So the question is why obj isn't a null while Index action first called? It's created new instance of SomeModel class with null SomeUsualField.
The ASP.NET MVC model binding infrastructure tries to fill all properties with data coming from the request object (query string, form fields, ...). Therefore it creates an instance of all parameters of the controller to try to match the properties.
Because you don't pass a SomeUsualField, it is null, but the parameter object has an empty instance.
You can initialize the property SomeUsualField when you call http://localhost/MySite/MyController/Index?SomeUsualField=test. The property SomeUsualField will automagically initialized with 'test'
If you don't want the parameter to be set, you can leave it away and make a 2nd action with the attribute [HttpPost]. A good tutorial is the music store.
public ActionResult Index()
{
var obj = new SomeModel();
return View(obj);
}
[HttpPost]
public ActionResult Index(SomeModel obj)
{
// update logic
return View(obj);
}

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.

Categories

Resources