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.
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:
And then you could use an extension method in your controller to map these errors onto the ModelState, something like:
And your controller would then use: