I've been tinkering with the client side validation features in ASP.net MVC after reading ScottGU's blog post on the subject. It's pretty easy to use the System.Componentmodel.DataAnnotations attributes like this:
[Required(ErrorMessage = "You must specify a reason")]
public string ReasonText { get; set; }
... but what happens if you need something just a little more complex. What if you have an Address class with a PostalCode and CountryCode field. You would want to validate the postal code against a different regex for each country. [0-9]{5} works for the USA, but you need a different one for Canada.
I got around that by rolling my own ValidationService class that takes the ModelState property of the controller and validates it accordingly. This works great on the server side but doesn't work with the fancy new client side validation.
In Webforms I would use javascript-emitting controls like RequiredFieldValidator or CompareValidator for the easy stuff and then use a CustomValidator for the complex rules. This way I have all my validation logic in one place, and I get the benefit of rapid javascript validation for simple stuff (90% of the time) while I still get the security of server side validation as a backstop.
What would be the equivalent approach in MVC?
Firstly, you just need to create an ASP.NET MVC application. To create a new ASP.NET MVC application, Open Visual Studio choose File, New, then Project. It will open a New Project window, from where you need to choose node Visual C# and then Web and from the right pane you need to choose ASP.NET Web Application.
Client side validation Vs server side validation The user input validation take place on the Server Side during a post back session is called Server Side Validation and the user input validation take place on the Client Side (web browser) is called Client Side Validation.
You can enable client-side validation for a specific view only by adding Html. EnableClientValidation(true) at the top on the view page.
Edit: This assumes you are using MVC 3. Unfortunately my code is in VB.NET since that's what I have to use at work.
In order to make everything work nicely with the new unobtrusive validation there are a few things you have to do. I powered through them a couple of weeks ago.
First, create a custom attribute class that inherits from ValidationAttribute
. A simple RequiredIf attribute class is below:
Imports System.ComponentModel
Imports System.ComponentModel.DataAnnotations
<AttributeUsage(AttributeTargets.Field Or AttributeTargets.Property, AllowMultiple:=False, Inherited:=False)> _
Public NotInheritable Class RequiredIfAttribute
Inherits ValidationAttribute
Private Const _defaultErrorMessage As String = "'{0}' is required."
Private ReadOnly _dependentProperty As String
Private ReadOnly _targetValues As Object()
Public Sub New(dependentProperty As String, targetValues As Object())
MyBase.New(_defaultErrorMessage)
_dependentProperty = dependentProperty
_targetValues = targetValues
End Sub
Public Sub New(dependentProperty As String, targetValues As Object(), errorMessage As String)
MyBase.New(errorMessage)
_dependentProperty = dependentProperty
_targetValues = targetValues
End Sub
Public ReadOnly Property DependentProperty() As String
Get
Return _dependentProperty
End Get
End Property
Public ReadOnly Property TargetValues() As Object()
Get
Return _targetValues
End Get
End Property
Public Overrides Function FormatErrorMessage(name As String) As String
Return String.Format(Globalization.CultureInfo.CurrentUICulture, ErrorMessageString, name)
End Function
Protected Overrides Function IsValid(value As Object, context As ValidationContext) As ValidationResult
' find the other property we need to compare with using reflection
Dim propertyValue = context.ObjectType.GetProperty(DependentProperty).GetValue(context.ObjectInstance, Nothing).ToString()
Dim match = TargetValues.SingleOrDefault(Function(t) t.ToString().ToLower() = propertyValue.ToLower())
If match IsNot Nothing AndAlso value Is Nothing Then
Return New ValidationResult(FormatErrorMessage(context.DisplayName))
End If
Return Nothing
End Function
End Class
Next, you need to implement a validator class. This class is responsible for letting MVC know the client validation rules that are required for the unobtrusive validation library to work.
Public Class RequiredIfValidator
Inherits DataAnnotationsModelValidator(Of RequiredIfAttribute)
Public Sub New(metaData As ModelMetadata, context As ControllerContext, attribute As RequiredIfAttribute)
MyBase.New(metaData, context, attribute)
End Sub
Public Overrides Function GetClientValidationRules() As IEnumerable(Of ModelClientValidationRule)
Dim rule As New ModelClientValidationRule() With {.ErrorMessage = ErrorMessage,
.ValidationType = "requiredif"}
rule.ValidationParameters("dependentproperty") = Attribute.DependentProperty.Replace("."c, HtmlHelper.IdAttributeDotReplacement)
Dim first As Boolean = True
Dim arrayString As New StringBuilder()
For Each param In Attribute.TargetValues
If first Then
first = False
Else
arrayString.Append(",")
End If
arrayString.Append(param.ToString())
Next
rule.ValidationParameters("targetvalues") = arrayString.ToString()
Return New ModelClientValidationRule() {rule}
End Function
End Class
Now you can register everything in the application start method of Global.asax
:
DataAnnotationsModelValidatorProvider.RegisterAdapter(GetType(RequiredIfAttribute), GetType(RequiredIfValidator))
This gets you 90% of the way there. Now you just need to tell JQuery validate and MS's unobtrusive validation layer how to read your new attributes:
/// <reference path="jquery-1.4.1-vsdoc.js" />
/// <reference path="jquery.validate-vsdoc.js" />
/* javascript for custom unobtrusive validation
==================================================== */
(function ($) {
// this adds the custom "requiredif" validator to the jQuery validate plugin
$.validator.addMethod('requiredif',
function (value, element, params) {
// the "value" variable must not be empty if the dependent value matches
// one of the target values
var dependentVal = $('#' + params['dependentProperty']).val().trim().toLowerCase();
var targetValues = params['targetValues'].split(',');
// loop through all target values
for (i = 0; i < targetValues.length; i++) {
if (dependentVal == targetValues[i].toLowerCase()) {
return $.trim(value).length > 0;
}
}
return true;
},
'not used');
// this tells the MS unobtrusive validation layer how to read the
// HTML 5 attributes that are output for the custom "requiredif" validator
$.validator.unobtrusive.adapters.add('requiredif', ['dependentProperty', 'targetValues'], function (options) {
options.rules['requiredif'] = options.params;
if (options.message) {
options.messages['requiredif'] = options.message;
}
});
} (jQuery));
Hope this helps, this was a real pain to get working.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With