My first introduction to MVC was the Nerd Dinner tutorial. From that tutorial I adopted the concept of redirecting the user to a “NotFound” page when a item was requested that does not exist. Here is an example of a controller action that returns the “NotFound” view:
public ActionResult Activate(int id)
{
User user = _repository.FindById(id);
if (user == null) return View("NotFound");
//other code....
return View("Activated");
}
However, a problem may come into play when you try to encapsulate logic in a model class. For example, let's suppose I have a class named EditUserViewModel which loads the details for a user account. The constructor takes a repository and an integer value which represents the primary key used in the database.
public class EditUserFormViewModel
{
public EditUserFormViewModel( IUserRepository repository, int id ) : base()
{
var user = repository.FindById(id);
//Load properties...
}
}
This is cool because the logic is now hidden inside the model class which has the desirable side effect of keeping the controller lightweight:
public ActionResult Edit(int id)
{
var model = new EditUserFormViewModel(_repository, id);
return View(model);
}
The only issue now is that the code that retrieves the user from the repository is inside the model class. This means that I can no longer can easily check if the user is null and return the request to the NotFound page. So as a result, I came up with this simple solution. The first step is to create a new custom exception class called NotFoundException:
public class NotFoundException : Exception
{
public NotFoundException() {}
public NotFoundException( string message )
: base( message )
{
}
public NotFoundException(string message, Exception innerException)
: base(message, innerException)
{
}
}
Next, inside the EditUserViewModel, when a repository call returns a null object, I throw a NotFoundException:
public EditUserFormViewModel( IUserRepository repository, int id ) : base()
{
var user = repository.FindById(id);
if( user == null ) throw new NotFoundException();
...
}
Finally, since I am always use a base controller class, I can easily add a little bit of "centralized" code to streamline the process of handling all those potential NotFoundExceptions. I start by creating a new method named NotFound(). The NotFound() method has two purposes:
- It creates a strongly typed reference to the NotFound view which can be used in any of my controllers. So if I decide to return the NotFound view anywhere in my application I can call NotFound() instead of View("NotFound").
- I override the OnException in the base controller class so whenever a NotFoundException is thrown I automatically redirect the user my NotFound view by calling the NotFound() method.
Here is the code:
public class ApplicationController : Controller
{
public ActionResult NotFound()
{
return View("NotFound");
}
protected override void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception is NotFoundException)
{
filterContext.Result = NotFound();
filterContext.ExceptionHandled = true;
return;
}
base.OnException(filterContext);
}
...
}
Anyway, I thought this was a great approach to handling NotFound errors. Hopefully you will find it useful too.