Idea for integration

Feb 15, 2013 at 5:43 PM
I working with FluentValidation and EF Migrations.


Basically the EF Migrations have a class Complex/EntityTypeConfiguration that sets the constraints in the database (Required, Max length etc).

Database settings
public class TestimonyConfiguration : EntityTypeConfiguration<Testimony>
{
    public TestimonyConfiguration()
    {
        Property(p => p.Name).IsRequired().HasMaxLength(70);
        Property(p => p.Email).IsRequired().HasMaxLength(126);
        Property(p => p.Message).IsRequired();
    }
}
So in my application I create a class to validate this object:

View model validations
public class TestimonyValidator : AbstractValidator<Testimony>
{
    public TestimonyValidator()
    {
        RuleFor(p => p.Name).NotEmpty().Length(0, 70);
        RuleFor(p => p.Email).NotEmpty().EmailAddress().Length(0, 126);
        RuleFor(p => p.Message).NotEmpty();
    }
}
Did you see the similarity?


The idea is to create a plugin that created a AbstractValidator for each configuration of the EF Migrations with the rules already defined.

I thought of an abstract class, something like:

New way
public class TestimonyValidator : EntityTypeValidator<Testimony /* Type to validate */, Testimony /* Entity to get configuration */>
{
    public TestimonyValidator()
    {
        // All rules will be created based on the configuration of this object
               // You can add new rules
    }
}
Important to emphasize the possibility of seeking another class settings.
This is because in many cases the data class is often different of view class (VM)

Coordinator
Feb 19, 2013 at 8:03 AM
Hi

Thanks for the suggestion. This isn't something that I personally intend to build in to FluentValidation - I'd consider it out-of-scope for the project, but it certainly looks like an interesting idea, so if you intend to implement this, please let me know as I'd be interested in seeing how you do it.

Jeremy
Feb 19, 2013 at 9:56 AM
Ok, this is something that I personally would like to see in FluentValidation.
I will try to implement it as soon as finish a project I'm working on.

'll keep you informed

Thanks,

Riderman
Mar 25, 2013 at 3:41 AM
This is something I'd really like to see as well. If you're using migrations, you pretty much have to use EntityTypeConfiguration's fluent api heavily to guide the migrations, otherwise you end up editing the migrations directly (which is problematic) to get the details in.

The fact that you have to duplicate this effort for validation feels incredibly dirty to me. The EntityTypeConfiguration classes will contain 90% or more of the validation required by your model, why not keep everything dry and build directly on top of them?

I use ViewModels extensively, but I still reference my model classes frequently within my ViewModels, making them complex types. If I can have my model validations covered without having to duplicate the rules, that would be wonderful.

I haven't figured out the best way to do this yet, however. If there's generation involved, we'd have to make sure the generated files were in lockstep with the EntityTypeConfiguration files and their changes, creating maintenance issues. If there's no generation involved, performance is always a concern. If you have any strategies in mind, please share and we can collaborate.

Tim
May 16, 2013 at 2:09 PM
You could probably use T4 templates to generate your validation classes. Look at the Reverse Engineer Code First in Entity Framework Power Tools. It gives you the option to generate your model, mappings, and context from the DB. It also gives you the option to customize the T4 templates. I am actually using this to generate my models, mappings, and context - and I was able to copy a lot of the mapping functionality and translate it into FluentValidation syntax.
namespace <#= code.EscapeNamespace(efHost.Namespace) #>.Validation
{
    public partial class <#= efHost.EntityType.Name #>Validator : AbstractValidator<<#= efHost.EntityType.Name #>>
    {
        public <#= efHost.EntityType.Name #>Validator()
        {
<#
foreach (var prop in efHost.EntityType.Properties)
{
    var isKey = efHost.EntityType.KeyMembers.Contains(prop);

    if (!isKey)
    {
        var validationLines = new List<string>();
        var formattedPropertyName = System.Text.RegularExpressions.Regex.Replace(prop.Name, "((?<=[a-z])[A-Z]|[A-Z](?=[a-z]))", " $1").Trim();

        if (!prop.Nullable)
        {
            validationLines.Add(String.Format(".NotNull().WithMessage(\"{0} cannot be empty.\")", formattedPropertyName));
        }

        var isString = prop.TypeUsage.ToString().Equals("Edm.String");

        if (!prop.Nullable && isString)
        {
            validationLines.Add(String.Format(".NotEmpty().WithMessage(\"{0} cannot be empty.\")", formattedPropertyName));
        }

        var maxLengthFacet = (Facet)prop.TypeUsage.Facets.SingleOrDefault(f => f.Name == "MaxLength");
        if (maxLengthFacet != null && !maxLengthFacet.IsUnbounded)
        {
            validationLines.Add(string.Format(".Length(0, {0}).WithMessage(\"{1} cannot be over {0} characters in length.\")", maxLengthFacet.Value, formattedPropertyName));
        }


        if (validationLines.Any())
        {
#>
            RuleFor(m => m.<#= prop.Name #>)
                <#= string.Join("\r\n                ", validationLines) #>;

<#
        }
    }
}
#>
        }
    }
}