Validate object based on external factors (ie. data store uniqueness)
Asked Answered
H

4

10

Description

My solution has these projects:

  • DAL = Modified Entity Framework
  • DTO = Data Transfer objects that are able to validate themselves
  • BL = Business Layer Services
  • WEB = presentation Asp.net MVC application

DAL, BL and WEB all reference DTO which is great.
The process usually executes this way:

  1. A web request is made to the WEB
  2. WEB gets DTOs posted
    • DTOs get automagically validated via custom ActionFilter
    • validation errors are auto-collected
  3. (Validation is OK) WEB calls into BL providing DTOs
  4. BL calls into DAL by using DTOs (can either pass them through or just use them)

DTO Validation problem then...

My DTOs are able to validate themselves based on their own state (properties' values). But right now I'm presented with a problem when this is not the case. I need them to validate using BL (and consequently DAL).

My real-life example: User registers and WEB gets a User DTO that gets validated. The problematic part is username validation. Its uniqueness should be checked against data store.
How am I supposed to do this?

There's additional info that all DTOs implement an interface (ie. User DTO implements IUser) for IoC purposes and TDD. Both are part of the DTO project.

Impossible tries

  1. I can't reference BL in DTO because I'll get circular reference.
    Compilation error
  2. I can't create an additional DTO.Val project that would reference partial DTO classes and implement their validation there (they'd reference BL + DTO).
    Partial classes can't span assemblies.

Possible tries

  1. Create a special ActionFilter that would validate object against external conditions. This one would be created within WEB project thus seeing DTO and BL that would be used here.
  2. Put DTOs in BL and keep DTO interfaces as actual DTOs referenced by other projects and refactor all code to use interfaces instead of concrete classes.
  3. Don't handle external dependant validation and let external dependencies throw an exception - probably the worst solution to this issue

What would you suggest?

Hypothetical answered 12/11, 2009 at 10:31 Comment(2)
Why do you think the 3 is the worst choice, just because the exception is thrown?Selfgratification
Yes because basically that's something we can prevent user from seeing, because they didn't do anything THAT wrong to throw an exception.Hypothetical
H
1

Resulting solution

I ended up using controller action filter that was able to validate object against external factors that can't be obtained from the object itself.

I created the filter that takes the name of the action parameter to check and validator type that will validate that particular parameter. Of course this validator has to implement certain interface to make it all reusable.

[ValidateExternalFactors("user", typeof(UserExternalValidator))]
public ActionResult Create(User user)

validator needs to implement this simple interface

public interface IExternalValidator<T>
{
    bool IsValid(T instance);
}

It's a simple and effective solution to a seemingly complex problem.

Hypothetical answered 4/5, 2011 at 19:53 Comment(2)
If I want to validation that I have a unique email address and unique user name then I can either create two different validators or put both the validations in one validation. In the former approach, I would be having a chatty conversation with the layer that validates for me. The second approach would not be helpful while editing the profile when I only want to check that edited email is unique. I can tweak the design to make this better but the fundamental things about validations is most of the times you validate operations and not the state as mentioned by @rinat-abdullinHypermeter
Robert, can you share the full code of UserExternalValidator?Cyb
R
6

I would suggest an experiment that i have only been trialling for the last week or so.

Based on this inspiration i am creating DTOs that validate a little differently to that of the DataAnnotations approach. Sample DTO:

public class Contact : DomainBase, IModelObject
{
    public int ID { get; set; }
    public string Name { get; set; }
    public LazyList<ContactDetail> Details { get; set; }
    public DateTime Updated { get; set; }


    protected override void ConfigureRules()
    {
        base.AddRule(new ValidationRule()
        {
            Properties = new string[] { "name" },
            Description = "A Name is required but must not exceed 300 characters in length and some special characters are not allowed",
            validator = () => this.Name.IsRequired300LenNoSpecial()
        });

        base.AddRule(new ValidationRule()
        {
            Properties = new string[] { "updated" },
            Description = "required",
            validator = () => this.Updated.IsRequired()
        });
    }
}

