Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Service Layer Validation

I am trying to implement a validation strategy in my application. I have an MVC layer, Service layer, Repository and Domain POCOs. Now at the MVC layer, i use data annotations on my viewmodels to validate user input, allowing me to give the user quick feedback. In the controller, I call ModelState.IsValid to check the input before using automapper to set up a domain object.

Here's where my trouble is. I pass my domain object into the Service which needs to validate it against my business rules but how do I pass validation errors back to the controller? Examples I have found do one of the following:

  • Throw an exception in the Service layer and catch in the Contoller. But this seems wrong, surely exceptions are for exceptional cases, and we should return something meaningful.
  • Use a ModelStateWrapper and inject the ModelStateDictionary into the Service. But this method ultimately unfolds into a circular dependency (controller depends on service, service depends on controller) and this seems to be a bad code smell.
  • Add a Validate method to the POCO. Problem with this is that a business rule may rely on other POCO objects so surely this should be done in the Service which has access to the required tables and objects.

Is there a simpler method I am missing? I have seen lots of questions regarding this but no concrete solution other than those mentioned above. I am thinking that any method in the Service which does validation could just pass back some key/value object that I can use in the controller but am unsure if this strategy could be problematic later on.

like image 371
James Avatar asked Dec 13 '11 10:12

James


People also ask

What layer should validation be?

Implement validations in the domain model layer. Validations are usually implemented in domain entity constructors or in methods that can update the entity.

Should I validate controller or service?

As a general rule of thumb, I would say that business logic of this sort should be in the service. Controllers should be light-weight and pass on requests. Further, there may be other clients of your service, not just controllers, so this allows you to keep validation in one place.

What does service layer do?

Service layer is an architectural pattern, applied within the service-orientation design paradigm, which aims to organize the services, within a service inventory, into a set of logical layers. Services that are categorized into a particular layer share functionality.

What is Java service layer?

The service layer consists of a collection of Java classes that implement business logic (data retrieval, updates, deletions, and so on) through one or more high-level methods. In other words, the service layer controls the workflow.


2 Answers

I think quite an elegant way would be to have a validate method on your service that returns a dictionary of model errors from business logic. That way, there is no injection of the ModelState to the service - the service just does validation and returns any errors. It is then up to the controller to merge these ModelState errors back into it's ViewData.

So the service validation method might look like:

public IDictionary<string, string> ValidatePerson(Person person)
{
    Dictionary<string, string> errors = new Dictionary<string, string>();

    // Do some validation, e.g. check if the person already exists etc etc

    // Add model erros e.g.:
    errors.Add("Email", "This person already exists");
}

And then you could use an extension method in your controller to map these errors onto the ModelState, something like:

public static class ModelStateDictionaryExtensions
{
    public static void Merge(this ModelStateDictionary modelState, IDictionary<string, string> dictionary, string prefix)
    {
        foreach (var item in dictionary)
        {
            modelState.AddModelError((string.IsNullOrEmpty(prefix) ? "" : (prefix + ".")) + item.Key, item.Value);
        }
    }
}

And your controller would then use:

ModelState.Merge(personService.ValidatePerson(person), "");
like image 112
Ian Routledge Avatar answered Nov 15 '22 08:11

Ian Routledge


As an alternative to creating an intermediate dictionary as suggested by Ian, you could also do this with a validator that accepts a function.

e.g. in the service layer:

public void ValidateModel(Customer customer, Action<string, string> AddModelError)
{
  if (customer.Email == null) AddModelError("Email", "Hey you forgot your email address.");
}

Then in your controller you validate with a single call:

myService.ValidateModel(model, ModelState.AddModelError);

Or say you want to use your validator in a console app without access to a ModelStateDictionary, you could do this:

errors = new NameValueDictionary();
myService.ValidateModel(model, errors.Add);

Both of these work because ModelStateDictionary.AddModelError() and NameValueDictionary.Add() match the method signature for Action<string, string>.

like image 23
Jacob Avatar answered Nov 15 '22 09:11

Jacob