I'm new in using MVC and I have a question about summarizing the row data in a table.
Some background:
First I collect a lot of data with an ordinary SQL statement. I use a DataTable to store the data. Then I output the data with an ordinary HTML table. This works very nicely and I have no problems with it.
I would now like to summarize all of the values for each row and display the result in the bottom row.
I know that I could do this already in the data layer. Loop through the datatable and summarize the values for the rows into a new row. Then finally append the "summary row" as the last row.
Some code:
<tr class="header">
<th>Person</th>
<th>NrOfRows</th>
</tr>
<% foreach (System.Data.DataRow row in Model.Rows) { %>
<tr>
<td><%: row["Name"].ToString()%></td>
<td><%: row["NrOfRows"].ToString()%></td>
</tr>
Could you please advice me which would be the best/easiest way to do this
Do the calculation in the ControllerAction. something like this...
public ActionResult Index()
{
var rows = this.repository.GetAll();
this.ViewData["total"] = rows.AsEnumerable().Select(o => o["NrOfRows"]).Select(o => int.Parse(o)).Sum();
return this.View(rows);
}
You should consider if you don't want to "pack" your data into a model (class). in mvc project in model section add class:
public class YourModel
{
public string Name
public int NrOfRows
public YourModel(string name, int nrOfRows)
{
Name = name;
NrOfRows = nrOfRows;
}
}
then in your controller method you do:
public ActionResult Summarize(/*parameters list*/)
{
var rows = (get data) //In here you assign here your table content
ViewData.Model = rows.Select(row => new YourModel(row["Name"], int.Parse(row["NrOfRows"])));
ViewData["nrRowCount"] = rows.Select(row => row.NrOfRows).Sum();
return View();
}
and you go to the view:
<table>
<th>Person</th>
<th>nrOfRows</th>
<%: foreach(var yourModel in Model) { :%>
<tr>
<td>yourModel.Name</td>
<td>yourModel.NrOfRows</td>
</tr>
<%: } :%>
<tr>
<td>Summary: </td>
<td> <%: ViewData["nrRowCount"] %:></td>
</tr>
</table>
Related
I try to pass value from 'class' in the View to controller and save it to database. Date picked by the user is stored in class 'finalDate' (about 100 rows with such date field and 'Save' button):
View:
<tr>
<td>
#Html.DisplayFor(modelItem => item.custName)
</td>
<td class="choice">
<input class="finalDate" type="date">
</td>
<td>
#Html.ActionLink("Save","Update", new { cust = item.custName, manualDate = "{finalDate.value}" }) //can I pass class (or id) in that way?
</td>
</tr>
Controller:
public ActionResult Update(string cust, DateTime? manualDate)
{
_docs.UpdateData(cust, manualDate); //method UpdateData takes args and saves changes to db
return RedirectToAction("Index");
}
The issue is that no error shows up. Nothing has been changed to db anyway. So I'm wondering if:
I pass class value to ActionLink correctly (and is it possible at all)?
I need to wrap up my View code in <form action="#Url.Action("ActionName", "Home"))" method="post"> to send data to server?
Add [HttpPost] to ActionResult?
Not even sure if I asked the question the right way. Been looking at this for about an hour and its too simple to take to long. Trouble is I am too simple to know the answer or even how to correctly phrase a search to find the answer.
I have a history of jobs completed for a site set up.
Controller:
public async Task<IActionResult> JobSiteHistory(int id, int? page)
{
var jobs = from j in _context.Job
.Include(j => j.Site)
.Include(j=>j.WaterBody)
.Where(j=>j.Site.SiteID==id)
.OrderByDescending(j=>j.BookingDate)
select j;
int pageSize = 9;
return View(await PaginatedList<Job>.CreateAsync(jobs.AsNoTracking(), page ?? 1, pageSize));
}
This is returning the correct records all good.
I then have a view set up:
<h2> Site Jobs History</h2>
<p>
<a asp-action="Create">Add New Job</a>
</p>
<table class="table">
<thead>
<tr>
<th>Booking Date</th>
<th>Job Number</th>
<th>Waterbody</th>
<th>Job Description</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr>
<td>#Html.DisplayFor(modelItem => item.BookingDate)</td>
<td>#Html.DisplayFor(modelItem => item.JobNumber)</td>
<td>#Html.DisplayFor(modelItem => item.WaterBody.WBName)</td>
<td>#item.JobDescription.Substring(0, Math.Min(item.JobDescription.Length, 30))</td>
<td>
<a asp-action="Edit" asp-route-id="#item.JobID">Edit</a> |
<a asp-action="Details" asp-route-id="#item.JobID">Details</a> |
<a asp-action="Delete" asp-route-id="#item.JobID">Delete</a> |
</td>
</tr>
}
</tbody>
</table>
This is working wellish so far.
All I want to do is add something like:
#Html.DisplayFor(ModelItem=>item.Site.SiteName)
To the <h2> element. I know this wont work as typed, thanks for thinking that.
I just cant see a way to add it. I considered ViewData, but may be using it wrong as I cant get it to populate with SiteName.
Is there a way to do this or am I thinking all ass about as usual?
The easiest change would be to use this:
<h2>#Html.DisplayFor(m => m[0].Site.SiteName);</h2>
Other options:
Is there any reason why you can't use the ViewBag?
In controller:
ViewBag.SiteName = Site.name
In view:
<h2>#ViewBag.SiteName</h2>
If you must use your model to pass the whole site object then change your view model that you pass to the view.
You are currently returning a list of jobs with the site object for each job, but it looks like you only need it once.
I would change your view model to be something like:
public class SiteJobsHistoryModel
{
public Site Site { get; set;}
public PaginatedList<Job> Jobs { get; set; }
}
Then you don't have to include the site on your query, and just retrieve it once from the database:
var site = _context.Site.Single(j => j.Site.SiteID==id);
var jobs = from j in _context.Job
//.Include(j => j.Site) -- this can be removed
.Include(j=>j.WaterBody)
.Where(j=>j.Site.SiteID==id)
.OrderByDescending(j=>j.BookingDate)
select j;
return View(new SiteJobsHistoryModel
{
Site = site,
Jobs = await PaginatedList<Job>.CreateAsync(jobs.AsNoTracking(), page ?? 1, pageSize)
});
Then for the title in <h2> tag you can use:
#Html.DisplayFor(ModelItem=>model.Site.SiteName)
And your foreach loop becomes:
#foreach (var item in Model.Jobs)
I have a ViewModel called MyViewModel which contains:
public IList<Config> Config { get; set; }
public bool Disabled { get; set; }
public string Name { get; set; }
Config is a object which contains ID & Value
on the Controller side I'm populating all the values and in the view i need to access this List of Config individual items where id = x and it should display corresponding value in label. so i need a strongly typed label for this something along the lines of:
#Html.LabelFor(a => ....)
I'm not sure about what should be my Linq statement to display Config.Value in label where Config.Id = x. any help will be appreciated. thanks
To display table grouped by ID values, first create a list of all existing Id numbers.
ViewBag.Ids = Config.Select(x => x.Id).Distinct(); //create list of all existing Ids
Pass that list into your View via the ViewBag, and iterate through it as well as your list of objects:
#foreach(int IdNum in ViewBag.Ids)
{
<table>
#foreach(Config c in Config.Where(x=> x.Id == IdNum))
{
<tr>
<td> #c.ID </td>
<td> #c.property1 </td>
<td> #c.property2 </td>
</tr>
}
</table>
}
In this case, I wouldn't bother with the LabelFor() method, since it is easy enough to just use the shorthand #object.property inline with HTML.
to display a list by group try this
<table>
#{
foreach (var group in Model.Config.GroupBy(x => x.Id))
{
<tr>
<td>#group.Key</td> </tr>
int index =0;
#foreach(Config c in Model.Config.Where(x=> x.Id == group.Key))
{
<tr>
<td> #Model.Config[index].ID </td>
<td> #Model.Config[index].property1 </td>
<td> #Model.Config[index].property2 </td>
</tr>
index++;
}
}
}
</table>
After sleeping over it & trying few times I finally got it. This is what I was looking for:
#Html.Label(Model.Config.FirstOrDefault(x => x.ConfigId == "x").Value)
For some reason though, it won't work on #Html.Display. Any idea why? Thanks for all the comments and suggestions.
I have this view based on a list of a model where I create strongly-typed checkboxes for each items of the model based on a boolean.
Here's my view:
#using MyApp.Models
#model IList<MyApp.Models.ObjInfo>
#{
ViewBag.Title = "Obj Inventory";
}
<h2>Search Inventory</h2>
<p>
#using (Html.BeginForm())
{
(Many search filters which are non-relevant)
<p>
Send Items: #Html.ActionLink("Click Here", "SendItems")
</p>
}
<table>
<tr>
<th>
Obj Name
</th>
<th>
Number In Stock
</th>
(...)
<th>
Select Item
</th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.OtherObj.m_Name)
</td>
(...)
<td>
#Html.CheckBoxFor(modelItem => item.m_IsSelected)
</td>
</tr>
}
</table>
The whole process works fine and I can actually generate a view with checkboxes for each item of my list of model.
Now my question is that I want to create a list which would regroup only the items in the list which are checked and send them to the controller. How could I do that? Can anyone help me or suggest me a way to work?
Thank you!
* EDIT *
Here is the HttpPost Method used to get the List of items as mentioned below:
//
// GET: /Inventory/SendItems
[HttpPost]
public ActionResult SendItems(IList<ObjInfo> listToSend)
{
m_ListObjToSend = new List<ObjInfo>();
foreach (var item in listToSend.Where(item => item.m_IsSelected))
{
m_ListObjToSend .Add(item);
}
return View(m_ListObjToSend );
}
However I have encountered many problems:
This method does NOT work if I put the [HttpPost] attribute (it will show as "Not Found");
The list I am supposed to receive is null;
Each hiddenfield linked with the checkbox has default value as false even if the checked value shows true;
I am using an actionlink because I do not want to use a button, there is already one that is doing another job.
I am open for any comments / help available, thank you!
If you use the CheckBoxFor helper to generate checkboxes you will notice that it generates an additional hidden field along with each checkbox. This means that all values will be sent to the controller and you will have to filter in your controller those that are checked.
Also I would recommend you using indexes to ensure proper model binding. You just need to use an IList<ObjInfo> or ObjInfo[] which is trivially easy achievable by calling .ToList() or .ToArray() extension methods on your view model before passing it to the view:
#using MyApp.Models
#model IList<ObjInfo>
...
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(x => x[i].OtherObj.m_Name)
</td>
(...)
<td>
#Html.CheckBoxFor(x => x[i].m_IsSelected)
</td>
</tr>
}
...
And now your controller action could directly take the list of items:
[HttpPost]
public ActionResult SomeAction(IEnumerable<ObjInfo> model)
{
...
}
and if you wanted to find the selected values, you could simply get them through LINQ:
[HttpPost]
public ActionResult SomeAction(IEnumerable<ObjInfo> model)
{
var selectedItems = model.Where(x => x.m_IsSelected);
...
}
Remark: m_Name and m_IsSelected is a disastrously bad naming convention for a properties in C#.
UPDATE:
Another issue you have with your code is that your Html.BeginForm doesn't contain any input field. It has only a single ActionLink which obviously only does a GET request. If you want to submit the values you should wrap your entire table with the form and use a submit button and not some action links:
#using MyApp.Models
#model IList<ObjInfo>
#{
ViewBag.Title = "Obj Inventory";
}
<h2>Search Inventory</h2>
<p>
#using (Html.BeginForm("SendItems", null, FormMethod.Post))
{
(Many search filters which are non-relevant)
<table>
<tr>
<th>Obj Name</th>
<th>Number In Stock</th>
(...)
<th>Select Item</th>
</tr>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>
<!--
This will not be sent to your controller because it's only a label.
You will need a corresponding hidden field if you want to get that value back
-->
#Html.DisplayFor(x => x[i].OtherObj.m_Name)
</td>
(...)
<td>
#Html.CheckBoxFor(x => x[i].m_IsSelected)
</td>
</tr>
}
</table>
<p>
Send Items: <button type="submit">Click Here</button>
</p>
}
</p>
So really, 2 things you should learn:
The naming convention that the default model binder expects when binding to a list
How to use a javascript debugging tool (such as FireBug and/or Chrome Developper Toolbar) which will allow you to inspect all the values that are sent to your server and immediately recognized whether you respected the convention you learned in 1.
I'm using the guide found here http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style and am using MVC2.
I've got it working with a controller method as such:
[HttpPost]
public ActionResult CreateStockRequest(StockRequestModel viewModel, List<StockRequestModel.StockRequestItem> items)
{
viewModel.Items = items;
// Validate the request and submit it
return View(viewModel);
}
As you can see, even though my Model contains an Items method, i've had to add an items parameter, as the property on the model wasn't getting populated.
I've tried changing items to Items in the BeginCollectionItem method and have tried various other values but I can't get it to work without adding the seperate items parameter in the controller method.
tl;dr: How can I add/remove/edit items in a list property of a model, from a view?
View
<table>
<thead>
<tr>
<td><%= Html.LabelFor(m => m.Items[0].Item )%></td>
<td><%= Html.LabelFor(m => m.Items[0].Quantity )%></td>
</tr>
</thead>
<tbody id="editorRows">
<% foreach (var item in Model.Items)
{
Html.RenderPartial("StockRequestItemEditor", item);
}%>
</tbody>
<tfoot>
<tr><td colspan="2"> </td></tr>
<tr>
<td colspan="2"><%= Html.ActionLink("Add Item...", "BlankEditorRow", null, new { id = "addItem" })%></td>
<script type="text/javascript">
$("#addItem").click(function() {
$.ajax({
url: this.href,
cache: false,
success: function(html) { $("#editorRows").append(html); }
});
return false;
});
</script>
</tr>
</tfoot>
</table>
Partial View
<tr>
<% using(Html.BeginCollectionItem("Items")) { %>
<td>
<%= Html.ComboBoxFor(m => m.Item,
null,
Url.Action("Products", "Data", new { area = (string)null }),
Model.Item,
2)%>
</td>
<td><%= Html.TextBoxFor(m => m.Quantity)%></td>
<% } %>
</tr>
It's a long shot but maybe this is the problem:
Html.RenderPartial("StockRequestItemEditor", item);
I noticed when inspecting the viewModel in the POST action that it would have the correct number of items in the collection, but they would all be null. That suggests to me that this is a prefixing issue with the model binder. So maybe something like this will work:
var dictPrefix = new ViewDataDictionary();
dictPrefix.TemplateInfo.HtmlFieldPrefix = "SomePrefix";
Html.RenderPartial("StockRequestItemEditor", item, dictPrefix);
I don't think RenderPartial() passes along the prefix without using this overload (might be wrong though). I'm not completely sure how the bind prefixing works, so I don't actually know what the name would be, but it seems like it's relevant here. The collection definitely has the correct number of items for me, but none of them are correctly bound.
Hopefully this will be enough to push someone else to giving you the right answer.
Why don't you use a view model?
public class StockRequestModel
{
public List<StockRequestItem> Items { get; set; }
... some other properties
}
and then have your controller action take this view model as parameter:
[HttpPost]
public ActionResult CreateStockRequest(StockRequestModel viewModel)
{
// TODO: do something with viewModel.Items ...
return View(viewModel);
}
and inside your view:
<div class="editorRow">
<% using(Html.BeginCollectionItem("Items")) { %>
...
<% } %>
</div>