Can a display template be invoked from within an editor template? - c#

I have created a display template in ~/Views/Shared/DisplayTemplates named ImpactMatrix.cshtml. It accepts a nullable int and renders a two-dimensional matrix with the selected number highlighted:
#model int?
#{
var matrix = ImpactMatrix.GetMatrix();
}
<div class="impactmatrix">
<table>
#for (int i = 0; i < matrix.GetLength(0); i++)
{
<tr>
#for (int j = 0; j < matrix.GetLength(1); j++)
{
var cell = matrix[i, j];
<td data-color="#cell.Color"
class="matrix #(Model == cell.Value ? cell.Color.ToString() : "")">
#cell.Value
</td>
}
</tr>
}
</table>
</div>
It's easily reusable and works great. I can invoke it within my view like so:
#Html.DisplayFor(m=> m.ImpactFactor, "ImpactMatrix")
Now I've decided to extend that and make it an editor as well. The idea is to add a hidden input for the selected number and wrap the input along with the matrix template with a div. From there it should be a simple matter to use Javascript to interact with my display grid and populate the hidden input.
I've created an editor template, also named ImpactMatrix.cshtml, within my ~/Views/Shared/EditorTemplates folder. Here's the code:
#model int?
<div class="impactmatrix-editor">
#Html.HiddenFor(m => m)
#Html.DisplayFor(m => m, "ImpactMatrix")
</div>
My problem is that the hidden input renders correctly, but the nested display template does not render inside my editor template. Is what I am trying to do possible?

It seems that it is not currently supported.
However, I have found a solution using the Html.Partial in this article: Nested #Html.DisplayFor(model => baseClass, "BaseClass") for base class template not rendering
Rewrite you editor template like this:
#model int?
<div class="impactmatrix-editor">
#Html.HiddenFor(m => m)
#Html.Partial("~/Views/Shared/DisplayTemplates/ImpactMatrix.cshtml", Model)
</div>
Note: #Paul Hadfield commented on this issue in the article I have mentioned above, that this issue has been fixed for ASP MVC 4. But even though I run this version on my PC, I was not able to make nested templates working.

Related

ASP.NET MVC 5 - LabelFor displays property name instead of value, but TextBoxFor works fine with the same property

I'm working on a view with IList<MyModel> model assigned and I'm stuck at displaying values with LabelFor (property name instead of its value is displayed), however, everything works fine with TextBoxFor even when it's used with the same property.
Part of the view code:
#model IList<MyModel>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>#Html.LabelFor(m => m[i].Code)</td>
<td>#Html.TextBoxFor(m => m[i].Price)</td>
</tr>
}
Model class:
public class MyModel
{
public string Code { get; set; }
public double Price { get; set; }
}
#Html.TextBoxFor(m => m[i].Code) also works fine so the issue is apparently not related to the property itself.
I could use just Label(), but I need to POST the values from a form, so I also have to add a hidden field for Code and I feel it's not the most elegant solution.
Why TextBoxFor works, but LabelFor doesn't?
LabelFor displays the name of the field (it's used to create a label for a control).
If you want to display the value of a field, use DisplayFor:
<tr>
<td>#Html.DisplayFor(m => m[i].Code)</td>
<td>#Html.TextBoxFor(m => m[i].Price)</td>
</tr>
Note: DisplayFor just displays a text value (like a label) which isn't included when the form is submitted. If you need the value to be returned with the form/model, then you need to add a hidden field. You can use #Html.HiddenFor(m => m[i].Code) which will create a hidden input rendered like <input type="hidden" name="myModels[0].Code" value="abc" />.
The first parameter of LabelFor defines the expression that identifies the property to display and the second parameter displays the texl label.
LabelExtensions.LabelFor Method
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>#Html.LabelFor(m => m[i].Code, Model[i].Code)</td>
<td>#Html.TextBoxFor(m => m[i].Price)</td>
</tr>
}

Dynamically Access Properties in ASP.NET Razor

