Lets say I have two classes one base class:
public class BaseModel {
}
And a couple of children:
public class FooModel : BaseModel {
}
public class BarModel : BaseModel {
}
Now my view I would like to expect a model like this:
#model IList<BaseModel>
So that I can edit these models on one page.
However when I pass in a collection of these models in the action I get an exception saying:
The model item passed into the dictionary is of type
'System.Collections.Generic.List1[BarModel]', but this dictionary
requires a model item of type
'System.Collections.Generic.IList1[BaseModel]'.
Like this:
var models = new List<BaseModel>(){ new BarModel(), new FooModel ()}
return View(models);
How can I achieve this?
Thanks
Assuming you're not modify the model collection on the page, your page shouldn't take IList<T>. Rather it should take IEnumerable<T>:
#model IEnumerable<BaseModel>
That should take care of things.
You can alseo use KnownType:
[Serilizable]
[KnownType(typeof(FooModel))]
[KnownType(typeof(BarModel))]
public class BaseModel
{
}
Hope this helps.
Related
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);
}
I'm having a problem with a polymorphic collection of ViewModels in my MVC application. I received this via a web service call and i need to iterate through them and give them their own partial view, based on the object type.
public abstract class ProvinceViewModel
{
public string Code { get; set; }
}
public sealed class OntarioViewModel : ProvinceViewModel { }
public sealed class QuebecViewModel : ProvinceViewModel {}
In my view i am trying to iterate through them and assign a partial view. I have to do a lot of type casting here to make it work. If I try and move this to a controller action and pass in the abstract type, i will get an error that we cannot create an instance of abstract class.
ICollection<ProvinceViewModel> ProvinceList; // collection receive via service
#for (int i = 0, c = ProvinceList.Count; i < c; i++)
{
var currentProvince = this.Model.ElementAt(i);
#switch (additionalRegistry.Code)
{
case "QC":
#Html.Partial("AlbertaDetail", (QuebecViewModel)currentProvince)
break;
case "ON":
#Html.Partial("OntarioDetail", (OntarioViewModel)currentProvince)
break;
default:
#Html.Partial("ProvinceDetail", ProvinceViewModel)
break;
}
}
I have strongly type View, so that i can access the different properties.
How would i go about solving this in a more elegant way? Would I need to create a new surrogate base class for the abstract class to create a instance of it easier?
You can achieve this with display templates. Create a display template for each type in the DisplayTemplates folder within your Controller's Views directory:
+-- Views
+-- Provinces
+-- DisplayTemplates
+-- OntarioViewModel.cshtml
+-- QuebecViewModel.cshtml
Display each model using the DisplayFor helper in your view:
#model ICollection<ProvinceViewModel>
#foreach (var province in Model)
{
#Html.DisplayFor(_ => province)
}
Upon encountering the same problem in the past, I have created the following solution:
First, decorate your (concrete) view-model with ExportMetadata attribute that denotes the view name to be used. For example:
[ExportMetadata("View", "Ontario")]
public sealed class OntarioViewModel : ProvinceViewModel { }
[ExportMetadata("View", "Quebec")]
public sealed class QuebecViewModel : ProvinceViewModel {}
Then extend your HtmlHelper with the following Partial method:
public static MvcHtmlString Partial<T>(this HtmlHelper htmlHelper, T model, string prefix = null)
{
var modelType = typeof (T);
var partialAttr = modelType.GetCustomAttributes<ExportMetadataAttribute>().SingleOrDefault(x => x.Name == "View");
if (partialAttr == null)
throw new Exception(modelType.Name + " doesn't define any view to be used");
var partialName = (prefix ?? String.Empty) + partialAttr.Value;
return htmlHelper.Partial(partialName, model, htmlHelper.ViewData);
}
Then use it:
#Html.Partial(currentProvince);
And in case your partials reside in some sub-directory:
#Html.Partial(currentProvince, "Partials/")
(If you need help registering the custom HTML helper see https://stackoverflow.com/a/5052790)
I had a similar requirement and this is how I managed to solve this issue.
My viewmodel (BusinessEventEmailViewModel ) has a list of interfaces (IBusinessEventEmail) resolved at runtime with unity. A IBusinessEventEmail has an EventCode property.
public class BusinessEventEmailViewModel : MailHeaderViewModel
{
#region members
public List<IBusinessEventEmail> Events { get; set; }
In my view, I render the partial view using a naming convention :
Html.RenderPartial("~/Views/Shared/Email/_" + businessEvent.EventCode + ".cshtml", businessEvent);
Then, I have a XXXEventEmail implementing IBusinessEventEmail with the EventCode XXX and a partial view _XXX.cshtml
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.
For example, I have 3 models with an Id (int), a Name (string) and an active (bool). It's possible to use just one view for these models with the same properties ? A technique like a generic object ? Inheritance ? It's to avoid to write multiple views with the same HTML code.
You could create a ViewModel.
For sample:
public class CustomerViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
}
And create another ViewModel with a type like this:
public class CustomersViewModel
{
public CustomerViewModel Customer1 { get; set; }
public CustomerViewModel Customer2 { get; set; }
public CustomerViewModel Customer3 { get; set; }
}
and type your view with this type:
#model CustomersViewModel
Or just use an collection to type your view
#model List<CustomerViewModel>
Take a look at this anwser!
https://stackoverflow.com/a/694179/316799
In a view you can either
specify shared base class for all models and use that.
use dynamic as model
split view in shared (extract into separate view) and specific part. Than call shared sub-view with either existing model (if using base class/dynamic) or simply new model constructed based on data in specific model.
Sample of extracting shared portion with inheritance. Using Html.Partial to render shared portion:
class SharedModel { /* Id,...*/ }
class SpecificModel : SharedModel { /* Special... */ }
SpecificView:
#model SpecificModel
#Html.Partial("SharedView", Model)
<div>#Model.Special</div>
SharedView:
#model SharedModel
<div>#Model.Id</div>
Side note: You can specify view name when returning result by using View if view name does not match action name:
return View("MySharedView", model);
In ASP.NET MVC4 you have the opportunity not to define a model for the view. This means leave the definition of the model empty (don't use #model MyNamespace.MyClass) and then it will automatically use "dynamic" as model.
Greetings
Christian
I am new to MVC (i.e. the last few days) and i would like to know if what i have done is the best way.
I have a view that is a combination of an insert item form with a list details form underneath for a particular entity. As such i kind of need 2 models for the page in order to avoid doing things like #Html.LabelFor(model => model.FirstOrDefault().EventTypeID, "Event Type").
What i have done is set the model to be Tuple<IEnumerable<Event>,Event> that way i have both the single item and the collection of items. Can anyone suggest a less hacky alternative or is this the best way of doing this?
There are 2 solutions.
You should create a different View Model Class (a simple class with both models as properties)
You can assign it to the ViewBag.Model1 ... ViewBag.Model2 ... But this is dynamic so you will have no intellisense and you can get errors at runtime.
You should use a ViewModel like this
public class ViewModel
{
public TypeOfYourModel MyModel1 { get; set; }
public TypeOfYourModel MyModel2 { get; set; }
}
I suggest you create a ViewModel that would contain both objects you want to pass.
public class NewEventViewModel
{
public Event NewEvent { get; set; }
public Event EventDetails { get; set; }
}
You could also use ViewBag, but it is not strongly typed so you would not get IntelliSense.
I would create a Model object just for the view, with 2 properties, one for the single entity and one for the collection, and then you can pass this composed object as the model for the view