Updating multiple items within same view - asp.net-mvc-3

I am trying to make a stock take application, My view loads all my stock with one editor.
My controller is not getting any of the data from the view?
I want to be able to edit all my stock at the same time?
How can I do this
Model Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace FlatSystem.Models
{
public class Stock
{
public int ID { get; set; }
public string Item_Name { get; set; }
public int Actual_Stock { get; set; }
public int Wanted_Stock { get; set; }
}
}
View Code
#model IEnumerable<FlatSystem.Models.Stock>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<div class="sidemenu">
<div class="sidemenu-heading">
ReStock
</div>
<div class="div-body">
<table>
<tr>
<th>
Item Name
</th>
<th>
Wanted Stock
</th>
<th>
Stock On Hand
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Item_Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Wanted_Stock)
</td>
<td>
<div class="editor-field">
#Html.EditorFor(modelItem => item.Actual_Stock)
#Html.ValidationMessageFor(modelItem => item.Actual_Stock)
</div>
</td>
#Html.HiddenFor(modelItem => item.ID)
</tr>
}
</table>
</div>
</div>
<input type="submit" value="Submit" />
}
Controller Code
[HttpPost]
public ActionResult ReStock(List<Stock> stock)
{
foreach (var item in stock)
{
if (ModelState.IsValid)
{
GR.InsertOrUpdate(item);
}
}
GR.Save();
return RedirectToAction("Restock");
}

It's hard to answer your question without model class, but idea is that your edit inputs must contain index in name attribute.
Something like this:
#for(int i = 0: i < Model.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model[i].Item_Name)
</td>
<td>
#Html.DisplayFor(modelItem => Model[i].Wanted_Stock)
</td>
<td>
<div class="editor-field">
#Html.EditorFor(modelItem => Model[i].Actual_Stock)
#Html.ValidationMessageFor(modelItem => Model[i].Actual_Stock)
</div>
</td>
#Html.HiddenFor(modelItem => Model[i].ID)
</tr>
}
Added:
Sorry, thanks to Darin Dimitrov, you can't access IEnumerable by index, use List or Array.

You could use editor templates. I would recommend you to first read the following article in order to understand why your code doesn't correctly bind the collection. Once you understand that you could do the following:
#model IEnumerable<FlatSystem.Models.Stock>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<div class="sidemenu">
<div class="sidemenu-heading">
ReStock
</div>
<div class="div-body">
<table>
<thead>
<tr>
<th>Item Name</th>
<th>Wanted Stock</th>
<th>Stock On Hand</th>
<th></th>
</tr>
</thead>
<tbody>
#Html.EditorForModel()
</tbody>
</div>
</div>
<input type="submit" value="Submit" />
}
and now define a custom editor template for the Stock type which will automatically be rendered for each element of the collection (~/Views/Shared/EditorTemplates/Stock.cshtml) - the name and location of the editor template is important as it works by convention:
#model FlatSystem.Models.Stock
<tr>
<td>
#Html.DisplayFor(x => x.Item_Name)
</td>
<td>
#Html.DisplayFor(x => x.Wanted_Stock)
</td>
<td>
<div class="editor-field">
#Html.EditorFor(x => x.Actual_Stock)
#Html.ValidationMessageFor(x => x.Actual_Stock)
</div>
</td>
#Html.HiddenFor(x => x.ID)
</tr>
Remark: You might also want to include the Wanted_Stock and Item_Name as hidden fields along with the ID in the editor template in order for their values to be sent to the server, because you don't have a corresponding input field for them.

Related

How to pass search criteria to controller using MVC AJAX PagedList

