Click here to Skip to main content
15,896,063 members
Articles / Web Development / ASP.NET

Custom Remote Attribute for Client/Server Validation

Rate me:
Please Sign up or sign in to vote.
4.56/5 (3 votes)
14 Nov 2013CPOL2 min read 16.8K   12  
Customise the existing Remote attribute that performs cleint validation using Javascript to trigger Server Side validation also

Introduction

We know that the Remote Attribute in the MVC4 validates the code in the Client Side that works when Javascript is enabled. If Javascript is disabled we must do the validation in the Server side also.

This code will give you an idea of how you can create custom Remote Attribute in MVC4 that handles both client and server validation so that if Js is disabled in browser, it still validates the code in the server side without repeating the validation code logic for server side separately.

Background

I was creating a simple Data entry screen for the creation of Industry which has Name and ID. To check if the Industry already exists in the Database I need to do a validation which returns the Error message :Industry already exists if available. This piece of code using Remote attribute will fire only in Client Side and for Server side we need to make another call. So to avoid repetition I had created the below implementaion.

For that I need to have validation that takes care both Client and server side.

Here I use Entity Framework and IOC principle.

Using the code

Step 1

Open Visual Studio - Create new MVC project using the template Internet application. Add reference to Entity Framework to the project and do the necessary set up for the project to use the EF.

Step 2

Go to Models folder- Create the class (model) for the table that exists in the Database named Industry to which we are going to do the validation .

C#
[MetadataType(typeof(IndustryMetaData))]
public  class Industry 
{ 
    public int IndustryId { get; set; }
    public string Name { get; set; }
    public string Code { get; set; } 
}

and a separate class for metadata for the Industry class to have separation concern with just using the Remote attribute which fires validation only in Client side if Javascript is enabled.

C#
public class IndustryMetaData
{
    //[RemoteAttribute(Class/Controller Name, Action/Method Name, Any additional fields
    //to be sent, What is the error message to be displayed)] eg Remote("Home","Index")
    [Remote("ValidateRegistrationNo", "ClientServerValidator", 
      AdditionalFields = "IndustryId", ErrorMessage = "Registration No already Exists")] 
    public int IndustryId { get; set; }
    [Remote("ValidateCode", "ClientServerValidator", 
      AdditionalFields = "IndustryId", 
      ErrorMessage = "Company Code already Exists")]
    public string Code { get; set; } 
}

Step 3

To have the generic implementation using the IOC principle, I am going to create the Interface and its implementation for the validator.

C#
public interface IValidate
{ 
    bool ValidateName(string Name, int IndustryId);
    bool ValidateCode(string Code, int IndustryId);
}

and the implementation goes here:

C#
public class ClientServerValidator : IValidate
{
    public IDbContext Db { get; set; }
    //Write your own logic here to check if the item already exists in the Database or not
    public bool ValidateName(string Name, int IndustryId)
    {
        //used for the new Industry Creation page
        if (IndustryId == 0)
            return !Db.Repository<Industry>().Any(Industry => Industry.Name == Name);
        else
        //used for the Edit/Update Industry page            
            return !Db.Repository<Industry>().Any(Industry => Industry.Name == 
                   Name && Industry.IndustryId=IndustryId);            
    }
    public bool ValidateCode(string Code, int IndustryId)
    {
        //used for the new Industry Creation page
        if (IndustryId == 0)
            return !Db.Repository<Industry>().Any(Industry => Industry.Code == Code);
        else
        //used for the Edit/Update Industry page            
            return !Db.Repository<Industry>().Any(Industry => Industry.Code == 
                    Code && Industry.IndustryId != IndustryId);
    }
}

Note: If you don't use the IOC principle then simply create the class ClientServerValidator without Interface or you can also directly write the method into the Controller

Step 4

(Only those who use IOC container) Others Skip this step

Register your interface in the installer, so that whenever you ask for Interface the IOC container will give its implementation:

C#
public class Installer : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {            
        container.Register(
          Component.For<IValidate>().ImplementedBy<ClientServerValidator>().LifeStyle.PerWebRequest
          );
    }
}

Step 5

Create the custom Remote Attribute class deriving from Remote Attribute and override the IsValid method of the Remote Attribute Class

you need to import the below namespaces for the code to work

C#
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using Microsoft.Practices.ServiceLocation; //for Dependency Injection
using System.Web.Mvc 

public class CustomRemoteAttribute : RemoteAttribute
{
    protected override ValidationResult IsValid(object value,ValidationContext Context)
    {
        List<object> propValues = new List<object>();
        propValues.Add(value);
        if(!(string.IsNullOrWhiteSpace(this.AdditionalFields)||
                string.IsNullOrEmpty(this.AdditionalFields)))
          {
            string[] additionalFields = this.AdditionalFields.Split(',');
            foreach (string additionalField in additionalFields)
            {
                PropertyInfo prop = validationContext.ObjectType.GetProperty(additionalField);
                if (prop != null)
                {
                    object propValue = prop.GetValue(validationContext.ObjectInstance, null);
                    propValues.Add(propValue);
                }
            }
         }
         // IOC will give the implementation  class
         // "ClientServerValidator" when we requested for Ivalidate 
         //you can make it more generic by   T validator = ServiceLocator.Current.GetInstance<T>(); 
         IValidate validator     = ServiceLocator.Current.GetInstance<IValidate>();
         Type type               = validator.GetType();
         MethodInfo method       = type.GetMethods().FirstOrDefault(callingMethod => 
           callingMethod.Name.ToLower() == (string.Format("{0}", 
           this.RouteData["action"]).ToString().ToLower()));
         object response         = method.Invoke(validator, propValues.ToArray()); 
         //If you don't use the IOC then you can achieve 
         // the same using Reflection using the /below code blocks
         /*
            Type type =Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(
              validationtype=>validationtype.Name==string.Format("{0}controller",
              this.RouteData["controller"].ToString());
            if(type!=null)
            {
                 MethodInfo method = type.GetMethods().FirstOrDefault(callingMethod => 
                   callingMethod.Name.ToLower() == (string.Format("{0}", 
                   this.RouteData["action"]).ToString().ToLower()));
                 if(method!=null)
                {
                    object instance =Activator.CreateInstance(type);
                    object response = method.Invoke(instance,propValues.ToArray());
                }
            }     */            
            if (response is bool)
            {
                return (bool)response ? ValidationResult.Success : 
                       new ValidationResult(this.ErrorMessage);
            }
            return ValidationResult.Success; 
        }     
        //inheriting the Remote Attribute constructors for our custom class
        public RemoteClientServerAttribute(string routeName) : base(routeName) { }
        public RemoteClientServerAttribute(string action, string controller) : base(action, controller) { }
        public RemoteClientServerAttribute(string action, string controller, 
               string area) : base(action, controller, area) { }
}

Step 6

Replace the Remote attribute of the metadata class with our custom remote attribute:

C#
public class IndustryMetaData
{
    [CustomRemote("ValidateName", "ClientServerValidator", 
       AdditionalFields = "IndustryId", ErrorMessage = "Industry already Exists")]
    public string Name { get; set; }
    [CustomRemote("ValidateCode", "ClientServerValidator", 
      AdditionalFields = "IndustryId", 
      ErrorMessage = "Industry Code already Exists")]
    public string Code { get; set; }
}

That's all place the breakpoint and debug to see the validation fires both in client side and server side.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --