суббота, 26 февраля 2011 г.

Remote validation in ASP.NET MVC 2 with jQuery

In my research for CQRS concept project I want to use ASP.NET DataAnnotations for automatically support validation for my view models, but I want to use in the same way validation rules on client side, in standard DataAnnotations it supports client code associations for standard attributes provided with ASP.NET script. Unfortunately, in current version of ASP.NET MVC 2 doesn't support remote validation using ajax call, in MvcContrib project we can take implementation, but it doesn't meet my requirements and during testing I find some cases where it completely even doesn't be called. Simple case was when we submit form, so we can't process validation result in JavaScript callback, because page have been already sent.
I create additional params, so right now we can control processing remote validation, using jQuery synchronous ability.
JavaScript validation function:
Sys.Mvc.ValidatorRegistry.validators.remote = function (rule) {
    var url = rule.ValidationParameters.url;
    var parameterName = rule.ValidationParameters.parameterName;
    var isAsync = rule.ValidationParameters.isAsync=='true' || false;
    var message = rule.ErrorMessage;
    var result = true;

    var onComplete = function (responseData) {
        var lowerData = responseData.toLowerCase();
        if (lowerData != 'true') {
            var newMessage = (lowerData == 'false' ? message : responseData);
            result = newMessage;
        }
    };

    return function (value, context) {
        if (!value || !value.length) {
            return true;
        }

        if (context.eventName == 'blur') {
            return true;
        }

        var ajaxCallParam = {
            url: url,
            async: isAsync,
            data: $.param([{ name: parameterName, value: value}])
        }

        if (isAsync) {
            ajaxCallParam.success = onComplete;
        }

        var responseData = $.ajax(ajaxCallParam);

        if (!isAsync) {
            onComplete(responseData.responseText);
        }

        return result;
    };
}; 
Where custom Remote validation attribute:
public sealed class RemoteValidationAttribute : ValidationAttribute
{
    public string Action { get; set; }
    public string Area { get; set; }
    public string Controller { get; set; }
    public string ParameterName { get; set; }
    public string RouteName { get; set; }

    public bool IsAsync { get; set; }


    public override bool IsValid(object value)
    {
        return true;
    }
} 
With actual Remote Validator:
public class RemoteValidator : DataAnnotationsModelValidator
{
    public RemoteValidator(ModelMetadata metadata, ControllerContext context,
                           RemoteValidationAttribute attribute) :
                               base(metadata, context, attribute)
    {
    }

    public override IEnumerable GetClientValidationRules()
    {
        var rule = new ModelClientValidationRule
                       {
                           ErrorMessage = ErrorMessage,
                           ValidationType = "remote"
                       };

        rule.ValidationParameters["url"] = GetUrl();
        rule.ValidationParameters["parameterName"] = Attribute.ParameterName;
        rule.ValidationParameters["isAsync"] = Attribute.IsAsync.ToString().ToLower();
        return new[] {rule};
    }

    private string GetUrl()
    {
        var rvd = new RouteValueDictionary
                                       {
                                           {"area", Attribute.Area ?? string.Empty },
                                           {"controller", Attribute.Controller},
                                           {"action", Attribute.Action}
                                       };

        var virtualPath = RouteTable.Routes.GetVirtualPath(ControllerContext.RequestContext,
                                                           Attribute.RouteName, rvd);
        if (virtualPath == null)
        {
            throw new InvalidOperationException("No route matched!");
        }

        return virtualPath.VirtualPath;
    }
}
That we have to register on application start up:
DataAnnotationsModelValidatorProvider.RegisterAdapter(
    typeof(RemoteValidationAttribute),
    typeof(RemoteValidator)
    );

ASP.NET MVC 3 presented remote validation out of box, but previous implementation still need such type of functionality. My implementation based on original post by Brad Wilson.

Thank you, and I am always waiting for any feedback.