I have a model with a list of data and my View accesses the model properties and renders the database status. There are several boxes in my view which displays color according to their respective values in the database. And each div corresponds to one item in the database.
I know I haven't coded my View in a correct way but it is what I want my view to look like (each div ID corresponding to the respective boxID in the database and displaying its respective color). And I want 500 database values represented in the View.
#model List<My.Namespace.BoxStatusModel>
<table>
<tr class="first">
<td class="dclass1">
<div id="#boxStatus.boxID" class="top #boxStatus.Color">
<div id="#boxStatus.boxID" class="top #boxStatus.Color">
<div id="#boxStatus.boxID" class="bottom #boxStatus.Color">
<div id="#boxStatus.boxID" class="bottom #boxStatus.Color">
</td>
<td class="dclass2">
<div id="#boxStatus.boxID" class="top #boxStatus.Color">
<div id="#boxStatus.boxID" class="top #boxStatus.Color">
<div id="#boxStatus.boxID" class="bottom #boxStatus.Color">
<div id="#boxStatus.boxID" class="bottom #boxStatus.Color">
</td>
...
</tr>
<tr class="second">
...
</tr>
</table>
My Controller
public class HomeController : Controller
{
public ActionResult Index()
{
MySQL msql = new MySQL();
List<List<string>> results = msql.SelectTable("Select boxID, boxStatus from BoxStatusModel");
List<BoxStatusModel> model = new List<BoxStatusModel>();
for (int i = 0; i < results.Count; i++)
{
model.Add(new BoxStatusModel()
{
slotID = results[i][0],
currentStatus = results[i][1]
});
}
return View(model);
}
}
My ViewModel
namespace My.ViewModels
{
public class BoxStatusModel
{
public int ID { get; set; }
public string boxID { get; set; }
public string boxStatus { get; set; }
public string Color
{
get
{
switch (boxStatus)
{
case "busy":
return "red";
case "available":
return "green";
default:
return "";
}
}
}
}
}
Related
I'm unsure what I'm doing wrong when trying to map form values to my model, FieldMappingCollection. I've been able to get the Id and Name back for the model but not the list of mappings, either the previously existing or newly created ones.
The page contains a text box for the mapping name and a table of rows which contains a select list and a text box. The FromField relates to the select list and the ToField relates to the text box.
I'm not super familiar with ASP.NET Core and even less familiar with older versions, so I'm unsure if #Html... is totally phased out or what the proper syntax is for automating binding or how to implement custom binding or HTML generators etc. I'm also not concerned with exactly how I should be handling the select list in particular, it's a bit of temporary code to get the page working and I'll come back to it later.
public class FieldMappingCollection
{
public int Id { get; set; }
public string Name { get; set; } = "";
public List<FieldMapping> FieldMappings { get; set; } = new();
}
public class FieldMapping
{
public int Id { get; set; }
public string FromField { get; set; } = "";
public string ToField { get; set; } = "";
}
public class MyController
{
public static List<string> AvailableFields = new()
{
// predefined field names for select list...
};
private readonly IMappingRepository m_repo;
// constructor, Index, etc...
public IActionResult EditMappingCollection(int id)
{
return View(m_repo.GetById(id));
}
[HttpPost]
public IActionResult EditMappingCollection(FieldMappingCollection model)
{
m_repo.Update(model);
m_repo.Save();
return RedirectToAction(nameof(Index));
}
}
#model FieldMappingCollection
// other required stuff...
<form asp-action="EditMappingCollection" method="post">
<input hidden asp-for="Id" type="number" />
<input type="text" asp-for="Name" />
<table class="table">
<thead>
<tr>
<th scope="col">From Field</th>
<th scope="col">To Field</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.FieldMappings.Count; i++)
{
<tr>
// Nothing to store the mapping Id yet
<td><select asp-for="FieldMappings[#i].FromField" style="width: 100%;" asp-items="#MyController.AvailableFields.Select(field => new SelectListItem(field, field, Model.FieldMappings[i].FromField == field))"></select></td>
<td><input asp-for="FieldMappings[#i].ToField" style="width: 100%;" type="text" placeholder="To Field" value="#Model.FieldMappings[i].ToField" /></td>
</tr>
}
</tbody>
</table>
<input class="btn btn-primary" type="submit" value="Save Changes" />
</form>
You could read the offcial document how to bind collection target:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0
I tried as below:
public class FieldMappingCollection
{
public FieldMappingCollection()
{
Selectlist = new List<SelectListItem>();
}
........
public List<SelectListItem> Selectlist { get; set; }
}
In controller I tried:
public IActionResult EditMappingCollection()
{
var FieldMapping1 = new FieldMapping() { Id = 1, FromField = "fromfield1" };
var FieldMapping2 = new FieldMapping() { Id = 2, FromField = "fromfield2" };
var FieldMappingCollection = new FieldMappingCollection();
FieldMappingCollection.Selectlist = new List<SelectListItem>()
{
new SelectListItem() { Text= FieldMapping1.FromField,Value=FieldMapping1.Id.ToString() },
new SelectListItem() { Text= FieldMapping2.FromField,Value=FieldMapping2.Id.ToString() }
};
return View(FieldMappingCollection);
}
In view:
<table class="table">
<thead>
<tr>
<th scope="col">From Field</th>
<th scope="col">To Field</th>
</tr>
</thead>
<tbody>
<select name=FieldMappings[0].FromField class="form-control" asp-items="#Model.Selectlist"></select>
<input name=FieldMappings[0].ToField value="1"/>
<select name=FieldMappings[1].FromField class="form-control" asp-items="#Model.Selectlist"></select>
<input name=FieldMappings[1].ToField value="2"/>
</tbody>
</table>
<input class="btn btn-primary" type="submit" value="Save Changes" />
Result:
I'm a bit confused because I thought this a very straight-forward thing, it's possibly something simple tripping me up.
I have a view:
#model IEnumerable<CarViewModel>
#using (Html.BeginForm("SummarySaveAll", "VroomVroom", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div>
<table>
<thead>
<tr>
<th width="1">
#Html.DisplayNameFor(model => model.Driver)
</th>
<th width="1">
#Html.DisplayNameFor(model => model.Colour.Name)
</th>
</tr>
</thead>
<tbody>
#foreach (var element in Model)
{
<tr>
<td width="1">
#Html.DisplayFor(m => element.Driver)
</td>
<td width="1">
#Html.DropDownListFor(m => element.Colour, element.Colours, "Unknown")
</td>
</tr>
}
</tbody>
</table>
<div>
<input type="submit" value="Save Changes" class="btn" />
#Html.ActionLink("Cancel Changes", "Index", null, new { #class = "btn" })
</div>
</div>
}
and the list/enumerable of CarViewModel is supposed to bounce back to the VroomVroom controller, action SummarySaveAll which it does - but the viewmodel on the page doesn't get passed back to it:
[HttpPost]
public ActionResult SummarySaveAll(IEnumerable<CarViewModel> summaries)
{
// Want to do stuff with summaries but it's always null
return View();
}
I tried to encapsulate the List in another ViewModel and cycle through elements using a for i loop but that wouldn't pass back to the controller either.
Surely it's possible to send a List or IEnumerable of models back to a controller?
My CarVM:
public class CarViewModel
{
[MaxLength(150)]
[Display(AutoGenerateField = true, Name = "Entered By")]
public string Driver { get; set; }
[Display(AutoGenerateField = true)]
public Colour Colour { get; set; }
[Key]
[Display(AutoGenerateField = false)]
public int Id { get; set; }
[Display(AutoGenerateField = false)]
public bool IsDeleted { get; set; } = false;
public IEnumerable<SelectListItem> Colours { get; set; }
public CarViewModel() { }
public CarViewModel(Model CarModel summaryModel, CarPropertyCollection propertyCollection)
{
Driver = summaryModel.Driver;
Id = summaryModel.Id;
IsDeleted = summaryModel.IsDeleted;
Colour = summaryModel.Colour == null ? null :
propertyCollection.Colours.Where(x => x.Id == summaryModel.Colour.Id).FirstOrDefault();
Colours = propertyCollection.Colours.Select(x => new SelectListItem { Value = x.Id.ToString(), Text = x.Name });
}
}
}
Must stress that Colour is a custom class but only has Id and Name properties
Colours doesn't relate to a specific car, it relates to cars in general, so rather than using a collection as your view model, create a wrapper:
class EditCarsViewModel
{
public IEnumerable<SelectListItem> Colours { get; set; }
public IList<CarViewModel> Cars { get; set; }
}
Then your view:
#model EditCarsViewModel
#for (int i = 0; i < Model.Cars.Length; i++)
{
<td>
#Html.DropDownListFor(m => Model.Cars[i].Colour, Model.Colours, "Unknown")
</td>
}
Any other CarViewModel properties will need their own input as well. HiddenFor can be used if they should be readonly:
#model EditCarsViewModel
#for (int i = 0; i < Model.Cars.Length; i++)
{
#Html.HiddenFor(m => Model.Cars[i].Id)
#Html.HiddenFor(m => Model.Cars[i].Driver)
<!-- etc. -->
<td>
#Html.DropDownListFor(m => Model.Cars[i].Colour.Id, Model.Colours, "Unknown")
</td>
}
And your controller:
[HttpPost]
public ActionResult SummarySaveAll(EditCarViewModel model)
{
// model.Cars should be populated
return View();
}
Note that an indexable collection, such as IList<T> should be used, as the form field names need to include the index to differentiate the items.
Edit by OP
The Colour class consists of a [Key] int Id property and a string Name property. For DropDownList items I had to make sure the Id property was specified on the m => Model.Cars[i].Colour.Id line otherwise that particular prop was coming back as null even though other items were coming through fine.
try
[HttpPost]
public ActionResult SummarySaveAll(IList<CarViewModel> summaries)
{
// Want to do stuff with summaries but it's always null
return View(summaries);
}
I've also added this model as a param for your view
This how you do it:
First my View which posts back to a controller named Home and an action named ListView:
#model List<MyModel>
#{
ViewData["Title"] = "Using a list as model";
}
<h1>#ViewData["Title"]</h1>
#using (Html.BeginForm("ListView", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div>
<table>
<thead>
<tr>
<th width="1">
Name
</th>
<th width="1">
Description
</th>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td width="1">
#Html.DisplayFor(m => Model[i].Name)
</td>
<td width="1">
#Html.TextBoxFor(m => Model[i].Description)
</td>
</tr>
}
</tbody>
</table>
<div>
<input type="submit" value="Save Changes" class="btn" />
#Html.ActionLink("Cancel Changes", "Index", null, new { #class = "btn" })
</div>
</div>
}
Notice how I used an indexer to render the controls [i]
This is my model:
public class MyModel
{
public string Name { get; set; }
public string Description { get; set; }
}
This is my controller action:
[HttpPost]
public IActionResult ListView(IEnumerable<MyModel> model)
{
return View(model);
}
And this is the result:
My model is set up so the controller can deliver a Json response of data from user input. The 'rgba' property of my model is an array of ints. If a user enters text of say '255, 0, 0, 1' into the TextBoxFor for 'rgba', the text is not mapping into an array (which I thought was supposed to happen automagically). Instead, int[0] is what makes it to the controller.
I've tried all the potential solutions I could find on here, including passing a FormCollection object to controller. I've tried to get the TextBoxFor value using JS/jQuery and manipulate the data, but can't figure out how to pass the manipulated data back to the model (this seems less than ideal, like there should be an easy way to do this in .Net).
Controller:
public class HomeController : Controller
{
public IActionResult NewColor()
{
Rootobject newColor = new Rootobject();
return View(newColor);
}
[HttpPost]
public IActionResult NewColor(Rootobject color)
{
var json = JsonConvert.SerializeObject(color);
return Json(json);
}
}
Model:
public class Rootobject
{
public Color[] colors { get; set; }
}
public class Color
{
public string color { get; set; }
public string category { get; set; }
public string type { get; set; }
public Code code { get; set; }
}
public class Code
{
public int[] rgba { get; set; }
public string hex { get; set; }
}
View:
#model WebAppPlayground.Models.Rootobject
#{
ViewData["Title"] = "New Color";
}
<style>
input:focus {
border-radius: 5px;
}
input {
padding: 2px;
border-radius: 5px;
border-style: ridge;
}
</style>
<h2>New Color</h2>
<h4>Color</h4>
<hr />
<center>
#using (Html.BeginForm("NewColor", "Home", FormMethod.Post, new { id = "form" }))
{
<table style="border-collapse:separate; border-spacing: 5px 5px">
<tbody>
<tr class="form-group" for="Color">
<td>Color</td>
<td>#Html.TextBoxFor(m => m.colors[0].color)</td>
</tr>
<tr class="form-group">
<td class="">Category</td>
<td>#Html.TextBoxFor(m => m.colors[0].category)</td>
</tr>
<tr class="form-group">
<td class="">Type</td>
<td>#Html.TextBoxFor(m => m.colors[0].type)</td>
</tr>
<tr class="form-group">
<td class="">rgba</td>
<td>#Html.TextBoxFor(m => m.colors[0].code.rgba, new { id = "rgba"})</td>
</tr>
<tr class="form-group">
<td class="">Hex</td>
<td>#Html.TextBoxFor(m => m.colors[0].code.hex)</td>
</tr>
</tbody>
</table>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
}
<div>
<a asp-action="Index">Back to List</a>
</div>
</center>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
I would like my Controller to receive an array of Int's via rgba TextBoxFor where user enters text eg 255, 0, 0, 1.
I think there is something (obvious) I am overlooking or not understanding.
****Updated controller post method to remove added 'rgba_str' prop per #i_ll_be_back 's answer and deliver desired Json data:
[HttpPost]
public IActionResult NewColor(Rootobject color)
{
var json = JsonConvert.SerializeObject(color);
JObject jsonObject = JObject.Parse(json);
JObject code = (JObject)jsonObject["colors"][0]["code"];
code.Property("rgba_str").Remove();
return Json(jsonObject);
}
Try the following reshaping the Code class in the following:
public class Code
{
public int[] rgba { get; set; }
public string rgba_str
{
get
{
var res = "";
for(int i = 0; i < rgba.Length; i++) {
res += rgba[i] + (i != rgba.Length - 1 ? "," : "");
}
return res;
}
set
{
var strArray = value.Split(',');
rgba = new int[strArray.Length];
for(int i = 0; i < strArray.Length; i++) {
rgba[i] = int.Parse(strArray[i]);
}
}
}
public string hex { get; set; }
}
Now bind the TextBoxFor with the rgba_str field
I'm trying to pass a list from the model and a string from my view to my controller. I get the data from Home controller to the Dashboard.cshtml successfully, but when I try to pass a list from the model to the DetailController, the list doesn't go through but the string TicketGroupName does. Any thoughts on what I'm doing wrong?
Dashboard.cshtml
#model testlogin.Models.DashboardViewModel
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>#Model.AreaName</h2>
<table id="dashboardContainer" class="table">
<tr class="data-row-1">
<td class="tile text-center">
<img style="height: 125px" id="Image1" src="https://logo.jpg" />
</td>
<td class="tile btn text-center">
<a href="#Url.Action("ShowTickets", "Detail", new { TicketList = Model.OpenNow, TicketGroupName = "Open Now" })">
<div>
<div class="row tile-head">
<h4>Open Now</h4>
</div>
<div class="row">
<h1><strong>#Model.OpenNow.Count</strong></h1>
</div>
</div>
</a>
</td>
</tr>
</table>
HomeController.cs
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Dashboard(string AreaName)
{
full_area_name = "Test Area";
var dash = GetDashData(board_number); //Not including it here, but this method works
dash.AreaName = full_area_name;
return View(viewName: "Dashboard", model: dash);
}
}
DashboardViewModel.cs
public class DashboardViewModel
{
public List<TicketModel> OpenNow { get; set; }
}
DetailController.cs
public class DetailController : Controller
{
public ActionResult ShowTickets(List<TicketModel> TicketList, string TicketGroupName)
{
TicketViewModel vm = new TicketViewModel()
{
GroupName = TicketGroupName,
Tickets = TicketList
};
return View(vm);
}
}
TicketViewModel.cs
public class TicketViewModel
{
public string GroupName { get; set; }
public IEnumerable<TicketModel> Tickets { get; set; }
}
ShowTickets.cshtml
#model testlogin.Models.TicketViewModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="container">
<div>
<h1>#Model.GroupName</h1>
</div>
<table class="table table-striped table-bordered table-hover">
<tr>
<th>#Html.LabelFor(model => model.Tickets.FirstOrDefault().TicketNbr)</th>
<th>#Html.LabelFor(model => model.Tickets.FirstOrDefault().Company_Name)</th>
<th>#Html.LabelFor(model => model.Tickets.FirstOrDefault().Priority_Description)</th>
<th>#Html.LabelFor(model => model.Tickets.FirstOrDefault().Summary)</th>
<th>#Html.LabelFor(model => model.Tickets.FirstOrDefault().Last_Update)</th>
<th>#Html.LabelFor(model => model.Tickets.FirstOrDefault().OwnerOrCloser)</th>
<th>#Html.LabelFor(model => model.Tickets.FirstOrDefault().Date_Entered_UTC)</th>
<th>#Html.LabelFor(model => model.Tickets.FirstOrDefault().Date_Responded_UTC)</th>
<th>#Html.LabelFor(model => model.Tickets.FirstOrDefault().Date_Closed_UTC)</th>
</tr>
#Html.DisplayFor(x => x.Tickets)
</table>
</div>
As suggested by #Shyju and #Stephen_Muecke, I rearranged my code in the controller to get the data again from the Http cache when the view called the action.
Just to make it clear in case anyone else is searching for this answer, you cannot pass a list (or IEnumerable) object from view to controller. The list data has to be gathered again in the controller.
Thanks for your help, guys!
I'm still trying to get my head wrapped around using ViewModels and IEnumerable/List as properties. I'm obviously missing something that is not allowing me to see my IEnumerable in my View.
What I'm ultimately trying to do is have a view that will show a list of unassigned users not assigned to a Group (called Patrols in this case and would have PatrolId=0). The table will have a checkbox next to each member. Above that table will be a DropDownList of the available Patrols. The Admin will come to the page to see those that aren't assigned, select a Patrol from the DDL at the top, check the users he want's to assign to that Patrol, and then submit the form that will pass the PatrolID from the DDL and find all the selected members in order to update their record with that PatrolId.
In the View below when I go to loop through the Scout property, I would assume I would be able to use Model.Scout in this part in order to loop through and write out all the members in that IEnumerable.
#for (var i = 0; i < Model.Count(); i++)
However, when I try to use intellisense to see the Scout property when using the Model, I don't see the property. In addition to that I would think I would need to first check the Scout property to see if there is a count before I write out all the rows. Again, I can't see the Scout property in order to check it's count.
ViewModel
public class PatrolMemberViewModel
{
[Key]
public int MemberId { get; set; }
public int PatrolId { get; set; }
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Phone")]
public string PhonePrimary { get; set; }
[Display(Name = "Email")]
public string EmailPrimary { get; set; }
public bool IsSelected { get; set; }
}
public class PatrolUnassignedViewModel
{
public SelectList Patrols { get; set; }
public IEnumerable<PatrolMemberViewModel> Scout { get; set; }
}
Controller
// GET:
public ViewResult Unassigned()
{
PatrolUnassignedViewModel unassinged = new PatrolUnassignedViewModel();
unassinged.Patrols = new SelectList(repository.SelectAllPatrols());
unassinged.Scout = repository.SelectAllUnassigned();
return View(unassinged);
}
Repository
public IEnumerable<PatrolMemberViewModel> SelectAllUnassigned()
{
using (DataContext db = new DataContext())
{
var results = (from p in db.Person
where p.IsActive == true
&& p.IsScout == true
&& p.PatrolId == 0
select new PatrolMemberViewModel
{
MemberId = p.PID,
FirstName = p.FirstName ?? string.Empty,
LastName = p.LastName ?? string.Empty,
EmailPrimary = p.EmailPrimary ?? string.Empty,
PhonePrimary = p.PhonePrimary ?? string.Empty,
PatrolId = p.PatrolId,
IsSelected = false
}
).OrderBy(o => o.LastName).ThenBy(o => o.FirstName).ToList();
return results;
}
}
public IEnumerable<PatrolName> SelectAllPatrols()
{
return db.PatrolNames;
}
View
#model IList<ProjectName.ViewModels.PatrolUnassignedViewModel>
#{
ViewBag.Title = "Unassigned";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Patrols</h2>
<h4>Assign Scouts to a Patrol.</h4>
#using (Html.BeginForm("Update", "Patrol", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(false, "", new { #class = "alert alert-danger" })
<table class="table table-bordered table-striped table-hover table-condensed tbackground">
<tr>
<th class="text-center">
</th>
<th class="text-center">
First Name
</th>
<th class="text-center">
Last Name
</th>
<th class="text-center">
Email
</th>
<th class="text-center">
Phone
</th>
</tr>
#for (var i = 0; i < Model.Count(); i++)
{
<tr>
<td class="text-center">
#Html.CheckBoxFor(m => m[i].IsSelected)
</td>
<td>
#Html.DisplayFor(m => m[i].FirstName)
</td>
<td>
#Html.DisplayFor(m => m[i].LastName)
</td>
<td>
#Model[i].EmailPrimary
</td>
<td class="text-center">
#Html.DisplayFor(m => m[i].PhonePrimary)
</td>
</tr>
}
</table>
<div class="control-wrapper">
<input type="submit" id="btnSubmit" value="Assign Scouts" class="btn btn-success" />
</div>
}
<p> </p>
You controller is returning a single instance of class PatrolUnassignedViewModel
public ViewResult Unassigned()
{
PatrolUnassignedViewModel unassinged = new PatrolUnassignedViewModel();
unassinged.Patrols = new SelectList(repository.SelectAllPatrols());
unassinged.Scout = repository.SelectAllUnassigned();
return View(unassinged);
}
Your view is expecting an IList
#model IList<ProjectName.ViewModels.PatrolUnassignedViewModel>
When it should be expecting
#model ProjectName.ViewModels.PatrolUnassignedViewModel
Your scout is an IEnumerable so doesn't have a count method so should be
public IList<PatrolMemberViewModel> Scout { get; set; }
public IList<PatrolMemberViewModel> SelectAllUnassigned()
{
}
You should be doing your loop like this
#for (var i = 0; i < Model.Scout.Count(); i++)
{
<tr>
<td class="text-center">
#Html.CheckBoxFor(m => m.Scout[i].IsSelected)
</td>
<td>
#Html.DisplayFor(m => m.Scout[i].FirstName)
</td>
<td>
#Html.DisplayFor(m => m.Scout[i].LastName)
</td>
<td>
#Model.Scout[i].EmailPrimary
</td>
<td class="text-center">
#Html.DisplayFor(m => m.Scout[i].PhonePrimary)
</td>
</tr>
}
Am I missing something here?
From your GET action method, you are passing a single object of PatrolUnassignedViewModel. But your view is bound to a collection of PatrolUnassignedViewModel. So change your view to be like
#model PatrolUnassignedViewModel
Now you can use the Model.Scout property which is a collection.
#model PatrolUnassignedViewModel
<h2>Total : #Model.Scout.Count()</h2>
#foreach(var item in Model.Scout)
{
<label>#item.FirstName</label>
}