I have client side validation enabled and indeed it is working in that I see error message for required fields when I submit the form. But a postback is also occuring despite the fact that client side validation has fired. It is my understanding that client side validation is supposed to suppress the postback. Can someone confirm for me that is the expected behaviour and see if there is anything amiss with this view. Many thanks.
This is the view in question
#model Intranet.ViewModels.Student.CreateStudentViewModel
#{
ViewBag.Title = "Create Student";
}
<h2>
Create Student</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<table class="detailViewTable">
<tr>
<td>#Html.LabelFor(c=>c.Contact.Title)</td>
<td>#Html.EditorFor(c=>c.Contact.Title)</td>
<td>#Html.ValidationMessageFor(c=>c.Contact.Title)</td>
</tr>
<tr>
<td>#Html.LabelFor(c=>c.Contact.FirstName)</td>
<td>#Html.EditorFor(c=>c.Contact.FirstName)</td>
<td>#Html.ValidationMessageFor(c=>c.Contact.FirstName)</td>
</tr>
<tr>
<td>#Html.LabelFor(c=>c.Contact.LastName)</td>
<td>#Html.EditorFor(c=>c.Contact.LastName)</td>
<td>#Html.ValidationMessageFor(c=>c.Contact.LastName)</td>
</tr>
<tr>
<td>#Html.LabelFor(c=>c.Contact.Phone)</td>
<td>#Html.EditorFor(c=>c.Contact.Phone)</td>
<td>#Html.ValidationMessageFor(c=>c.Contact.Phone)</td>
</tr>
<tr>
<td>#Html.LabelFor(c=>c.Contact.AltPhone)</td>
<td>#Html.EditorFor(c=>c.Contact.AltPhone)</td>
<td>#Html.ValidationMessageFor(c=>c.Contact.AltPhone)</td>
</tr>
<tr>
<td>#Html.LabelFor(c=>c.Contact.Email)</td>
<td>#Html.EditorFor(c=>c.Contact.Email)</td>
<td>#Html.ValidationMessageFor(c=>c.Contact.Email)</td>
</tr>
<tr>
<td>Guardian 1</td>
<td>#Html.DropDownListFor(c=>c.Guardian1ID, new SelectList(Model.Contacts, "ID", "FullName"))</td>
</tr>
<tr>
<td>Guardian 2</td>
<td>#Html.DropDownListFor(c=>c.Guardian2ID, new SelectList(Model.Contacts, "ID", "FullName"))</td>
</tr>
</table>
<p>
<input type="submit" value="Create" />
</p>
}
<div>
#Html.ActionLink("Back to List", "List")
</div>
And here is the CreateStudentViewModel
public class CreateStudentViewModel
{
public int ID { get; set; }
public ContactViewModel Contact { get; set; }
public int Guardian1ID { get; set; }
public int Guardian2ID { get; set; }
public List<ContactViewModel> Contacts { get; set; }
}
And the ContactViewModel that contains the validation properties.
public class ContactViewModel
{
public int ID { get; set; }
[Required(ErrorMessage = "Title is required")]
public string Title { get; set; }
[Required(ErrorMessage = "First Name is required")]
[DisplayName("First Name")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last Name is required")]
[DisplayName("Last Name")]
public string LastName { get; set; }
public string Phone { get; set; }
[DisplayName("Alternate Phone")]
public string AltPhone { get; set; }
public string Email { get; set; }
public string FullName
{
get { return string.Format("{0} {1} {2}", Title, FirstName, LastName); }
}
Related
I'm trying to make a simple shopping cart app in Asp.Net Core 2.0 MVC. I'm not doing any Ajax-ing. I have three models:
public class Product
{
public int Id { get; set; }
public string Title { get; set; }
public string Info { get; set; }
public decimal Price { get; set; }
}
public class Cart
{
public int Id { get; set; }
public int CartItemId { get; set; }
public int CustomerId { get; set; } // not in use yet
}
public class CartItem
{
public int Id { get; set; }
public int ProductId { get; set; }
public int NumEach { get; set; } // not in use yet
}
From either one of the two views below, I want to update Cart and CartItem, and then get redirected back to the view where I clicked the Add to cart-button:
1) Index-view:
#model IEnumerable<simpleShop.Models.Product>
<table class="table">
<thead>
<tr>
<th>#Html.DisplayNameFor(model => model.Title)</th>
<th>#Html.DisplayNameFor(model => model.Info)</th>
<th>#Html.DisplayNameFor(model => model.Price)</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
<a asp-action="Details" asp-route-id="#item.Id">
#Html.DisplayFor(modelItem => item.Title)
</a>
</td>
<td>#Html.DisplayFor(modelItem => item.Info)</td>
<td>#Html.DisplayFor(modelItem => item.Price)</td>
<td>
<form asp-action="AddToCart">
<button type="submit" value="#item.Id">Add to cart</button>
</form>
</td>
</tr>
}
</tbody>
</table>
2) Details-view:
#model simpleShop.Models.Product
#{
ViewData["Title"] = Html.DisplayFor(model => model.Title);
}
<h2>#Html.DisplayFor(model => model.Title)</h2>
<h4>#Html.DisplayFor(model => model.Info)</h4>
<h1>#Html.DisplayFor(model => model.Price)</h1>
<form asp-action="AddToCart">
<input type="hidden" asp-for="Id" />
<p>
<input type="submit" value="Add to cart" />
</p>
</form>
<div>
<a asp-action="Index">Return to list</a>
</div>
Below is my faulty AddToCart-method in the home controller, which at the moment certainly isn't doing anything to save data to the Cart or CartItem tables. How can I get it to?
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddToCart([Bind("Id")] Product product)
{
if (ModelState.IsValid)
{
_context.Add(product);
await _context.SaveChangesAsync();
if (product.Id > 0) // added to cart via the details-view
{
return RedirectToAction("Details", "Home", product.Id);
}
else // added to cart via the index-view
{
return RedirectToAction(nameof(Index));
}
}
return View(product);
}
for one thing Bind() requires way more than just "id", remember what ever you wanted to bind has to include all properties in question of the object you wanted to pass to new action.
reference comments in line -->>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddToCart([Bind("Id")] Product product)
{
if (ModelState.IsValid)
{
_context.Add(product); //**BAD this will probably try to
// add product to the product table again.**
// With error to follow about already exists
// exception at the SaveChangesAsync call.
await _context.SaveChangesAsync();
if (product.Id > 0)
{
return RedirectToAction("Details", "Home", product.Id);
}
else // added to cart via the index-view
{
return RedirectToAction(nameof(Index));
}
}
return View(product);
}
My suggestion goes along these lines where you will have ProductId and Qty of the Product (your NumEach)
[HttpPost]
[ValidateAntiForgeryToken]
public asyync Task<IActionResult> AddToCart([Bind("ProductId", "NumEach", "CartId")] CartItem model, string ReturnUrl){
if(ModelState.IsValid){
_context.CartItem.Add(model);
await _context.SaveChangesAsync();
//model.Id greater than 0 indicates a save occurred.
if(model.Id > 0)
return RedirectToAction(ReturnUrl); // forced return for further shopping?
else
return RedirectToAction("Index");
}
//this assumes bad model state with no error unless model is
//annotated accordingly.
return View(model);
}
problem with Cart though is right now you can only store 1 item...
public class Cart{
public Cart(){}
public int Id {get;set;} //cart's integer id
public List<CartItem> CartItems {get;set;} //collection of cartitems
//FK
public int CustomerId {get;set;} //customer's id
//NAV
public Customer CustomerId {get;set;} //customer nav
...
}
public class CartItem{
public CartItem(){}
public int Id {get;set;} //cart item id
public int ProductId {get;set;} //productid
public Product Product {get;set;} //nav property
public int NumEach {get;set;} //quantity of each product
//FK
public int CartId {get;set;} //Foreign Key
//NAV
public Cart Cart {get;set;} //nav property
}
One other thing your index view wouldn't just have the model be a collection of Products it would probably be a viewmdodel that had a collection of products in it but it would also contain the cartid that would have been generated with the visitor. That CartId would follow that visitor until the transaction was completed and you then have an OrderId or it dies once the session is closed.
#model simpleShop.Models.IndexViewModel
<table class="table">
<thead>
<tr>
<th>#Html.DisplayNameFor(model => model.Title)</th>
<th>#Html.DisplayNameFor(model => model.Info)</th>
<th>#Html.DisplayNameFor(model => model.Price)</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Products)
{
<tr>
<td>
<a asp-action="Details" asp-route-id="#item.Id">
#Html.DisplayFor(modelItem => item.Title)
</a>
</td>
<td>#Html.DisplayFor(modelItem => item.Info)</td>
<td>#Html.DisplayFor(modelItem => item.Price)</td>
<td>
<form asp-action="AddToCart">
<input type="Hidden" asp-for="#Model.CartId" />
<button type="submit" value="#item.Id">Add to cart</button>
</form>
</td>
</tr>
}
</tbody>
</table>
public class IndexViewModel
{
public IndexViewModel(){}
public int CartId{get;set;}
public List<Product> Products{get;set;}
}
I think you get the idea from there.
I am having issues with a view model that constantly return null properties after a post. Below is my code (it could be a syntax issue or two calling a class or property the same name as i saw in other posts but i could not see any such issue in code):
VIEW MODEL:
public class ProductItem
{
public int ProductID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string LongDescription { get; set; }
public int SupplierID { get; set; }
public string Dimensions { get; set; }
public double Price { get; set; }
public bool On_Sale { get; set; }
public double DiscountedPrice { get; set; }
public string Thumbnail { get; set; }
public string LargeImage { get; set; }
public string LargeImage2 { get; set; }
public string LargeImage3 { get; set; }
public string CrossRef { get; set; }
public byte Available { get; set; }
public double Weight { get; set; }
public byte Important { get; set; }
public virtual ICollection<ProductCategory> ProductCategories { get; set; }
// this is required on the page to allow products to be marked for deletion
public bool IsForDelete { get; set; }
}
public class ProductListViewModel
{
public IEnumerable<ProductItem> ProductItems { get; set; }
public IEnumerable<Category> CategoryItems { get; set; }
}
CONTROLLER:
public ActionResult ProductList()
{
var productList = new ProductListViewModel();
productList.ProductItems = productRepository.GetProductsWithDeleteOption().ToList();
productList.CategoryItems = categoryRepository.GetCategories().ToList();
return View(productList);
}
[HttpPost]
public ActionResult ProductList(ProductListViewModel productViewModel, FormCollection formCollection, string submit)
{
if (ModelState.IsValid)
{
// Check for submit action
if (submit == "Change Sort")
{
if (formCollection["Sortby"] == "ProductID")
{
OrderBy(productViewModel, formCollection, "m.ProductID");
}
else if (formCollection["Sortby"] == "Code")
{
OrderBy(productViewModel, formCollection, "m.Code");
}
else if (formCollection["Sortby"] == "Name")
{
OrderBy(productViewModel, formCollection, "m.Name");
}
else if (formCollection["Sortby"] == "Price")
{
OrderBy(productViewModel, formCollection, "m.Price");
}
}
else if (submit == "Delete all selected")
{
}
else if (submit == "Update All")
{
}
else if (submit == "Restrict Display")
{
}
}
return View(productViewModel);
}
VIEW:
#model Admin.Models.ViewModels.ProductListViewModel
#{
ViewBag.Title = "View Products";
}
#using (Html.BeginForm())
{
<h2>Product List as at #DateTime.Now.ToString("dd/MM/yyyy")</h2>
<table>
<tr>
<td>Sort by:</td>
<td>
<select name="Sortby">
<option value="ProductID">ProductID</option>
<option value="Code">Code</option>
<option value="Name">Name</option>
<option value="Price">Price</option>
</select>
</td>
<td>
<input type="radio" name="sortDirection" checked="checked" value="Asc" /> Ascending
<input type="radio" name="sortDirection" value="Desc" /> Descending
</td>
<td>
<input type="submit" name="submit" value="Change Sort" />
</td>
</tr>
<tr>
<td>Display only : (category)</td>
<td>#Html.DropDownList("CategoryID", new SelectList(Model.CategoryItems, "CategoryID", "Name"), "All Categories")</td>
<td colspan="2"><input type="submit" name="submit" value="Restrict Display" /></td>
</tr>
<tr>
<td colspan="4"><br />Total Number of products: #Model.ProductItems.Count()</td>
</tr>
</table>
<table>
<tr>
<th>
Edit
</th>
<th>
Code
</th>
<th>
Name
</th>
<th>
Price
</th>
<th>
On_Sale
</th>
<th>
DiscountedPrice
</th>
<th>
Weight
</th>
<th>
Delete
</th>
<th></th>
</tr>
#for (var i = 0; i < Model.ProductItems.ToList().Count; i++)
{
<tr>
<td>
#Html.HiddenFor(m => m.ProductItems.ToList()[i].ProductID)
#Html.ActionLink(Model.ProductItems.ToList()[i].ProductID.ToString(), "ProductEdit", new { id = Model.ProductItems.ToList()[i].ProductID })
</td>
<td>
#Html.DisplayFor(m => m.ProductItems.ToList()[i].Code)
</td>
<td>
#Html.DisplayFor(m => m.ProductItems.ToList()[i].Name)
</td>
<td>
#Html.EditorFor(m => m.ProductItems.ToList()[i].Price)
</td>
<td>
#Html.CheckBoxFor(m => m.ProductItems.ToList()[i].On_Sale, new { id = "On_Sale_" + Model.ProductItems.ToList()[i].ProductID })
</td>
<td>
#Html.EditorFor(m => m.ProductItems.ToList()[i].DiscountedPrice)
</td>
<td>
#Html.EditorFor(m => m.ProductItems.ToList()[i].Weight)
</td>
<td>
#Html.CheckBoxFor(m => m.ProductItems.ToList()[i].IsForDelete, new { id = Model.ProductItems.ToList()[i].ProductID })
</td>
<td>
#Html.ActionLink("Edit", "ProductEdit", new { id = Model.ProductItems.ToList()[i].ProductID }) |
#Html.ActionLink("Details", "Details", new { id = Model.ProductItems.ToList()[i].ProductID }) |
#Html.ActionLink("Delete", "Delete", new { id = Model.ProductItems.ToList()[i].ProductID })
</td>
</tr>
}
</table>
<p>
<input name="submit" type="submit" value="Delete all selected" />
</p>
<p>
<input name="submit" type="submit" value="Update All" />
</p>
<p>
#Html.ActionLink("Add a new product", "ProductAdd")
</p>
}
In the post action, the productViewModel argument has the ProductItems and CategoryItems properties as null.
Ok, so there are two problems.
I don't understand why you want to post list of the CategoryItems You should only expect the selected category and not the list
The problem with ProductItems is the name generated for <input> tags. Currently, the name being generated is name="[0].Price" whereas it should have been name="ProductItems[0].Price"
I changed the following code
#Html.EditorFor(m => m.ProductItems.ToList()[i].Price)
to
#Html.EditorFor(m => m.ProductItems[i].Price)
and it worked.
Note: I changed IEnumerable<ProductItem> ProductItems to List<ProductItem> ProductItems in ProductListViewModel
Yes it will be null on post back. I have a similar table in one of my projects and this is what I would have done given my situation.
You could change your view model to look like this then you don't have to do so many converting to lists in your view:
public class ProductListViewModel
{
public List<ProductItem> ProductItems { get; set; }
public List<Category> CategoryItems { get; set; }
}
Now in you view it could look something like this (this is just part of it, then rest you can just go and add):
#for (int i = 0; i < Model.ProductItems.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(m => m.ProductItems[i].Name)
#Html.HiddenFor(m => m.ProductItems[i].Name)
</td>
</tr>
<tr>
<td>
#Html.CheckBoxFor(m => m.ProductItems[i].IsForDelete)
</td>
</tr>
}
Add the code and do some debugging to see how the values are returned on submit
I hope this helps.
I am working on a web app using MVC, where all pages have header section consiting of three dropdowns one text box and an image button. So no matter on which page user is, he/she has ability to select a combination out of three dropdowns, enter some text in text box and hit image button to search in database.
So i am using a strongly typed(BaseViewModel) master _Layoutthought, and then add two partial views on it, one to display user info and another one to display all dropdowns, text box and imaage button. Now, Models i have 3 models for each dropdown option/item and then a BaseViewModel which will have 3 properties as a list of each model and 3 int properties to hold selected item ID. And plan is to use this baseViewModel as parent class for all viewmodels so that i can fullfil my strongly tyed _Layout for each request and responce. OK i am not sure that if i have explained my requirement clearly :(.
so here is the code
all models:
namespace XYZNameSpace.Models
{
public class ManageCompany
{
public long ManageCompanyID { get; set; }
public string ManageCompanyName { get; set; }
}
}
namespace XYZNameSpace.Models
{
public class SuperCompany
{
public long SuperCompanyID { get; set; }
public string SuperCompanyName { get; set; }
}
}
public class User
{
public long UserID { get; set; }
public string UserName { get; set; }
public string UserLoginName { get; set; }
public string UserLevel { get; set; }
public int UserLevelId { get; set; }
}
namespace XYZNameSpace.Models
{
public class SearchResult
{
public string CompanyName { get; set; }
public string SubCompanyName { get; set; }
public string UserName { get; set; }
}
}
ViewModel
namespace XYZNameSpace.ViewModels
{
public class BaseViewModel : User
{
public long SelectedManageCompanyID { get; set; }
public List<SelectListItem> ManageCompanyList { get; set; }
public string SelectedManageCompanyName { get; set; }
public long SelectedSuperCompanyID { get; set; }
public List<SelectListItem> SuperCompanyList { get; set; }
public string SelectedSuperCompnayName { get; set; }
public int SelectedSearchOptionID { get; set; }
public List<SelectListItem> SearchOptionsList { get; set; }
public string SelectedSearchOptionName { get; set; }
public string SearchWord { get; set; }
}
public class SearchOption
{
public int SearchOptionID { get; set; }
public string SearchOptionName { get; set; }
}
}
namespace XYZNameSpace.ViewModels
{
public class SearchResultsViewModel : BaseViewModel
{
public List<SearchResult> ResultsList { get; set; }
}
}
Controller
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Home()
{
BaseViewModel bVM = new BaseViewModel()
{
ManageCompanyList = (new List<ManageCompany>() {
new ManageCompany()
{
ManageCompanyID = 1,
ManageCompanyName = "MC 1"
},
new ManageCompany()
{
ManageCompanyID = 2,
ManageCompanyName = "MC 2"
},
new ManageCompany()
{
ManageCompanyID = 3,
ManageCompanyName = "MC 3"
},
new ManageCompany()
{
ManageCompanyID = 4,
ManageCompanyName = "MC 4"
},
new ManageCompany()
{
ManageCompanyID = 5,
ManageCompanyName = "MC 5"
}
}).Select(x => new SelectListItem { Value = x.ManageCompanyID.ToString(), Text = x.ManageCompanyName }).ToList(),
SuperCompanyList = (new List<SuperCompany>(){
new SuperCompany(){
SuperCompanyID=6,
SuperCompanyName="SC 6"
},
new SuperCompany(){
SuperCompanyID=7,
SuperCompanyName="SC 7"
},
new SuperCompany(){
SuperCompanyID=8,
SuperCompanyName="SC 8"
},
new SuperCompany(){
SuperCompanyID=9,
SuperCompanyName="SC 9"
},
new SuperCompany(){
SuperCompanyID=10,
SuperCompanyName="SC 10"
}
}).Select(y => new SelectListItem { Value = y.SuperCompanyID.ToString(), Text = y.SuperCompanyName }).ToList(),
SearchOptionsList = (new List<SearchOption>(){
new SearchOption(){
SearchOptionID=11,
SearchOptionName="SO 11"
},
new SearchOption(){
SearchOptionID=12,
SearchOptionName="SO 12"
},
new SearchOption(){
SearchOptionID=13,
SearchOptionName="SO 13"
},
new SearchOption(){
SearchOptionID=14,
SearchOptionName="SO 14"
},
new SearchOption(){
SearchOptionID=15,
SearchOptionName="SO 15"
}
}).Select(y => new SelectListItem { Value = y.SearchOptionID.ToString(), Text = y.SearchOptionName }).ToList()
};
return View(bVM);
}
[HttpPost]
public ActionResult Search(BaseViewModel bVM)
{
SearchResultsViewModel sRVM = new SearchResultsViewModel();
sRVM.ResultsList = new List<SearchResult>(){
new SearchResult()
{
CompanyName = bVM.SelectedSuperCompanyID.ToString(),
SubCompanyName = bVM.SelectedManageCompanyID.ToString(),
UserName = "Duh"
}
};
return View("SearchResults", sRVM);
}
}
Views /Home/Home.cshtml
#{
ViewBag.Title = "Home";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div style="float: left; vertical-align: top;">
</div>
<br />
/Home/SearchResults.cshtml
#model XYZNameSpace.ViewModels.SearchResultsViewModel
<table width="100%">
<thead>
<tr>
<td>
COMPANY
</td>
<td>
SUB COMPANY
</td>
<td>
USER NAME
</td>
</tr>
</thead>
<tbody>
#foreach (XYZNameSpace.Models.SearchResult sr in Model.ResultsList)
{
<tr>
<td>
#sr.CompanyName
</td>
<td>
#sr.SubCompanyName
</td>
<td>
#sr.UserName
</td>
</tr>
}
</tbody>
</table>
/Shared/SearchDDLPartialView.cshtml
#model XYZNameSpace.ViewModels.BaseViewModel
<div>
<div style="float: left">
<span class="Label">Search: </span>
#using (Html.BeginForm("Search", "Home", FormMethod.Post))
{
#Html.DropDownListFor(x => x.SelectedSuperCompanyID, Model.SuperCompanyList)
#Html.DropDownListFor(x => x.SelectedManageCompanyID, Model.ManageCompanyList)
#Html.DropDownListFor(x => x.SelectedSearchOptionID, Model.SearchOptionsList)
<input type="image" src="../../Content/images/search.gif" />
}
</div>
</div>
/Shared/_Layout.cshtml
#model XYZNameSpace.ViewModels.BaseViewModel
<!DOCTYPE html>
<html>
<head>
<title>#ViewBag.Title</title>
<link href="#Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<link href="#Url.Content("~/Content/BEClientProfile.css")" rel="Stylesheet" type="text/css" />
<script src="#Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
</head>
<body>
<table>
<tr>
<td>
<img src="../../Content/images/logo124.gif" alt="Logo" />
</td>
<td valign="top">
<div style="float: left; vertical-align: top; text-align: left">
<img src="../../Content/images/user-info.gif" alt="User Infor" />
<span class="Label">Welcome </span><span class="HeaderLabelText">#Model.UserLevel</span>
</div>
<div>
<div style="float: left; width: 100%;">
<u><span class="Label">Currently Editing:</span> </u>
</div>
<div style="float: left; width: 100%;">
<span class="Label">Company:</span> <span class="HeaderLabelText">#Model.SelectedManageCompanyID</span>
</div>
<div style="float: left; width: 100%;">
<span class="Label">User:</span> <span class="HeaderLabelText">#Model.UserLoginName</span>
</div>
</div>
</td>
<td>
</td>
<td>
#{Html.RenderPartial("SearchDDLPartialViews", Model);}
</td>
</tr>
<tr>
<td colspan="4">
#RenderBody()
</td>
</tr>
</table>
</body>
</html>
/Shared/UserPartialView.cshtml
#model XYZNameSpace.Models.User
<div style="float: left; margin-left: .5em;">
<img src="../../Content/images/user-info.gif" alt="User Infor" />
<span style="font-weight: bold; float: left;">Welcome </span>
</div>
OK, i was able to select one item from each dropdown but when i click on image button at search Action in home control, the BaseViewModel has nothing other than selectedIDs, so when action is trying to display SearchResults view back, them _Layout.cshtml throughs error saying there nothing in lists that are used by dropdowns.
so how do we persist this information for every request...?
Quick solution is store the List < SelectListItem > or the viewmodel itself in session and reintroduce them to the view every time you return the viewmodel. However for cloud based solutions, same concept, but you'll need to use other caching storage such as appfabric, memcache or other addons allowed by your PaaS.
I have a problem which I don't understand and there doesn't seem to be an easy way to debug the problem. I'm sure it's simple.
#model StartStop.ServiceResources.UserSettings
my MVC3 view is bound a specific model;
public class Setting
{
public Int64 SettingID { get; set; }
public Int64 UserID { get; set; }
public int PreferenceType { get; set; }
public string PreferenceName { get; set; }
public bool PreferenceBool { get; set; }
public int PreferenceInt { get; set; }
public string PreferenceString { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime ModifiedOn { get; set; }
}
public class UserSettings
{
public Int64 UserID { get; set; }
public List<Setting> Settings { get; set; }
}
the view lists out the check boxes which represent the list;
#using (Html.BeginForm("ManageAccount","Account", FormMethod.Post))
{
<table class="tbl" cellspacing="0">
<tr>
<th>Preference</th>
<th>Setting</th>
</tr>
#if (Model != null)
{
foreach (var item in Model.Settings.ToList())
{
<tr>
<td>#item.PreferenceName
</td>
<td>
#if (item.PreferenceType == 2)
{
#Html.CheckBoxFor(modelItem => item.PreferenceBool)
}
</td>
</tr>
}
}
</table>
<input type="submit" value="Save Changes" class="action medium" />
}
All good, I load the data into the view it renders the view and picks up the correct settings. However, when I do a post at the bottom, the view model returns a null! I'm not sure why...
[HttpPost]
[Authorize]
public ActionResult ManageAccount(StartStop.ServiceResources.UserSettings model)
{
if (ModelState.IsValid)
{
foreach (StartStop.ServiceResources.Setting oSetting in model.Settings)
{
StartStop.Helpers.UserPreferences.SaveUserSetting(oSetting);
}
}
return View(model);
}
Can anyone help?
The problem is on the following line in your view:
#Html.CheckBoxFor(modelItem => item.PreferenceBool)
I see people writing the following lambda expression modelItem => item.SomeProperty in their views very often and asking why the model binder doesn't correctly bind collection properties on their view models.
This won't generate proper name for the checkbox so that the default model binder is able to recreate the Settings collection. I would recommend you reading the following blog post to better understand the correct format that the model binder expects.
Try like this:
#model StartStop.ServiceResources.UserSettings
#using (Html.BeginForm("ManageAccount", "Account", FormMethod.Post))
{
<table class="tbl" cellspacing="0">
<tr>
<th>Preference</th>
<th>Setting</th>
</tr>
#if (Model != null)
{
for (var i = 0; i < Model.Settings.Count; i++)
{
<tr>
<td>#Model.Settings[i].PreferenceName</td>
<td>
#if (Model.Settings[i].PreferenceType == 2)
{
#Html.CheckBoxFor(x => x.Settings[i].PreferenceBool)
}
</td>
</tr>
}
}
</table>
<input type="submit" value="Save Changes" class="action medium" />
}
This being said, I would recommend you using editor templates, like so:
#using (Html.BeginForm("ManageAccount","Account", FormMethod.Post))
{
<table class="tbl" cellspacing="0">
<tr>
<th>Preference</th>
<th>Setting</th>
</tr>
#if (Model != null)
{
#Html.EditorFor(x => x.Settings)
}
</table>
<input type="submit" value="Save Changes" class="action medium" />
}
and then define a custom editor template which will automatcially be rendered for each element of the Settings collection (~/Views/Shared/EditorTemplates/Setting.cshtml):
#model StartStop.ServiceResources.Setting
<tr>
<td>#Model.PreferenceName</td>
<td>
#if (Model.PreferenceType == 2)
{
#Html.CheckBoxFor(x => x.PreferenceBool)
}
</td>
</tr>
Also the only input field that I can see in this form is the checkbox which is bound to the PreferenceBool property on your model. So inside your POST controller action you will get the Settings list property initialized but don't expect to find any values for the other properties in this Setting class unless of course you include input fields for them in the form (and more precisely in the editor template that I have shown).
I would like to add a textbox (date) and button to my report, which filters the data.
The below mvc is working, but the input must still be validated (must be a DATE) on client side (and server side if possible)
My Model looks like this :
public class DailyReport
{
public int DailyReportID { get; set; }
public DateTime? ReportDate { get; set; }
}
View :
#model IEnumerable<project_name.Models.DailyReport>
#* text box and button: *#
#using (Html.BeginForm("Index", "DailyReport", FormMethod.Get))
{ <p>
Title: #Html.TextBox("SearchDateString")
<input type="submit" value="Filter" />
</p>
}
#* display dates*#
#foreach (var item in Model)
{ #Html.DisplayFor(modelItem => item.ReportDate)
}
my controller:
public ViewResult Index(String SearchDateString)
{
var dailyreport = db.DailyReport.Include(d => d.Site);
if (!String.IsNullOrEmpty(SearchDateString))
{
DateTime search_date = Convert.ToDateTime(SearchDateString);
dailyreport = dailyreport.Where(r => r.ReportDate == search_date);
}
return View(dailyreport.ToList());
}
Can someone help me please?
How do I make sure a valid date is entered in the textbox?
Should I create a another model with a date field for this input?
Utilize the DataTypeAttribute from the DataAnnotations namespace in your Model, like so:
public class DailyReport
{
public int DailyReportID { get; set; }
public DateTime? ReportDate { get; set; }
}
public class DrViewModel
{
[DataType(DataType.Date)]
public string DateTimeSearch { get; set; }
List<DailyReport> DailyReports { get; set; }
}
In your View, have something like:
#model project_name.Models.DrViewModel
#using (Html.BeginForm("Index", "DailyReport", FormMethod.Get))
{
<p>
Title: #Html.TextBoxFor(m => m.DateTimeSearch)
<input type="submit" value="Filter" />
</p>
}
#foreach (var item in Model.DailyReports)
{
#Html.DisplayFor(m => item.ReportDate)
}
#Shark Shark pointed me in the right direction to use a viewmodel, this is the end result that is now working. JS validation added as well.
(DBSet was not necessary because DrViewModel is a viewmodel.)
controllers :
public ActionResult Index(DrViewModel dvm)
{
var dailyreport = db.DailyReport.Include(d => d.Site);
if (dvm.DateTimeSearch != null)
{
dailyreport = dailyreport.Where(r => r.ReportDate == dvm.DateTimeSearch);
}
dvm.DailyReport = dailyreport.ToList();
return View(dvm);
}
models :
public class DrViewModel
{
public DateTime? DateTimeSearch { get; set; }
public List<DailyReport> DailyReport { get; set; }
}
public class DailyReport
{
public int DailyReportID { get; set; }
public DateTime? ReportDate { get; set; }
}
view :
#model myproject.Models.DrViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm("Index","DailyReport", FormMethod.Get ))
{
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.DateTimeSearch)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DateTimeSearch)
#Html.ValidationMessageFor(model => model.DateTimeSearch)
<input type="submit" value="Filter" />
</div>
}
#foreach (var item in Model.DailyReport)
{
#Html.DisplayFor(modelItem => item.ReportDate)
}