How to: Implement Remote Validation in ASP.NET MVC

ASP.NET MVC 3 provides a mechanism that can make a remote server call in order to validate a form field without posting the entire form to the server. This is useful when you have a field that cannot be validated on the client and is therefore likely to fail validation when the form is submitted. For example, many Web sites require you to register using a unique user ID. For popular sites, it can take several attempts to find a user ID that is not already taken, and the user's input is not considered valid until all fields are valid, including the user ID. Being able to validate remotely saves the user from having to submit the form several times before finding an available ID.

The following illustration shows a new-user form that is displaying an error message that indicates that the requested ID is not available. The ID that users enter is validated as soon as they leave the User Name text box (that is, when the text box loses focus). Validation does not require a full postback.

Remote UID Validation

As an example of remote validation, this topic shows how to implement a form similar to the one in the previous illustration. The example can serve as a starting point to create your application-specific remote validation.

A Visual Studio starter project and a completed solution project are available to accompany this topic: Download.

Creating the Starter Project

  1. Create a new ASP.NET MVC 3 project with Razor syntax by following the instructions in Creating a MVC 3 Application with Razor and Unobtrusive JavaScript. Alternatively, open the ASP.NET MVC Web application starter project that is included in the download listed earlier.

  2. Run the application and click the Create New link.

  3. In the UserName text box, enter an existing user name such as "BenM".

  4. Enter values for the remaining fields and click Create.

    The following image shows the error message that results from submitting a non-unique user name.

    Create Full Fails

    As currently configured, the page performs a full postback to the server. On the server, the user name is not unique, so an exception is thrown and returned to the Entity Framework. The process of submitting the page to the server, attempting to make a database update, catching an exception, and returning an error message can take a noticeable amount of time.

    Tip

    You can use Fiddler or Firebug to show that client validation errors are handled by the client and not submitted to the server.

