How to render list items in a form? - c#

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>
}**

Related

.Net Core 5.0 .cshtml Razor Pages "if" Block Open HTML Tag Without Closing It

I'm not sure I quite have the vocabulary to asking this question properly, I hope the title of this question is worded correctly ... either way, I'm trying to make a table on a .cshtml Razor page using .Net Core 5.0 (Visual Studio 16.10.0). I'm using Entity Framework Core 5.0.11, though I'm not sure that's relevant to this.
I'm pulling data from the database, and what I want to do is build a table with two columns. So, I want the first item I pull to be in the first column, the second item I pull to be in the Second column, the third item I pull to be in the first column (now underneath item one), the fourth item I pull to be in the second column (now under item 2) and so on and so forth.
I'm trying to produce a two column table with an arbitrary amount of items.
(EDIT: Sorry if this table looks all messed up. When I'm in the edit page it looks like I want it to, but when I'm looking at my question on the final page it looks just like the raw text. Just wanted to include that I'm aware of this but not sure what to do about it. Or, maybe it's just my screen and it looks great on yours. Fingers crossed.)
Like this:
|Header (but ignore headers in the actual code)|Another Header (it just says a header row is required here)|
|---------------------------------------------|---------------|
|Item 1|Item 2|
|Item 3|Item 4|
Now, here's the code that I'm trying to use, but it doesn't seem to be working:
#if (Model.MyListOfItems is not null) // I can verify that it is not null
{
<table>
<tbody>
#{ int count = 0; }
#foreach (var item in Model.MyListOfItems)
{
count++;
#if (count % 2 == 1)
{
<tr>
<td>
#Html.DisplayFor(i => item.Text)
</td>
}
else
{
<td>
#Html.DisplayFor(i => item.Text)
</td>
</tr>
}
}
#if (count % 2 == 1)
{
</tr>
}
</tbody>
</table>
}
Now, you can see what I'm doing, right? For odd numbered items, open up a new table row. For even numbered items, close it. If we end on an odd numbered item, we'll have a row open, so that last if statement there closes it up. This way, we get a table like I described above.
However, this doesn't work. It won't compile. I get errors like ; expected for the very first line of the file. It starts complaining about code that comes before this code. I start getting a bunch of such and such doesn't exist in this current context
But here's the thing... I know the rest of the code is good, because if I change it to this
#if (Model.MyListOfItems is not null) // I can verify that it is not null
{
<table>
<tbody>
#{ int count = 0; }
#foreach (var item in Model.MyListOfItems)
{
count++;
#if (count % 2 == 1)
{
<tr>
<td>
#Html.DisplayFor(i => item.Text)
</td>
</tr> <!-- Added this -->
}
else
{
<tr> <!-- Added this -->
<td>
#Html.DisplayFor(i => item.Text)
</td>
</tr>
}
}
#if (count % 2 == 1)
{
<!-- </tr> --> <!-- Removed This -->
}
</tbody>
</table>
}
then it works and compiles just fine. The table obviously doesn't look how I want it to look, but I'm including this to illustrate that I know it's not a problem with my code, or even with the rest of my code, it's the compiler not seeing the end of that <tr> tag in the opening if statement and then not recognizing the } else { as C# code (and because of that is throwing errors all over the page).
I know the compiler is trying to save me from myself, but if I can just force it to compile I'm... at least fairly confident that the table will be properly formed HTML. Even if it's not, if I can get it to compile then I can see what kind of table this code does produce and fix it from there. But, the compiler clearly doesn't appreciate me opening a tag within an if block and then not closing it.
I've run into similar problems to this before, which is also why I'm confident that it's the compiler and not me, but usually I just find some other way to do it. And I'm sure I'll do that this time, I'm going to start trying as soon as I send this, but I figured I'd check with y'all anyway; I figure there must be something I'm missing.
Also, yes, I tried changing else to #else, but that didn't solve the problem, just started throwing errors saying that #else wasn't allowed there.
So, I think, that's a long way around asking what I'm trying to ask, but I think all the exposition was necessary. Anyway, I suppose here's the question:
How do I force the compiler to recognize my closing brackets as code when I open an HTML tag within that block, but don't close the HTML tag?
(EDIT: Added an explicit <tbody> to the table as per response from #Dai)
You can chunk your list of items so that you can iterate through your list of sublists like this:
<table>
<tbody>
#{
var listOfSubLists = Model.MyListOfItems
.Select((x, i) => new { Index = i, Value = x })
.GroupBy(x => x.Index / 2)
.Select(x => x.Select(v => v.Value).ToList())
.ToList();
}
#foreach (var subList in listOfSubLists)
{
<tr>
#foreach (var item in subList)
{
<td>
#Html.DisplayFor(i => item.Text)
</td>
}
</tr>
}
</tbody>
</table>
In this way, you can then use the <table> tag. I think this is a much proper approach than creating the table with the Bootstrap col- class in UI.
And also .NET6 LINQ will support a built-in chunk method that allows you to chunk your list into a single line of code:
var listOfSubLists = Model.MyListOfItems.Chunk(2);
Alright, well, if anyone knows the answer to my question I would still really appreciate that, because like I mentioned, I've run into stuff like this before and always have to find a workaround. But, in case anyone is wondering, here's my workaround.
I'm using the Bootstrap framework, and it has this column organization to it. 12 columns per page, and within each container, 12 more columns. Basically, you can keep dividing the page into smaller and smaller groups of twelve. That's not the best explanation but... here's their webpage on it https://getbootstrap.com/docs/4.0/layout/grid/
Anyway, instead of using a table, I'm making use of the grid system, like this:
<div class="row">
#if (Model.MyListOfItems is not null) // I can verify that it is not null
{
int count = 0;
#foreach (var item in Model.MyListOfItems)
{
count++;
<div class="col-6">
#Html.DisplayFor(i => item.Text)
</div>
#if (count % 2 == 0)
{
<br />
}
}
#if (count % 2 ==0)
{
<br />
}
}
</div>
I know that those last two if (count % 2 == 0) blocks probably aren't necessary at all since Bootstrap's grid system is responsive, but I just like being explicit. For the sake of everyone's sanity, though, this should yield the same result:
<div class="row">
#if (Model.MyListOfItems is not null) // I can verify that it is not null
{
#foreach (var item in Model.MyListOfItems)
{
<div class="col-6">
#Html.DisplayFor(i => item.Text)
</div>
}
}
</div>

asp.net mvc - creating complex view based on stored procedure

I'm tinkering with a ASP.NET MVC 4 template, and I need guidance on how to design a complex view layout.
I already created a model that is getting data returned by a stored procedure in a SQL Server DB. A view is getting data from the model in an IEnumerable<> object. If you were to view the raw output of the stored procedure it would look something like this:
**Name** **Objects**
John Orange
John Banana
John Apple
I used a view template to create a simple table based on logic below, but as expected, it is rendered exactly like it is stored in the IEnumerable<> object. The name appears on each row of output in the table, like above.
#model IEnumerable<projectname.Models.ObjectsModel>
<table class="table">
...
...
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Objects)
</td>
...
Instead, I want to create a view layout that is not in table form, that would display the Name as a heading, the objects as a bulleted list, and other attributes elsewhere, with no redundant data. My problem is that if I display the items in the IEnumerable<> using #foreach (var item in Model) I end up getting three duplicate entries, since the values in the Name field are not unique.
What is the 'best practice' way to do this? Can someone please point me in the right direction?
Not sure what your controller looks like, or even your view model, but maybe i can point you in the right direction.
foreach is just that a loop of objects and should be used for just that, i have seen many people manipulate foreach loops with little or no success, You should be using a #for loop. besides The for-loop is approximately 2 times faster than the foreach-loop as it only has to call get_Item for every element in the list.
there are a few ways to acomplis what you want, one way would be what #colinD stated above by using LINQ in the controler or viewmodel to pass the proper data. the other way would be somthing similar to the following.
var models = ObjectsModel();
var grouped = models.GroupBy(s => s.ObjectsModel.Name)
.Select(x => x.Select(y => y))
.ToList();
return View(grouped);
#for(int i = 0; i < Model.Count; i++)
{
<h2>#Html.DisplayFor(model => model[i].First().ObjectsModel.Name)</h2>
<ul>
for(int j = 0; j < Model[i].Count; j++)
{
<li>#Html.DisplayFor(model => model[i][j].Object)</li>
}
</ul>
}
I haven't tested the code but I hope this helps you get the right solution you are looking for.
The main thing i'm trying to figure out is how to display this data
without having the #foreach (var item in Model) loop generate
duplicate entries for each row in the SP output.
Usually processing data is done in action, like groupping data, and then make some loop to display. As you want to avoid #foreach I thought using linq. This is just an idea but keep in mind you should separate concerns in MVC. I hope this helps ;)
IEnumerable<ObjectsModel> model = new List<ObjectsModel>()
{
new ObjectsModel(){ Name = "John", Objects = "Orange" },
new ObjectsModel(){ Name = "John", Objects = "Banana" },
new ObjectsModel(){ Name = "John", Objects = "Apple" }
};
var htmlModel = model
.GroupBy(a => a.Name)
.Select(a => new
{
Name = a.Key,
GroupObjects = string.Join("", a.Select(b => $"<li>{b.Objects}</li>"))
})
.Select(a => $"<h1>{a.Name}</h1><ul>{a.GroupObjects}</ul>")
.ToList();
var result = string.Join("", htmlModel); // <h1>John</h1><ul><li>Orange</li><li>Banana</li><li>Apple</li></ul>
Final result:
<h1>John</h1>
<ul>
<li>Orange</li>
<li>Banana</li>
<li>Apple</li>
</ul>

