Issue with asp-validation-for when used in a loop - asp.net-mvc

I am having an issue with the asp-validation-for on a span element in the code below.
<div class="form-group">
#if (Model.Property.Options.ElementAt(i).OptionTypeId == PropertyOptionType.CheckBox)
{
var ItemChecked = (Model.Property.Options.ElementAt(i).OptionValue == "on") ? " checked" : "";
<text><input type="checkbox" class="form-check-input" name="Options[#i].Code" id="Options[#i].Code" #ItemChecked data-val="false" />
<label class="form-check-label" for="Options[#i].Value"> #(Model.Property.Options.ElementAt(i).OptionValue)</label></text>
}
else if (Model.Property.Options.ElementAt(i).OptionTypeId == PropertyOptionType.List)
{
<label class="control-label">
#Model.Property.Options.ElementAt(i).OptionValue
</label>
<select class="form-control" name="Options[#i].Code"></select>
}
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="Options.#(i).Code"></span>
When it is rendered in HTML it comes out as
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="Options.[3].Code"></span>
But no validation error messages ever land in the span. They are picked up in a page level validation summary when set to all so the unobtrusive code is all working correctly but the ID of the span is being broken by the square brackets.
Any ideas?
Thanks
Mark

To display Validation Summary, you need to include:
#Html.ValidationSummary(false/*excludePropertyErrors?*/, "", new { #class = "text-danger" })
To display field specific error message, you need to use ValidationMessageFor:
#Html.EditorFor(model => model.StudentName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.StudentName, "", new { #class = "text-danger" })
See this tutorial
Don't manually generate the validation span, html helpers will do that for you... you would need something like this:
#for (int i = 0; i < Model.Property.Options.Count(); i++)
{
/* this one generates the input */
#Html.EditorFor(m => m.Property.Options[i], new { htmlAttributes = new { #class = "form-control" } })
/* this one generates the validation message */
#Html.ValidationMessageFor(m => m.Property.Options[i], "", new { #class = "text-danger" })
}

Related

How to make #Html.EditorFor readonly depending on Drop List

I want to make #Html.EditorFor read only until a certain drop down selection is made. So my drop down looks like this:
List<SelectListItem> listDepartments = new List<SelectListItem>();
listDepartments.Add(new SelectListItem
{
Text = "Sales",
Value = "Sales"
});
listDepartments.Add(new SelectListItem
{
Text = "Staff",
Value = "Staff",
Selected = true
});
and I want to make the #Html.EditorFor read only unless the dropdown list is Staff. Is that hard to do?
<div class="form-group">
#Html.LabelFor(model => model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
You have to use Javascript to react to client browser events like a change of the entry selected in the dropdown. The following code uses jQuery to achieve this.
1) You will need a handle on the dropdown so you can listen to its change event. Here I use the #Html.IdFor() helper to get the Ids of the input elements that were generated by#Html.DropDownListFor()/ #Html.EditorFor(). Then I create jQuery selectors based on these Ids.
2) Register an JS event handler for the change event of the dropdown.
3) Toggle the readonly attribute of the text input depending on the value selected in the dropdown.
In your cshtml page:
<script type="text/javascript">
$(document).ready(function () {
// 1) get selectors
var dropDownSelector = $('##Html.IdFor(m => m.listDepartments)');
var textBoxSelector = $('##Html.IdFor(m => m.Description)');
// 2) listen to change event
dropDownSelector.on('change', function() {
// 3) toggle readonly attribute
var selectedValue = dropDownSelector.val();
if (selectedValue === 'Staff') {
textBoxSelector.removeAttr('readonly');
}
else {
textBoxSelector.attr('readonly', 'readonly');
// maybe you also want to clear the text input?
// textBoxSelector.val('');
}
});
});
</script>
How about something like below with a ternary operator ? ?
#{
var isReadonly = Model.Description == "Staff"
}
<div class="form-group">
#Html.LabelFor(model => model.Description, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description, (isReadonly
? new { #class = "form-control", #readonly = "readonly" }
: new { #class = "form-control" }
))
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
I prefer to do my selects without a helper as I find them a bit clumsy so something like...
#{
var isReadonly = Model.Description == "Staff"
}
<div class="form-group">
#Html.LabelFor(model => model.Description, new { #class = "control-label col-md-2" })
<div class="col-md-10">
<select name="Description" class="form-control #(isReadonly ? "readonly" : "")">
<option value="" selected="selected" disabled="disabled">Please select...</option>
#foreach (var option in Model.listDepartments)
{
<option value="#option.Value" #(option.Value == Model.Description ? "selected='selected'" : "")>#option.Text</option>
}
</select>
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>

Dynamically added elements validation error

I've put some Regex expression on the properties of my model that only accept numbers.
public class MaterialsViewModel
{
[Display(Name = "Material")]
public string MaterialName { get; set; }
[Range(typeof(int), "0", "999")]
[RegularExpression(#"^\d+$", ErrorMessage = "Please enter proper value")]
public int? Quantity { get; set; }
[RegularExpression(#"^\d+$", ErrorMessage = "Please enter proper value")]
public double? Cost { get; set; }
public IEnumerable<SelectListItem> CategoryList { get; set; }
public int SelectedCategory { get; set; }
public string SelectedCategoryName { get; set; }
}
I have my View in which I can add controls dynamically
Dynamically add ScopeOfWork and Materials<br />
<div id="scopes">
<h3>Scopes</h3>
Add Scope of Work
#for (int i = 0; i < Model.ScopeOfWork.Count; i++)
{
<div class="scope">
<div class="form-group">
#Html.LabelFor(m => m.ScopeOfWork[i].ScopeOfWorkName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.ScopeOfWork[i].ScopeOfWorkName, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.ScopeOfWork[i].ScopeOfWorkName)
</div>
</div>
<input type="hidden" class="scopeindex" name="ScopeOfWork.Index" value="#i" />
<div class="indent materials">
<h4>Material</h4>
Add Material
#for (int j = 0; j < Model.ScopeOfWork[i].Materials.Count; j++)
{
<div class="material">
<div class="form-group">
#Html.LabelFor(m => m.ScopeOfWork[i].Materials[j].MaterialName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-2">
#Html.TextBoxFor(m => m.ScopeOfWork[i].Materials[j].MaterialName, new { #class = "form-control" })
</div>
<div class="col-md-10 col-md-offset-2">
#Html.ValidationMessageFor(m => m.ScopeOfWork[i].Materials[j].MaterialName)
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.ScopeOfWork[i].Materials[j].Quantity, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-1">
#Html.TextBoxFor(m => m.ScopeOfWork[i].Materials[j].Quantity, new { #class = "form-control" })
</div>
<div class="col-md-10 col-md-offset-2">
#Html.ValidationMessageFor(m => m.ScopeOfWork[i].Materials[j].Quantity)
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.ScopeOfWork[i].Materials[j].Cost, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-1">
#Html.TextBoxFor(m => m.ScopeOfWork[i].Materials[j].Cost, new { #class = "form-control" })
</div>
<div class="col-md-10 col-md-offset-2">
#Html.ValidationMessageFor(m => m.ScopeOfWork[i].Materials[j].Cost)
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2">Category</label>
<div class="col-xs-2">
#Html.DropDownListFor(m => m.ScopeOfWork[i].Materials[j].SelectedCategory, Model.ScopeOfWork[i].Materials[j].CategoryList, "Please select", htmlAttributes: new { #class = "form-control" })
</div>
</div>
<input type="hidden" class="materialindex" name="ScopeOfWork[#i].Materials.Index" value="#j" />
</div>
}
</div>
</div>
}
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-success" />
</div>
</div>
</div>
}
<div id="newScope" style="display:none">
<div class="scope">
<div class="form-group">
<label for="_#__ScopeOfWorkName" class="control-label col-md-2">Scope Of Work</label>
<div class="col-md-10">
<input class="form-control" type="text" id="_#__ScopeOfWorkName" name="ScopeOfWork[#].ScopeOfWorkName" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="ScopeOfWork[#].ScopeOfWorkName" data-valmsg-replace="true"></span>
</div>
</div>
<input type="hidden" class="scopeindex" name="ScopeOfWork.Index" value="#" />
<div class="materials">
<h4>Material</h4>
Add Material
</div>
<hr />
</div>
</div>
<div id="newMaterial" style="display:none">
<div class="form-group">
<label for="_#__Materials_%__MaterialName" class="control-label col-md-2">Material</label>
<div class="col-md-2">
<input class="form-control" type="text" id="_#__Materials_%__MaterialName" name="ScopeOfWork[#].Materials[%].MaterialName" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="ScopeOfWork[#].Materials[%].MaterialName" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label for="_#__Materials_%__Quantity" class="control-label col-md-2">Quantity</label>
<div class="col-md-1">
<input class="form-control" type="text" id="_#__Materials_%__Quantity" name="ScopeOfWork[#].Materials[%].Quantity" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="ScopeOfWork[#].Materials[%].Quantity" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label for="_#__Materials_%__Cost" class="control-label col-md-2">Cost</label>
<div class="col-md-1">
<input class="form-control" type="text" id="_#__Materials_%__Cost" name="ScopeOfWork[#].Materials[%].Cost" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="ScopeOfWork[#].Materials[%].Cost" data-valmsg-replace="true"></span>
</div>
</div>
#*Drop down*#
<div class="form-group">
<label for="_#__Materials_%__SelectedCategory" class="control-label col-md-2">Category</label>
<div class="col-xs-2">
<select class="form-control category" id="_#__Materials_%__SelectedCategory" name="ScopeOfWork[#].Materials[%].SelectedCategory">
<option value="">--Select--</option>
</select>
</div>
</div>
<input type="hidden" class="materialindex" name="ScopeOfWork[#].Materials.Index" value="%" />
</div>
<script>
var form = $('form');
var scope = $('#newScope');
var material = $('#newMaterial');
var categories = #Html.Raw(Json.Encode(Model.CategoryList));
form.on('click', '.addmaterial', function () {
var clone = material.clone();
var scopeIndex = $(this).closest('.scope').find('.scopeindex').val();
clone.html($(clone).html().replace(/#/g, scopeIndex));
var materialIndex = new Date().getTime();
clone.html($(clone).html().replace(/%/g, materialIndex));
// drop down list
var select = clone.find('.category');
$.each(categories, function(index, item) {
select.append($('<option></option>').val(item.Value).text(item.Text));
});
$(this).closest('.materials').append(clone.html());
form.data('validator', null);
$.validator.unobtrusive.parse(form);
});
$('#addScope').click(function () {
var clone = scope.clone();
var scopeIndex = new Date().getTime();
clone.html($(clone).html().replace(/#/g, scopeIndex));
$('#scopes').append(clone.html());
form.data('validator', null);
$.validator.unobtrusive.parse(form);
});
</script>
Controller:
[HttpPost]
public ActionResult _CreateProject(ProjectViewModel project)
{
tblProject projectModel = new tblProject();
tblScopeOfWork scopeModel = new tblScopeOfWork();
tblMaterial materialModel = new tblMaterial();
if (ModelState.IsValid)
{
projectModel.ProjectName = project.ProjectName;
projectModel.ProjectLocation = project.ProjectLocation;
projectModel.ProjectDescription = project.ProjectDescription;
projectModel.WorkArea = project.WorkArea;
projectModel.ModeOfPayment = project.ModeOfPayment;
projectModel.Duration = project.Duration;
projectModel.StartDate = project.StartDate;
projectModel.EndDate = project.EndDate;
projectModel.ProfitSupervision = project.ProfitSupervision;
projectModel.ProjectStatus = project.ProjectStatus;
projectModel.ForemanId = project.ForemanId;
projectModel.ClientId = project.ClientId;
db.tblProjects.Add(projectModel);
db.SaveChanges();
//Get the recently created ProjectId
var recentProjectId = db.tblProjects.OrderByDescending(x => x.ProjectId).FirstOrDefault().ProjectId;
//Get all values from List of ScopeOfWork
//Add each ScopeOfWork to the database
for (int scopeIndex = 0; scopeIndex < project.ScopeOfWork.Count; scopeIndex++)
{
scopeModel = new tblScopeOfWork();
scopeModel.ScopeOfWork = project.ScopeOfWork[scopeIndex].ScopeOfWorkName;
scopeModel.ProjectId = recentProjectId;
db.tblScopeOfWorks.Add(scopeModel);
db.SaveChanges();
//Get the recently created ScopeOfWorkId
var recentScopeOfWorkId = db.tblScopeOfWorks.OrderByDescending(x => x.ScopeOfWorkId).FirstOrDefault().ScopeOfWorkId;
//Get all materials from its corresponding ScopeOfWork and save to database
for (int materialIndex = 0; materialIndex < project.ScopeOfWork[scopeIndex].Materials.Count; materialIndex++)
{
materialModel = new tblMaterial();
materialModel.Description = project.ScopeOfWork[scopeIndex].Materials[materialIndex].MaterialName;
materialModel.Quantity = project.ScopeOfWork[scopeIndex].Materials[materialIndex].Quantity;
materialModel.Cost = project.ScopeOfWork[scopeIndex].Materials[materialIndex].Cost;
materialModel.ScopeOfWorkId = recentScopeOfWorkId;
materialModel.CategoryId = project.ScopeOfWork[scopeIndex].Materials[materialIndex].SelectedCategory;
db.tblMaterials.Add(materialModel);
}
}
db.SaveChanges();
}
project.ScopeOfWork = new List<ScopeOfWorkViewModel>
{
new ScopeOfWorkViewModel()
{
Materials = new List<MaterialsViewModel>
{
new MaterialsViewModel()
{
CategoryList = new SelectList(db.tblCategories, "CategoryId", "CategoryName")
}
}
}
};
return View(project);
}
}
The problem is I tried to put some string ('ss') on the textbox of quantity and cost to see if the validation will work and this appears The value 'ss' is not valid for Quantity and The value 'ss is not valid for Cost.but it should be Please enter proper value. And also whenever the validation occurs, the Add Material link cannot add another newMaterial.
I have tried and check these answer and also DotNetFiddle but still it has some errors.
You do not need your regex (its an int so it can only accept a number anyway). And ditto for the cost property (but why have you made it decimal if you don't allow fractions?).
The validation for the type will be performed first, and because its invalid, no further validation is performed.
Change the attribute to to a [Required]
[Range(typeof(int), "0", "999")]
[Required(ErrorMessage = "Please enter proper value")]
public int? Quantity { get; set; }
Refer also this question/answer for changing the default error message for an invalid value for a type.
As a side note, your dynamically added items will not give client side validation because you have not added the necessary data-val-* attributes. You need to inspect the html generated for the elements in the for loops and copy the html exactly, except replacing the collection indexers.
You also have an issue with your POST method because you set the ScopeOfWork property to a new collection, and wipe out all the data the user has entered before you return the view. The basic structure of your POST method should be
[HttpPost]
public ActionResult _CreateProject(ProjectViewModel project)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(project);
return View(project);
}
// code to initialize your data models, save and redirect
}
private void ConfigureViewModel(ProjectViewModel model)
{
var categories = db.tblCategories;
model.CategoryList = new SelectList(categories , "CategoryId", "CategoryName");
foreach (var scope in model.ScopeOfWork)
{
foreach (var material in scope.Materials)
{
material.CategoryList = new SelectList(categories , "CategoryId", "CategoryName");
}
}
}
Note the ConfigureviewModel() method is also called from your GET method to populate the SelectLists

searching database and displaying on .cshtml page

I have this code in my controller:
public ActionResult Search(String searchstring)
{
var number = from n in db.UCountInfoes
select n;
if (!String.IsNullOrEmpty(searchstring))
{
number = number.Where(i => i.IDNumber.Equals(searchstring));
}
return View(number);
}
and on my .cshtml page i have this which i use to search:
<div class="form-group">
#Html.LabelFor(model => model.ID_Number, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ID_Number, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ID_Number, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" name="SearchButton" value="Search" class="btn btn-inverse" />
</div>
</div>
After clicking the search button i want to return the records found on another .cshtml page.
return RedirectToAction("Action Name", "Your Controller Name", number);
Instead of return View you can use the above method.
Here Action Name is the View Name you want to show the results into.

#Html.CheckboxFor not displaying in mvc

The checkbox is not displaying in page. I tried many solutions in google. Nothing worked. Here s the code:
#model project.gamestatus
#using (Html.BeginForm("Create", "Calculator", FormMethod.Post, new { id = "frmID" }))
{
//other codes
<div class="form-group">
#Html.Label("Show on Screen", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.CheckBoxFor(m => m.display_status)
#Html.ValidationMessageFor(model => model.display_status, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-info" />
</div>
</div>
}
Only if the checkbox is shown in page i can proceed with validation. In my view there is no checkbox
First of all, you need to ensure that the display_status model is of boolean type and assigned the display name to it along with any validation.
[Display(Name="CheckBox Display Name")]
[Required]
public bool display_status { get; set; }
Also, #Html.CheckBoxFor do not support the label of checkbox. Therefore, you can have the label of the checkbox using #Html.LabelFor as follow:
<div class="form-group">
#Html.Label("Show on Screen", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.CheckBoxFor(m => m.display_status)
#Html.LabelFor(m => m.display_status)
#Html.ValidationMessageFor(m => m.display_status, "", new { #class = "text-danger" })
</div>
</div>
though its a very old post but I undergo this issue todat, but when I inspected I found that the CSS property opacity was set to 0 so changing it to 1 will solve the issue
#Html.EditorFor(model => model.Active, new { htmlAttributes = new { style = "opacity: 1" } })
I fixed the problem by setting the height to 20px explicitly. When I examined the page, it was being set to 20%:
#Html.CheckBoxFor(m => m.ExistingUser, new { style="height:20px" })

How do I post varied length list to controller?

MVC4. I have a dynamic list on a view, a new textbox is added with button click (which adds a partialView) so user can enter a list of stuff. That is contained in a form element with a submit button.
In the controller I have tried three different types:
[HttpPost]
public ActionResult Index(IEnumerable<AccessoryVM> form)
{
-- form is NULL
[HttpPost]
public ActionResult Index(AccessoryVM form)
{
-- form has only the first item in the list
[HttpPost]
public ActionResult Index(FormCollection form)
{
-- seems to receive the list, but having trouble getting the values
All the examples I have seen are using a for list to add an index to each item, but they aren't using a dynamic list (it has a fixed length).
What should the Controller receiving type be?
EDIT to add more detail:
Button click appends partial view:
$("#add-item").on("click", function () {
$.ajax({
url: '/Accessory/AddItem',
cache: false,
success: function (html) {
$("#form-body").append(html);
}
})
return false;
})
Partial View:
#model EmployeeHardwareRequest.Models.ViewModels.AccessoryVM
<div class="form-group col-md-3">
#Html.LabelFor(model => model.ItemDescription, htmlAttributes: new { #class = "control-label" })
<div>
#Html.DropDownListFor(model => model.AccessoryId, Model.AccessoryDdl, new { #class = "form-control" })
</div>
</div>
<div class="form-group col-md-9">
#Html.LabelFor(model => model.ProductLink, htmlAttributes: new { #class = "control-label" })
<div>
#Html.EditorFor(model => model.ProductLink, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProductLink, "", new { #class = "text-danger" })
</div>
</div>
Main View - partial is appended to the #form-body:
#using (Html.BeginForm("Index", "Accessory", FormMethod.Post, new { #id="accessory-form", #class = "form-horizontal" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div id="form-body">
<div class="form-group col-md-3">
#Html.LabelFor(model => model.ItemDescription, htmlAttributes: new { #class = "control-label" })
<div>
#Html.DropDownListFor(model => model.SelectedAccessory, Model.AccessoryDdl, new { #class = "form-control" })
</div>
</div>
<div class="form-group col-md-9">
#Html.LabelFor(model => model.ProductLink, htmlAttributes: new { #class = "control-label" })
<div>
#Html.EditorFor(model => model.ProductLink, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProductLink, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<button id="add-item" class="btn btn-primary">Add Another Item</button>
</div>
<div class="col-md-6">
<input type="submit" value="Select Software" class="btn btn-default pull-right" />
</div>
</div>
}
Most likely, you have a binding issue. Assuming all you're posting is a collection of AccessoryVM, then the parameter should be List<AccessoryVM> form. However, in order for the modelbinder properly bind the posted values, your input names must be in a specific format, namely either form[N].Foo or just [N].Foo.
You haven't given any detail about what's going on in your view, but since these are dynamically added, you must be using JavaScript to add additional inputs to the page. If you're doing it via AJAX (returning a partial view), you'll need to pass the index to your endpoint so that it can be utilized to generate the right input names. If you're using something like Knockout, Angular, etc., you should ensure that whatever template is being used to generate the inputs takes an index into account.

Resources