14

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.

James
  • 1,979
  • 5
  • 24
  • 52
  • You may find the following answer useful: http://stackoverflow.com/a/4851953/29407 – Darin Dimitrov Dec 13 '11 at 11:00
  • Thanks Darin, this looks promising although it does go for the Exception approach. Is this the commonly accepted way to do this? – James Dec 13 '11 at 11:18
  • There is a topic about that in the docs: https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/models-data/validating-with-a-service-layer-cs – juFo Aug 21 '18 at 13:04

2 Answers2

13

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), "");
Ian Routledge
  • 4,012
  • 1
  • 23
  • 28
  • Would this be called from the controller before using Service.Create() (for example)? But what if Service.Create() has some specialised business logic which the passed in object fails - how would we inform the controller? It seems to me that each Service method requires its own unique validations and thus a way to inform the controller of problems. I guess my Service methods could return IDictionary? – James Dec 13 '11 at 11:42
  • Yes, you could call Validate separately, but validate should also be called from the service itself in the create method. That way, the create method could also return a dictionary of errors. If the dictionary is empty then validation was successful. – Ian Routledge Dec 13 '11 at 11:51
  • I think this may well be the way to go. I don't want to be throwing exceptions without good reason as suggested in many guides. Have you used this method yourself? My main concern is that if a Service method already has a return type, then I won't be able to return IDictionary. I guess this is why some guides use ModelStateWrapper. – James Dec 13 '11 at 12:38
  • Yes I see what you mean about returns types but I don't see why validate or create would need one. Yes, I've used this myself and it's worked well for me so far! – Ian Routledge Dec 13 '11 at 14:53
  • My current thinking is using a combination of the two techniques: have the Service expose a validate method which returns an IDictionary of validation errors. My Service create (or whatever) method will return void (or whatever) but it too will call the validate method as part of its process - if it fails validation, then create will throw an exception. This way caller can first check the POCO is valid before calling create. If they dont, potential exception! – James Dec 13 '11 at 15:22
2

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>.

Jacob
  • 7,741
  • 4
  • 30
  • 24