Performing client-side validation in MVC4 View against ViewModel - c#

I would like to know what is the best approach to handle client-side, javascript or jQuery driven validation of MVC4 fields against attributes placed on a ViewModel's fields.
First, let's pick the example. A login creation screen for Administrators shown the first time the application starts (just not to say to the site owner "use admin/admin as login the first time").
ViewModel:
public class AdministratorViewModel : AbstractViewModel
{
[Required]
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblUsername")]
public string Username { get; set; }
[Required]
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblEmailAddress")]
[EmailAddress]
public string EmailAddress { get; set; }
[Required]
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblPassword")]
[AdminPassword]
public string Password { get; set; }
[Required]
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblPasswordConfirm")]
[Compare("Password")]
public string PasswordConfirm { get; set; }
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblLastLogin")]
public DateTime? LastLogin { get; set; }
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblPasswordExpiry")]
public DateTime? PasswordExpiry { get; set; }
[Display(ResourceType = typeof(ManageAdminsViewModelResources), Name = "lblBlocked")]
public bool Blocked { get; set; }
}
Partial view (only a few fields needed when creating the first admin)
#using (Html.BeginForm())
{
#Html.ValidationSummary(false)
#Html.AntiForgeryToken()
<fieldset>
<legend>#ManageAdminsViewResources.legendCreateAdmin</legend>
<div class="desktoptile">
#Html.LabelFor(m=>m.Username)
#Html.EditorFor(m => m.Username)
#Html.ValidationMessageFor(m => m.Username)
</div>
<div class="desktoptile">
#Html.LabelFor(m=>m.Password)
#Html.PasswordFor(m => m.Password)
#Html.ValidationMessageFor(m => m.Password)
</div>
<div class="desktoptile">
#Html.LabelFor(m=>m.PasswordConfirm)
#Html.PasswordFor(m => m.PasswordConfirm)
#Html.ValidationMessageFor(m => m.PasswordConfirm)
</div>
<div class="desktoptile">
#Html.LabelFor(m=>m.EmailAddress)
#Html.EditorFor(m => m.EmailAddress)
#Html.ValidationMessageFor(m => m.EmailAddress)
</div>
<input type="submit" value="#ManageAdminsViewResources.btnCreate"/>
</fieldset>
}
Controller
[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateFirst(AdministratorViewModel viewModel)
{
if (!ModelState.IsValid) return View(viewModel);
[...................]
Currently
If I enter an invalid email address, an empty password, etc. in the form and hit Submit I'm correctly notified of the errors. Ok, let's go on
What I want
Since I'm doing a Metro-stylish design, I would like that every time the user unfocuses a text box validation for that field occurs.
Writing hard-coded jQuery fragments is not the best option. I would like a data-driven approach, possibly embedded in MVC4 which I'm currently learning.
So, given a ViewModel with standard and custom attributes (for which, no matter what, a little Javascript is required, think about the Org.Zighinetto.AdminPasswordAttribute that checks password complexity), how do I enforce client-side validation the most unobtrusive way, without specifying client-side tags on each and every html tag and writing the least possible amount of code?
Is still there any secret in ASP.NET MVC 4 validation that I have to unhide?

Well, you would have to invoke jQuery validate using jQuery (because it's written in jQuery :))
You could add a global event for your inputs, then invoke it on the blurred element. Something like:
$("input").blur(function () {
$(this).valid();
});

From my (learning) point of view, the correct answer should be:
Explanation:
Validation of common attributes is done by MVC4 automatically when jQuery Unobtrustive Validator is loaded into the page, otherwise only server-side validation is performed.
Most, if not all of MVC4 ValidationAttributes implement IClientValidation. This interface wraps jQuery Validator validation functions in server-side code. It's hard to explain how exactly it works, but saying that this interface returns the name of the client-side function (either provided by jQuery distribution or implemented by user), while basically wrong at least gives the idea to a novice user trying to understand how validation works.
Answer:
Continue using data-driven model/viewmodel annotations.
Check if NuGet package jQuery Unobtrusive Validation is loaded in the page, then implement IClientValidation as needed (I found a tutorial here about multiple errors), fields are validated automatically.

Related

Require field if checkbox is checked

I have a field in a form that I need to be required if a checkbox in the same form is checked.
There doesn't seem to be a way to do this with regular model validation, so I created a remote validation method in my controller.
The problem is, since the field isn't required always, the validation doesn't even fire if I put it on the field. So I tried putting the validation on the checkbox, and now I get a different problem where the validation doesn't fire when I add text to the field.
Is there a way to do what I'm needing with custom validation, or do I need to do something in JavaScript? If so, what do I need to do?
Form:
<form>
<input type="checkbox" asp-for="NotRecommended" checked=#Model.NotRecommended /> <label>Not Recommended</label>
<textarea class="form-control" id="Notes" asp-for="Notes"></textarea>
<span asp-validation-for="NotRecommended" class="text-danger"></span>
</form>
Model:
public class DetailsViewModel
{
[DisplayName("Not Recommended")]
[Remote("RequireNoteForFlag", AdditionalFields = "Notes", ErrorMessage = "Note is required when flagging someone.")]
public bool NotRecommended { get; set; }
[DataType(DataType.MultilineText)]
[MaxLength(1500)]
[DisplayName("Notes")]
public string Notes { get; set; }
}
Remote Validator
public IActionResult RequireNoteForFlag(bool NotRecommended, string Notes)
{
if (NotRecommended && String.IsNullOrWhiteSpace(Notes)) return Json("Note is required when flagging an Assessor");
else return Json(true);
}
Joe gave me pretty much the whole answer in the comments, but hasn't posted it as an answer, so I'll post it for anyone else who might need to do this.
Create the Attribute
https://github.com/joeaudette/cloudscribe/blob/master/src/cloudscribe.Web.Common/DataAnnotations/RequiredWhenAttribute.cs
Joe's Client-Side Validation
https://github.com/cloudscribe/cloudscribe/blob/master/src/cloudscribe.Web.StaticFiles/js/cloudscribe-validation-requiredwhen.js
Modified Client-Side Validation
jQuery.validator.addMethod("requiredwhen", function (value, element, param) {
var otherPropId = $(element).data('val-other');
if (otherPropId) {
var otherProp = $(otherPropId)[0].checked;
if (otherProp) {
return element.value.length > 0;
}
}
return true;
});
jQuery.validator.unobtrusive.adapters.addBool("requiredwhen");
View
<input type="checkbox" asp-for="NotRecommended" /> <label asp-for="NotRecommended"></label>
<textarea class="form-control" id="Notes" asp-for="Notes"></textarea>
<span asp-validation-for="NotRecommended" class="text-danger"></span>
Model
[DisplayName("Not Recommended")]
public bool NotRecommended { get; set; }
[DataType(DataType.MultilineText)]
[MaxLength(1500)]
[DisplayName("Notes")]
[RequiredWhen("NotRecommended", true, AllowEmptyStrings = false, ErrorMessage ="A note is required when flagging an Assessor.")]
public string Notes { get; set; }
I have implemented this with a custom validator in a similar way. However I applied the annotation to the field that would be required and included a condition in the arguments, eg:
[RequiredIf("NotRecommended = true")]
public string Notes { get; set; }
But, as you may already be aware, there is no way to cross-reference another property in data annotations so you will need to build in the javascript to handle this.
Unobtrusive validation adds a data-val="true" and data-val-required="error message" attribute to the input element so adding this in via javascript when the checkbox is checked will work.
You will also need to reinitialise the form validation so that the inputs with newly applied validation attributes are added.

Data Binding in ASP.NET MVC

I know it would be a basic question but I'm a newbie to ASP.Net MVC. I have fetched data from database using LINQ but there is an issue. I wanna bind that data with input fields of a customized webform. (I'm using MVC). I wanna populate the input fields of webform with fetched data. I'm using EF Database first approach.
My Controller and view is attached.
Controller ActionMethod
public class HomeController : Controller
{
public ActionResult Index()
{
AutoRTGSEntities_1 dc = new AutoRTGSEntities_1();
//dc.policies.Where(cb => cb.Section_Key.Contains("SenderBIC"));
return View(dc.policies.Where(cb => cb.Policy_Section.Contains("RTGS")).ToList()); //get RTGS policy section data
}
}
View
#model IEnumerable<Swift_MT_103.policy>
#{
ViewBag.Title = "Home Page";
}
<div> #Model{ #Html.TextBoxFor(x => x.data_Value)) } </div>
<div> <input type="text" name="ReceiverBIC" id="ReceiverBIC" /> </div>
Rest is HTML and CSS. Snap is attached.
Here's a very basic example of how to this. Let's say you have following class:
public class User
{
public int Id { get; set; }
[Display(Name = "Name")]
public string Name { get; set; }
[Display(Name = "E-mailaddress")]
public string E-mail { get; set; }
}
In the controller you get the user:
public ActionResult Index(int id)
{
var user = Db.Users.FirstOrDefault(x => x.Id == id);
if(user != null)
{
return View(user);
}
//Return to the 'Error' view as no user was found
return View("Error");
}
You also need a View to show everything on screen. Make it a strongly typed view, this way you can pass a Model to it. This class will hold all data you want to pass to the view. Code of the view:
//This line lets the view know which class represents the model
#model User
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
Using the Razor syntax instead of plain HTML it is very easy to construct and bind your form elements to the corresponding data. In this case the label will show the value of the Display attribute in the User class and the values of the user will be filled in the textboxes.
More reading:
Getting started with ASP.NET MVC 5
ASP.NET MVC Overview
Update:
In case you have a list of objects, you need to enumerate them in the view:
#model IEnumerable<string>
#foreach (var value in Model)
{
<div>#value</div>
}
And if the model is a class and has a property that is a list:
//Let's say a user has lots of names
public class User
{
public int Id { get; set; }
public List<string> Names { get; set; }
}
//View:
#model User
#Html.TextBoxFor(m => m.Id)
#foreach (var name in Model.Names)
{
<div>#name</div>
}
Try to implement a correct ASP.NET MVC architecture. To get this completed, you'll need to use proper Razor (.cshtml type) Syntax in your Views. Best practice:
Create a dedicated ViewModel class in the Model directory. You might call it CustomerCreditTransferViewModel for example. It should contain all Properties you want to display/edit anywhere on the page.
Once you selected your data from your DBContext in your Action, create an instance of CustomerCreditTransferViewModel and populate all fields from the result.
Update your View to use #model CustomerCreditTransferViewModel instead of Swift_MT_103.policy (believe me, this is going to make your live much easier in future)
Copy-paste your raw HTML Code into the page and start looking for all Fields you want to bind, e.g. Text fields (<input type="text" name="accountno" value="" />) and replace them with the Razor Syntax for Data Binding (#Html.TextBoxFor(x => x.AccountNo)). If done correctly, they should be populated now.
Next step is probably the POST. Follow the base MVC Post technique from the Tutorials. Ensure that the Posted Value is of type CustomerCreditTransferViewModel) again, so you can easily validate values and map back to type of Swift_MT_103.policy.

Display message when user exceed limit size of a textbox in asp.net mvc app

I am developing an asp.net mvc app where an user can fill a textbox. The textbox field is required and the validation is performed when he clicks on submit.
But I have another concern, I would like to display a message when the user types more than 10 characters in the textbox. This needs to validate without clicking on any validate button.
This is my current code :
<span class="field">#Html.TextBoxFor(d => d.Name,new {maxlength=10})</span>
With this, the user cannot exceed the character limit, but there is no message displayed. I would like to display a message automatically when he tries to write the eleventh character.
I have followed the suggestion of adricadar, so my property looks like this now :
[Required]
[StringLength(10, ErrorMessage = "Too many characters.")]
public string Name { get; set; }
But now, when I am trying to access the page, I am getting this exception :
errormessagestring or errormessageresourcename must be set but not both
You can add on the Name property the StringLength attribute. This validate the input while the user is typing. This will include server and client side validation.
[StringLength(10, ErrorMessage = "Too many characters.")]
public string Name { get; set; }
Note: The attribute isn't adding to the input maxlength=10 attribute, it's adding a custom attribute data-val-length-max=10.
Don't forget to include #Scripts.Render("~/bundles/jqueryval").
You can use StringLength of DataAnnotation along with unobtrusive jquery which will show your error messages in real time. But you will have to enable unobtrusive before you can use it.
[Required]
[StringLength(5, ErrorMessage = "Too many characters",MinimumLength=2)]
public string Name { get; set; }
In view you can enable unobtrusive jquery like this
HtmlHelper.ClientValidationEnabled = true;
HtmlHelper.UnobtrusiveJavaScriptEnabled = true;
You can also enable this for whole application if you set this in web.config.
Finally you will have to add ValidationMessageFor in view to show errors associated with model.
So you final view code looks like this
#using (Html.BeginForm()) {
HtmlHelper.ClientValidationEnabled = true;
HtmlHelper.UnobtrusiveJavaScriptEnabled = true;
#Html.ValidationSummary()
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
#Html.ValidationMessageFor(m => m.Name)
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")

How to pass back model to controller using razor dropdownlist

I have a DropDownListFor control that I am wanting to show a display value that resides in a property within a model/class (this is the Rule class.) The view's model is actually a collection of these model/classes. However, when I select the item from the DropDownList, I want to send back the entire model as a parameter. I have this working perfectly with the following code, but the Name property within the parameter is coming back as null. The other properties all have appropriate values.
View Code:
#model List<StockTrader.Web.Data.Rule>
#{
ViewBag.Title = "Configure Rules";
}
<h2>#ViewBag.Title</h2>
<h4>Choose a rule to edit:</h4>
<form method="post" id="rulesform" action="SaveRules">
#Html.DropDownListFor(m => m.First().RuleID, new SelectList(Model.AsEnumerable(), "RuleID", "Name"))
<div style="margin-bottom: 15px;">
<label>Value:</label><br />
<input type="number" name="Value" style="margin-bottom: 15px;" /><br />
<button>Save Value</button>
</div>
Controller Code:
public ActionResult SaveRules(Rule model)
{
//do something
}
Rule Class:
public class Rule
{
public int RuleID { get; set; }
public string Name { get; set; }
public int Value { get; set; }
public bool IsDeleted { get; set; }
}
We do have Kendo controls, so if another control would be more appropriate, that is an option.
I would be glad to provide anymore code or information you might need.
Any thoughts or ideas?
EDIT:
So it turns out this is what I needed to do, the accepted answer got me to this point so I'm going to leave it checked.
View Code (w/script included):
#Html.DropDownListFor(m => m.First().RuleID, new SelectList(Model.AsEnumerable(), "RuleID", "Name"), new { id = "ruleid", #onchange = "CallChangefunc(this)" })
#Html.HiddenFor(m => m.First().Name, new { id = "rulename" })
function CallChangefunc(e) {
var name = e.options[e.selectedIndex].text;
$("#rulename").val(name);
}
You will need a hidden field for it,and use dropdownlist on change event on client side to update hidden field:
#Html.DropDownListFor(m => m.First().RuleID, new SelectList(Model.AsEnumerable(), "RuleID", "Name"),new { id= "ruleid" })
#Html.HiddenFor(m=>m.First().Name,new { id="rulename" })
and jquery code:
$("#ruleid").change(function(){
$("#rulename").val($(this).text());
});
Second option isif Rule collection is coming from database you can fetch RuleName by using id to by querying db in action.
it can be achieved by using UIHint
On your model class, on the RuleID property, add an annotation for UIHint. It basically lets you render a partial (cshtml) for the property. So, on the partial, you can have the template for generating the dropdwon with required styling. When Page is generated. Now you can use the same Html.DropDownListFor for RuleID and UI generates a dropdown for it.
This will avoid having additional jQuery code to get the dropdown value, and code is more concise and testable.