Examining the Validation Code

  1. In the project, open the Mvc3RemoteVal\Models\UserModel file and examine the contents.

    The StringLengthAttribute, RequiredAttribute, and RegularExpressionAttribute attributes provide both client-side and server-side validation for the user model. For more information, see How to: Validate Model Data Using DataAnnotations Attributes.

  2. Examine the Mvc3RemoteVal\Views\Shared\_Layout file.

    The HTML head section includes script elements that reference jQuery scripts that are required for client-side validation using attributes in the System.ComponentModel.DataAnnotations namespace. The script references point to the Microsoft Ajax Content Delivery Network (CDN). By taking advantage of the Microsoft Ajax CDN, you can significantly improve the performance of your Ajax applications. The contents of the Microsoft Ajax CDN are cached on servers that are located around the world. In addition, the Microsoft Ajax CDN enables browsers to reuse cached JavaScript files for Web sites that are located in different domains. The Microsoft Ajax CDN supports SSL (https://) in case you need to serve a Web page using the Secure Sockets Layer.

  3. Open the Mvc3RemoteVal\Web.config file.

    The appSettings element contains the following keys to enable client validation and enable unobtrusive JavaScript validation.

    <appSettings>
        <add key="ClientValidationEnabled" value="true" />
        <add key="UnobtrusiveJavaScriptEnabled" value="true" />
      </appSettings>
    

    You can also enable or disable these settings by calling ClientValidationEnabled and EnableUnobtrusiveJavaScript(Boolean). If those lines of code are present in the Global.asax file, they enable or disable unobtrusive JavaScript and client validation for the whole application. If the code appears in a controller or view, client validation and unobtrusive JavaScript will be enabled or for the current action only.

    Note

    Because no JavaScript is emitted when you use unobtrusive client validation, if you forget to include the validation scripts, you will not see any errors when the page loads. The only result is that the form values will not be validated in the browser.

  4. Optionally, disable client script in your browser, run the page again, and enter data that violates the validation constraints.

    As you leave the field that contains the invalid data, you do not see a validation error because scripting is disabled. Because ASP.NET MVC is using unobtrusive JavaScript, you do not see client-side script errors. However, server-side validation is performed when you submit the form. (It is a good practice to test your Web application with a browser that has scripting disabled.)

Adding Custom Remote Validation

This section describes how to add remote validation to verify that the user name that was entered is unique. If the user name is not unique, the server suggests a unique user name.

Configuring Remote Validation on the Server

  1. In Solution Explorer, right-click the Controllers folder, click Add, and then click Controller.

  2. Give the controller a name such as ValidationController. You could add this action method to any controller, such as the Home controller. However, a best practice is to put validation in its own controller in order to separate concerns and to make testing simpler.

  3. Replace the contents of the validation controller class with the following code:

    using System;
    using System.Globalization;
    using System.Web.Mvc;
    using System.Web.UI;
    using Mvc3RemoteVal.Models;
    
    namespace Mvc3RemoteVal.Controllers {
        [OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
        public class ValidationController : Controller {
    
            IUserDB _repository;
    #if  InMemDB
              public ValidationController() : this(InMemoryDB.Instance) { }
    #else
            public ValidationController() : this(new EF_UserRepository()) { }
    #endif
    
    
            public ValidationController(IUserDB repository) {
                _repository = repository;
            }
    
            public JsonResult IsUID_Available(string Username) {
    
                if (!_repository.UserExists(Username))
                    return Json(true, JsonRequestBehavior.AllowGet);
    
                string suggestedUID = String.Format(CultureInfo.InvariantCulture,
                    "{0} is not available.", Username);
    
                for (int i = 1; i < 100; i++) {
                    string altCandidate = Username + i.ToString();
                    if (!_repository.UserExists(altCandidate)) {
                        suggestedUID = String.Format(CultureInfo.InvariantCulture,
                       "{0} is not available. Try {1}.", Username, altCandidate);
                        break;
                    }
                }
                return Json(suggestedUID, JsonRequestBehavior.AllowGet);
            }
    
        }
    }
    
    Imports System.Globalization
    Imports System.Web.Mvc
    Imports System.Web.UI
    
    Namespace Mvc3RemoteVal
        <OutputCache(Location:=OutputCacheLocation.None, NoStore:=True)>
        Public Class ValidationController
            Inherits Controller
    
            Private _repository As IUserDB
    #If InMemDB Then
            Public Sub New()
                Me.New(InMemoryDB.Instance)
            End Sub
    #Else
              Public Sub New()
                  Me.New(New EF_UserRepository())
              End Sub
    #End If
    
            Public Sub New(ByVal repository As IUserDB)
                _repository = repository
            End Sub
    
            Public Function IsUID_Available(ByVal Username As String) As JsonResult
    
                If Not _repository.UserExists(Username) Then
                    Return Json(True, JsonRequestBehavior.AllowGet)
                End If
    
                Dim suggestedUID As String = String.Format(CultureInfo.InvariantCulture, 
                                            "{0} is not available.", Username)
    
                For i As Integer = 1 To 99
                    Dim altCandidate As String = Username & i.ToString()
                    If Not _repository.UserExists(altCandidate) Then
                        suggestedUID = String.Format(CultureInfo.InvariantCulture, 
                            "{0} is not available. Try {1}.", Username, altCandidate)
                        Exit For
                    End If
                Next i
                Return Json(suggestedUID, JsonRequestBehavior.AllowGet)
            End Function
    
        End Class
    End Namespace
    

    The code contains a constructor that initializes the validation controller that has a data-repository interface parameter. The constructor mimics the home controller constructor in how it initializes the data repository. If you want to work with a different data store, you can change the project properties so that the compilation constant InMemDB is not defined. In that case, the Entity Framework and a SQL Server database are used as the data repository. The unit tests included in the downloadable project use the in-memory data store.

    The OutputCacheAttribute attribute is required in order to prevent ASP.NET MVC from caching the results of the validation methods.

    The IsUID_Available method checks the candidate user name for uniqueness. If the submitted user name is unique, the method returns true as JSON-formatted content. If the submitted user name is taken, the method tries to find a unique name by appending an integer to the submitted name. If a unique name is found, the method returns the suggested name as JSON-formatted content. Any response other than true is considered false, so the client displays the message returned as an error message like "BenM is taken, try BenM1".

  4. Open the Models\UserModel file and add the RemoteAttribute attribute to the user name property in the create user model. The following example shows the completed code.

    public class CreateUserModel : EditUserModel {
        [Required]
        [StringLength(6, MinimumLength = 3)]
        [Remote("IsUID_Available", "Validation")]
        [RegularExpression(@"(\S)+", ErrorMessage = "White space is not allowed.")]
        [Editable(true)]
        public override string UserName { get; set; }
    }
    
    Public Class CreateUserModel
        Inherits EditUserModel
        <Remote("IsUID_Available", "Validation")>
        <Required()>
        <StringLength(6, MinimumLength:=3)>
        <RegularExpression("(\S)+", ErrorMessage:="White space is not allowed.")>
        <Editable(True)>
        Public Overrides Property UserName() As String
    End Class
    

    The RemoteAttribute attribute in the example takes the name of the action method IsUID_Available and the name of the controller where the validations is performed.

    The RemoteAttribute class creates a string that represents the URL to use to invoke the server-based validation. The ASP.NET MVC framework then submits the JSON-encoded GET request for the URL. In this example, if the user enters "BenM" in the User Name text entry box, the client will request the following URL:

    /Validation/IsUID_Available?UserName=BenM

Testing and Examining the Completed Application

In this section you will test the application and examine the HTML emitted by the Create view.

Testing Remote Validation

  1. Start the application and click the Create New link.

  2. In the user name input field, enter "BenM" and press Tab.

    The following validation error is displayed in red: "BenM is not available. Try BenM1." The ID that users enter is validated as soon as they leave the User Name text box (that is, when the text box loses focus). Validation does not require a full postback.

  3. Optionally, use a tool like Fiddler or Firebug to monitor the IsUID_Available validation call to the server. The following image shows the request and response from the Web server for the IsUID_Available validation call in Fiddler.

    Fiddler tool

    Note

    With Internet Explorer version 8 and earlier versions, you must append a period to localhost in the request URL for the Fiddler proxy in order to capture the HTTP traffic. Alternatively, use http://ipv4.fiddler or http://machinename/ instead of localhost in the URL.

  4. Examine the HTML source displayed by the browser for the Create view.

    The input element for the user name field contains HTML5 data-val-* attributes. (The -val portion signifies validation.) These HTML5-compatible attributes describe the validators to attach to the input fields and provide unobtrusive jQuery validation. The unobtrusive attributes have the format data-val-rule="Error Message", where rule is the validation rule (such as required, minimum string length, and maximum string length). If an error message is provided in the attribute, it is displayed as the value for the data-val-rule attribute. The RegularExpressionAttribute attribute applied to the user model in combination with the EditorForModel(HtmlHelper, Object) method in the Create view causes the following HTML5 unobtrusive client attributes and attribute values to be generated:

       data-val-regex="White space is not allowed." 
       data-val-regex-pattern="(\S)+" 
    

    The error message "White space is not allowed" comes from the error message in the RegularExpressionAttribute attribute applied to the user name property of the user model.

    The RemoteAttribute attribute used in the user model causes the following unobtrusive client validation attributes and attribute values to be generated:

    data-val-remote="&amp;#39;UserName&amp;#39; is invalid." data-val-remote-additionalfields="*.UserName" data-val-remote-url="/Validation/IsUID_Available"
    

    The &amp;#39;UserName&amp;#39; sequence is HTML-encoded and represents "Username". The data-val-remote attribute value contains the default error message ("'Username' is invalid"). UserName is a placeholder that for the actual user name submitted. The data-val-remote-additionalfields attribute value specifies the field or property to be validated. The data-val-remote-url attribute value specifies the action method that will validate the field.

See Also

Tasks

How to: Validate Model Data Using DataAnnotations Attributes

Walkthrough: Using Templated Helpers to Display Data in ASP.NET MVC

Other Resources

Unobtrusive Client Validation in ASP.NET MVC 3

Fiddler PowerToy - Part 1: HTTP Debugging

Custom Generic Remote Validatin

How to: Customize Data Field Validation in the Data Model Using Custom Attributes