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 .
[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.
public class IndustryMetaData
{
[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.
public interface IValidate
{
bool ValidateName(string Name, int IndustryId);
bool ValidateCode(string Code, int IndustryId);
}
and the implementation goes here:
public class ClientServerValidator : IValidate
{
public IDbContext Db { get; set; }
public bool ValidateName(string Name, int IndustryId)
{
if (IndustryId == 0)
return !Db.Repository<Industry>().Any(Industry => Industry.Name == Name);
else
return !Db.Repository<Industry>().Any(Industry => Industry.Name ==
Name && Industry.IndustryId=IndustryId);
}
public bool ValidateCode(string Code, int IndustryId)
{
if (IndustryId == 0)
return !Db.Repository<Industry>().Any(Industry => Industry.Code == Code);
else
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:
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
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using Microsoft.Practices.ServiceLocation; 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);
}
}
}
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 (response is bool)
{
return (bool)response ? ValidationResult.Success :
new ValidationResult(this.ErrorMessage);
}
return ValidationResult.Success;
}
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:
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.