Custom MVC Model Binder

No Comments

When you expect data in non-standard way and you want it to magically bind to your model, custom model binders could be a way to go.

Let’s say we want to post xml data to our controller method and we have a model for it.

public class XmlModel
        {
            public int Number { get; set; }
        }

Which we want to accept in our controller method:

public ActionResult Xml(XmlModel xmlModel)
        {
            return Content(xmlModel.Number.ToString());
        }

Now, let’s add our xml binder:

public class XmlModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var modelType = bindingContext.ModelType;
            return new XmlSerializer(modelType).Deserialize(controllerContext.HttpContext.Request.InputStream);
        }
    }

And XMLModelBinderProvider:

public class XMLModelBinderProvider : IModelBinderProvider
    {      
        public IModelBinder GetBinder(Type modelType)
        {
            var contentType = HttpContext.Current.Request.ContentType;
            if (string.Compare(contentType, @"text/xml", StringComparison.OrdinalIgnoreCase) != 0)
            {
                return null;
            }

            return new XmlModelBinder();
        }
    }

Add the provider to our Application_Start method in the Global.asax:

ModelBinderProviders.BinderProviders.Insert(0, new XMLModelBinderProvider()); 

To test this we can use Composer from Fiddler:
http://localhost/home/xml
User-Agent: Fiddler
Content-Type: text/xml

Request Body:

<XmlModel>
<Number>42</Number>
</XmlModel>

If we add breakpoint in our controller, we should see 42 coming through in our model.

Categories: C#, MVC, Web

Passing custom objects to FluentValidation

No Comments

This is a guide for passing custom objects to Fluent Validators. This can be helpful for more complex validation scenarios.

Here’s an example of what we want to achieve:

public class OurModelValidator: AbstractValidator<OurMVCViewModel>
{
public OurModelValidator(OurClassWeWantToPassToValidator ourClassWeWantToPassToValidator)
{
if (ourClassWeWantToPassToValidator.SomeCondition)
{
RuleFor(m => m.PropertyA).NotEmpty();
}
else
{
RuleFor(m => m.PropertyB).NotEmpty();
}
}
}

In order to achieve this, we need to make some changes to the FluentValidationModelValidatorProvider class by overriding its CreateValidator method:

public class OurCustomFluentModelValidatorProvider : FluentValidationModelValidatorProvider
{

protected override IValidator CreateValidator(ModelMetadata metadata, ControllerContext context)
{
var type = this.IsValidatingProperty(metadata) ? metadata.ContainerType : metadata.ModelType;
return GetOurModelValidator(type) ?? this.ValidatorFactory.GetValidator(type);
}

private IValidator GetOurModelValidator(Type modelType)
{
if (modelType == typeof(OurClassWeWantToPassToValidator))
{
var ourClassWeWantToPassToValidator = new OurClassWeWantToPassToValidator();
return new OurModelValidator(ourClassWeWantToPassToValidator);
}

return null;
}
}

Since we want FluentValidation to handle all situation when OurModelValidator is not applicable, we use built-in ValidatorFactory to return default Validator when validating anything but OurMVCViewModel.

And the last step is to register our new provider:

ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new DataAnnotationsModelValidatorProvider());
ModelValidatorProviders.Providers.Add(new DataErrorInfoModelValidatorProvider());
ModelValidatorProviders.Providers.Add(new ClientDataTypeModelValidatorProvider());
ModelValidatorProviders.Providers.Add(new OurCustomFluentModelValidatorProvider());
Categories: MVC, Web Tags: Tags:

FluentValidation Client-Side Custom Validation with MVC

5 Comments

FluentValidation is “A small validation library for .NET that uses a fluent interface and lambda expressions for building validation rules for your business objects.” It is quite awesome. However, the provided client-side validation is limited:  “any rules defined using a condition (with When/Unless), custom validators, or calls to Must will not run on the client side.” Below is some my first take on using FluentValidation on both server and client-side.

First, you need to install the nuget package. In our case, it is FluentValidation.MVC4.

We needed to make a certain property required, but only when some other property is true. To achieve this on client-side (server-side is trivial), we added these:

	public class RequiredIfClientSideValidator : PropertyValidator
	{
		public string DependentProperty { get; set; }
		public object TargetValue { get; set; }

		public RequiredIfClientSideValidator(string errorMessage, string dependentProperty, object targetValue)
			: base(errorMessage)
		{
			this.DependentProperty = dependentProperty;
			this.TargetValue = targetValue;
		}

		protected override bool IsValid(PropertyValidatorContext context)
		{
			//This is not a server side validation rule. So, should not effect at the server side.  
			return true;
		}
	}

And this

	public class RequiredIfClientSideFluentPropertyValidator : FluentValidationPropertyValidator
	{
		private RequiredIfClientSideValidator RequiredIfClientSideValidator
		{
			get { return (RequiredIfClientSideValidator)Validator; }
		}

		public RequiredIfClientSideFluentPropertyValidator(ModelMetadata metadata,
													   ControllerContext controllerContext,
													   PropertyRule propertyDescription,
													   IPropertyValidator validator)
			: base(metadata, controllerContext, propertyDescription, validator)
		{
			ShouldValidate = false;
		}

		public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
		{
			if (!ShouldGenerateClientSideRules()) yield break;

			var formatter = new MessageFormatter().AppendPropertyName(Rule.GetDisplayName());
			string message = formatter.BuildMessage(RequiredIfClientSideValidator.ErrorMessageSource.GetString());

			var rule = new ModelClientValidationRule()
			{
				ValidationType = "requiredif",
				ErrorMessage = message
			};

			string depProp = BuildDependentPropertyId(Metadata, ControllerContext as ViewContext);
			// find the value on the control we depend on;
			// if it's a bool, format it javascript style 
			// (the default is True or False!)
			string targetValue = (RequiredIfClientSideValidator.TargetValue ?? "").ToString();
			if (RequiredIfClientSideValidator.TargetValue.GetType() == typeof(bool))
				targetValue = targetValue.ToLower();

			rule.ValidationParameters.Add("dependentproperty", depProp);
			rule.ValidationParameters.Add("targetvalue", targetValue);

			yield return rule;
		}

		private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
		{
			// build the ID of the property
			string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(RequiredIfClientSideValidator.DependentProperty);
			// unfortunately this will have the name of the current field appended to the beginning,
			// because the TemplateInfo's context has had this fieldname appended to it. Instead, we
			// want to get the context as though it was one level higher (i.e. outside the current property,
			// which is the containing object (our Person), and hence the same level as the dependent property.
			var thisField = metadata.PropertyName + "_";
			if (depProp.StartsWith(thisField))
				// strip it off again
				depProp = depProp.Substring(thisField.Length);
			return depProp;
		}
	}

Additionally, we introduced a validator for simple client-side validation (ClientSideValidator) where you just need to pass an error message to your JavaScript validation.

public class ClientSideValidator : PropertyValidator
	{
		public string JavascriptValidationAdapterName { get; set; }

		public ClientSideValidator(string errorMessage, string javascriptValidationFunctionName)
			: base(errorMessage)
		{
			JavascriptValidationAdapterName = javascriptValidationFunctionName;
		}

		protected override bool IsValid(PropertyValidatorContext context)
		{
			//This is not a server side validation rule. So, should not effect at the server side.  
			return true;
		}
	}