I'm trying to access the properties of a class dynamically in ASP.NET Razor when generating an HTML Table. This problem is normally easily solved with reflection, but the #Html.DisplayFor method is giving me issues.
I am attempting to generate an HTML table that has 3 cells per row, with the title of the item in bold as the first line of the cell, and the value of the item in the second line of the cell. The contents of the table should not include cells which are on the 'Excluded Fields' list, and I do not want to have to statically reference each column.
<table class="blpSecurityTable">
<tr>
#{
int _rowCount = 0;
foreach (var property in item.GetType().GetProperties())
{
#if (!Model.ExcludedFields.Contains(#property.Name))
{
dynamic test = #property.GetValue(item);
<td><b>#Html.DisplayFor(m => #property.Name)</b><br />#Html.DisplayFor(m => #test)</td>
_rowCount++;
}
#if (_rowCount % numCols == 0)
{
#:</tr><tr>
}
}
}
</tr>
</table>
I've tried calling #Html.DisplayFor(m => #property.GetValue(item)) but that just creates a runtime error. I can simply call #property.GetValue(item) and the value displays, but this is not ideal because I use display templates to do things like set dates to the ShortDateString format.
I understand that DisplayFor is using reflection to determine the type of the property, and that is why I am trying to use the dynamic variable to facilitate reflection for the method. However, when I run the method, it throws errors indicating the variable is not a generic parameter, and therefore cannot share its attributes. The resulting page has mostly blank values, and some cells filled in with unexpected descriptive information.
I feel like I'm getting close, but I don't know how to proceed. The page won't look right if I don't pass the values into an HTML display method, and I cannot think of any other way to get the type of table I want to be generated. Thoughts?
The issue is solved by creating a Display Template for the Security object, which then allowed me to properly use the #Html.Display Method, because the Model for the Display Template has an entry for the property.
Here is what the page code looks like now:
Display Template
#model Interface.Models.Security
#{int numCols = 3;}
<table class="blpSecurityTable">
<tr>
#{
int _rowCount = 0;
foreach (var property in Model.GetType().GetProperties())
{
#if (!BLPDLModel.ExcludedFields.Contains(#property.Name))
{
<td><b>#Html.DisplayFor(m => #property.Name)</b><br />#Html.Display(property.Name)</td>
_rowCount++;
}
#if (_rowCount % numCols == 0)
{
#:</tr><tr>
}
}
}
</tr>
</table>
Razor Page
#foreach (var item in Model.Security)
{
<div class="blpSecurityItem">
<button type="button" class="collapsible">{button text}</button>
#{
<div class="collapsible-content">
<hr />
#Html.DisplayFor(m => item)
</div>
}
</div>
Replace #Html.DisplayFor(m => #test) with #Html.Display(property.Name)
and your model property for date should have [DisplayFormat(DataFormatString = "{0:yyyy/MM/dd}")] or anything you like

ASP.NET MVC 4.5 Map Partial View to Main View on Form submit

