ASP.net MVC - Display Template for a collection - asp.net-mvc

I have the following model in MVC:
public class ParentModel
{
public string Property1 { get; set; }
public string Property2 { get; set; }
public IEnumerable<ChildModel> Children { get; set; }
}
When I want to display all of the children for the parent model I can do:
#Html.DisplayFor(m => m.Children)
I can then create a ChildModel.cshtml display template and the DisplayFor will automatically iterate over the list.
What if I want to create a custom template for IEnumerable?
#model IEnumerable<ChildModel>
<table>
<tr>
<th>Property 1</th>
<th>Property 2</th>
</tr>
...
</table>
How can I create a Display Template that has a model type of IEnumerable<ChildModel> and then call #Html.DisplayFor(m => m.Children) without it complaining about the model type being wrong?

Like this:
#Html.DisplayFor(m => m.Children, "YourTemplateName")
or like this:
[UIHint("YourTemplateName")]
public IEnumerable<ChildModel> Children { get; set; }
where obviously you would have ~/Views/Shared/DisplayTemplates/YourTemplateName.cshtml:
#model IEnumerable<ChildModel>
<table>
<tr>
<th>Property 1</th>
<th>Property 2</th>
</tr>
...
</table>

This is in reply to Maslow's comment. This is my first ever contribution to SO, so I don't have enough reputation to comment - hence the reply as an answer.
You can set the 'TemplateHint' property in the ModelMetadataProvider. This would auto hookup any IEnumerable to a template you specify. I just tried it in my project. Code below -
protected override CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype(CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor)
{
var metaData = base.CreateMetadataFromPrototype(prototype, modelAccessor);
var type = metaData.ModelType;
if (type.IsEnum)
{
metaData.TemplateHint = "Enum";
}
else if (type.IsAssignableFrom(typeof(IEnumerable<object>)))
{
metaData.TemplateHint = "Collection";
}
return metaData;
}
You basically override the 'CreateMetadataFromPrototype' method of the 'CachedDataAnnotationsModelMetadataProvider' and register your derived type as the preferred ModelMetadataProvider.
In your template, you cannot directly access the ModelMetadata of the elements in your collection. I used the following code to access the ModelMetadata for the elements in my collection -
#model IEnumerable<object>
#{
var modelType = Model.GetType().GenericTypeArguments[0];
var modelMetaData = ModelMetadataProviders.Current.GetMetadataForType(null, modelType.UnderlyingSystemType);
var propertiesToShow = modelMetaData.Properties.Where(p => p.ShowForDisplay);
var propertiesOfModel = modelType.GetProperties();
var tableData = propertiesOfModel.Zip(propertiesToShow, (columnName, columnValue) => new { columnName.Name, columnValue.PropertyName });
}
In my view, I simply call #Html.DisplayForModel() and the template gets loaded. There is no need to specify 'UIHint' on models.
I hope this was of some value.

In my question about not getting output from views, I actually have an example of how to template a model with a collection of child models and have them all render.
ASP.NET Display Templates - No output
Essentially, you need to create a model that subclasses List<T> or Collection<T> and use this:
#model ChildModelCollection
#foreach (var child in Model)
{
Html.DisplayFor(m => child);
}
In your template for the collection model to iterate and render the children. Each child needs to strongly-typed, so you may want to create your own model types for the items, too, and have templates for those.
So for the OP question:
public class ChildModelCollection : Collection<ChildModel> { }
Will make a strongly-typed model that's a collection that can be resolved to a template like any other.

The actual "valid answer" is -IMHO- not correctly answering the question. I think the OP is searching for a way to have a list template that triggers without specifying the UIHint.
Magic stuff almost does the job
Some magic loads the correct view for a specified type.
Some more magic loads the same view for a collection of a specified type.
There should be some magic that iterates the same view for a collection of a specified type.
Change the actual behavior?
Open your favorite disassembler. The magic occurs in System.Web.Mvc.Html.TemplateHelpers.ExecuteTemplate. As you can see, there are no extensibility points to change the behavior. Maybe a pull request to MVC can help...
Go with the actual magic
I came up with something that works. Create a display template ~/Views/Shared/DisplayTemplates/MyModel.cshtml.
Declare the model as type object.
If the object is a collection, iterate and render the template again. If it's not a collection, then show the object.
#model object
#if (Model is IList<MyModel>)
{
var models = (IList<MyModel>)Model;
<ul>
#foreach (var item in models)
{
#Html.Partial("DisplayTemplates/MyModel", item)
}
</ul>
} else {
var item = (MyModel)Model;
<li>#item.Name</li>
}
}
Now DisplayFor works without UIHint.

Related

Model send from PartialView to controller

I start work in asp.net-mvc and I have problem to send model from partialview to controller.
So first this is the way I create partialview
#Html.Partial("Weather", ShopB2B.Controllers.HomeController.GetWeather())
GetWeather() is controller metod that initializes first data to model. Model looks like this
public class Weather_m
{
public IEnumerable<SelectListItem> City_dropdown { get; set; }
public string Temperature { get; set; }
}
It is nesesery to DropDownListFor, and partialview looks like this
#model ShopB2B.Models.Weather_m
#using (#Html.BeginForm("GetWeatherNew", "Home", new { weather = Model }, FormMethod.Post))
{
<table>
<tr>
<td>#Html.DropDownListFor(x => x.City_dropdown, Model.Miasta_dropdown)</td>
</tr>
<tr>
<td>#Html.LabelFor(x => x.Temperature, Model.Temperatura)</td>
<td><<input type="submit" value="Send" class="submitLink" style=" height: 40px;" /></td>
</tr>
</table>
}
And here is problem because I want send this model to controller and then check which field is selected, add something, and send this model again to partialview. Any idea, how to do it?????
You really should not be getting the data for your ViewModel type on view rendering.
Your data is type of ShopB2B.Models.Weather_m. Your strongly typed partial view expects this, this is all good. But instead of getting your ShopB2B.Models.Weather_m instentiated with ShopB2B.Controllers.HomeController.GetWeather(), you should create a ViewModel and return it to your strongly typed view, say MyViewModel. This should wrap an instance of ShopB2B.Models.Weather_m. So in your main view, your view would be strongly typed for:
#model ShopB2B.Models.MyViewModel
and you render your partial view like
#Html.Partial("Weather", Model.MyWeather_m)
I usually wrap the partial view inside the form as well, like:
#using (#Html.BeginForm("GetWeatherNew", "Home", new { weather = Model }, FormMethod.Post))
{
#Html.Partial("Weather", Model.MyWeather_m)
}
Hope this helps.
You should define the property bind to Dropdown appropriately. Since, you have defined the city_dropdown defined as IEnumarable so model binding will fails while sending from data to server due to data type mismatch (at client side City_dropdown will be generated as string for select control). In this case, you should change the property of Model as follows.
public class Weather_m
{
public string City_dropdown { get; set; }
public string Temperature { get; set; }
}
And
#Html.DropDownListFor(x => x.City_dropdown, Model.Miasta_dropdown)

Best way to get table header names in MVC 4

I'm trying to display a list of objects in a table. I can iterate over each individual item to find it's value (using an for loop or a DisplayTemplate), but how do I abitriarily pick one to display headers for the whole group.
Here's an simplified example:
Model:
public class ClientViewModel
{
public int Id { get; set; }
public List<ClientDetail> Details { get; set; }
}
public class ClientDetail
{
[Display(Name="Client Number")]
public int ClientNumber { get; set; }
[Display(Name = "Client Forname")]
public string Forname { get; set; }
[Display(Name = "Client Surname")]
public string Surname { get; set; }
}
View
#model MyApp.ViewModel.ClientViewModel
#{ var dummyDetail = Model.Details.FirstOrDefault(); }
<table>
<thead>
<tr>
<th>#Html.DisplayNameFor(model => dummyDetail.ClientNumber)</th>
<th>#Html.DisplayNameFor(model => dummyDetail.Forname)</th>
<th>#Html.DisplayNameFor(model => dummyDetail.Surname)</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Details.Count; i++)
{
<tr>
<td>#Html.DisplayFor(model => model.Details[i].ClientNumber)</td>
<td>#Html.DisplayFor(model => model.Details[i].Forname)</td>
<td>#Html.DisplayFor(model => model.Details[i].Surname)</td>
</tr>
}
</tbody>
</table>
Notice: I'm using var dummyDetail = Model.Details.FirstOrDefault(); to get a single item whose properties I can access in DisplayNameFor.
What would be the best way to access those headers ?
Will this break if the collection is null?
Should I just replace them with hard coded plain text labels?
The Problem
As Thomas pointed out, Chris's answer works in some cases, but runs into trouble when using a ViewModel because the nested properties don't enjoy the same automatic resolution. This works if your model type is IEnumerable<Type>, because the DisplayNameFor lambda can still access properties on the model itself:
However, if the ClientDetail collection is nested inside of a ViewModel, we can't get to the item properties from the collection itself:
The Solution
As pointed out in DisplayNameFor() From List in Model, your solution is actually perfectly fine. This won't cause any issues if the collection is null because the lambda passed into DisplayNameFor is never actually executed. It's only uses it as an expression tree to identify the type of object.
So any of the following will work just fine:
#Html.DisplayNameFor(model => model.Details[0].ClientNumber)
#Html.DisplayNameFor(dummy => Model.Details.FirstOrDefault().ClientNumber)
#{ ClientDetail dummyModel = null; }
#Html.DisplayNameFor(dummyParam => dummyModel.ClientNumber)
Further Explanation
If we want to see some of the fancy footwork involved in passing an expression, just look at the source code on DisplayNameFor or custom implementations like DescriptionFor. Here's an simplified example of what happens when we call DisplayNameFor with the following impossible expression:
#Html.DisplayNameFor3(model => model.Details[-5].ClientNumber)
Notice how we go from model.Details.get_Item(-5).ClientNumber in the lambda expression, to being able to identify just the member (ClientNumber) without executing the expression. From there, we just use reflection to find the DisplayAttribute and get its properties, in this case Name.
Sorry, your question is a little hard to understand, but I think the gist is that you want to get the display names for your properties, to use as headers, without requiring or first having to pick a particular item out of the list.
There's already built-in support for this. You simply just use the model itself:
#Html.DisplayNameFor(m => m.ClientNumber)
In other words, just don't use a particular instance. DisplayNameFor has logic to inspect the class the list is based on to get the properties.

How to list the records from different tables inside cshtml view?