I'm creating Advanced Search using MVC. What I mean by Advanced Search is user can do combination search, for example, user can search by category = 'Child' and name contains 'A' or age <= 10. then the result is displayed using MVC PagedList.
I followed UnobtrusiveAjax tutorial based on example from PagedList source. I defined 2 files, Index.cshtml for View and _List.cshtml for PartialView. Index contains search criteria and a button to add more clause.
and _List contains table for displaying the result using AJAX
The problem is after I filtered by code for example contains 'A' and click search then navigate to page 2 the filter value is missing and it will display code which not contains 'A'. How to solve this problem?
ViewModel:
public class WorkflowStateListViewModel
{
public IEnumerable<string> FilterField { get; set; }
public IEnumerable<string> FilterOperator { get; set; }
public IEnumerable<string> FilterString { get; set; }
public IEnumerable<string> FilterLogical { get; set; }
public PagedList<WorkflowStateListDetailViewModel> WorkflowStateListDetailVM { get; set; }
public int? Page { get; set; }
}
Controller:
public ActionResult Filter(WorkflowStateListViewModel workflowStateListVM, int? page)
{
var workflowStatesQuery = from ws in db.WorkflowStates
where ws.RowStatus == true
select ws;
if (workflowStateListVM.FilterField != null)
{
for (int i = 0; i < workflowStateListVM.FilterField.Count(); i++)
{
workflowStatesQuery = DoFilter(workflowStatesQuery, workflowStateListVM.FilterField.ElementAt(i), workflowStateListVM.FilterOperator.ElementAt(i), workflowStateListVM.FilterString.ElementAt(i));
}
}
workflowStatesQuery = workflowStatesQuery.OrderBy(ws => ws.Code);
var workflowStateListDetailVM = from ws in workflowStatesQuery
select new WorkflowStateListDetailViewModel()
{
WorkflowStateID = ws.WorkflowStateID,
Code = ws.Code,
Name = ws.Name,
Level = ws.Level,
PIC = ws.PIC
};
int pageNumber = (page ?? 1);
return PartialView("_List", workflowStateListDetailVM.ToPagedList(pageNumber, 5));
}
Index.cshtml
#model ViewModels.WorkflowStateListViewModel
#using (Ajax.BeginForm("Filter", "WorkflowState",
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "GET",
UpdateTargetId = "workflowStateList"
},
new { #class = "form-inline" }))
{
<div id="clauses">
</div>
<p>
<div class="form-group">
#Html.DropDownList("FilterField", new SelectList(Model.FilterField), new { #class = "form-control" })
</div>
<div class="form-group">
#Html.DropDownList("FilterOperator", new SelectList(Model.FilterOperator), new { #class = "form-control" })
</div>
<div class="form-group">
#Html.TextBoxFor(model => model.FilterString, new { #class = "form-control" })
</div>
<input type="submit" value="Search" class="btn btn-default" />
</p>
<p>
<div class="form-group">
<span class="glyphicon glyphicon-plus-sign"></span> Add new clause
</div>
</p>
<div id="workflowStateList">
#{ Html.RenderPartial("_List", Model); }
</div>
}
_List.cshtml
#model JOeBilling.ViewModels.WorkflowStateListViewModel
#using PagedList.Mvc;
<div class="table-responsive">
<table class="table table-striped table-hover table-condensed">
<tr>
<th></th>
<th>
Code
</th>
<th>
Name
</th>
<th>
Level
</th>
<th>
PIC
</th>
</tr>
#foreach (var item in Model.WorkflowStateListDetailVM)
{
<tr>
<td>
<span class="glyphicon glyphicon-pencil"></span>
<span class="glyphicon glyphicon-eye-open"></span>
<span class="glyphicon glyphicon-remove"></span>
</td>
<td>
#Html.DisplayFor(modelItem => item.Code)
</td>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Level)
</td>
<td>
#Html.DisplayFor(modelItem => item.PIC)
</td>
</tr>
}
</table>
</div>
<br />
Page #(Model.WorkflowStateListDetailVM.PageCount < Model.WorkflowStateListDetailVM.PageNumber ? 0 : Model.WorkflowStateListDetailVM.PageNumber) of #Model.WorkflowStateListDetailVM.PageCount
#Html.PagedListPager(Model.WorkflowStateListDetailVM,
page => Url.Action("Filter", new { page }),
PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing(new AjaxOptions() { HttpMethod = "GET", UpdateTargetId = "workflowStateList" }))
UPDATE
UPDATE 2
UPDATE 3 Response tab
<link href="/Content/PagedList.css" rel="stylesheet" type="text/css" />
<div class="table-responsive">
<table class="table table-striped table-hover table-condensed">
<tr>
<th></th>
<th>
Code
</th>
<th>
Name
</th>
<th>
Level
</th>
<th>
PIC
</th>
<th>
Created By
</th>
<th>
Created Date
</th>
<th>
Modified By
</th>
<th>
Modified Date
</th>
</tr>
<tr>
<td>
<span class="glyphicon glyphicon-pencil"></span>
<span class="glyphicon glyphicon-eye-open"></span>
<span class="glyphicon glyphicon-remove"></span>
</td>
<td>
A06
</td>
<td>
Preparing Task Code
</td>
<td>
6
</td>
<td>
</td>
<td>
JKTWLA
</td>
<td>
28/04/2014 4:24:52 PM
</td>
<td>
</td>
<td>
</td>
</tr>
<tr>
<td>
<span class="glyphicon glyphicon-pencil"></span>
<span class="glyphicon glyphicon-eye-open"></span>
<span class="glyphicon glyphicon-remove"></span>
</td>
<td>
A07
</td>
<td>
Closed
</td>
<td>
7
</td>
<td>
</td>
<td>
JKTWLA
</td>
<td>
28/04/2014 4:25:06 PM
</td>
<td>
</td>
<td>
</td>
</tr>
<tr>
<td>
<span class="glyphicon glyphicon-pencil"></span>
<span class="glyphicon glyphicon-eye-open"></span>
<span class="glyphicon glyphicon-remove"></span>
</td>
<td>
C01
</td>
<td>
New Invoice
</td>
<td>
1
</td>
<td>
</td>
<td>
JKTWLA
</td>
<td>
13/06/2014 10:49:00 AM
</td>
<td>
</td>
<td>
</td>
</tr>
</table>
</div>
<br />
Page 2 of 2
<div class="pagination-container"><ul class="pagination"><li class="PagedList-skipToPrevious"><a data-ajax="true" data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#workflowStateList" href="/WorkflowState/Filter?page=1" rel="prev">«</a></li><li><a data-ajax="true" data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#workflowStateList" href="/WorkflowState/Filter?page=1">1</a></li><li class="active"><a>2</a></li></ul></div>

MVC 3 Model property is not being set in partial view called by html.action

I have an issue with my app, ill try explain by example, I have a form, this form exist of several textboxes and dropdown lists. For reusability I merged 3 drop down lists into a partial view, this partial view i load with #Html.Action, this works ok, when i start the form i see everything appears as it should be, although I dont know why but those required drop down lists directly are shown with red start and said it is required fields.
But when i fill everything in, and i select the values from the drop down lists, and i click on OK, the values from drop down lists are NULL.
I think it will be more comprehendable with a code example:
Main model:
public class FormModel
{
[Required]
public string UserName { get; set; }
[Required]
[Display(Name = "Birthdate")]
public DateTime? Birthdate { get; set; }
//This is what i'm talking about, that is not being set, the location
public Location Location { get; set; }
}
Here is the location class, which is then passed to the partial view and normally i think should be set, it looks like this:
public class Location
{
[Required]
[Display(Name = "Country")]
public string CountryId { get; set; }
[Required]
[Display(Name = "Region")]
public string RegionId { get; set; }
[Required]
[Display(Name = "City")]
public string CityId { get; set; }
}
Now we have a partial view for the location:
#model TestWebsite.Models.Location
<tr>
<td class="editor-label">
#Html.LabelFor(m => m.CountryId)
</td>
<td class="editor-field">
#Html.DropDownListFor(m => m.CountryId, Model.Countries, "---select--", null)
</td>
<td>
#Html.ValidationMessageFor(m => m.CountryId)
</td>
</tr>
<!-- Region -->
<tr>
<td class="editor-label">
#Html.LabelFor(m => m.RegionId)
</td>
<td class="editor-field">
#Html.DropDownListFor(m => m.RegionId, Enumerable.Empty<SelectListItem>(), "---select--", null)
</td>
<td>
#Html.ValidationMessageFor(m => m.RegionId)
</td>
</tr>
<!-- City -->
<tr>
<td class="editor-label">
#Html.LabelFor(m => m.CityId)
</td>
<td class="editor-field">
#Html.DropDownListFor(m => m.CityId, Enumerable.Empty<SelectListItem>(), "---select--", null)
</td>
<td>
#Html.ValidationMessageFor(m => m.CityId)
</td>
</tr>
Then we call this partial view in the forms like this:
#Html.Action("LocationGroup", "Account", Model.Location)
And finally in the controller:
[ChildActionOnly]
public ActionResult LocationGroup(Location model)
{
model.Countries = GetCountries();
return PartialView("_LocationView", model);
}
I know it is a lot of code, but i hope you could help me ...
I think you should use Editor Templates:
#model TestWebsite.Models.FormModel
#using(Html.BeginForm())
{
#Html.EditorFor(x => x.Location)
<input type="submit" value="save"/>
}
and add the partial inside ~/Views/[ControllerName]/EditorTemplates/Location.cshtml or ~/Views/Shared/EditorTemplates/Location.cshtml
code of template:
#model TestWebsite.Models.Location
<tr>
<td class="editor-label">
#Html.LabelFor(m => m.CountryId)
</td>
<td class="editor-field">
#Html.DropDownListFor(m => m.CountryId, Model.Countries, "---select--", null)
</td>
<td>
#Html.ValidationMessageFor(m => m.CountryId)
</td>
</tr>
<!-- Region -->
<tr>
<td class="editor-label">
#Html.LabelFor(m => m.RegionId)
</td>
<td class="editor-field">
#Html.DropDownListFor(m => m.RegionId, Enumerable.Empty<SelectListItem>(), "---select--", null)
</td>
<td>
#Html.ValidationMessageFor(m => m.RegionId)
</td>
</tr>
<!-- City -->
<tr>
<td class="editor-label">
#Html.LabelFor(m => m.CityId)
</td>
<td class="editor-field">
#Html.DropDownListFor(m => m.CityId, Enumerable.Empty<SelectListItem>(), "---select--", null)
</td>
<td>
#Html.ValidationMessageFor(m => m.CityId)
</td>
</tr>
But if you prefer to have partial in some location (not follow conventions) you could specify the location:
#Html.EditorFor(x => x.Location, "~/Views/SpecifyLocation/_Location.cshtml")
I agree with Xordal, EditorTemplates are the way to go. Incredibly useful stuff. One thing I noticed the lack of a SelectList. I may be missing something here, but have you tried the following?:
<td class="editor-label">
#Html.LabelFor(m => m.CountryId)
</td>
<td class="editor-field">
#Html.DropDownListFor(m => m.CountryId, new SelectList(Model.Countries, "CountryId" , "CountryName", null))
</td>
<td>
#Html.ValidationMessageFor(m => m.CountryId)
</td>
Try Html.RenderPartial instead of Html.Action

MVC3 IPagedList in ViewModel

I want to have a page that has a form on the top of the page to enter movies in a system, and on the bottom of the page I want to have a table to display all of the movies in inventory. I am getting an error saying: Value cannot be less than 1. Parameter name: Page Size.
I have a viewmodel that currently looks like this:
public class InventoryViewModel
{
public Inventory Inventory { get; set; }
public IPagedList<Inventory> InventoryList { get; set; }
}
In my controller I have:
public ActionResult Index(int? page)
{
ViewBag.MoviesList = new SelectList(inventoryRepository.Movies, "MovieId", "Title");
InventoryViewModel vm = new InventoryViewModel
{
Inventory = new Inventory(),
InventoryList = inventoryRepository.GetInventory.ToPagedList(page.HasValue ? page.Value - 1 : 0, defaultPageSize)
};
return View(vm);
}
In my view I have:
<div class="well">
<h4>Enter Movie in System:</h4>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true, "Movie was not entered in system. Please correct the errors and try again.")
<div>
<div class="input-prepend">
<span class="add-on"><i class="icon-film"></i></span>
#Html.DropDownListFor(m => m.Inventory.MoviesId, (SelectList)ViewBag.MoviesList)
#Html.ValidationMessageFor(m => m.Inventory)
</div>
<div class="input-prepend">
<span class="add-on"><i class="icon-calendar"></i></span>
#Html.TextBox("Quantity")
</div>
<p><button class="btn btn-primary" type="submit" value="Submit">Submit</button></p>
#Html.ValidationSummary()
</div>
}
</div>
<div>
<h3>Current Inventory:</h3>
</div>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th style="width: 15%;">Checkout Number</th>
<th style="width: 15%;">Title</th>
<th style="width: 23%;">Availability</th>
<th style="width: 17%;"></th>
</tr>
</thead>
<tbody>
#foreach (var vm in Model.InventoryList.OrderBy(m => m.CheckoutNum))
{
<tr>
<td>#vm.CheckoutNum</td>
<td>#vm.Movies.Title</td>
<td>#vm.isAvail</td>
<td>
</td>
</tr>
}
</tbody>
<tfoot>
<tr>
<td colspan="4">
#Html.Pager(Model.InventoryList.PageSize, Model.InventoryList.PageNumber, Model.InventoryList.TotalItemCount).Options(o => o
.DisplayTemplate("BootstrapPagination").RouteValues(new { q = ViewBag.Query, area = "" } )
.AlwaysAddFirstPageNumber().MaxNrOfPages(5))
</td>
</tr>
</tfoot>
</table>
I figured it out. I was setting the defaultPageSize in the wrong constructor, so the variable was never getting set, causing the page size to be 0.
int defaultPageSize;
private IInventoryRepository inventoryRepository;
public InventoryController()
{
this.inventoryRepository = new InventoryRepository(new MovieContext());
this.defaultPageSize = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["defaultPageSize"]);
}
public InventoryController(IInventoryRepository inventoryRepository)
{
this.inventoryRepository = inventoryRepository;
this.defaultPageSize = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["defaultPageSize"]);
}

How to do a bulk persist on a partial view?

I have written a partial view that displays a list of rows, where some fields in the row are editable using a textbox, checkbox, or whatever.
I would like the parent view to have a "submit" button that posts the whole collection of updated rows so that I can do a group update rather than posting once for every updated row.
Here's what I have:
public class GroupModel {
public string SomeGenericProperty { get; set; }
public IEnumerable<ItemModel> Items { get; set; }
}
public class ItemModel {
public long ID { get; set; }
public string Field1 { get; set; }
public string Field2 { get; set; }
}
Then I have a view "GroupDetails":
#model MyNamespace.GroupModel
...
#using (Html.BeginForm("SaveItems", "Home")) {
<fieldset>
...
#Html.Partial("ItemList", Model.Items)
...
<input type="submit" value="Approve" />
</fieldset>
}
And a partial view "ItemList":
#model IEnumerable<MyNamespace.ItemModel>
<table>
<tr>
<th>
Field 1
</th>
<th>
Field 2
</th>
<th>
</th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.TextBoxFor(modelItem => item.Field1)
</td>
<td>
#Html.TextBoxFor(modelItem => item.Field2)
</td>
<td>
#Html.HiddenFor(i => item.ID)
</td>
</tr>
}
</table>
But when I post, the information from partial view is not being posted; the GroupModel object's Items property is null.
What's the right way to do this?
I would recommend you to always use editor templates. The reason your code doesn't work is because you used a partial and this partial doesn't inherit the parent template context which means that helpers that are used inside it will generate wrong names for the input fields.
For example if you look at the source code of your page you will see this:
<td>
<input type="text" name="[0].Field1" id="[0]_Field1" />
</td>
instead of the correct name which is:
<td>
<input type="text" name="Items[0].Field1" id="Items[0]_Field1" />
</td>
I would recommend you reading the following blog post to better understand the wire format that the default model binder expects.
So start by fixing your main view first and replace the partial with an editor template:
#model MyNamespace.GroupModel
#using (Html.BeginForm("SaveItems", "Home"))
{
<fieldset>
...
<table>
<thead>
<tr>
<th> Field 1 </th>
<th> Field 2 </th>
<th></th>
</tr>
</thead>
<tbody>
#Html.EditorFor(x => x.Items)
</tbody>
</table>
...
<input type="submit" value="Approve" />
</fieldset>
}
and then define a custom editor template that will be rendered for each element of the model (~/Views/Shared/EditorTemplates/ItemModel.cshtml - this works by convention, it should be either inside ~/Views/Shared/EditorTemplates if you want it to be reused among controllers or inside ~/Views/Home/EditorTemplates if you want this template to be available only for the Home controller - and the name of the template must be the type of the collection - in your case it is ItemModel.cshtml):
#model MyNamespace.ItemModel
<tr>
<td>
#Html.TextBoxFor(x => x.Field1)
</td>
<td>
#Html.TextBoxFor(x => x.Field2)
</td>
<td>
#Html.HiddenFor(x => x.ID)
</td>
</tr>
Now everything will bind fine and dandy in your controller action:
[HttpPost]
public ActionResult SaveItems(GroupModel model)
{
...
}

Post mvc3 table to controller action

I have a simple table
#using (Html.BeginForm("Save", "Subscription", FormMethod.Post))
{
<table>
<tr>
<th>
Name
</th>
<th>
Report
</th>
</tr>
#foreach (var item in Model.ReportSubscriptions)
{
<tr>
<td>
#Html.HiddenFor(item.Id)
#Html.TextBoxFor(item.Name)
</td>
<td>
#Html.TextBoxFor(item.Report)
</td>
</tr>
}
</table>
<button type="submit">Save</button>
}
The save action
public ViewResult Save(IEnumerable<ReportSubscription> list)
{
throw new NotImplementedException();
}
}
How can I get MVC to deserialize the post back to my model?
For collections you need to do a Editor template and use
#Html.EditorFor(m => m.Collection);

Resources