I have a display for a collection of "SomeObject"
public List<SomeObject> SomeObjects
#Html.DisplayFor(m => m.SomeObjects)
This all works fine.. Then I had a requirement that meant I would need a second different display template for the same collection..
#Html.DisplayFor(m => m.SomeObjects, "NameOfTemplate")
I get the below message when I do this.. I have used named display templates before, but maybe it only works for primitive types - strings etc?
'System.Collections.Generic.List1[SomeObject]', but this dictionary
requires a model item of type 'SomeObject'.
Adding code as request
"Test" DisplayTemplate
#model ViewModels.SomeObject
<p class="Namme">
<span>#Model.Name</span>
</p>
SimpleViewModel
public class SomeObject
{
public string Name { get; set; }
}
Property in ParentViewModel
public List<SomeObject> SomeObjects { get; set; }
Cshtml file..
#Html.DisplayFor(m => m.SomeObjects, "Test")
To me its a simple question, can a named template be used with a collection or not? Is it another limitation of mvc..
Cheers,
John
Yes you are passing a list of SomeObject to your display template, when it is expecting:
#model ViewModels.SomeObject
You either need to change the template to something like this and accept a list as the view model:
#model IList<ViewModels.SomeObject>
#foreach (var item in Model) {
<p class="Namme">
<span>#item</span>
</p>
}
Or change what you are passing into the Display Template in the parent CSHTML file and make it a single object of SomeObject something like this:
public List<SomeObject> SomeObjects
#foreach (var item in Model.SomeObjects) {
Html.DisplayFor(item, "TemplateName")
}
Its hard to answer because im not sure what your trying to achieve, and what SomeObject is, but this should point you in the right direction.
The type of SomObjects is List<SomeObjects> so the view template must have a matching model:
#model `ICollection<ViewModels.SomeObject>`
I prefer to not restrict the view to the concrete List<> class but rather use an interface.
That also means that you have to adjust your template to use a loop to iterate through the items in the collection.
Related
I have a model like this
class MyClass
{
}
class MyModel
{
public EditorModel<MyClass> EditorProperty { get; set; }
}
class EditorModel<T>
{
public int MyProp { get; set; }
}
and razor view template under EditorTemplates folder like this
#model Project.EditorModel<MyClass>
#Html.DisplayFor(x => x.MyProp)
and Index razor view
#model Project.MyModel
#Html.EditorFor(x => x.EditorProperty)
But when I run the code to render the template, the template is not rendered, instead what is rendered is a string with the full name of the type used as a model in the editor template view.
Is the problem that EditorFor method doesn't support generic models? is there a way around this?
I think the problem starts with the template name, which cannot really satisfy the generic class name pattern. So the Template is not being picked automatically.
Method EditorFor also has an override where you can pass the Template Name.
What you could do is to update the usage of EditorFor, while providing the Template Name, now the trick is to make that part dynamic, and it could also be part of your ViewModel class:
#model Project.MyModel
#Html.EditorFor(x => x.EditorProperty, Model.TemplateName)
You need to extend MyModel to provide the TemplateName property. It's now up to you how you determine the correct name. You could make it a named pattern convention:
public string TemplateName
{
get
{
// EditorProperty must not be null... mind this is C#6 but its easy to downgrade it
var genericType = EditorProperty?.GetType().GetGenericArguments().FirstOrDefault()?.Name;
return $"EditorModelOf{genericType}";
}
}
And TemplateName will for example return
EditorModelOfMyClass
So you should name your EditorTemplate EditorModelOfMyClass.cshtml. This will only work if your expected amount of generic classes is limited.
I'm having an issue with the bindings of my model to a partial view and feel that there must be a way to do what I want. I wonder if my design is flawed and a small refactoring might be necessary.
A very simplified (and abstract) version of my models would be like so:
Public Class BaseClass
{
Public string Name { get; set; }
Public List<SomeClass> Things { get; set; }
}
Public Class DerivedClass : BaseClass
{
Public List<LineItem> Items { get; set; }
}
Public Class Library
{
Public List<LineItem> Items { get; set; }
}
Public Class LineItem
{
Public string Name { get; set; }
Public string Value { get; set; }
}
I have Editor Templates for the BaseClass, SomeClass, and LineItem. These are shown in the view for DerivedClass and work as intended by submitting changes to the controller. The LineItem template is wrapped in a LineItemList partial view because I intend to use it for a view for Library and don't want to repeat all of that layout and javascript. The LineItemList partial view is included on the DerivedClass view by Html.PartialView since there doesn't seem to be a way to create an Editor Template for the List type. So my views look like this:
DerivedClassView
BaseClassPartialView
SomeClassPartialView
LineItemListPartialView
LineItemParialView
When I submit my form, the controller gets all of the data for the BaseClass and SomeClass list but none for the LineItem list. The difference of course being that one is rendered using Html.EditorFor and the other Html.PartialView.
Refactoring the classes will be tough as they have to be backwards compatible with an old XML format for serialization, but I'm sure I can work some magic if necessary.
As Chris Pratt mentioned, I forgot to include my controller methods:
Public ActionResult DerivedClassEditor()
{
Return View(New DerivedClass());
}
[HttpPost]
Public ActionResult DerivedClassEditor(DerivedClass dc)
{
// Do Stuff
}
I just noticed in the rendered Html, the SomeClass controls are named SomeClass.[0].Name while those of the LineItem are [0].Name. I have a feeling that might be a symptom of the issue.
And my views look similar to this:
DerivedClassEditor
#model DerivedClass
#using (Html.BeginForm())
{
#Html.EditorFor(model => model)
#Html.Partial("LineItemListPartialView")
<input type="submit" />
}
LineItemListPartialView
#model List<LineItem>
<div name="Items">
#Html.EditorFor(model => model)
</div>
LineItemPartialView
#model LineItem
<div name="LineItem">
#Html.LabelFor(model => model.Name)
#Html.TextEditorFor(model => model.Name)
</div>
Edit:
A link to my view: https://github.com/melance/TheRandomizer/blob/Initial/TheRandomizer.WebApp/Views/UserContent/AssignmentEditor.cshtml
I've narrowed down the issue to the fact that when I load one of the lists using #Html.EditorFor it names the inputs Collection[index].Property yet when I add one dynamically using the same call it simply names the input Property. Is there an easy and reusable way to have the addition of new items have the same naming structure?
Crucially, you failed to post your controller code, so I'm stabbing in the dark, but I think I can guess pretty well what's happening. You most likely have something like:
[HttpPost]
public ActionResult MyAwesomeAction(BaseClass model)
{
...
}
And you're assuming that since your view is working with DerivedClass and posting DerivedClass that you should end up with an instance of DerivedClass rather than BaseClass in your action. That assumption is incorrect.
All that exists on post is a set of string key-value pairs. The modelbinder looks at the action signature and attempts to bind the posted data to an instance of the parameter(s), newing up classes as necessary. Given this, the only information the modelbinder has in this scenario is that it's expected to bind values to an instance of BaseClass and a set of data to attempt to do that with. As a result, it will create an instance of BaseClass and bind what data it can, dropping anything it can't. Importantly, since BaseClass doesn't include stuff like your Items property, all of that data will be discarded.
Long and short, polymorphism isn't supported with action parameters. The type of your parameter must be the type you want. Period.
For what it's worth, you can use editor templates for list properties. EditorFor will simply render the editor template for the contained type for each item in the list. For example, calling:
#Html.EditorFor(m => m.Items)
Is essentially the same as:
#for (var i = 0; i < Model.Items.Count(); i++)
{
#Html.EditorFor(m => m.Items[i])
}
So after much research, trial and error and help from Erik, I was able to solve my problem. The issue turned out to be the naming of the form elements in my partial view. When added by the model they are indexed as such: name = "ListProperty[Index].PropertyName". When I was adding my partial views using ajax, the were named for just the Property Name. In order to fix this, I had to handle my ajax calls for the partial view like this:
public ActionResult CreateParameter(Int32 index)
{
ViewData.TemplateInfo.HtmlFieldPrefix = $"Parameters[{index}]";
return PartialView("~/Views/Shared/EditorTemplates/Configuration.cshtml");
}
I think I am missing something simple here.
In my controller I have
public ActionResult ListPage()
{
return View(db.Sections.OrderBy(x=>x.Order).ToList());
}
On the ListPage.cshtml I have
#model IEnumerable<Section>
#foreach (var item in Model)
{
#Html.DisplayFor(item => item, "SectionTemplate")
}
And in the Views/Shared/DisplayTemplates/SectionTemplate.cshtml I'm not sure what to put. Ideally this view would look like
#model Section
<h1>#Model.Title</h1>
But a little more robust obviously.
When I do that, I get an error:
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[Section]', but this dictionary requires a model item of type 'Section'.
How can I change this code to access the properties on the individual model in the template?
Thanks!
In your code, inside of the lambda, item resolves to the parameter of said lambda, and not the item declared in the outer scope. Just rename the parameter of your lambda and it should work as expected:
#foreach (var item in Model)
{
#Html.DisplayFor(_ => item, "SectionTemplate")
}
I am new to MVC and I am trying to display information from a nested model collection on the page.
so my model is as follow:
public partial class Parent
{
public Parent()
{
this.Childs = new HashSet<Child>();
}
public int ParentID { get; set; }
public string Name { get; set; }
public virtual ICollection<Child> Childs { get; set; }
}
To display the information on the Parent view i used :
#foreach (Child c in Model.Childs)
{
#c.Name
}
The above works but i would like to use a different view for the childs so i have tried the following:
#Html.DisplayFor(model => model.Childs)
and defined the following view:
#model WebApplication1.Models.Child
<div>
#Html.DisplayFor(model => model.Name)
</div>
This doesn't work and what I am getting is a System.Data.Entity.DynamicProxies displayed instead of the list of child names. I have read that this is because MVC5 doesn't know what view to use. I have tried specifying the view in the Display for as #Html.DisplayFor(model => model.Childs, "ViewName1.cshtml") but that didn't help at all.
In addition to the above I would like to use something similar for #Html.EditorFor(model => model.Childs).
Start by creating a DisplayTemplates sub-folder in your Parent view folder. Then create a view in that folder called Child.cshtml.
Child.cshtml
#model WebApplication1.Models.Child
#Model.Name
...more HTML markup if needed
Then all you do is call #Html.DisplayFor(m => m.Childs) and the framework will do the rest for you. Note that if you use the overload that lets you specify which view to use, the loop will not be done automatically for you.
Repeat the same process for editor templates, with a EditorTemplates folder and following the same conventions (view name = type name) for naming your views.
You need to tell Razor which view to use for each child object. Something like this (obviously replace ChildViewName with the name of your child view):
#foreach (Child c in Model.Childs)
{
#Html.Partial("ChildViewName", c)
}
Adding my comments as an extension to the answers above because I think many people who are new to asp.net mvc miss this. #panais mentioned that my comment helped him\her. So, I think it might be helpful for others as well.
The editor template views should be created in EditorTemplates folder.
The display template views should be created in DisplayTemplates folder.
Name of the template view should be same as view model name.
I have a view model as such:
public class MyViewModel
{
public MyObject myObject{ get; set; }
public List<MyList> myList{ get; set; }
}
I have a view with a form strongly typed to MyViewModel
This view allows you to enter values for the properties of MyObject, as well as create a list of MyList objects. The List part works fine although I thought that would be the more difficult of the two.
Assuming MyObject has a property Description I create a textbox to enter the value as such:
#Html.EditorFor(x => x.myObject.Description);
The text box renders with an id of MyObject_Description...The problem is when I post this to my controller action, MyObject does not get bound at all(althought the list items do as they recieve the appropriate IDs of "MyViewModel_MyList[guid].myListValue")
What am I doing wrong here??
EDIT: more info
The first line of the view is:
#model MyApp.ViewModels.MyViewModel
And the Action method:
[HttpPost]
public ActionResult Create(MyViewModel myViewModel)
{
}
I am passing a new MyViewModel into the partial view to begin...
public ActionResult Create()
{
MyViewModel model = new MyViewModel();
return PartialView(model);
}
EDIT 2
Ok When I render my partial view that contains the forms I call :
#{Html.RenderAction("Create", "MyController");}
this is called from within a View of type #model IEnumerable<MyApp.Models.MyObject>
(this view displays a list of currently existing MyOjects, and at the bottom the partial is rendered to allow the user to add another MyObject to the DB)
If you are not already doing so, try creating a editor template (e.g., Views->Shared->EditorTemplates) called MyObject.cshtml. Move your partial view content to this view and then call
#Html.Editor("myObject").
from your parent partial view.
Change your ViewModel to have the Description directly
public class MyViewModel
{
public string Description { get; set; }
public List<MyList> myList{ get; set; }
}
then bind accordingly
#Html.EditorFor(x => x.Description);
I would expect the top line of your view to look something like this:
<%# Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<MyViewModel>" %>
This tells the view that the model it is supplied is of type MyViewModel (a la <T> style).
I don't think the out of the box model binding knows how to bind to complex objects. You're probably going to have to write up some sort of custom model binder.
I'm afraid it's not something I've done since MVC1 so I'm a bit hesitant to give you any sample code because the mechanism may well have changed completely since then. A quick google did turn up this article http://www.learnxpress.com/asp-net-mvc-hosting-6-tips-for-asp-net-mvc-model-binding-2.html and this article http://bradwilson.typepad.com/blog/2010/10/service-location-pt9-model-binders.html.
Edit: I've just seen this answer which might help Retrieving data from view, should I use model binder?