I am having a problem in a ASP.NET MVC project. I have a database where I have several tables, and I want to list all the records inside each table in my View, I am able to do this using only one table, I am going to provide here the code that works with one table and what I am trying to do.
Controller:
Using only one table I would do:
Mp5DataclassesDataContext db = new Mp5DataclassesDataContext();
public ActionResult Admin()
{
return View(db);
}
This is what I am trying to do:
return View(db);
At this point I can debug and see that all my tables are in that db object, with all the correct data. Then the problem is in my View
View:
#*This is defined at the top of my .cshtml*#
#model IEnumerable<Interface_AutoUtad.Models.Mp5DataclassesDataContext>
#*This is the code that works with only one table*#
#foreach (var item in Model)
{
#Html.DisplayFor(modelItem => item.table_column)
}
#*What I am trying to do:*#
#foreach (var item in Model)
{
#Html.DisplayFor(modelItem => item.some_other_table.table_column)
}
I can't get this last part to work because "some_other_table" doesn't show up.
Is there a way I can achieve this ? I want to Iterate through each table and inside iterate through each record in that table.
Thank you all in advance :)
It looks like you've set the "model" for your view to be your application's DbContext. Don't do that. Views are only processed at runtime, and querying into your database is way too much logic for a view. Any errors will only be exposed at runtime, and you have a huge potential for errors.
Views are designed to work with only one type. If you need to work with multiple types in the same view, you can either utilize a view model or use child actions.
View Model
public class MultipleTypesViewModel()
{
public IEnumerable<SomeType> SomeTypes { get; set; }
public IEnumerable<OtherType> OtherTypes { get; set; }
...
}
Then in your view:
#model Namespace.To.MultipleTypesViewModel
...
#foreach (var item in Model.SomeTypes)
{
#Html.DisplayFor(modelItem => item.table_column)
}
#foreach (var item in Model.OtherTypes)
{
#Html.DisplayFor(modelItem => item.table_column)
}
...
Child Actions
[ChildActionOnly]
public ActionResult ListSomeTypes()
{
var someTypes = db.SomeTypes.ToList();
return PartialView(someTypes);
}
[ChildActionOnly]
public ActionResult ListOtherTypes()
{
var otherTypes = db.OtherTypes.ToList();
return PartialView(otherTypes);
}
...
Then, create an appropriate partial view for each:
ListSomeTypes.cshtml
#model IEnumerable<Namespace.To.SomeType>
#foreach (var item in Model)
{
#Html.DisplayFor(modelItem => item.table_column)
}
ListOtherTypes.cshtml
#model IEnumerable<Namespace.To.OtherType>
#foreach (var item in Model)
{
#Html.DisplayFor(modelItem => item.table_column)
}
Etc. And then finally in your main view:
#Html.Action("ListSomeTypes")
#Html.Action("ListOtherTypes")
(In this case, the model of the main view is totally irrelevant.)

ASP MVC3 - passing collection item into partial view

I have a view model that I've created with a collection (a List) of a separate model. In our database, we have two tables: BankListMaster and BankListAgentId. The primary key of the "Master" table serves as the foreign key for the Agent Id table.
As the Master/Agent ID tables have a one-to-many relationship, the view model I've created contains a List object of BankListAgentId's. On our edit page, I want to be able to both display any and all agent Ids associated with the particular bank, as well as give the user the ability to add or remove them.
I'm currently working through Steve Sanderson's blog post about editing a variable length list. However, it doesn't seem to cover this particular scenario when pulling existing items from the database.
My question is can you pass a specific collection item to a partial view, and if so how would you code that into the partial view correctly? The following code below states that
The name 'item' does not exist in the current context
However, I've also tried using a regular for loop with an index and this syntax in the partial view:
model => model.Fixed[i].AgentId
but that just tells me the name i does not exist in the current context. The view will not render using either method.
Below is the code from the view
#model Monet.ViewModel.BankListViewModel
#using (Html.BeginForm())
{
<fieldset>
<legend>Stat(s) Fixed</legend>
<table>
<th>State Code</th>
<th>Agent ID</th>
<th></th>
#foreach(var item in Model.Fixed)
{
#Html.Partial("FixedPartialView", item)
}
</table>
</fieldset>
}
Here is the partial view
#model Monet.ViewModel.BankListViewModel
<td>
#Html.DropDownListFor(item.StateCode,
(SelectList)ViewBag.StateCodeList, item.StateCode)
</td>
<td>
#Html.EditorFor(item.AgentId)
#Html.ValidationMessageFor(model => model.Fixed[i].AgentId)
<br />
Delete
</td>
And here is the view model. It currently initialized the Fixed/Variable agent Id lists to 10, however that is just a work-around to get this page up and running. In the end the hope is to allow the lists to be as large or small as needed.
public class BankListViewModel
{
public int ID { get; set; }
public string BankName { get; set; }
public string LastChangeOperator { get; set; }
public Nullable<System.DateTime> LastChangeDate { get; set; }
public List<BankListAgentId> Fixed { get; set; }
public List<BankListAgentId> Variable { get; set; }
public List<BankListAttachments> Attachments { get; set; }
public BankListViewModel()
{
//Initialize Fixed and Variable stat Lists
Fixed = new List<BankListAgentId>();
Variable = new List<BankListAgentId>();
Models.BankListAgentId agentId = new BankListAgentId();
for (int i = 0; i < 5; i++)
{
Fixed.Add(agentId);
Variable.Add(agentId);
}
//Initialize attachment Lists
Attachments = new List<BankListAttachments>();
Attachments.Add(new BankListAttachments());
}
}
The problem is with your partial view. In your main view, in the loop, you're passing a BankListAgentId object. However, your partial view's Model type is #model Monet.ViewModel.BankListViewModel.
Furthermore, you are trying to access a variable called item in your partial view, when none exist. Instead of using item to access your data, use Model like in any other view. Each view (even a partial one) has it's own Model type.
Your partial view should look something like this:
#model Monet.ViewModel.BankListAgentId
<td>
#Html.DropDownListFor(model => model.StateCode,
(SelectList)ViewBag.StateCodeList, Model.StateCode)
</td>
<td>
#Html.EditorFor(model => model.AgentId)
#Html.ValidationMessageFor(model => model.AgentId)
<br />
Delete
</td>
The model you are passing into the Partial View is a BankListAgentId--- because you are looping over a collection of them when you are creating the partial view.
You're doing everything right so far. You're looping through your list, call the partial for each list item and passing the item into the partial. It seems that the part you're missing is that when you pass the item into the partial, the item becomes the model for the partial. So you interact with it like you would in any other views, i.e. #Model.BankName, #Html.DisplayFor(m => m.BankName), etc.