Hello Mighty Stackoverflowers,
I'm currently working on an ASP.NET MVC 4.5 application. I need to map the input values from my partial view to my main View Model, when I submit the create form.
In my View "Create.cshtml" I call a partial view "_SwotPart.cshtml". I pass a part of my ViewModel to the Partial View, like this:
Create.cshtml:
#model MyCoolApp.BLL.Models.MainVm
#foreach (var swot in Model.Swots)
{
<tr>
#foreach (var swotPart in swot.SwotParts)
{
#Html.Partial("~/Views/Shared/_SwotPart.cshtml", swotPart)
}
</tr>
}
My partial View looks as follows, _SwotPartial.cshtml :
<td class="form-group">
#Html.TextAreaFor(model => model.Label, htmlAttributes: new { Name = nameField, ID = nameField, #class = "form-control", placeholder = Model.SwotTypeId.GetLabel() })
</td>
Anyways, when I submit my form, the values from the partial view never arrive in the controller.
Do you have any ideas how to map this properly?
Thanks!
The problem is in the input names that will be generated the way you're currently trying to achieve this. Razor needs the context of the entire list, or at least the item's position in it, in order to generate correct input names. In other words, the easiest way to solve your issue (with a caveat) is:
#for (var i = 0; i < Model.Swots.Count(); i++)
{
...
#for (var j = 0; j < Model.Swots[i].SwotParts.Count(); j++)
{
if (Model.Swots[i].SwotParts[j].SwotTypeId == SwotType.InternalHelpful || Model.Swots[i].SwotParts[j].SwotTypeId == SwotType.InternalHarmful)
{
#Html.Partial("~/Views/Shared/_SwotPart.cshtml", Model.Swots[i].SwotParts[j])
}
}
...
Then, the partial has the correct context to work with and your inputs will be named like Swots[0].SwotParts[0].Label, which the modelbinder will be able to work with.
However, the caveat here is that you're splitting this list into two loops. That's still not going to work, as you're effectively messing with the overall context of the item(s) position within the model. To fix that, you should split your list in your model, which is better anyways, as you can remove this business logic from your view:
public class SwotVm
{
...
public List<SwotPartVm> InternalSwotParts { get; set; }
public List<SwotPartVm> ExternalSwotParts { get; set; }
}
Then, you can simply iterate over each list individually, and the values will naturally post back to the appropriate list.
Given that you're using a partial to render fields for a particular class type, though, you'd be better served by creating an editor template. If you simply move your partial code to the view: Views\Shared\EditorTemplates\SwotPartVm.cshtml, then in your main view, you can just do:
#for (var i = 0; i < Model.Swots.Count(); i++)
{
...
<tr>
<th class="swot-heading">Internal</th>
#Html.EditorFor(m => m.Swots[i].InternalSwotParts)
</tr>
<tr>
<th class="swot-heading">External</th>
#Html.EditorFor(m => m.Swots[i].ExternalSwotParts)
</tr>
}
That's obvious much cleaner, and you can take this concept even further by adding a SwotVm.cshtml editor template, allowing you replace even this little bit of code with just:
#Html.EditorFor(m => m.Swots)
Note: In your SwotVm.cshtml editor template, you would only include the code for a single SwotVm. In other words, not including the for statement.
In order for your application to parse the posted values and properly and bind it to your view model. Names of posted form data needs to be like.
swots[x1].swotParts[x2].label
Where x1 is a number ranging from 0 and up for each swot.
Where x2 is a number ranging from 0 and up for each swot part in swots.
Now when you are posting, the form data names is just label.
Instead of :
#Html.TextAreaFor(model => model.Label, htmlAttributes: new { Name = nameField, ID = nameField, #class = "form-control", placeholder = Model.SwotTypeId.GetLabel() })
try :
<textarea name="swots[x1].swotParts[x2].label" class="form-control" placeholder="#Model.SwotTypeId.GetLabel()" >#Model.Label</textarea>
Don't forget to replace the x1 and x2 with a number.
You can read more about model bindings to collections here.
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

How to render list items in a form?

I have a small problem where I can't seem to reliably render list items inside of a form using MVC4. Here is a small slug of code where the problem comes up:
...
#{int count = Model.Details.Count;}
#for (int i = 0; i < count; i++)
{
<tr class="added-item">
<td>
#Html.EditorFor(x => Model.Details[i].WidthFeet)
#Html.EditorFor(x => Model.Details[i].WidthInches)
</td>
...
Basically I am going through the loop, and adding rows as I go. I have stepped through this process manually, and I have confirmed that the correct indexes / data are being used, and I have confirmed that the HTML is correct as far as names are concerned (properly indexed, etc.) Despite these facts, the problem that I am having is that the values in all of the input boxes wind up being the same on a row by row basis. Basically, if I change a field in one, that value will be displayed in all of the other rows when the form renders again.
Can anyone tell me what is going on, or how I can fix this ?
I think you should use foreach:
#foreach (var Item in Model.Details)
{
#Html.EditorFor(x => Item.WidthFeet)
#Html.EditorFor(x => Item.WidthInches)
}
Hi Could you please try the below? It is working for me.. If it still load the same data, please have a look in to your model data whether it is pulling duplicate data.
**#for (int count = 0; count < Model.Details.Count; count++)
{
<tr class="added-item">
<td>
#Html.EditorFor(m => m.Details[count].WidthFeet)
#Html.EditorFor(m => m.Details[count].WidthInches)
</td>
</tr>
}**

IEnumerable property is null after submit

I am new to MVC and have some difficulties understanding this.
To make it simple, I have a "Person" object and this object has an IEnumerable property called "EmailaddressList".
I have generated an edit page through Visual Studio 2012. The main objects properties, are generated on the edit page with textboxes like Name and LastName.
However the list of e-mail addresses in the IEnumerable list of sub-objects are not generated automatically in my view.
This is OK, I have written that code by hand using a tab for each type of e-mailaddress.
So far so good.
Problem:
When I recieve the model (person object) in my HTTP-Post method, the EmailAddressList is null.
Why is it like this, It was not null when I sent it to the view.
I the tab where the e-mailadresses are listed is in a partial view.
Can anyone give me some tips, is it something I'm missing here?*
View-Code
<div id="tabs">
<ul>
#foreach (var item in Model.EmailAddressList)
{
<li>#Html.Label(item.AddressType)</li>
}
</ul>
#foreach (var item in Model.EmailAddressList)
{
<div id="#item.AddressType">
<p>
#Html.TextBoxFor(s => item.EmailAddress, new { #class = "input-xxlarge" })
</p>
</div>
}
</div>
Controller (recieving method)
Here person.EmailAddressList is null
[HttpPost]
public ActionResult Create(Person person)
{
if (ModelState.IsValid)
{
personRepository.InsertOrUpdate(person);
personRepository.Save();
return RedirectToAction("Index");
}
else
{
return View();
}
}
That's because in order to correctly index your fields (so model binder can do it's work), you have to use a for loop.
First, change your IEnumerable to be a List (so we can use an indexor in the view).
Then change your foreach to be the following for loop:
#for (int i = 0; i < Model.EmailAddressList.Count; i++)
{
<div id="#Model.EmailAddressList[i].AddressType">
<p>
#Html.TextBoxFor(m => m.EmailAddressList[i].EmailAddress, new { #class = "input-xxlarge" })
</p>
</div>
}
Based on your update, the reason this doesn't work is because the default model binder only relies on order for a collection of simple data. When it comes to complex type you need to provide the relevant index per item otherwise it doesn't know which item property your referring to e.g.
#for (int i = 0; i < Model.EmailAddressList.Count; i++) {
Html.TextBoxFor(m => m.EmailAddressList[i].EmailAddress) %>
}
See Phil Haack's article on model binding to a list.
It's due to your elements not being ID'd the correct thing for MVC to pick them up on the post back, what you need is:
#Html.EditorFor(model => model.EmailAddressList);
Then, please refer to my post located here on how to make this look to how you want it to.

Categories