Hide property of model in dynamic view

I have a dynamic view, this will display any model that has been passed to it.
#model dynamic
#using (Html.BeginForm("Edit", null, FormMethod.Post, new { id="FrmIndex" }))
{
#Html.ValidationSummary(true);
#Html.EditorForModel()
<input type="submit" value="Edit" />
}
Say one of my model is PartyRole
public partial class PartyRole
{
[Key, Display(Name = "Id"]
[UIHint("Hidden")]
public int PartyRoleId { get; set; }
[UIHint("TextBox")]
public string Title { get; set; }
}
I dont want to show Id in edit mode, so I am hiding it in Hidden.cshtml editorfortemplate as below:
#Html.HiddenFor(m => Model)
This is hiding the editor, but not the label "Id".
And I cannot use the answers provided here, How to exclude a field from #Html.EditForModel() but have it show using Html.DisplayForModel()
because IMetadataAware requires System.Web.Mvc namespace which I cannot add in my Biz projects that are having the poco model classes.
I cannot use [HiddenInput(DisplayValue = false)] also because this is also party of web.mvc
can somebody give a solution??
I think that the thing to do is create a custom Object.cshtml editor template, as described in
http://www.headcrash.us/blog/2011/09/custom-display-and-editor-templates-with-asp-net-mvc-3-razor/
(nb. I found How to add assembly in web.config file of mvc 4 to be helpful with the System.Data.EntityState reference.)
Within that template you can put appropriate code to hide the label. The following is a dumb example - I guess that I'd probably try to pick up a custom attribute, though apparently this would involve an overload of DataAnnotationsModelMetadataProvider.
if (prop.HideSurroundingHtml)
{
#Html.Editor(prop.PropertyName)
}
else if (prop.PropertyName == "PartyRoleId")
{
<div></div>
}
else if (!string.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString()))
{
<div class="editor-label">#Html.Label(prop.PropertyName)</div>
}

Categories

Resources