How to use passed select list in an editor template

I'm trying to create an editor template that will create a "bootstrap style" radio buttons for each value from a passed select list (just like the Html.DropDownFor method create a dropdown list)
So i have the call in my view:
#Html.EditorFor(model => model.FaultTypeID,"RadioButtonList",
new SelectList(Model.AllowdeFaultTypes, "FaultTypeID", "FaultTypeName"))
and now the template of RadioButtonList:
#foreach (var item in ViewData["Items"] as SelectList)
{
<a>#item.Text</a> <b>#item.Value</b>
}
but the conversion fails and i get a NullReferanceExeption.
By reflection i see that the ViewData["Items"] value is of type System.Collections.Generic.List<CamelotFaultManagement.DAL.FaultType>
The problem is i really don't want to tightly couple the RadioButtonList editor template with CamelotFaultManagement.DAL.FaultType class, its just don't make any sense to do that. I want a generic editor template.
In your editor template you seem to be using some ViewData["Items"] property which you never set. If you want to use such property make sure you have assigned it:
#Html.EditorFor(
model => model.FaultTypeID,
"RadioButtonList",
new { Items = new SelectList(Model.AllowdeFaultTypes, "FaultTypeID", "FaultTypeName") }
)
This being said, your approach with using some ViewData stuff seems totally wrong to me.
I would simply define a view model (as always in ASP.NET MVC):
public class RadioListViewModel
{
public string Value { get; set; }
public IEnumerable<SelectListItem> Values { get; set; }
}
and then you could have your editor template strongly typed to this view model. Of course your editor template will be now stored in ~/Views/Shared/EditorTemplates/RadioListViewModel.cshtml:
#model IRadioListViewModel
#foreach (var item in Model)
{
<a>#item.Text</a> <b>#item.Value</b>
}
and now all that's left is to use this view model in your main view model:
public class MyViewModel
{
public RadioListViewModel FaultTypes { get; set; }
...
}
and then inside your view simply render the corresponding editor template:
#model MyViewModel
...
#Html.EditorFor(x => x.FaultTypes)
Simple, conventional, strongly typed.

Resources