This might look more work than DataAnnotations and well, that's coz it is, but it's not huge. I think it's more presentable in the class (i have some really ugly DTO classes now with DataAnnotations attributes - you can't even see the properties any more). And the power of anonymous delegates in this application is almost book-worthy (so i'm discovering).

Base class:

public partial class DomainBase : IDataErrorInfo
{
    private IList<ValidationRule> _rules = new List<ValidationRule>();

    public DomainBase()
    {
        // populate the _rules collection
        this.ConfigureRules();
    }

    protected virtual void ConfigureRules()
    {
        // no rules if not overridden
    }

    protected void AddRule(ValidationRule rule)
    {
        this._rules.Add(rule);
    }





    #region IDataErrorInfo Members

    public string Error
    {
        get { return String.Empty; }    // Validation should call the indexer so return "" here
    }                                   // ..we dont need to support this property.

    public string this[string columnName]
    {
        get
        {
            // get all the rules that apply to the property being validated
            var rulesThatApply = this._rules
                .Where(r => r.Properties.Contains(columnName));

            // get a list of error messages from the rules
            StringBuilder errorMessages = new StringBuilder();
            foreach (ValidationRule rule in rulesThatApply)
                if (!rule.validator.Invoke())   // if validator returns false then the rule is broken
                    if (errorMessages.ToString() == String.Empty)
                        errorMessages.Append(rule.Description);
                    else
                        errorMessages.AppendFormat("\r\n{0}", rule.Description);

            return errorMessages.ToString();
        }
    }

    #endregion
}

ValidationRule and my validation functions:

public class ValidationRule
{
    public string[] Properties { get; set; }
    public string Description { get; set; }
    public Func<bool> validator { get; set; }
}


/// <summary>
/// These extention methods return true if the validation condition is met.
/// </summary>
public static class ValidationFunctions
{
    #region IsRequired

    public static bool IsRequired(this String str)
    {
        return !str.IsNullOrTrimEmpty();
    }

    public static bool IsRequired(this int num)
    {
        return num != 0;
    }

    public static bool IsRequired(this long num)
    {
        return num != 0;
    }

    public static bool IsRequired(this double num)
    {
        return num != 0;
    }

    public static bool IsRequired(this Decimal num)
    {
        return num != 0;
    }

    public static bool IsRequired(this DateTime date)
    {
        return date != DateTime.MinValue;
    }

    #endregion


    #region String Lengths

    public static bool IsLengthLessThanOrEqual(this String str, int length)
    {
        return str.Length <= length;
    }

    public static bool IsRequiredWithLengthLessThanOrEqual(this String str, int length)
    {
        return !str.IsNullOrTrimEmpty() && (str.Length <= length);
    }

    public static bool IsRequired300LenNoSpecial(this String str)
    {
        return !str.IsNullOrTrimEmpty() &&
            str.RegexMatch(@"^[- \r\n\\\.!:*,@$%&""?\(\)\w']{1,300}$",
                RegexOptions.Multiline) == str;
    }

    #endregion

}

If my code looks messy well that's because i've only been working on this validation approach for the last few days. I need this idea to meet a few requirements:

  • I need to support the IDataErrorInfo interface so my MVC layer validates automatically
  • I need to be able to support complex validation scenarios (the whole point of your question i guess): I want to be able to validate against multiple properties on the same object (ie. StartDate and FinishDate); properties from different/multiple/associated objects like i would have in an object graph; and even other things i haven't thought of yet.
  • I need to support the idea of an error applying to more than one property
  • As part of my TDD and DDD journey i want my Domain Objects to describe more my 'domain' than my Service layer methods, so putting these complex conditions in the model objects (not DTOs) seems to achieve this

This approach i think will get me what i want, and maybe you as well.

I'd imagine if you jump on board with me on this that we'd be pretty 'by ourselves' but it might be worth it. I was reading about the new validation capabilities in MVC 2 but it still doesn't meet the above wish list without custom modification.

Hope this helps.

Rugging answered 16/11, 2009 at 12:17 Comment(3)
Re. Domain Objects (in CQRS/DDDD understanding) shouldn't have properties at all - just the methods. Also I found it too inflexible to define universal business rules that apply to the entire object; there often happen situations that require validating the actual operation, rather than the state (which might have different requirements in order for it to be consistent). Hence - validating the commands/actions.Shuma
"Domain Objects (in CQRS/DDDD understanding) shouldn't have properties at all".. seems odd - perhaps i'll take a deeper look into this 'CQRS/DDDD'. With regard to the second statement however, you'll notice that my approach above incorporates the flexibility of applying the "rule" to any number of objects. The string[] Properties array simply creates an entry point for invoking the validation but the underlying Func<bool> validator may call upon parent or child objects, or even objects from a different Aggregate Root (think AOP).Rugging
FTR, I no longer subscribe to this suggestion. Use Specification.Rugging
M
2

The S#arp Architecture has an [DomainSignature] method identifier that used with the class level validator [HasUniqueDomainSignature] will do the work. See the sample code below:

[HasUniqueDomainSignature]
public class User : Entity
{
    public User()
    {
    }

    public User(string login, string email) : this()
    {
        Login = login;
        Email = email;
    }

    [DomainSignature]
    [NotNullNotEmpty]
    public virtual string Login { get; set; }

    [DomainSignature]
    public virtual string Email { get; set; }

}

Take a closer look at http://www.sharparchitecture.net/

Moody answered 2/12, 2009 at 16:51 Comment(0)
M
1

I had this exact same problem and after trying to find a work around for days and days and days, I ended up merging my DTO, DAL, and BL into one library. I kept my presentation layer separate. Not sure if that is an option for you or not. For me, I figured that my chances of ever changing the data store were very slight, and so the separate tier wasn't really needed.

I also have implemented the Microsoft Validation Application Block for all my DTO validations. They have a "Self Validation" method that lets you perform complex validations.

Madore answered 8/12, 2009 at 19:30 Comment(0)
H
1

Resulting solution

I ended up using controller action filter that was able to validate object against external factors that can't be obtained from the object itself.

I created the filter that takes the name of the action parameter to check and validator type that will validate that particular parameter. Of course this validator has to implement certain interface to make it all reusable.

[ValidateExternalFactors("user", typeof(UserExternalValidator))]
public ActionResult Create(User user)

validator needs to implement this simple interface

public interface IExternalValidator<T>
{
    bool IsValid(T instance);
}

It's a simple and effective solution to a seemingly complex problem.

Hypothetical answered 4/5, 2011 at 19:53 Comment(2)
If I want to validation that I have a unique email address and unique user name then I can either create two different validators or put both the validations in one validation. In the former approach, I would be having a chatty conversation with the layer that validates for me. The second approach would not be helpful while editing the profile when I only want to check that edited email is unique. I can tweak the design to make this better but the fundamental things about validations is most of the times you validate operations and not the state as mentioned by @rinat-abdullinHypermeter
Robert, can you share the full code of UserExternalValidator?Cyb

© 2022 - 2024 — McMap. All rights reserved.