I want to bind product from database and want to display in grid in MVC

Now I am doing this with html but I want it with MVC Grid option. So anybody can help me on this also when changing quantity in grid, I want to update also row total and column total.
You can create a table object based on your line items.
<table>
#for(int i = 0; i < Model.LineItems.Count; i++)
{
<tr>
<td>Details #Html.HiddenFor(m => m.LineItems[i].ID)</td>
<td>........</td>
<td>........</td>
.
.
.
<td>#Html.TextboxFor(m => m.LineItems[i].Qty, new { #class="form-input"})</td>
</tr>
}
</table>
So now when you submit your model, your LineItems Array will be populated

Can a display template be invoked from within an editor template?

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.

#Html.HiddenFor does not work on Lists in ASP.NET MVC

I'm using a model that contains a List as a property. I'm populating this list with items i grab from SQL Server. I want the List to be hidden in the view and passed to the POST action. Later on i may want to add more items to this List with jQuery which makes an array unsuitable for expansion later on. Normally you would use
#Html.HiddenFor(model => model.MyList)
to accomplish this functionality, but for some reason the List in POST is always null.
Very simple question, anyone know why MVC behaves like this?
I've just come across this issue and solved it simply by doing the following:
#for(int i = 0; i < Model.ToGroups.Count; i++)
{
#Html.HiddenFor(model => Model.ToGroups[i])
}
By using a for instead of a foreach the model binding will work correctly and pick up all of your hidden values in the list. Seems like the simplest way to solve this problem.
HiddenFor is not like a DisplayFor or EditorFor. It won't work with collections, only single values.
You can use the Serialize HTML helper available in the MVC Futures project to serialize an object to a Hidden field, or you will have to write the code yourself. A better solution is to simply serialize an ID of some sort and re-get the data from the database on postback.
It's a bit of a hack, but if #Html.EditorFor or #Html.DisplayFor work for your list, if you want to make sure it's sent on the post request but not visible, you could just style it to using display: none; to hide it instead, e.g:
<div style="display: none;">#Html.EditorFor(model => model.MyList)</div>
What about using Newtonsoft to deserialize the object into a json string and then insert that into your Hidden field e.g.
(Model.DataResponse.Entity.Commission is a List of simple "CommissionRange" objects as you'll see in the JSON)
#using (Ajax.BeginForm("Settings", "AffiliateProgram", Model.DataResponse, new AjaxOptions { UpdateTargetId = "result" }))
{
string commissionJson = JsonConvert.SerializeObject(Model.DataResponse.Entity.Commission);
#Html.HiddenFor(data => data.DataResponse.Entity.Guid)
#Html.Hidden("DataResponse_Entity_Commission", commissionJson)
[Rest of my form]
}
Renders as:
<input id="DataResponse_Entity_Commission" name="DataResponse_Entity_Commission" type="hidden" value="[{"RangeStart":0,"RangeEnd":0,"CommissionPercent":2.00000},{"RangeStart":1,"RangeEnd":2,"CommissionPercent":3.00000},{"RangeStart":2,"RangeEnd":0,"CommissionPercent":2.00000},{"RangeStart":3,"RangeEnd":2,"CommissionPercent":1.00000},{"RangeStart":15,"RangeEnd":10,"CommissionPercent":5.00000}]">
In my case I do some JS stuff to edit the json in the hidden field before posting back
In my controller I then use Newtonsoft again to deserialize:
string jsonCommissionRange = Request.Form["DataResponse_Entity_Commission"];
List<CommissionRange> commissionRange = JsonConvert.DeserializeObject<List<CommissionRange>>(jsonCommissionRange);
Html.HiddenFor is designed for only one value. You will need to serialize your list in some way before creating the hidden field.
For example, if your list is of type string, you could join the list into a comma separated list, then split the list after post back in your controller.
I've just found out (after a couple of hours of trying to figure out why model values weren't going back to the controller) that hidden for should follow the EditorFor.
Unless I am doing something else wrong this is what I found. I will not make the mistake again.
In the context of a Model that contains a list of another class.
This will NOT work:
#{
for (int i = 0; i < Model.Categories.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(modelItem => Model.Categories[i].Id)
#Html.HiddenFor(modelItem => Model.Categories[i].ProductCategoryId)
#Html.HiddenFor(modelItem => Model.Categories[i].CategoryName)
#Html.DisplayFor(modelItem => Model.Categories[i].CategoryName)
</td>
<td>
#Html.HiddenFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
#Html.EditorFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
#Html.ValidationMessageFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
</td>
<td style="text-align: center">
#Html.HiddenFor(modelItem => Model.Categories[i].IsSelected)
#Html.EditorFor(modelItem => Model.Categories[i].IsSelected)
</td>
</tr>
}
}
Where as this WILL......
for (int i = 0; i < Model.Categories.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(modelItem => Model.Categories[i].Id)
#Html.HiddenFor(modelItem => Model.Categories[i].ProductCategoryId)
#Html.HiddenFor(modelItem => Model.Categories[i].CategoryName)
#Html.DisplayFor(modelItem => Model.Categories[i].CategoryName)
</td>
<td>
#Html.EditorFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
#Html.HiddenFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
#Html.ValidationMessageFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
</td>
<td style="text-align: center">
#Html.EditorFor(modelItem => Model.Categories[i].IsSelected)
#Html.HiddenFor(modelItem => Model.Categories[i].IsSelected)
</td>
</tr>
}
I started digging through the source code for HiddenFor, and I think the roadblock you're seeing is that your complex object MyList is not implicitly convertible to type string, so the framework treats your Model value as null and renders the value attribute empty.
You can take a look on this solution.
Put only HiddenFor inside the EditorTemplate.
And in your View put this: #Html.EditorFor(model => model.MyList)
It should works.
Faced the same issue. Without for loop, it only posted the first element of the list. After iterating through for loop, it can keep full list and post successfully.
#if (Model.MyList!= null)
{
for (int i = 0; i < Model.MyList.Count; i++)
{
#Html.HiddenFor(x => x.MyList[i])
}
}
Another option would be:
<input type="hidden" value=#(string.Join(",", Model.MyList)) />
The foreach loop instead of a for loop might be a slightly cleaner solution.
#foreach(var item in Model.ToGroups)
{
#Html.HiddenFor(model => item)
}
Another possible way to fix this would be to give each object in your List an ID, then use #Html.DropDownListFor(model => model.IDs) and populate an array which holds the IDs.
maybe late, but i created extension method for hidden fields from collection (with simple data type items):
So here it is:
/// <summary>
/// Returns an HTML hidden input element for each item in the object's property (collection) that is represented by the specified expression.
/// </summary>
public static IHtmlString HiddenForCollection<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression) where TProperty : ICollection
{
var model = html.ViewData.Model;
var property = model != null
? expression.Compile().Invoke(model)
: default(TProperty);
var result = new StringBuilder();
if (property != null && property.Count > 0)
{
for(int i = 0; i < property.Count; i++)
{
var modelExp = expression.Parameters.First();
var propertyExp = expression.Body;
var itemExp = Expression.ArrayIndex(propertyExp, Expression.Constant(i));
var itemExpression = Expression.Lambda<Func<TModel, object>>(itemExp, modelExp);
result.AppendLine(html.HiddenFor(itemExpression).ToString());
}
}
return new MvcHtmlString(result.ToString());
}
Usage is as simple as:
#Html.HiddenForCollection(m => m.MyList)
Adding to this answer, I had a Model with various properties, some of which were IEnumerables. What I did was to serialize it in a variable in the view with:
#{
var serializedObject = JsonSerializer.Serialize(Model);
}
And then, put that string into a Hidden element (since HiddenFor is intended for a single value from a Model) like this:
#Html.Hidden("serialized", #serializedObject)
And finally, in the controller, I could deserialize it with
JsonSerializer.Deserialize<MyType>(Request.Form["serialized"])

Categories