0

I have a multi-layered project. Layers are as follows :

  • Business
  • DataAccess
  • Entities
  • Core
  • MvcWebUI

I have a Category class in entity layer:

public class Category : IEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

I also have a CategoryValidator class in the business layer:

public class CategoryValidator : AbstractValidator<Category>
{
    public CategoryValidator(IEnumerable<Category> categories)
    {
        RuleFor(x => x.Name).NotEmpty().MaximumLength(50);
    }
}

I have a class in the Core layer for validation.

public class ValidatorTool
{
    public static void FluentValidate(IValidator validator, object entity)
    {
        var result = validator.Validate(entity);
        if (result.Errors.Any())
            throw new ValidationException(result.Errors);
    }
}

I'm performing validation in the Business layer with the FluentValidate method.

But I got stuck when it came to the MvcWebUI layer. According to the FluentValidation documentation, I need to apply an attribute to the entity class as follows:

[Validator(typeof(PersonValidator))]

But since the business layer references the entity layer, I can't reach the CategoryValidator class in the entity layer. (circle reference)

How can I solve this problem? Did I create the layers incorrectly? Or should I define the entities as a model again in the Web layer? Please help me.

2 Answers 2

1

Firstly, you probably shouldn't be exposing your entities directly in the UI so I'm going to recommend you create new models there and write validators specifically for them.

Assuming this is wired up correctly, this approach means the validators are automatically fired during the HTTP POST in the MVC app and the model state is automatically updated with a list of errors.

I use this approach extensively, albeit in MVC apps that call an internal API.

In the majority of my cases, the MVC client validates the model and if it passes the checks it then calls the API or service layer after with a DTO / service / entity model which is mapped with Automapper.

The MVC validation is typically light and checks for required fields, lengths, etc.

The API does validation again but it does it on the entity and it goes much deeper this time as it checks for duplicates, invalid entity state, etc. .

One last comment that I would like to add. I wouldn't throw exceptions on validation errors. The UI should use ModelState and the service layer returns a result which the client knows how to merge back into ModelState so either scenario results in the users getting a nice list of errors to deal with.

Hope this helps!

Sign up to request clarification or add additional context in comments.

Comments

0

Generally you have 2 ways to perform validation:

  1. Validate View Models (used in most cases)
  2. Internal Business Entities validation (which most often used with 1-st)

For 1-st point you validate view models (on client and server), which placed in your Web project. In that case you should place view model validators in Web project too. [Validator(typeof(PersonValidator))] attribute is needed for linking view model parameter of action and action itself to perform validation before action execution. As in documentation:

Internally, FluentValidation’s MVC integration makes use of a validator factory to know how to work out which validator should be used to validate a particular type. By default, FluentValidation ships with an AttributedValidatorFactory that allows you to link a validator to the type that it validates by decorating the class to validate with an attribute that identifies its corresponding validator.

If you want to validate business models (2-nd point), not/not only view models, you need to place entity validators to Business project and register them in your IoC container (example with Castle Windsor), and change validator tool next way:

public class ValidatorTool
{
    public static void FluentValidate<T>(IContainer container, T entity) // replace IContainer with your actual container interface name
    {
        var validator = container.Resolve<IValidator<T>>();
        var result = validator.Validate(entity);
        if (result.Errors.Any())
            throw new ValidationException(result.Errors);
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.