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...
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/