I have nested foreach loops in my view, and am having problems with the radio buttons.
#foreach (var header in Model.surveyHeaders)
{
<h1>#header.Name</h1>
foreach (var subHeader in Model.surveySubHeaders.Where(x => x.SurveryHeaderID == header.ID))
{
<h2>#subHeader.Name</h2>
foreach (var question in Model.surveyQuestions.Where(x => x.SurveySubHeaderID == subHeader.ID))
{
#Html.RadioButtonFor(x => x.surveyResults.Where(y => y.SurveyQuestionID == question.ID).First().Value, 1);
#Html.RadioButtonFor(x => x.surveyResults.Where(y => y.SurveyQuestionID == question.ID).First().Value, 2);
#Html.RadioButtonFor(x => x.surveyResults.Where(y => y.SurveyQuestionID == question.ID).First().Value, 3);
#question.Question
}
}
}
The radio button name is always 'Value', so the radio buttons are not grouped. What can I do to achieve the grouping that is desired?
Ah collections in MVC, the joy! In order to make sure all fields are named accordingly in order to be correctly model bound on post, you need to use for loops, that will set the indexes correctly.
Before doing so, you're going to have to tweak your model structure to save your headaches. You need to re-arrange your logic so that you have a hierarchical object model in which you can iterate more cleaner (this way we're getting away from logic in the view too!)
Your survey header class, can't you put a list of subheaders on it? Then your subheader class, can you put the list of questions for that subheader? That way you can do:
#for (var i = 0; i < Model.SurveyHeaders.Count; i++)
{
<h1>#Model.SurveyHeaders[i].Name</h1>
for (var j = 0; j < Model.SurveyHeaders[i].SubHeaders.Count; j++)
{
<h2>#Model.SurveyHeaders[i].SubHeaders[j].Name</h2>
for (var k = 0; k < Model.SurveyHeaders[i].SubHeaders[j].Questions.Count; k++)
{
#Html.RadioButtonFor(x => x.SurveyHeaders[i].SubHeaders[j].Questions[k].Value , 1);
#Html.RadioButtonFor(x => x.SurveyHeaders[i].SubHeaders[j].Questions[k].Value , 2);
#Html.RadioButtonFor(x => x.SurveyHeaders[i].SubHeaders[j].Questions[k].Value , 3);
#Model.SurveyHeaders[i].SubHeaders[j].Questions[k].Question
}
}
}
This assumes your new model structure is something like (with the following classes):
public class MyModel
{
public List<SurveyHeader> SurveyHeaders { get; set; }
}
public class SurveyHeader
{
public string Name { get; set; }
public List<SubHeader> SubHeaders { get; set; }
}
public class SubHeader
{
public string Name { get; set; }
public List<Question> Questions { get; set; }
}
public class Question
{
public int Value { get; set; }
public string Question { get; set; }
}
Also one other tip, for future reference, you use the following LINQ in your code:
x => x.surveyResults.Where(y => y.SurveyQuestionID == question.ID).First().Value
You can simplify it because First can actually take a lamba, like so (although you should use FirstOrDefault and null check it in future just for safety):
x => x.surveyResults.First(y => y.SurveyQuestionID == question.ID).Value
You could use the overload of the RadioButtonFor extension that takes in HtmlAttributes and set the name of each of the RadioButtons.
Related
There are two different objects.
UserDto.cs
public Guid GradeId { get; set; }
public bool isActiveUser { get; set; }
User.cs
public Guid GradeId { get; set; }
public bool isActiveUser { get; set; }
public Guid ParentUserId { get; set; }
Both object have some data and I am trying to get intersection and update only those objects in DB.
var toBeUpdated = userDtos
.Intersect(mapper.Map<List<User>>(users)) //USING AutoMapper here
.ToList();
foreach (var item in toBeUpdated)
{
userRepository.Update(item);
}
These objects must be intersected with GradeId. With the code I wrote, I am not getting any objects by that query.
Example :
users :
GradeId - 1
isActive - false
userDtos :
GradId - 1
isActive - true
I want to be able to intersect these two collections by GradeId and set isActive to true(as in DTO)
There are several ways how this can be done. The most intuitive way to find intersections of collections is to iterate over both collections in a double loop:
foreach (var dto in userDtos)
{
foreach (var user in users)
{
if (user.GradeId == dto.GradeId)
{
// Do something work here...
userRepository.Update(user);
}
}
}
You can also do the same using Linq:
IEnumerable<User> usersToUpdate =
from dto to userDtos
from user to users
where user.GradeId == dto.GradeId
select user;
foreach (var user in usersToUpdate)
{
// Do something work here...
userRepository.Update(user);
}
Or like this:
IEnumerable<User> usersToUpdate = userDtos
.SelectMany(dto => users, (dto, user) => new { dto, user })
.Where(pair => pair.user.GradeId == pair.dto.GradeId)
.Select(pair => pair.user);
foreach (var user in usersToUpdate)
{
// Do something work here...
userRepository.Update(user);
}
There is no need to use nested Foreach.
var items = userDtos.Select(x => x.GradeId)
.Intersect(users.Select(y => y.GradeId))
.ToList();
In the following controller, I need to loop through each element of a List. MyVieModel has quite a number of attrubutes (columns) and the list has thousands of rows. So, for brevity I need to use an outer and an inner loop. But, the VS2015 is complaining at the following lines in the controller. How can I resolve the issue?
Error at inner loop for (var j = 0; j < testlist[i].Count(); j++){...}: MyViewModel does not contain a definition of Count()
Error at line if (testlist[i][j] ....){...}: cannot apply indexing with [] to an extension of type MyViewModel
ViewModel:
public class MyViewModel
{
[Key]
public int ProductId { get; set; }
public float laborCost { get; set; }
public float ManufCost { get; set; }
public float Price { get; set; }
....
....
}
Controller:
....
....
var testlist = (qry to load MyViewModel).ToList();
for (var i = 0; i < testlist.Count; i++)
{
for (var j = 0; j < testlist[i].Count(); j++)
{
if (testlist[i][j] ....)
{
....
....
}
}
}
In your code testlist[i] is an instance of MyViewModel class. You can't simply iterate over all it's members (properties, methods etc) with a for/foreach loop.
1) Use System.Reflection to obtain list of properties in your object (slow!)
2) Manually make array from required property values
var testlist = (qry to load MyViewModel)
.Select(x => new object[] { x.ProductId, x.laborCost, x.ManufCost ...})
.ToList();
Your model will be List<object[]> instead of List<MyViewModel>
3) Manually check required properties:
if (testlist[i].ManufCost ....)
I have an IEnumerable
IEnumerable<Pets> pets;
It consists of
public string Name { get; set; }
public string Other { get; set; }
public decimal? Price { get; set; }
I want to iterate through this and find all prices that are duplicate and set those duplicate prices to null.
Say A cat and a Dog have same price: 10.55. I want to keep first one but remove all remaining prices.
Ways:
1) Remove duplicates (I recommend it):
var filtered = pets.GroupBy(pet => pet.Price).Select(group => group.First());
2) Sort & evalute - set null in place of duplicates as you wish (Are you sure that you want to set nulls instead of removing like in 1) ?).
var newPets = pets.OrderBy(per => pet.Price).ToList();
if (!newPets.Any()) return newPets;
var last = 0;
for (var i = 1; i < newPets.Count; i++)
{
if (newPets[i].Price == newPets[last].Price) newPets[i] = null;
else last = i;
}
return newPets;
I think that ordering is sufficient in that case: O(n * log n) + O(n) against O(n^2) in custom iterates to search duplicates for each element.
3) Classic way (without sorting, slowest)
var newPets = pets.ToList();
for (var i = 0; i < newPets.Count; i++)
{
if (newPets[i] == null) continue;
var price = newPets[i].Price;
for (var j = i + 1; j < newPets.Count; j++)
{
if (newPets[j].Price == price) newPets[j] = null;
}
}
As D Stanley has noticed (but I've missed it) you may have to set Price to null instead of whole record. Then simply change it to decimal? and then write newPets[i].Price = null; instead of null`ing whole record.
Well for starters a decimal can't be null, so I'll answer it as if you had a decimal? type so you understand the process.
Linq is for querying, not updating. You could project a new collection based on the original, but a foreach may be more appropriate:
// list to keep tack of found prices
var prices = new List<decimal>();
foreach(Pet pet in pets)
{
if(prices.Contains(pet.Price.Value))
// price was found - set this one to null
pet.Price = null;
else
// add to the list of "found" prices
prices.Add(pet.Price.Value);
}
public class Pet
{
public string Name { get; set; }
public string Other { get; set; }
public decimal? Price { get; set; }
}
Note that the Price is now nullable (decimal?)
return pets
.OrderBy(x => x.Name)
.GroupBy(x => x.Price)
.OrderBy(x => x.Key)
.SelectMany(x => (new[] { x.First() }).Union(x.Skip(1).Select(n => new Pet { Name = n.Name, Other = n.Other, Price = null })))
.ToList();
I am trying to bind a view to a model which contains a list in a list. Naturally I would prefer to use out of the box model binding. Having spent some time on it yesterday I found a workaround which is really a hack and I would like to correct this. The basic structure of my models are as follows:
public class MyMPIModel
{
public List<ScoreInfo> ScoreInfo { get; set; }
}
public class ScoreInfo
{
public int ScorePrefId { get; set; }
public List<Category> Categories { get; set; }
}
public class Category
{
public int Id;
public string Name;
public bool Checked;
}
The view InterestCategories.cshtml contains the following form:
#using (Html.BeginForm())
{
for (var i = 0; i < Model.ScoreInfo.Count; i++)
{
#Html.EditorFor(x => x.ScoreInfo[i])
}
}
The editor template ScoreInfo.cshtml:
#Html.HiddenFor(x => x.ScorePrefId)
<div class="preferences-block">
#for (var i = 0; i < Model.Categories.Count; i++)
{
#Html.EditorFor(x => x.Categories[i])
}
</div>
Finally the editor template Category.cshtml:
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.Name)
<label>
#Html.CheckBoxFor(x => x.Checked, new { #class = "check"})
<span>#Model.Name</span>
</label>
Inspecting the form using firebug I can see that all the hidden fields have been populated. Also when I submit the form, Fiddler shows the correct data. Here is a sample:
ScoreInfo[0].Categories[1].Id 2
ScoreInfo[0].Categories[1].Name Managing Money
ScoreInfo[0].Categories[1].Checked false
However, when I post to the controller, set a breakpoint and inspect the model, the list of ScoreInfo objects have been populated but the lists of Category objects inside the ScoreInfo object have not.
I have the following POST action in my controller:
[HttpPost]
public ActionResult InterestCategories(MyMPIModel model, FormCollection form)
{
...
// model would not bind forcing me to populate via form collection
for (var i = 0; i < model.ScoreInfo.Count; i++)
{
...
for (var j = 0; j < scoreInfo.Categories.Count; j++)
{
var category = scoreInfo.Categories[j];
var prefix = "ScoreInfo[" + i + "].Categories[" + j + "]";
category.Name = form[prefix + ".Name"];
var sId = form[prefix + ".Id"];
if (sId != null) category.Id = Int32.Parse(sId);
var sChecked = form[prefix + ".Checked"];
if (sChecked != null) category.Checked = sChecked.Contains("true");
}
}
}
You have to use Properties instead of Fields in your Category class:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
}
I am using asp.mvc 4. Assumend I have a Model called Person with the fields
public class Person
{
public int ID { get; set; }
public string FirstName { get; set; }
public string SecondName { get; set; }
public DateTime DateOfBirth { get; set; }
public DateTime DateOfWorkstart { get; set; }
public int NumberOfChildren { get; set; }
public int DepartmentID { get; set; }
public virtual Department Department { get; set; }
}
public class Department
{
public int ID { get; set; }
public int NameOfDepartment { get; set; }
}
In my automatically generated razor-edit-view fields are shown like this (For clearness, I only included important lines in this post)
#Html.DisplayFor(modelItem => item.FirstName)
#Html.DisplayFor(modelItem => item.SecondName)
now I would like to store the linq-lambda expressions in a list an use it later, I don't know how to do that, I need something like this:
#{
string itemsToShow = "namepart"; // this could also be "otherpart"
List <Expression<>> list = new List();
if (itemsToShow.equals("namepart")
{
list.add(modelItem => item.FirstName);
list.add(modelItem => item.SecondName);
}
else
{
list.add(modelItem => item.DateOfBirth);
list.add(modelItem => item.DateOfWorkStart);
list.add(modelItem => item.NumberOfChildren);
}
}
and finally I would like to use the generated list like this
#foreach (var lambda in list)
{
#Html.DisplayFor(lambda)
}
I'd write a custom helper for this:
public static class HtmlExtensions
{
public static IHtmlString MyHelper(this HtmlHelper<MyViewModel> html, string itemsToShow)
{
var sb = new StringBuilder();
if (itemsToShow == "namepart")
{
sb.Append(html.DisplayFor(x => x.FirstName));
sb.Append(html.DisplayFor(x => x.SecondName));
}
else
{
sb.Append(html.DisplayFor(x => x.DateOfBirth));
sb.Append(html.DisplayFor(x => x.DateOfWorkStart));
sb.Append(html.DisplayFor(x => x.NumberOfChildren));
}
return new HtmlString(sb.ToString());
}
}
and then inside the view simply:
#Html.MyHelper("namepart")
and if you want to render the other part:
#Html.MyHelper("otherpart")
As an alternative simply put this content into 2 different partial views and then:
#if (itemsToShow == "namepart")
{
#Html.Partial("_NamePart", Model)
}
else
{
#Html.Partial("_OtherPart", Model)
}
There is small progress in my brain. Thanks for your advices. As #darin-dimitrov said, the secret is to store an Expression-Tree. I updated my first post and added a related table.
This example works only, when the model has fetched "1 single row from database-table" for example in an edit view;
// first a small helper, which creates the member and checks nullable fields
public static Expression getExpressionPart(ParameterExpression param,
String s1, String s2)
{
Expression member = null;
if (s2 == null)
{
member = Expression.Property(param, s1);
}
else
{
// the second string is to deal with foreign keys/related data
member = Expression.PropertyOrField(
Expression.PropertyOrField(param, s1), s2
);
}
Type typeIfNullable = Nullable.GetUnderlyingType(member.Type);
if (typeIfNullable != null)
{
member = Expression.Call(member, "GetValueOrDefault", Type.EmptyTypes);
}
}
now create the list and the expressions
List<Expression<Func<Person, object>>> list =
new List<Expression<Func<Person, object>>>();
ParameterExpression param = Expression.Parameter(typeof(Person), "p");
// maps to expression p => p.FirstName
Expression member = getExpressionPart(param, "Firstname", null);
list.Add(Expression.Lambda<Func<Person, object>>(member, param));
// maps to expression p => p.Department.NameOfDepartment
member = getExpressionPart(param, "Department", "NameOfDepartment");
list.Add(Expression.Lambda<Func<Person, object>>(member, param));
and now it works!
#foreach (var lambda in list)
{
#Html.DisplayNameFor(lambda)
#Html.DisplayFor(lambda)
}
Have you tried to store the lambda like this:
Func<Person,bool> personExpression = (u => u.FirstName == firstname);
#Html.DisplayFor(personExpression)
And for 2 input types your code would look something like this:
Func<Person,Ticket,bool> personExpression =
((u,t) => u.FirstName == firstname && u.SecondName == t.SecondName);
To loop through model properties in a razor view you should use ViewData.ModelMetadata.Properties as in this answer. For example:
#* Loop through properties. *#
#foreach (var property in ViewData.ModelMetadata.Properties)
{
#Html.Display(property.PropertyName)
}