.NET MVC error client side with custom validation attribute

1.8k views Asked by At

I have written a very simple custom validation attribute to check for an alphanumeric string and it is working server side, but I get a NullReferenceException for validationContext when trying to use the client side model validation in .NET MVC. My custom attribute are written in a separate class library that the MVC application uses. The attribute and validator classes are here:

/// <summary>
    /// Custom validation attribute for an alphanumeric string
    /// </summary>
    public class AlphaNumericAttribute : ValidationAttribute
    {
        public AlphaNumericAttribute()
            : base()
        {
            ErrorMessage = ValidationMessages.AlphaNumeric;
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var RegExMatch = ValidationExpressions.AlphaNumeric.Match(value.ToString());
            if (!RegExMatch.Success)
            {
                return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
            }
            return null;
        }
    }

    /// <summary>
    /// Custom validator class for the AlphaNumeric attribute defined above.
    /// </summary>
    public class AlphaNumericValidator : DataAnnotationsModelValidator<AlphaNumericAttribute>
    {
        string errorMsg = string.Empty;

        public AlphaNumericValidator(ModelMetadata metadata,
        ControllerContext controllerContext, AlphaNumericAttribute attribute)
            : base(metadata, controllerContext, attribute) 
        {
            errorMsg = attribute.ErrorMessage;
        }

        public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
        {
            ModelClientValidationRule validationRule = new ModelClientValidationRule();
            validationRule.ValidationType = "alphanumeric";
            validationRule.ErrorMessage = Attribute.FormatErrorMessage(Metadata.DisplayName);
            return new[] { validationRule };
        }
    }

I then have a simple model class in my MVC application with a data annotation using the [AlphaNumeric] decorator:

[Required, Display(Name = "Product Code"), AlphaNumeric]
        public string ProductCode { get; set; }

Lastly I have my controller being passed the model class and use the standard ModelState.IsValid way of validating...

[HttpPost]
public ActionResult Index(CreateModel Model)
{
   if (ModelState.IsValid)
   {
      // Do some stuff
   }
   else return View(Model);
}

When I access my web form and submit, I get the null reference exception immediately before the controller's action method is ever called:

[NullReferenceException: Object reference not set to an instance of an object.]
   LibData.Validation.AlphaNumericAttribute.IsValid(Object value, ValidationContext validationContext) +75
   System.ComponentModel.DataAnnotations.ValidationAttribute.GetValidationResult(Object value, ValidationContext validationContext) +29
   System.Web.Mvc.DataAnnotationsModelValidator.Validate(Object container) +372
   System.Web.Mvc.<Validate>d__1.MoveNext() +393
   System.Web.Mvc.DefaultModelBinder.OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) +401
   System.Web.Mvc.DefaultModelBinder.BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Object model) +123
   System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) +2541
   System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) +633
   System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) +496
   System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor) +199
   System.Web.Mvc.Async.<>c__DisplayClass21.<BeginInvokeAction>b__19(AsyncCallback asyncCallback, Object asyncState) +1680
   System.Web.Mvc.Async.WrappedAsyncResult`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +59
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +151
   System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate`1 endDelegate, Object tag, Int32 timeout) +94
   System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state) +559
   System.Web.Mvc.Controller.<BeginExecuteCore>b__1c(AsyncCallback asyncCallback, Object asyncState, ExecuteCoreState innerState) +82
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +73
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +151
   System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object callbackState, BeginInvokeDelegate`1 beginDelegate, EndInvokeVoidDelegate`1 endDelegate, TState invokeState, Object tag, Int32 timeout, SynchronizationContext callbackSyncContext) +105
   System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state) +588
   System.Web.Mvc.Controller.<BeginExecute>b__14(AsyncCallback asyncCallback, Object callbackState, Controller controller) +47
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +65
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +151
   System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object callbackState, BeginInvokeDelegate`1 beginDelegate, EndInvokeVoidDelegate`1 endDelegate, TState invokeState, Object tag, Int32 timeout, SynchronizationContext callbackSyncContext) +139
   System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +484
   System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +50
   System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__4(AsyncCallback asyncCallback, Object asyncState, ProcessRequestState innerState) +98
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +73
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +151
   System.Web.Mvc.Async.AsyncResultWrapper.Begin(AsyncCallback callback, Object callbackState, BeginInvokeDelegate`1 beginDelegate, EndInvokeVoidDelegate`1 endDelegate, TState invokeState, Object tag, Int32 timeout, SynchronizationContext callbackSyncContext) +106
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +446
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +88
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +50
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +301
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

What am I missing here? If I use the other built in data annotations like regular expression, etc. I do not get this error. Do I need to register a handler of some sort with the MVC application? I am really at a loss with this. And yes, I could use a simple regular expression validation attribute for something this simple, but I need to understand the root of the problem as I have many more complicated validation attributes to write and would like to understand how to wire them up appropriately. Thanks...

1

There are 1 answers

3
Eduardo On

Rick, you do need some extra things for this to work on the client side. Here is a good tutorial on how to implement this: http://thewayofcode.wordpress.com/tag/custom-unobtrusive-validation/