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:

Migrating Windows Server 2003 to 2012

13 Comments

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.

Categories: Uncategorized Tags: Tags:

TFS Build Error SB4062 Microsoft.TeamFoundation.Build.Tasks.dll not found

No Comments

I encountered an issue where a local build would fail on my machine, while succeeding on the other. I used Visual Studio 2010 for the build. This is the error message:

Error SB4062: The “Microsoft.TeamFoundation.Build.Tasks.BuildStep” task could not be loaded from the assembly C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\PrivateAssemblies\Microsoft.TeamFoundation.Build.Tasks.dll.

The solution was to replace my local file from C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets with the one from a different machine with a working build. Not quite sure why I had an older targets file. Possibly, at some point I installed an older version of Visual Studio and it updated the .targets file with the older version.

Categories: TFS Tags: Tags:

Hosting SignalR on GoDaddy in Full Trust

4 Comments

I had some free time recently and decided to learn a truly wonderful library SignalR. SignalR provides the “ability to have your server-side code push content to the connected clients as it happens, in real-time.” It eliminates the extra work of checking browser compatibility with whatever technology you decide to use for pushing the events to the client (WebSockets, EventSource, Long Polling, Forever Frame).

I implemented a simple Chat application using tutorial from Tim Teebken. It worked beautifully on local machine. However, when deployed to GoDaddy my application went down with System.Security.SecurityException. As it turns out, SignalR can run only in Full Trust mode. What I didn’t realize is that to enable full trust you have to explicitly specify Trust Level in Web.Config:

<configuration>
    <system.web>
        <trust level="Full" />
    </system.web>
</configuration>

Once this line was added to the configuration and deployed, my simple Chat client started working.

Categories: Web Tags: Tags: ,

Removing Duplicate Data from SQL Query Caused by New Line Character

No Comments

We had a query retrieving data from a linked Oracle server. We needed unique rows only. This is the original query:

SELECT Column
     FROM OPENQUERY( LinkedServer, 'SELECT DISTINCT Column from TABLE;' );

However, this still returned some duplicates despite using DISTINCT. As it turned out, some rows had a new line character in them. The solution:

SELECT distinct replace(replace(Column,CHAR(13),''),CHAR(10),'')
     FROM OPENQUERY( LinkedServer, 'SELECT DISTINCT Column from TABLE;' )
Categories: SQL Tags: Tags:

How to Find Column Causing “String or binary data would be truncated” Exception in Entity Framework 5.0.

1 Comment

I recently encountered an issue with Entity Framework when it would throw “String or binary data would be truncated. The statement has been terminated.” Since the query contained close to 80 fields, I needed an easy way to determine the culprit. Since I already had a class which inherits from DbContext, I added suggested try-catch block in my SaveChanges() override method:

public partial class MyContext : DbContext
	{
		public override int SaveChanges()
		{
			try
			{
				return base.SaveChanges();
			}
			catch (DbEntityValidationException ex)
			{
				foreach (var error in ex.EntityValidationErrors)
				{
					Console.WriteLine("====================");
					Console.WriteLine(
						"Entity {0} in state {1} has validation errors:",
						error.Entry.Entity.GetType().Name,
						error.Entry.State);
					foreach (var ve in error.ValidationErrors)
					{
						Console.WriteLine("\tProperty: {0}, Error: {1}", ve.PropertyName, ve.ErrorMessage);
					}
					Console.WriteLine();
				}
				throw;
			}
		}
	}

You have to make sure that you don’t set ValidateOnSaveEnabled to false. It is set to true by default, but sometimes it is useful to disable it to improve performance.

Categories: C#, Entity Framework Tags: Tags:

Bootstrap Font Awesome Icons not working on IE9, IE10

No Comments

If you are using an older version of Font Awesome you may discover an issue occurring on IE 9 and IE 10, when font is displayed as squares without any text. After some extensive googling the issue was resolved by modifying font-awesome.css and changing the format('eot') to format('embedded-opentype') for the fontawesome-webfont.eot:

from this:

@font-face {
font-family: "FontAwesome";
src: url('../font-awesome/fontawesome-webfont.eot');
src: url('../font-awesome/fontawesome-webfont.eot?#iefix') format('eot'), url('../font-awesome/fontawesome-webfont.woff') format('woff'), url('../font-awesome/fontawesome-webfont.ttf') format('truetype'), url('../font-awesome/fontawesome-webfont.svg#FontAwesome') format('svg');
font-weight: normal;
font-style: normal;
}

to this:

@font-face {
font-family: "FontAwesome";
src: url('../font-awesome/fontawesome-webfont.eot');
src: url('../font-awesome/fontawesome-webfont.eot?#iefix') format('embedded-opentype'), url('../font-awesome/fontawesome-webfont.woff') format('woff'), url('../font-awesome/fontawesome-webfont.ttf') format('truetype'), url('../font-awesome/fontawesome-webfont.svg#FontAwesome') format('svg');
font-weight: normal;
font-style: normal;
}

Even better, you can update to the latest version of Font Awesome, which already has this fix.

Categories: Web Tags: Tags: