Disclaimer: The following is my approach and not holy scripture. I'm at work so I wrote this swiftly and some code may not work/compile!
Controllers
They are components of the presentation layer so they shouldn't be concerned with business or database logic. Validation logic is a subset of business logic.
Services
I implement the Service Layer pattern which is basically a bunch of classes that are loosely associated with your domain models and act upon them. These service classes do all of the business logic. This includes the database stuff. If you're not too keen on putting database logic here you can implement the Repository pattern and inject the repository classes into your service classes.
So now the flow of things is becoming clear
Controller -> Service -> Repository
and this equates to
ViewModel to Model mapping -> Validation and other business logic -> CRUD operations
Every service class is described by an interface so now you can use DI to inject them into your Controllers like so:
readonly IClientService _clientService;
public ClientsController(IClientService clientService)
{
_clientService = clientService;
}
It's now clear exactly what dependencies your Controller has since it's all there in the constructor. The Controllers are also cleaner since all of the business logic is in the service layer. Here's an insert example:
[HttpPost]
public ActionResult Insert(ClientViewModel viewModel)
{
if (!ModelState.IsValid)
return View(viewModel);
var client = new Client();
viewModel.MapTo(client);
_clientService.Insert(client);
return View(viewModel);
}
Now the validation part
The type of validation that constitutes checking the database is what I call deep validation. I put simple (shallow) validation in the ViewModel (like yourself) and deep validation in the business layer aka services.
So the deep validation that needs to take place when you insert a Client
is found in the Insert
method of the ClientService
. All the database checks are done there, maybe by calling a repository or just directly in the service if you're not using repositories.
So if the client
is invalid how do we return the validation errors back to the Controller?
We throw an exception since that's what exceptions are for. But it's going to be our own custom exception:
public class MyException : Exception
{
public MyException()
{
}
public MyException(string message)
: base(message)
{
}
public MyException(string message, Exception inner)
: base(message, inner)
{
}
}
So when things are invalid (in our ClientService
):
public void Insert(Client client)
{
if (client == null)
{
throw new MyException("This thing's not right..");
}
We've made sure the insert failed at the soonest possible moment, which is good.
Now all that's left is to catch these custom exceptions in our Controller. You can do this many ways. One is to use try/catch
clauses:
try
{
_clientService.Insert(client);
}
catch (MyException e)
{
// Add error to ModelState perhaps
}
but they will thoroughly litter your Controller.
The proper MVC approach is to catch all the errors in the OnException
method of your Controller:
protected override void OnException(ExceptionContext exceptionContext)
{
exceptionContext.ExceptionHandled = true;
if (exceptionContext.Exception is MyException)
{
// Perhaps add to ModelState
}
else
{
// Add a generic error message to ModelState and log error
}
filterContext.Result = new ViewResult
{
ViewName = ...
};
}
You can put this code in a BaseController
from which all of your other Controllers will inherit in order to avoid code duplication.
That's the basic framework I work in. I hope I didn't make things too complicated or confusing.