And an accompanying class:

	
	public class ClientSideFluentValidator : FluentValidationPropertyValidator
	{
		private ClientSideValidator ClientSideValidator
		{
			get { return (ClientSideValidator)Validator; }
		}

		public ClientSideFluentValidator(ModelMetadata metadata,
			ControllerContext controllerContext,
			PropertyRule propertyDescription,
			IPropertyValidator validator)
			: base(metadata, controllerContext, propertyDescription, validator)
		{
			ShouldValidate = false;
		}

		public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
		{
			if (!ShouldGenerateClientSideRules()) yield break;

			var formatter = new MessageFormatter().AppendPropertyName(Rule.GetDisplayName());
			string message = formatter.BuildMessage(ClientSideValidator.ErrorMessageSource.GetString());

			var rule = new ModelClientValidationRule()
			{
				ValidationType = ClientSideValidator.JavascriptValidationAdapterName,
				ErrorMessage = message
			};
			
			yield return rule;
		}
	}

Register your custom validators in Global.asax.cs:

FluentValidationModelValidationFactory requiredIfClientSideValidationFactory =
				(metadata, context, rule, validator) =>
					new RequiredIfClientSideFluentPropertyValidator(metadata, context, rule, validator);
			
			FluentValidationModelValidationFactory clientSideValidationFactory =
				(metadata, context, rule, validator) =>
					new ClientSideFluentValidator(metadata, context, rule, validator);
			
			FluentValidationModelValidatorProvider.Configure(provider =>
			{
				provider.Add(typeof(RequiredIfClientSideValidator), requiredIfClientSideValidationFactory);
				provider.Add(typeof(ClientSideValidator), clientSideValidationFactory);
			});

Don’t forget to add the JavaScript part:
For requiredif validation:

$.validator.addMethod('requiredif',
    function (value, element, parameters) {
    	var id = '#' + parameters['dependentproperty'];

    	// get the target value (as a string, 
    	// as that's what actual value will be)
    	var targetvalue = parameters['targetvalue'];
    	targetvalue =
          (targetvalue == null ? '' : targetvalue).toString();

    	// get the actual value of the target control
    	// note - this probably needs to cater for more 
    	// control types, e.g. radios
    	var control = $(id);
    	var controltype = control.attr('type');
    	var actualvalue =
            controltype === 'checkbox' ?
            control.attr('checked').toString() :
            control.val();

    	// if the condition is true, reuse the existing 
    	// required field validator functionality
    	if (targetvalue.toLowerCase() === actualvalue.toLowerCase())
    		return $.validator.methods.required.call(
              this, value, element, parameters);

    	return true;
    }
);

$.validator.unobtrusive.adapters.add(
    'requiredif',
    ['dependentproperty', 'targetvalue'],
    function (options) {
    	options.rules['requiredif'] = {
    		dependentproperty: options.params['dependentproperty'],
    		targetvalue: options.params['targetvalue']
    	};
    	options.messages['requiredif'] = options.message;
    });

And for propertycvalidator:

$.validator.addMethod('propertycvalidator', function (value, element, parameters) {
			//put your client-side validation logic here
			return true;
		});

		$.validator.unobtrusive.adapters.addBool('propertycvalidator');

And, now we are ready to create our model validator:

		public class MyValidator()
		{
			//Make SomeProperty required. It will be validated on server and client side.
			RuleFor(m => m.SomeProperty)
				.NotEmpty().WithMessage("Some Property is required");
			//SomeOtherProperty will be validated on server side - it is required when PropertyB is true.
			//Client side validation is done by using RequiredIfClientSideValidator and passing dependent property 
			//name to unubtrusive JavaScript validation
			RuleFor(m => m.SomeOtherProperty)
				.NotNull()
				.WithMessage("Some other property is required")
				.When(model => model.PropertyB)
				.SetValidator(new RequiredIfClientSideValidator(
					"Some other property is required",
					"PropertyB",
					true));

			//this will get validated on client side only
			RuleFor(m => m.PropertyC)				
				.SetValidator(
					new ClientSideValidator(
						"Error message",
						"propertycvalidator")); // must be lower case
		}

The last part is adding the attribute to your model:
[Validator(typeof(MyValidator))]
public class YourModel
{
}

Categories: MVC, Web Tags: Tags: