7

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?

2
  • I am not sure on this but I think you pretty much have to roll out your own client validation for custom things like that. Maybe look at Jquery Validation-docs.jquery.com/Plugins/validation Commented Feb 9, 2011 at 23:04
  • Which version of ASP.NET MVC are you targetting? Commented Feb 9, 2011 at 23:04

5 Answers 5

7

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Good explanation, makes sense seeing it from start to finish
Thanks, @CRice. Hopefully this helps somebody else. I would have killed for something like this while MVC 3 was in beta, but it was all in bits and pieces.
@SheaDaniels "Unfortunately my code is in VB.NET since that's what I have to use at work." Thats reason enough for a new job!
3

ScottGu tweeted this morning how Pluralsight has free MVC 3 training for the next 48 hours. They have a video showing how to do this sort of custom validation. The relevant videos are under 'Models in ASP.NET MVC 3.0', specifically 'Custom Validation Attributes' and 'Self-validating models'.

2 Comments

Great videos. And brilliant marketing to give them away for two days.
+1 Great videos. I just registered for the free trial and watched the "Custom client validation" one. It was pretty thorough and has helped. I am now on a 10 day trial and might watch a few more before it runs out. They provide a good service.
1

I just saw something about the IValidatableObject interface in MVC 3 and I'll give that a try.

Comments

1

Derive your own validation attribute from ValidationAttribute and apply logic accordingly. In MVC 2, in order to perform validation of a property based on the value of another property, this has to be done inside of a Validator that you register to use with the custom validation attribute using (assuming your using DataAnnotationsModelValidatorProvider)

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(ValidationAttribute), typeof(ValidationValidator)); 

because in the validation attribute, you only have access to the property value to which the attribute is bound and not the model.

Take a look at MVC FoolProof Validation to see how this approach is done.

6 Comments

Does that automatically get picked up in the out of the box client side validation then?
@CRice - if deriving from one of the attributes in System.ComponentModel.DataAnnotations, then the client side will be taken care of for you. If defining your own validation attirbute, you will also need to take care of writing the client side logic yourself. Assuming your using jQuery validation, you would add a validator function to call to jQuery validation keyed by a string to use when the validation rule matches the key.
How does the system know what javascript to output to use for validating any custom class inheriting from System.ComponentModel.DataAnnotations.ValidationAttribute?
@CRice- your Validator registered against your Validation attribute has a method called GetClientValidationRules. In this method, you assign a string value to ValidationType on a returned ModelClientValidationRule type that indicates the name of the rule on the client side to use.
|
0

I think solution to your problem is System.ComponentModel.DataAnnotations in MVC

For complex logic you can Implement your own logic. Its very easy. Have a look for customising on this link : http://msdn.microsoft.com/en-us/library/cc668224.aspx

For basic thing make you View Binded with Model Class and add attribute on top of properties... Like

public class CustomerSearchDE
{
    [StringLength(2, ErrorMessageResourceType = typeof(Translation), ErrorMessageResourceName = MessageConstants.conCompanyNumberMaxLength)]
    public string CompanyNumber { get; set; }
 }

View Strongly type with this Class

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.