FluentValidation Client-Side Custom Validation with MVC

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
{
}

Migrating Windows Server 2003 to 2012

This is an overview of my effort to upgrade some of our old 2003 servers to Windows Server 2012. It is quite possible there are better ways to accomplish this, as I’m just a developer and server upgrade is not something I do… well it was the first time I did it.

Migrating IIS 6.0 to 8.5

First, I needed to migrate IIS applications, app pools and app pools account. In order to automate this as much as possible, I installed Microsoft Web Platform installer on 2003 and Web Deploy 3.5 (The Web Deployment Tool). I used this guide http://www.iis.net/learn/publish/using-web-deploy/synchronize-iis-60-web-sites to help me.

Create backup on IIS6: iisback /backup /b PreWebDeploy

View dependencies on IIS 6: msdeploy -verb:getDependencies -source:metakey=lm/w3svc/1

Create package on IIS 6:

“C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe” -verb:sync  -source:metakey=lm/w3svc/1 -dest:package=C:\YourExportedPackage.zip,encryptPassword=YourPassword  -enableLink:AppPoolExtension

The password is required since we are exporting App Pools and the identities they use (by specifying enableLink:AppPoolExtension).

Run whatif analysis on IIS 2012:

“C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe” -verb:sync -source:package=c:\YourPackage.zip,encryptPassword=YourPassword -dest:metakey=lm/w3svc/1 -whatif > msdeploysync.log  -enableLink:AppPoolExtension

This will run what-if analysis without actually migrating anything. Check the log and install needed dependencies. Once all dependencies are installed, run the actual migration:

“C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe” -verb:sync -source:package=C:\YourPackage.zip,encryptPassword=YourPassword -dest:metakey=lm/w3svc/1 -enableLink:AppPoolExtension

 Another way (this does not copy the content, so you have to copy the folders with sites contents):
[appcmd seems to be available only starting with IIS7. Thanks to Nick for the correction.]
Run On Source:

C:\Windows\System32\inetsrv\appcmd list apppool /config /xml > C:\ apppools.xml

C:\Windows\System32\inetsrv\appcmd list site /config /xml > C: \sites.xml

Run On Destination:

C:\Windows\System32\inetsrv\appcmd add apppool /in < C:\apppools.xml

C:\Windows\System32\inetsrv\appcmd add site /in < C: \sites.xml

Migrating Shares and related permissions

First, create all of the folders for the shares. Then export the whole folder from the registry: Created registry export for the whole folder from 2003: HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Services \ LanmanServer \ Shares

Restore it on the destination server. This almost takes care of all the shares, except for NTFS permissions.

To copy NTFS permissions run on source:

Icacls C:\YourFolderThatNeedsNtfsPermissionesCopied  /save ExportedFileWithPermissions.txt /t

Run on destination:

Icacls C:\ /restore ExportedFileWithPermissions.txt

 

Migrate Users and Groups

First, install Windows Server Migration. Then use SmigDeploy to migrate users and groups.

Run on Destination (some parameters may be different for other configurations. Google SmigDeploy):

SmigDeploy.exe /package /architecture X86 /os WS03 /path C:\userMigrationTool

Go to Source server and run from userMigrationTool folder (created in a previous step). This will register SmigDeploy on source.

.\Smigdeploy.exe

Run on source:

Export-SmigServerSetting -User All -Group -Path C:\YourMIgrationFolder -Verbose

It’ll ask you a password. Pick one.

Run on destination:

Import-SmigServerSetting -Group -Path C:\YourMigrationFolder -Verbose

 

COM

Since I needed to migrate some COM’s, I had to add a feature COM+ Network Access to enable COM.

Tasks

Open Task Scheduler on destination.

Go to Action – Connect to another Computer. Connect to your source server.

Export the tasks to a temporary location as xml files. I didn’t see a way to export them all at once, so I did it one by one.

Open Task Scheduler on your destination again. Import the tasks.

It may pop up a message saying the account that you used to set up the task needs “Log on as batch job” privileges. In that case, follow a few more steps:

Run secpol.msc /s

Select “Local Policies” in MSC snap in

 Select “User Rights Assignment”

 Right click on “Log on as batch job” and select Properties

Click “Add User or Group”, and include the relevant user.