ASP.NET MVC unobtrusive client validation with multiple models - asp.net-mvc

The ThingController creates a model which contains (amongst other properties) a collection of Things. These can be edited in the view like so:
<form action="#Url.Action("Update", "Thing")" method="post">
<table>
<tr>
<th>Foo</th>
<th>Bar</th>
</tr>
#foreach (var thing in ViewData.Model.Things)
{
<tr class="raw-data">
<td class="raw-data"><input name="things[#rowCount].Foo" class="raw-data" readonly="readonly" type="text" value="#thing.Foo" /></td>
<td class="raw-data"><input name="things[#rowCount].Bar" class="raw-data" type="text" value="#thing.Bar" /></td>
</tr>
rowCount++;
}
</table>
<br />
<input type="submit" value="OK" />
</form>
The controller contains the following action, which allows multiple Things to be updated simultaneously:
public ActionResult Update(ThingModel[] things)
{
...
}
I've added some validation attributes to properties on the Thing class:
[Required]
[Range(0, 500000, ErrorMessage = "Foo must be within 0 and 500,000.")]
public double Foo{ get; set; }
[Required]
[Range(0, 500000, ErrorMessage = "Bar must be within 0 and 500,000.")]
public double Bar { get; set; }
The thing is, I can't figure out how to add unobtrusive validation using the TextBoxFor helpers etc.
At this point I think the correct approach is to manually mark up the input fields with the validation attributes, but I was wondering if anyone can point me to some documentation, a tutorial etc. that demonstrates the use of helpers, multiple models and unobtrusive validation?

I had a similar problem where users could dynamically add multiple emails to their account.
I fixed it with Jquery by adding the validation manually. You should give your form a name and add the validation to all your items. It should be something like this I quess:
$('#frmYourForm').validate();
for (var i = 0; i < 'CountOfAllFields'; i++) {
$('#Things_' + i + '__Foo').rules('add', { required: true, messages: { required: 'The Foo field is required'} });
$('#Things_' + i + '__Bar').rules('add', { required: true, messages: { required: 'The Bar field is required'} });
}
Typed from my memory so don't shoot me if I made an error. I don't know the exact syntax for the Range thingy but you should look around the Jquery.validate file.

Related

How to make a CREATE view & controller method for a model that has a list field?

I have these 2 models:
public class Invoice
{
public string InvoiceID {get; set; }
public List<InvoiceElement> InvoiceElements {get; set;}
[...other fields...]
}
public class InvoiceElement
{
public string InvoiceElementID {get; set; }
[ForeignKey("Invoice")]
public string InvoiceID { get; set; }
public virtual Invoice Invoice { get; set; }
public string Item {get; set;}
[...other fields...]
}
I am unable to make a CREATE view for new Invoices that lets me add InvoiceElements.
I want to have a "CurrentInvoiceElements" table where to dinamically add rows.
Just trying to making it simple. You can use the name attribute (the attribute that asp.net uses for modal binding) and post a list along with other properties of the class. You can use javaScript to append new elements to your form. Using the above modals you've provided, I have written a simple example using simple jQuery functions.
Razor View:
<button class="btn btn-success" id="add_btn">Add Invoice Element</button>
#using (#Html.BeginForm("SaveInvoice", "Invoice", FormMethod.Post))
{
<!--Other modal attributes inputs goes here -->
<!--You can use normal html helper extensions -->
<table id="element_table">
<thead>
<tr>
<td>Element Id</td>
<td>Item</td>
</tr>
</thead>
<tbody>
<tr>
<td><input name="invoice.InvoiceElements[0].Item" id="InvoiceElements[0].Item" /></td>
<td><input name="invoice.InvoiceElements[0].InvoiceElementID" id="InvoiceElements[0].InvoiceElementID" /></td>
</tr>
</tbody>
</table>
<input type="submit" />
}
JavaScript:
<script type="text/javascript">
$("#add_btn").on('click', function (e) {
var table = $("#element_table");
var idx = $(table).find("tbody>tr").length;
var htmlToAppend = `<tr>
<td><input name="invoice.InvoiceElements[${idx}].Item" id="InvoiceElements[${idx}].Item" /></td>
<td><input name="invoice.InvoiceElements[${idx}].InvoiceElementID" id="InvoiceElements[${idx}].InvoiceElementID" /></td>
</tr>`;
$(table).find("tbody").append(htmlToAppend);
});
</script>
Controller / Action:
[HttpPost]
public ActionResult SaveInvoice(Invoice invoice)
{
/* your logic here */
if(ModelState.IsValid)
_invoiceBusiness.SaveInvoice(invoice);
return View();
}
Please make sure the variable name in the parameter of the action method matches the name used in the name attribute of the input tag. i.e. name = "invoice.***" public ActionResult SaveInvoice(Invoice invoice)
I followed this solution: https://github.com/danludwig/BeginCollectionItem some years ago, and worked fine.
If I'm not mistaken, at the time I managed to do it using only the HTML Helper: HtmlPrefixScopeExtensions. Then just make sure the name you give on your View when you do Html.BeginCollectionItem("name") is exactly the same as your collection on your ViewModel.
That's for binding back to the controller.
With this, you can dynamically add rows using AJAX per example.
I hope it's clear enough. If you don't understand it I may make a Github repository with this.

