I can't find or figure out how to take a list of items (cupcakes) and display them in razor with a quantity field.
What is happening is I am not able to get the values for each cupcake quantity in the list. Can you do textbox arrays in Razor?
VIEW
<div class="form-group">
<label>Cupcakes</label>
#foreach (var cupcake in Model.CupcakeList)
{
#Html.TextBox("CupcakeQuantities", cupcake.Id) #cupcake.Name <br/>
}
</div>
MODEL
public List<Cupcake> CupcakeList { get; set; }
public List<int> CupcakeQuantities { get; set; }
CONTROLLER
public ActionResult Create()
{
var model = new PartyBookingModel()
{
CupcakeList = db.Cupcakes.ToList(),
CupcakeQuantities = new List<int>()
};
return View(model);
}
CUPCAKE (ENTITY)
public class Cupcake
{
public int Id { get; set; }
public string Name { get; set; }
public decimal PerDozen { get; set; }
}
You have to use an index, rather than foreach for it to work.
#for (int i = 0; i < Model.CupcakeList.Count; i++)
{
#Html.TextBoxFor(x=>Model.CupcakeQuantities[i]) #Model.CupcakeList[i].Name <br/>
}
This will create sequentially named+number entries that will be recombined back into the model on post back.
I realise this may seem like "why doesn't foreach work?", but with foreach there is not enough reflected information available to TextBoxFor (as it is just a single object), whereas the array index is extracted by reflection from the Model.CupcakeQuantities[i] expression.
The receiving controller method should take the same as the model passed to the view:
e.g.
[HttpPost]
public ActionResult(PartyBookingModel model)
Try it this way:
view:
#for (int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(x=>Model[i].Id) #Model[i].Name
#Html.TextBoxFor(x => Model[i].Quantity) <br/>
}
model:
public class CupcakeViewModel
{
public int Id {get;set;}
public string Name {get;set;}
public int Quantity {get;set;}
}
controller:
public ActionResult Create()
{
var model = db.Cupcakes.Select(c => new CupcakeViewModel {
Id = c.Id,
Name = c.Name,
Quantity = 0
})
.ToList();
return View(model);
}
[HttpPost]
public ActionResult Create(CupcakeViewModel[] cakes)
{
//Save choosen cakes
}
Related
I am trying to display a checklist that gets data from MySQL Database and displays it in a view and updates the value of the variable (IsChecked) of each element in the table by whether we have checked the amenity or not (i am displaying some amenities). The model of the view is Hotel_5.ViewModel.BookingRoom, where BookingRoom is a custom model i created where i use multiple models. I get the exception at Model.AmenitiesList.Count(). The model is null.
This is my view
<div class="form-group">
#for (var i = 0; i < Model.AmenitiesList.Count(); i++)
{
#Html.CheckBoxFor(m => m.AmenitiesList[i].IsChecked, new { #class = "form-control" });
<label>#Model.AmenitiesList[i].amenityType</label>
//If you need to hide any values and get them in your post
#Html.HiddenFor(m => m.AmenitiesList[i].AmenityId)
#Html.HiddenFor(m => m.AmenitiesList[i].AmenityPrice)
}
</div>
This is my ViewModel
public class BookingRoom
{
public Bookings bookings { get; set; }
public Rooms rooms { get; set; }
public List<Amenities> AmenitiesList { get; set; } = new List<Amenities>();
}
This is my Amenities Model
public class Amenities
{
[Key]
public int AmenityId { get; set; }
public double AmenityPrice { get; set; }
public AmenityType amenityType { get; set; }
public bool IsChecked { get; set; }
}
public enum AmenityType
{
tv,
wi_fi,
hair_dryer,
help
}
When Querying you should Include its AmenitiesList too, otherwise it will be null:
In Controller:
var bookingRoom = context.BookingRooms.Include(b => b.AmenitiesList).FirstOrDefault(b => b.Id == someId);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
please note that what I queried might not be what you want, it is just to demonstrate how to use Include() and also you should add using Microsoft.EntityFrameworkCore.
My razor view is bound to a ListViewModel delivered by an ActionResult controller; see below code snippets:
Viewmodel
public class TicketItemViewModel
{
public int Id { get; set; }
public int? UserId { get; set; }
public int? TicketId { get; set; }
public string Title { get; set; }
[AllowHtml]
public string Body { get; set; }
public DateTime? Posted { get; set; }
}
Controller
public ActionResult TicketHistoryC(int ticketId)
{
var ticket = UnitOfWork.GetById<Ticket>(ticketId);
var entities = UnitOfWork.GetAll<TicketItem>().Where(u => u.TicketId == ticketId).OrderByDescending(u => u.Posted).ToList();
List<TicketItemViewModel> listModel = new List<TicketItemViewModel>();
foreach (var entity in entities)
{
TicketItemViewModel model = new TicketItemViewModel();
model.Body = entity.Body;
model.Posted = entity.Posted;
model.UserId = entity.UserId;
model.TicketId = entity.TicketId;
model.Id = entity.Id;
model.Title = ticket.Title.ToString();
listModel.Add(model);
}
return View(listModel);
}
In the top part of the view I want to show the property of the first item of the ViewModel. Below is my best try:
#model IEnumerable<Presentation.Host.Models.TicketItemViewModel>
#{int i = 0;}
#foreach (var item in Model)
{
if (i == 1)
{
break;
}
else
{
<h2>#Html.DisplayFor(modelItem => item.Title)</h2>
i = i + 1;
}
}
The code works but I wonder if there is a better or more efficient way to get the first item only?
You can use Linq and first check if your Model has at lease one record then use Model.First() to get first record.
#model IEnumerable<Presentation.Host.Models.TicketItemViewModel>
#if (Model.Any())
{
<h2>#Html.DisplayFor(modelItem => Model.First().Title)</h2>
}
You can get the first element of the list with:
Model.First();
And the rest of the list with:
Model.Skip(1);
Use the following code.
var IstItem = Model.First();
#foreach (var item in Model)
{
// do something with each item
if (result.Equals(IstItem))
{
// do something different with the last item
}
else
{
// do something different with every item but the last
}
}
So the beginning of my View is
#foreach ( var G in ViewBag.GroupedBySec )
{
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<table class="table table-striped assessment-table">
<tbody>
<tr>
<td class="black-n-white" colspan="4">#G.Key</td>
</tr>
<tr>
<td>Section</td><td>Component</td><td>Completeness Level (0-100%)</td><td>Readines</td>
</tr>
#var GroupedBySubsection = G.GroupBy(x => x.SubsectionTitle);
#foreach ( var g in GroupedBySubsection )
{
#foreach ( var qa in g )
{
and I'm getting errors on the
#var GroupedBySubsection = G.GroupBy(x => x.SubsectionTitle);
#foreach ( var g in GroupedBySubsection )
lines which say
The name 'var' does not exist in the current context
and
The name 'GroupedBySection' does not exist in the current context
Any idea what I'm doing wrong?
Alternatively, any suggestions for how I can get a C# object that has the entire groupings and subgroupings so that I don't have to write all this crap in my View?
My Controller method for this page is like
[HttpGet]
public ActionResult Peek ( Guid pid )
{
ViewBag.PartnerId = pid;
List<AnswerInfo> AllAnswers = this._Db.GetAnswersByPartner(pid);
ViewBag.GroupedBySec = AllAnswers.GroupBy(A => A.SectionTitle);
return View();
}
Your error occurs because your missing the braces around your var statement. It should be
#{ var GroupedBySubsection = G.GroupBy(x => x.SubsectionTitle); }
This will then throw a different error because you referring an item in ViewBag which is dynamic so you need to cast it as follows
#{ var GroupedBySubsection = ((IEnumerable<yourModel>)G).GroupBy(x => x.SubsectionTitle); }
making your view even uglier.
how I can get a C# object that has the entire groupings and subgroupings so that I don't have to write all this crap in my View?
Use a view model. Based on some of your previous question, you have a data model which (abbreviated) is
public class Answer
{
public string Section { get; set; }
public string SubSection { get; set; }
public int QuestionID { get; set; }
public string QuestionText { get; set; }
....
}
And that you want to group it in the view by Section and then SubSection. Your view models would then be
public class SectionVM
{
public string Title{ get; set; }
public List<SubSectionVM> SubSections { get; set; }
}
public class SubSectionVM
{
public string Title { get; set; }
public List<QuestionVM> Questions { get; set; }
}
public class QuestionVM
{
public int QuestionID { get; set; }
public string QuestionText { get; set; }
....
}
Your controller code would then be
var answers = db.Answers..... // your previous query
List<SectionVM> model = answers.GroupBy(x => x.Section).Select(x => new SectionVM
{
Title = x.Key,
SubSections = x.GroupBy(y => y.SubSection).Select(y => new SubSectionVM
{
Title = y.Key,
Questions = y.Select(z => new QuestionVM
{
QuestionID = z.QuestionID,
QuestionText = z.QuestionText,
....
}).ToList()
}).ToList()
}).ToList();
return View(model);
and in the view
#model List<SectionVM>
#foreach(var section in Model)
{
<h2>#section.Title</h2>
foreach(var subSection in section.SubSections)
{
<h3>#subSection.Title</h3>
foreach(var question in subSection.Questions)
{
<p>#question.QuestionText</p>
....
}
}
}
Side note: I have made the collection properties List<T> assuming that you might want to also generate a form for editing the Questions/Answers and to do so you will need a for loop which required the collection to implement IList. Alternatively you could save the extra overhead of .ToList(), keep them asIEnumerableand use a customEditorTemplate` for each view model type.
For an example of how an edit view might look (using the for loop option), refer to this answer.
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 have the following model.
public class M
{
public int Id { get; set; }
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
public int D { get; set; }
....
}
The Asp.Net Mvc 4 page need only edit one column. And I had to put #Html.HiddenFor() for all other columns - otherwise the database column for B, C, D.... will be reset to 0s. Is it a way to avoid it?
#model MyMvc.Models.M
#using (Html.BeginForm()))
{
#Html.HiddenFor(m => m.Id)
#Html.EditorFor(m => m.A)
#Html.HiddenFor(m => m.B)
#Html.HiddenFor(m => m.C)
#Html.HiddenFor(m => m.D)
......
}
You can just put a hidden field for Id and handle the others in your action method like this:
public ActionResult SaveM(M m)
{
var mToEdit = db.find(m.Id);
mToEdit.A = m.A;
db.SaveChanges();
//.......
}
HiddenFor just generates hidden field, but this never ensures that value will not be edited. Simple F12 click and anyone can edit value with developer tools. Instead, you should create ViewModel with that single field and check everything on server side
public class EditMViewModel
{
public int A { get; set; }
}
And something like this in controller action
public ActionResult Edit(int id, EditMViewModel m)
{
var someObject = LoadFromDb(id);
if (ModelState.IsValid)
{
someObject.A = m.A;
SaveToDb(someObject)
}
return RedirectToAction("Index");
}