views:

205

answers:

2

There are lots of Fluent implementations out there now that work with Lambdas to do things that are quite neat. I'd like to wrap my brain around it so I can start creating some of these things, but I have yet to find an explanation that my brain understands.

Consider this simple example of a Person Validator

public class PersonValidator : IValidator<Person>
{
     public PersonValidator()
     {
          AddRule(p => p.FirstName).CannotBeNull().CannotBeBlank();
          AddRule(p => p.LastName).CannotBeNull().CannotBeBlank();
     }

     public List<ValidationResult> Validate(Person p)
     {
         // pseudo...
         apply all rules specified in constructor, return results
     }
}

I've managed to get part of all of this working using a method on my Validator like this...

public ValidationResult<T,TProp> AddRule<T,TProp>(Func<T,TProp> property)
{
    ... not sure what to do here.  This method gives me the ability to use the lambda
    ... for specifying which properties i want to validate
}

I can then create Extension methods that extend IValidator for the purposes of CannotBeNull and CannotBeEmpty.

So it seems I have the first half and the second half of the problem but I'm not sure how to bring them together.

Looking for a meaningful explanation...I'd like to "Get it". :)

+4  A: 

The key to fluent interfaces is that methods like CannotBeNull() and CannotBeBlank() return the current instance (i.e. this). If you want your AddRule method to be "fluent", instead of returning ValidationResult, you need to return the current instance of IValidator. Your extension methods would also need to return the instance of IValidator they are extending.

I think your exact implementation might need to be a bit more complex, and hopefully the example below will provide some insight. Same general rule, however...return "this" to create a fluent interface:

interface IValidator<T>
{
    IValidatorRule<T, TProp> AddRule<TProp>(Func<T, TProp> property);
}

interface IValidatorRule<T>
{
    T instance { get; }
    string PropertyName { get; }

    ValidationResult Apply(T instance);
}

public static IValidatorAugmentorExtensions
{
    public static IValidatorRule<T> CannotBeNull(this IValidatorRule<T> rule)
    {
        // ...

        return rule;
    }

    public static IValidatorRule<T> CannotBeBlank(this IValidatorRule<T> rule)
    {
        // ...

        return rule;
    }
}

The above could be used like so:

public class PersonValidator: IValidator<Person>
{
    public PersonValidator()
    {
        AddRule(p => p.FirstName).CannotBeNull().CannotBeEmpty();
        AddRule(p => p.LastName).CannotBeNull().CannotBeEmpty();
    }    

    public List<ValidationResult> Validate(Person p)
    {
        List<ValidationResult> results = new List<ValidationResult>();

        foreach (IValidatorRule<Person> rule in rules) // don't know where rules is, or what the AddRule method adds to...you'll need to figure that out
        {
            results = rule.Apply(p);
        }

        return results;
    }
}

While the above demonstrates how to create a fluent interface, I don't really know what it buys you in the long run in this particular situation. For the convenience of a fluent interface that seems to be used only internally to concrete validators, you have increased the complexity of your code by a fair amount, without really providing a useful, fluent interface to the consumers of your validators. I would think you would glean more value by providing a fluent validation framework to the developers who need to be performing validation, rather than providing a fluent framework for creating concrete validators.

jrista
+1 that's what I was going to write, but you beat me to it. So i took a different approach.
Stan R.
They key need here, is what happens with "Rules". What does the local list look like and how is it utilized?
Matthew
The simplest solution would be to have Rules be a local List<IValidatorRule<Person>>. The AddRule method should create the IValidatorRule and add it to that collection, and the extension methods can be used to modify that instance via the fluent interface. All that aside, I need to stress again that I think your expending a LOT of effort for very little gain. If you really want to realize the benefits of a fluent interface, I would rethink your validation framework. Rather than providing concrete validators (i.e. PersonValidator), provide a fluent interface for those doing validation.
jrista
A: 

jrista's answer is correct. Just for a different approach here is how I accomplished it.

public class PersonValidator : IValidator<Person>
    {
        List<Func<Person,bool>> validationRules = new List<Func<Person,bool>>();

    public PersonValidator()
    {
        AddRule( p => IsNullOrEmpty(p.FirstName)).AddRule(p1 => CheckLength(p1.FirstName));
    }

    PersonValidator AddRule(Func<Person,bool> rule)
    {
        this.validationRules.Add(rule);
        return this;
    }

    private bool IsNullOrEmpty(String stringToCheck)
    {
        return String.IsNullOrEmpty(stringToCheck);
    }

    private bool CheckLength(String stringToCheck)
    {
        return (String.IsNullOrEmpty(stringToCheck) ? false : stringToCheck.Length < 3);
    }

    #region IValidator<Person> Members

    public bool Validate(Person obj)
    {
        return validationRules.Select(x => x(obj)).All(result => result == false);
    }

    #endregion
}



        Person test = new Person() { FirstName = null };
        Person test1 = new Person() { FirstName = "St" };
        Person valid = new Person() { FirstName = "John" };

        PersonValidator validator = new PersonValidator();
        Console.WriteLine("{0} {1} {2}", validator.Validate(test), validator.Validate(test1), validator.Validate(valid));
Stan R.
I don't see how this example would facilitate this usage...AddRule(x => x.FirstName).IsNullOrEmpty();
Matthew
it wouldn't because its a different approach, i just wanted to finish my code instead of just forgetting it only because someone else answered before me.
Stan R.