Why does Razor view add "CS$<>8__locals1" prefix to input names when accessing indexer of model assigned to local variable?

I'm building a simple ASP.NET MVC 5.2.3.0 view that displays a user name and a checkbox list of roles. You can check or uncheck the checkboxes to control which roles the user is in. Here is my ViewModel:
public class EditUserVM
{
public string Id { get; set; }
public string UserName { get; set; }
public List<UserRoleVM> Roles { get; set; }
}
public class UserRoleVM
{
public string RoleId { get; set; }
public string RoleName { get; set; }
public bool IsMember { get; set; }
}
And it's displayed like this:
#model VMContainer<EditUserVM>
#* omitted for exmaple *#
var userVM = Model.ViewModel;
<div class="panel panel-primary">
<div class="panel-heading"><h4 style="display:inline">User: #userVM.UserName</h4></div>
<div class="panel-body">
#Html.ValidationSummary(false, "", new { #class = "text-danger" })
<h4>Users Roles</h4>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.HiddenFor(m => userVM.Id)
#Html.HiddenFor(m => userVM.UserName)
<table class="table table-bordered table-striped">
<thead><tr><th></th><th>Role</th></tr></thead>
<tbody>
#for (int x = 0; x<userVM.Roles.Count; x++){
<tr>
<td>
#Html.HiddenFor(m => userVM.Roles[x].RoleId)
#Html.CheckBoxFor(m => userVM.Roles[x].IsMember)
</td>
<td>#Html.DisplayFor(m => userVM.Roles[x].RoleName)</td>
</tr>
}
</tbody>
</table>
<div>
<input type="submit" value="Save" class="btn btn-success" />
#Html.ActionLink("Cancel", "Users", null, new { #class = "btn btn-default" })
</div>
</div>
}
Notice the model type is VMContainer. This is a wrapper used by my views so that in case there is an error generating the inner T ViewModel, it can display an error instead of the normal view content. To make it easier to access the EditUserVM and because that is all I need to submit, not the VMContainer wrapper, I assign it to a local variable var userVM = Model.ViewModel;. I've used this pattern a lot in previous projects. The problem is when I inspect the page in developer tools, all of my inputs are named wrong. They all get a prefix for what looks like a generated class. For example, #Html.HiddenFor(m => userVM.Id) renders:
Through trial and error, I narrowed this down to not happening if I remove the for loop. That is with the for loop commented out, the remaining inputs are named correctly. Through further trial and error I discovered that what really triggers it is accessing the Roles indexer using a variable. E.G. If I remove the loop and just do this, it produces the wrong name for all of the inputs:
#Html.HiddenFor(m => userVM.Id)
#Html.HiddenFor(m => userVM.UserName)
#{ int x = 0; }
#Html.HiddenFor(m => userVM.Roles[x].RoleId);
It does not generate the wrong names if I use the constant [0] in place of [x] or m.ViewModel.Roles[x] instead of userVM.Roles[x]. So to try to spell it out completely, it seems to occur when accessing the indexer of a local variable's descendant using a variable for the index parameter.
I found this or something similar acknowledged for MVC 6 (at the time) but I don't understand the problem. I'm a little surprised this still exists if it is in fact a bug. Although it's hard to describe, it doesn't seem like it would be a totally uncommon scenario. Can someone explain what is going on and hopefully offer a fix? I prefer to only post an EditUserVM to my action when the form submits.
#for (int x = 0; x<userVM.Roles.Count; x++){
A workaround could be declaring the variable x in the top of your page.
Source: https://github.com/aspnet/Mvc/issues/2890

Modified view-model value in post not showing up in DropDownListFor

I'm trying to implement a button to adding and removing a row using static HTML. I saw this question that seems to be what I want, but I found that the example doesn't work for drop-down lists. None of the drop-down lists' options ever are marked as selected. If I don't clear the model state, all of the old values are kept. How can I keep my changes to the view-model?
// Controller action
[HttpPost]
public virtual ActionResult DoSomething(DoSomethingViewModel viewModel)
{
if (viewModel != null)
{
if (viewModel.ButtonPressed != null)
{
if (viewModel.ButtonPressed.Trim() == "Cancel")
{
return Redirect(ApplicationUtilities.CancelRequestUrl);
}
else if (viewModel.ButtonPressed.Trim() == "AddRow")
{
ModelState.Clear();
// This only covers non-JavaScript users.
// One for the newest one.
viewModel.FieldOneValues.Add(String.Empty);
viewModel.FieldTwoValues.Add(String.Empty);
viewModel.FieldThreeValues.Add(null);
return View(viewModel);
}
else if (viewModel.ButtonPressed.Trim().StartsWith("Remove"))
{
ModelState.Clear();
String[] split = viewModel.ButtonPressed.Split('-');
if (split.Length == 2)
{
Int32 indexToRemove;
Regex regex = new Regex(#"\[([0-9]+)\]");
Match match = regex.Match(split[1]);
if (match.Success && Int32.TryParse(match.Groups[1].Value, out indexToRemove))
{
viewModel.FieldOneValues.RemoveAt(indexToRemove);
viewModel.FieldTwoValues.RemoveAt(indexToRemove);
viewModel.FieldThreeValues.RemoveAt(indexToRemove);
}
}
return View(viewModel);
}
}
}
if (ModelState.IsValid)
{
return WhateverIsDoneOnSuccess(viewModel);
}
else
{
return View(viewModel);
}
}
// View Model
public class DoSomethingViewModel
{
public DoSomethingViewModel()
{
this.FieldOneValues = new List<String>();
this.FieldTwoValues = new List<String>();
this.FieldThreeValues = new List<Int32?>();
}
public virtual IList<String> FieldOneValues { get; set; }
public virtual IList<String> FieldTwoValues { get; set; }
public virtual IList<Int32?> FieldThreeValues { get; set; }
public virtual String ButtonPressed { get; set; }
}
<!-- Spark View -->
<tr each="var fieldOneValue in Model.FieldOneValues">
<td headers="FieldOneTh">${Html.TextAreaFor(m => m.FieldOneValues[fieldOneValueIndex])}</td>
<td headers="FieldTwoTh">${Html.TextAreaFor(m => m.FieldTwoValues[fieldOneValueIndex])}</td>
<td headers="FieldThreeTh">
${Html.TextBoxFor(m => m.fieldOneValueIndex], new { disabled="disabled", #readonly="readonly" })}
${Html.DropDownListFor(
m => m.FieldThreeValues[fieldOneValueIndex]
, ApplicationUtilities.FieldThreeSelectListItems
, " "
)}
</td>
<td headers="AddRemoveTh">
<button name="${Html.NameFor(m => m.ButtonPressed)}" class="Remove" type="submit" value="Remove-[${fieldOneValueIndex}]">Remove</button>
<button if="fieldOneValueIsLast" name="${Html.NameFor(m => m.ButtonPressed)}" class="Add" type="submit" value="AddRow">Add</button>
</td>
</tr>
<!-- HTML Output -->
<tr>
<td headers="FieldOneTh"><textarea cols="20" id="FieldOneValues_0_" name="FieldOneValues[0]" rows="2">
</textarea></td>
<td headers="FieldTwoTh"><textarea cols="20" id="FieldTwoValues_0_" name="FieldTwoValues[0]" rows="2">
</textarea></td>
<td headers="FieldThreeTh">
<input data-val="true" data-val-number="The field Nullable`1 must be a number." disabled="disabled" id="FieldThreeValues_0_" name="FieldThreeValues[0]" readonly="readonly" type="text" value="0" />
<select id="FieldThreeValues_0_" name="FieldThreeValues[0]"><option value=""> </option>
<option value="0">Option 1</option>
<option value="1">Option 2</option>
<option value="2">Option 3option>
</select>
</td>
<td headers="AddRemoveTh">
<button name="ButtonPressed" class="Remove" type="submit" value="Remove-[0]">Remove</button>
<button name="ButtonPressed" class="Add" type="submit" value="AddRow">Add</button>
</td>
</tr>
Plus I'm curious; I think there should be a way to do this.
There is, but you have to handle the post correctly. It's situations like these why PRG (Post-Redirect-Get) is recommended. When you click something like a remove button for a particular item, it's not appropriate to save all the other fields and do whatever else would happen when the whole form is actually submitted. All the user indicated was that they wanted to remove this one item.
Therefore, when you get the post, you remove that item from the database or wherever it's persisted and then you redirect back to the original form if that's what you want. The redirect process updates the page state so that the item is now gone and the rest of the form can then be edited without carrying around stale data. What you're trying to do is remove the item, but then just return the view directly which still has the posted item in the data backing it. That's where your problem is.
I think you went down this path because you're trying to maintain any edits the user made to other areas of the form, but that's simply not going to be possible. However, you do have some options:
Don't actually have a button that removes the item right this minute. Instead, provide a checkbox or something that indicates the item should be deleted when the user posts the entire form. Then you can save the all the form data, remove the indicated items, and redirect afterwards like you should.
Use local storage to save the user's edits on the client-side, and then read them back from local storage after the page loads again, following the redirect. However, this requires JS.
Use AJAX to submit the request to remove the item, and then remove the row from the DOM. However, this requires JS.
Also, remember that it's entirely possible to progressively enhance your form. So, you can implement #1 and #3, and then if JS isn't available, #1 still serves as a fallback.

Client validation not showing message

I have a MVC4 internet application with a form for creating user accounts. The form validation works but while the input fails validation no error message is displayed. It still prevents submitting until the validation problem is solved but there is no text
Razor View Form
<h2>Create New Account</h2>
<fieldset>
<legend></legend>
#using (Html.BeginForm("CreateUser",null)){
#Html.AntiForgeryToken()
<table class="create">
<tr>
<td colspan="2"><b>New Account</b>
</tr>
<tr>
<td>#Html.DisplayNameFor(model=>model.UserName)</td><td>#Html.TextBoxFor(model=>model.UserName)</td>
<td>#Html.DisplayNameFor(model=>model.EmailAddress)</td><td>#Html.TextBoxFor(model=>model.EmailAddress)</td>
<td><input type="submit" value="Create User" /></td>
</tr>
</table>
}
</fieldset>
#Html.ValidationSummary()
The bundles used include the validation files
bundles.Add(new ScriptBundle("~/bundles/asset").Include(
"~/Scripts/jquery-{version}.js",
"~/Scripts/jquery-ui-{version}.js",
"~/Scripts/jquery.validate*",
"~/Scripts/jquery.unobtrusive*"));
The Model used is an entity model, I have added a partial class to annotate the validation requirements
[MetadataType(typeof(UserProfileMetadata))]
public partial class UserProfile
{
//Empty Class just required for adding class level attribute
}
public class UserProfileMetadata
{
//Fields from user profile requiring annotations
[EmailAddress]
[Required]
[Display(Name = "Email Address")]
public string EmailAddress { get; set; }
[Required]
public string UserName { get; set; }
}
The validation working but now showing the message makes me think it must be a markup error but I just can't see it.
Moving the ValidationSummary inside the form will fix it.
<h2>Create New Account</h2>
<fieldset>
<legend></legend>
#using (Html.BeginForm("CreateUser",null)){
#Html.ValidationSummary()
#Html.AntiForgeryToken()
<table class="create">
<tr>
<td colspan="2"><b>New Account</b>
</tr>
<tr>
<td>#Html.DisplayNameFor(model=>model.UserName)</td> <td>#Html.TextBoxFor(model=>model.UserName)</td>
<td>#Html.DisplayNameFor(model=>model.EmailAddress)</td><td>#Html.TextBoxFor(model=>model.EmailAddress)</td>
<td><input type="submit" value="Create User" /></td>
</tr>
</table>
}
</fieldset>
For anyone stumbling across this who does not wish to be restricted to moving the #Html.ValidationSummary into the form, i.e. it lives in _Layout.cshtml, and you like it there, here's a way around that.
The below method is apparently what is used by Microsoft to populate #Html.ValidationSummary. In it's standard form, it looks for data-valmsg-summary-true in $('this'). this in this case is the calling form. Well, my #Html.ValidationSummary lives in the pageBody <div> on _Layout.cshtml to keep it DRY.
function onErrors(event, validator) { // '#pageBody' is the containing element
var container = $('#pageBody').find("[data-valmsg-summary=true]"),
list = container.find("ul"); if (list && list.length && validator.errorList.length) {
list.empty(); container.addClass("validation-summary-errors").removeClass("validation-summary-valid");
$.each(validator.errorList, function () {
$("<li />").html(this.message).appendTo(list);
});
}
}
So far, I've only changed this:
var container = $('this').find("[data-valmsg-summary=true]")
to this:
var container = $('#pageBody').find("[data-valmsg-summary=true]")
Now, I trigger my validation from a button click. To get onErrors(event, validator) to fire, I used the following jQuery:
$('#btnSave').click(function () {
if ($('form').valid()) {
// Do stuff
} else {
onErrors(event, $('form').data('validator'));
}
});
Voila, #Html.ValidationSummary populates even when using jQuery.unobtrusive.
A big thanks to Leniel Macaferi for pointing me in the right direction here:
http://www.leniel.net/2013/08/customizing-aspnet-mvc-html-validation-summary-with-bootstrap-3-panel.html#sthash.jGRguVuV.qSjYUlhS.dpbs

Class Libraries, MVC, and Data Annotation

I'm new to MVC and am unsure about proper design.
I have class objects which I use in a variety of applications. I have taken the approach to write a custom view model class so that I can have access to properties in all of these objects and have strong typing. Without re-typing all my class code in the view model is there any way to have the properties in these objects validated using data annotation? Please let me know if my approach and design is all wrong.
[Required]
public User user = new User("username");
//User has lots properites and methods, could i validate inside my class code?
//What I'd like to avoid is putting the following stuff in my custom view model class, //since I already have a class library with this stuff:
public class User
{
[Required]
[StringLength(160)]
public string prop1 { get; set; }
[Required]
[StringLength(160)]
public string prop2 { get; set; }
[Required]
[StringLength(160)]
public string prop3 { get; set; }
public User(string token)
{
SetUser(token);
}
public void SetUser(string token)
{
this.prop1 = "this";
this.prop2 = "this2";
this.prop3 = "this3";
}
============
Good to know I can, but I'm stumbling on some issues. In my view I have: #Html.EditorFor(modelItem => modelItem.user.prop1)
I put the data annotation stuff in my class domain. When it renders it does show the annoations.
<input class="text-box single-line" data-val="true" data-val-length="The field prop1 must be a string with a maximum length of 5." data-val-length-max="5" data-val-required="The prop1 field is required." id="user_prop1" name="user.prop1" type="text" value="somevalue" />
but when I go to my controller the parameter is null. I think because the name is user.prop1. I tried a textbox where i specified the name attribute and yet my controller still couldn't get a value for my parameter.
====================
#model TrainingCalendar.Models.Training
#{
ViewBag.Title = "Signup";
}
<h2>Signup</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("ConfirmSignup", "Training", FormMethod.Post))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Training</legend>
<p>
#Html.Label("date", Model.SpecifiedCourse.strClassDate)
</p>
<p>
#Html.Label("time", Model.SpecifiedCourse.Time)
</p>
<p>
#Html.Label("instructor", Model.SpecifiedCourse.Instructor)
</p>
<p>
#Html.Hidden("id", Model.SpecifiedCourse.ID)
</p>
<table>
<tr>
<td>#Html.LabelFor(modelItem => modelItem.ApplicationUser.prop1)</td>
<td>#Html.EditorFor(modelItem => modelItem.ApplicationUser.prop1)</td>
<td style="color:Red">#Html.ValidationMessageFor(modelItem => modelItem.ApplicationUser.prop1)</td>
</tr>
<tr>
<td>#Html.LabelFor(modelItem => modelItem.ApplicationUser.prop2)</td>
<td>#Html.EditorFor(modelItem => modelItem.ApplicationUser.prop2)</td>
<td style="color:Red">#Html.ValidationMessageFor(modelItem => modelItem.ApplicationUser.prop2)</td>
</tr>
</table>
<p>
<input type="submit" value="Sign Up" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
===================
public ActionResult ConfirmSignup(
int id,
string prop1,
string prop2)
{
SignUpForClass();
return View();
}
Absolutely you can have the data annotation attributes on your class code. If you're encapsulating the class in your view model, populated the properties of the encapsulated class in your view. During the validation process the class members will be validated against the data annotation attributes that you specify in the class declaration.
I stumbled along this too and what I ended up doing (I don't know if it is the best or most correct) is too have data annotations that effect primarily the database in the DOM of my class library, such as MaxLength, Required, etc, and then in my view models I have the data annotations that relate primarily to validation such as Regular Expression, Date Time Formats, Max Values, or Max Lengths. In this way I keep the the roles of the two different aspects of the system separate. The View Model is a translation from my DOM to a format that my View can work with and my DOM is specifically for defining what the data should look like in the database.

Resources