I have a View as Follows
#model List<item>
#using (Html.BeginForm("Create", "Item", FormMethod.Post))
{
#for (int i = 0; i < Model.Count; i++)
{
.....
#Html.EditorFor(model => Model[i].ItemName)
.....
}
<input type="submit" class="btn btn-primary" value="Create Item" />
}
In the ViewModel this ItemName has a Required attribute annotation for Validation purposes, but what I really need is at least one ItemName to be filled to assume that this Model is valid, but I will always get The ModelState IsValid = False
I was able to solve this by using:
public class CreateItemCustomValidation : ValidationAttribute
{
public override bool IsValid(object value)
{
var list = value as List<SingleItem>;
if (list != null)
{
if (list.Where(o => o.ItemName!= null && !String.IsNullOrEmpty(o.ItemName) && !String.IsNullOrWhiteSpace(o.ItemName)).Count() > 0)
{
return true;
}
}
return false;
}
}
The validation works, but the validation is firing before POST
Related
Can anyone tell me why, if my model clearly shows my values to be "true" or "false" MVC still thinks it says, "true,false." I assume this is because it is confusing the Request with my Model; however, I am explicitly telling it to use the Model.
As you can see in the image above, the model value is "true." However, in the image below, it thinks the value is "true,false." How can I make it just use "true?"
Model
public class TagCategorySearchViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<TagSearchViewModel> Tags { get; set; }
}
public class TagSearchViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
Controller
[Authorize]
public ActionResult FilterPrereqGrid(EditStudentPrerequisitesViewModel model, int page = 1)
{
model.Prerequisites = new List<PrerequisiteListViewModel>();
var businessPartner = _bpManager.GetBusinessPartnerByMapGuid(model.BusinessPartnerMapGuid);
model.Prerequisites.AddRange(_epManager.GetFullPrerequisitesLeftJoinedWithExperience(model.ExperienceId, businessPartner?.Id));
// fix for how MVC binds checkboxes... it send "true,false" instead of just true, so we need to just get the true
for (int i = 0; i < model.TagCategories?.Count(); i++)
{
for (int j = 0; j < model.TagCategories[i].Tags?.Count(); j++)
{
model.TagCategories[i].Tags[j].IsSelected = bool.Parse((Request.QueryString[$"TagCategories[{i}].Tags[{j}].IsSelected"] ?? "false").Split(',')[0]);
}
}
var selectedTagIds = model.TagCategories?.SelectMany(x => x.Tags).Where(x => x.IsSelected == true).Select(x => x.Id).ToArray();
// filter by selected tags
if (selectedTagIds.Any())
{
model.Prerequisites = (from p in model.Prerequisites
let prereqTagIds = p.Prerequisite.PrerequisiteTags.Select(et => et.TagId)
where selectedTagIds.All(x => prereqTagIds.Contains(x))
select p).ToList();
}
model.Prerequisites = (from m in model.Prerequisites
let ownerDocs = _deManager.GetDocumentsByOwnerAndSourceId(model.BusinessPartnerMapGuid, m.Prerequisite.Id).OrderByDescending(e => e.CreatedDate)
select new PrerequisiteListViewModel
{
Prerequisite = m.Prerequisite,
Selected = m.Selected,
Mandatory = m.Mandatory,
HasExpiration = m.Prerequisite.HasExpiration,
BusinessExpirationPeriod = m.Prerequisite.ExpirationPeriod == 0 ? "None" : m.Prerequisite.ExpirationPeriod.ToString(),
DocOwnerGuid = (ownerDocs.Any() ? model.BusinessPartnerMapGuid : Guid.Empty),
DocRowGuid = (ownerDocs.Any() ? ownerDocs.First().DocRowguid : Guid.Empty),
HasDocuments = ownerDocs.Any()
}).ToList();
int rowsPerPage = 1;
model.TotalRecords = model.Prerequisites.Count();
model.Prerequisites = model.Prerequisites.Skip(page * rowsPerPage - rowsPerPage).Take(rowsPerPage).ToList();
return PartialView("~/Views/BusinessExperience/_EditPrerequisites.cshtml", model);
}
That is by design. The Html.CheckBoxFor extension actually renders something like the following:
<input type="checkbox" .. />
<input type="hidden" />
So both of these have the same name as the property you are rendering out. When checked, the checkbox returns "True", but when unchecked, the checkbox returns nothing. That is the way checkboxes work with form posts. In order to determine that nothing was checked, the MVC framework included a hidden field so that it would return "False" when not checked, but "True,False" when checked (since multiple items with the same name return this way from the form post). And then MVC model binding converts "True,False" to true.
You could just render your own <input type="checkbox" /> with a value of true, and that would just return true, but when unchecked, nothing gets rendered. Be aware of that...
This actually works, it's a shame I can't just do the obvious way and have to write this all out though!
#model List<Prep2PracticeWeb.Models.ViewModels.TagCategorySearchViewModel>
#if (Model != null)
{
<div class="tag-categories">
#for (int i = 0; i < Model.Count(); i++)
{
#Html.HiddenFor(x => Model[i].Id)
#Html.HiddenFor(x => Model[i].Name)
<h4 data-toggle="collapse" data-target="#CollapsableTagCategory_#Model[i].Id" aria-expanded="false" aria-controls="CollapsableTagCategory_#Model[i].Id">
<span class="glyphicon glyphicon-chevron-right"></span>
<span class="glyphicon glyphicon-chevron-down"></span>
#Model[i].Name
</h4>
<div id="CollapsableTagCategory_#Model[i].Id" class="tag-container collapse">
#if (Model[i].Tags != null)
{
for (int j = 0; j < Model[i].Tags.Count(); j++)
{
#Html.HiddenFor(x => Model[i].Tags[j].Id)
#Html.HiddenFor(x => Model[i].Tags[j].Name)
#* the following commented out line won't work because MVC is funny *#
#*<label>#Html.CheckBoxFor(x => Model[i].Tags[j].IsSelected) #Model[i].Tags[j].Name</label>*#
<label>
<input #(Model[i].Tags[j].IsSelected ? #"checked=""checked""" : string.Empty) data-val="true" data-val-required="The IsSelected field is required." id="TagCategories_#(i)__Tags_#(j)__IsSelected" name="TagCategories[#i].Tags[#j].IsSelected" type="checkbox" value="true"> #Model[i].Tags[j].Name
<input name="TagCategories[#i].Tags[#j].IsSelected" type="hidden" value="false">
</label>
}
}
</div>
}
</div>
}
Update: I realized GET was not an appropriate strategy in this scenario given the fact that the querystring can only be so long before the server returns a 500 error. I switched to POST which alleviated the issue.
This is my first post to StackOverflow. I hope this is useful.
I have a Razor view that is intended to allow editing of the displayable properties of a model containing either pre-defined values or null values. The view should not change the contents of the model's properties unless the user changes them intentionally by editing them on in the UI based on the view. The view behaves correctly, except with regard to a property of type byte[] that contains image data: Model.ImageData
#using (Html.BeginForm("Edit", "Admin", FormMethod.Post, new { enctype = "multipart/form-data" } ))
{
#Html.EditorForModel()
<div class="editor-label">Image</div>
<div class="editor-field">
#if (Model.ImageData == null)
{
#: No image has been assigned in the database.
}
else
{
<img width="150" height="150" src="#Url.Action("GetImage", "Product", new { Model.ID} )" />
}
<div>Upload new image: <input type="file" name="Image" /></div>
</div>
<input type="submit" value="Save" />
}
The above view works as intended for all properties in the model except Model.ImageData. In this case, posting causes any previously set Model.ImageData to be set to null. I have confirmed that the Model.ImageData is set to null during post by verifying that prior to posting, Model.ImageData contains a valid byte array (with the expected image).
The controller code for the above view is:
public ViewResult Edit(int id)
{
Product product = repository.Products.FirstOrDefault(p => p.ID == id);
// breakpoint here shows that all model properties including product.ImageData are populated and valid.
return View(product);
}
[HttpPost]
public ActionResult Edit(Product product, HttpPostedFileBase Image)
{
if (ModelState.IsValid)
{
// breakpoint here shows that product.ImageData is null (but all other model properties are still populated with data).
if (Image != null)
{
product.ImageMimeType = Image.ContentType;
product.ImageData = new byte[Image.ContentLength];
Image.InputStream.Read(product.ImageData, 0, Image.ContentLength);
}
repository.SaveProduct(product);
TempData["Message"] = string.Format("{0} has been saved", product.Name);
return RedirectToAction("Index");
}
else
{
return View(product);
}
}
And here is the respository code that updates the model (though the model is changing before this code is called):
public void SaveProduct(Product product)
{
if (product.ID == 0)
{
context.Products.Add(product); // why doesn't this try to save ID = 0 to the ID field of the product in the database??
}
else
{
Product dbEntry = context.Products.Find(product.ID);
if (dbEntry != null)
{
dbEntry.Name = product.Name;
dbEntry.Description = product.Description;
dbEntry.Category = product.Category;
dbEntry.Price = product.Price;
dbEntry.ImageData = product.ImageData;
dbEntry.ImageMimeType = product.ImageMimeType;
}
}
context.SaveChanges();
}
What am I doing wrong?
This is really where you should consider using a ViewModel class instead. But to keep your current ways of doing things with minimal changes, try modifying the SaveProduct() method to the following:
public void SaveProduct(Product product, bool updateImage = false)
{
context.Products.Attach(product);
// mark product as modified
var entry = context.Entry(product);
entry.State = EntityState.Modified;
entry.Property(e => e.ImageData).IsModified = updateImage;
entry.Property(e => e.ImageMimeType).IsModified = updateImage;
context.SaveChanges();
}
and modify the controller code to:
...
var updateImage = false;
if (Image != null)
{
updateImage = true;
product.ImageMimeType = Image.ContentType;
product.ImageData = new byte[Image.ContentLength];
Image.InputStream.Read(product.ImageData, 0, Image.ContentLength);
}
repository.SaveProduct(product, updateImage);
....
I have search form to search by : site, user, status and date. After searched I will do Reject, Re-submit or Approve to call the controller action for update the status in the database. My code are as below:
/// View :
#Html.ValidationMessage("CustomError")
#Html.ValidationSummary(true)
#using (Html.BeginForm()) // Begin Form
{
<table>
<tr>
<td>Site:</td>
<td>#Html.DropDownList("sites", (IEnumerable<SelectListItem>)ViewBag.sites, "-- ALL --", new { style = "width: 300px;" })</td>
<td>Status:</td>
<td>#Html.DropDownList("status", (IEnumerable<SelectListItem>)ViewBag.status, "-- ALL --", new { style = "width: 150px;" })</td>
<td>PO No:</td>
<td>#Html.TextBox("PONumber", null, new { style = "width:150px" })</td>
</tr>
<tr>
<td>User: </td>
<td>#Html.DropDownList("user", (IEnumerable<SelectListItem>)ViewBag.user, "-- ALL --", new { style = "width: 300px;" })</td>
<td>Department:</td>
<td>
#Html.DropDownList("department", (IEnumerable<SelectListItem>)ViewBag.department, "-- ALL --", new { style = "width: 150px;" })
</td>
<td>Transaction Date:
</td>
<td>
#*<input type="text" id="TransactionDate" name="TransactionDate" style="width:210px" />*#
#*#Html.TextBox("TransactionDate", null, new { #class="datefield", style = "width:150px", #Value = DateTime.Now.ToShortDateString() })*#
#Html.TextBox("TransactionDate", null, new { #class="datefield", style = "width:150px" })
</td>
</tr>
</table>
<input type="submit" value="Search" />
// Here is the search result table
<input type="submit" value="Re-Submit" name="action" />
<input type="submit" value="Approve" name="action" />
<input type="submit" value="Reject" name="action" />
} // End Form
//// Controller
// HTTP Get
public ActionResult POHeader(string sites, string user, string department,
string status, string PONumber, string TransactionDate, bool IsRedirectAction = false)
{
// Populate Dropdown List
GetSiteDropdownList(SiteId);
GetUserDropdownList(UserId);
GetDepartmentDropdownList(DepartmentId);
GetStatusDropdownList(StatusId);
var PO = from p in db.X_PO_HDR
select p;
// Get Selected Site
if ((!string.IsNullOrEmpty(sites)) || ((TempData["selectedsite"] != null)))
{
if (sites.Length > 0)
{
if (IsRedirectAction)
{
SiteId = (string)TempData["selectedsite"];
//sites = SiteId;
}
else
{
SiteId = sites;
TempData["selectedsite"] = SiteId;
}
// Get Selected Site
PO = PO.Where(p => p.Site_Id == SiteId);
}
}
if (!string.IsNullOrEmpty(user) || ((TempData["selectedUser"] != null)))
{
if (user.Length > 0)
{
if (IsRedirectAction)
{
UserId = (string)TempData["selectedUser"];
}
else
{
UserId = user;
TempData["selectedUser"] = UserId;
}
// Filter by User
PO = PO.Where(p => p.Created_By == UserId);
}
}
// Get Selected Department
if (!string.IsNullOrEmpty(department) || ((TempData["selectedDepartment"] != null)))
{
if (department.Length > 0)
{
if (IsRedirectAction)
{
DepartmentId = (string)TempData["selectedDepartment"];
}
else
{
DepartmentId = department;
TempData["selectedDepartment"] = DepartmentId;
}
// Filter by Department
PO = PO.Where(p => p.Purch_Dept == DepartmentId);
}
}
PO = PO.OrderBy(o => o.Txn_DT);
// check if TempData contains some error message and if yes add to the model state.
if (TempData["CustomError"] != null)
{
ModelState.AddModelError(string.Empty, TempData["CustomError"].ToString());
}
return View(PO.ToList());
}
/// HttpPost Action
[HttpPost, ActionName("POHeader")]
[MultiButton(MatchFormKey = "action", MatchFormValue = "Reject")]
public ActionResult Reject(int[] selectedList)
{
string var1 = collection["sites"];
UpdateListStatus(selectedList, "X", "Rejected");
TempData["CustomError"] = selectedList.Count().ToString() + " record(s) has been successfully " + ActionString + "!";
return RedirectToAction("POHeader", new { IsRedirectAction = true });
}
Question: How to get back the search values?
1. Do I need to pass all the parameters again at HTTPPost Action? (any better way to solve?)
2. CustomError message is not working. (Message appear after next search is done)
Thanks,
Si Thu
I would start out by binding the View to a model. By doing this you can pass the entire model back to your method, then if there are issues with the form and you need to show it again with the values, you just return back the view along with the model you sent.
[HttpPost]
public ActionResult POHeader(FormModel model)
{
if (ModelState.IsValid)
{
// Do whatever you want to do and redirect to wherever you want to go from here.
}
// If the Model is invalid then simply return back the view along with the invalid model.
return View(model)
}
See the following article for more information on Model Binding
http://www.codeproject.com/Articles/159749/ASP-NET-MVC-Model-Binding-Part1
Here is another article on Client Side Model validation
http://www.codeproject.com/Articles/718004/ASP-NET-MVC-Client-Side-Validation
My viewmodel contains a integer list, the problem I have is that when I send my modified form viewmodel, it is always equal to null.
My ViewModel :
public class testViewModel
{
public List<int> itemTest { get; set;
}
Action in my Controller :
For example, I'll try to sum the new values entered into the form, but the sum calculated is always equal to 0, nothing changes.
public ActionResult form(int nbre)
{
testViewModel montest = new testViewModel()
{
itemTest = new List<int>()
};
for(int i=0;i<nbre ;i++)
{
montest.itemTest.Add(0);
}
return View(montest);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult form(testViewModel maListe)
{
int somme = 0;
if (maListe.itemTest != null)
{
if (maListe.itemTest.Count() != 0)
{
foreach (var item in maListe.itemTest)
{
somme += item;
}
}
}
//listtest = maListe;
return RedirectToAction("test2", new { qte = somme });
}
My view
#model MvcWebRole1.ViewModels.testViewModel
#{
ViewBag.Title = "Formulaire";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<table>
#foreach (var item in Model.itemTest)
{
<tr >
<td >
#Html.Label("Quantitée")
</td>
<td>
#Html.EditorFor(model => item)
#Html.ValidationMessageFor(model => item)
</td>
</tr>
}
</table>
<input type="submit" value="Valider" />
}
Thank you kindly help me
You need to index each item in your collection. The issue with your code seems to be the use of foreach. You really want to use for instead and pass in the index with the EditorFor call.
for (int i = 0; i < Model.Items.Count; i++) {
#Html.EditorFor(m => m.Items[i])
}
This only works for ordered lists that will never change their order. If you want to reorder items I suggest your read Phil Haack's great post on sending lists to the server.
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
list binding
<form>
#for(int i=0;i<Model.itemTest.Count ;i++)
{
#Html.TextBoxFor(x=>x.itemTest[i])
//or just <input type="text" name="itemTest "/> working to
}
for(int i=0;i<nbre ;i++)
{
montest.itemTest.Add(0);
}
return View(montest);
Looks like you are filling your int array with zeroes instead of i's. This should read montest.itemTest.Add(i); .
CheckBoxFor is not bounded when a property is defined in an object nested in the model?
Here is an example. I have a SearchOptions model that contains a List<Star> property. Each Star has a number, a name and a bool property that should be bounded:
public class SearchOptions
{
public SearchOptions()
{
// Default values
Stars = new List<Star>()
{
new Star() {Number=1, Name=Resources.Home.Index.Star1,
IsSelected=false},
new Star() {Number=2, Name=Resources.Home.Index.Star2,
IsSelected=false},
new Star() {Number=3, Name=Resources.Home.Index.Star3,
IsSelected=true},
new Star() {Number=4, Name=Resources.Home.Index.Star4,
IsSelected=true},
new Star() {Number=5, Name=Resources.Home.Index.Star5,
IsSelected=true},
};
}
public List<Star> Stars { get; set; }
}
In my strongly typed View (of SearchOptions) i loop over Stars property:
#using (Html.BeginForm("Do", "Home"))
{
<fieldset>
<legend>#MVC3TestApplication.Resources.Home.Index.Search</legend>
#{
foreach (Star s in Model.Stars)
{
#Html.CheckBoxFor(m => s.IsSelected)
<label>#s.Name</label>
}}
</fieldset>
<input type=submit value="Invia" />
}
The (relevant part of) controller is:
public ActionResult SearchOptions()
{
return View(new SearchOptions());
}
[HttpPost]
public ActionResult Do(SearchOptions s)
{
// Do some stuff
return View("SearchOptions", s);
}
It's because of how you're accessing the properties in the CheckBoxFor expression.
#for (int i = 0; i < Model.Stars.Count(); i++) {
#Html.CheckBoxFor(m => m.Stars[i].IsSelected)
<label>#Model.Stars[i].Name</label>
}
This should work for you.
Here's the output from the different methods:
//using the for loop
<input id="Stars_2__IsSelected" name="Stars[2].IsSelected" type="checkbox" value="true" />
//using the foreach
<input checked="checked" id="s_IsSelected" name="s.IsSelected" type="checkbox" value="true" />
You'll notice that the for foreach doesn't contain the proper name for it to match to when doing model binding.