Custom 404 when no route matches

Playing with MVC gets interesting every time.

Q: What if a user mistypes the url path in an MVC application?
A: A 404 gets displayed.

Q: What if I don’t want the 404 to get displayed.. no matter what the user types in the address bar?
A: Huh??

This is what I’ve been playing with. In theory, at some point the controller factory (the DefaultControllerFactory in most cases) gets to know that there’s no controller named ‘blah’ (when the user types ‘http://website.com/blah’) and decides to display a 404 to the user.

Now, at this point, what if one intercepts this process and says, wait a minute, instead of a 404, show this View (I’m talking MVC View)? I’ve heard MVC is extensible, but is this possible?

I started looking at the source code. I began with the DefaultControllerFactory class and hit the CreateController method and here’s what the implementation looks like:

   1:  public virtual IController CreateController(RequestContext requestContext, string controllerName)
   2:  {
   3:      if (requestContext == null)
   4:      {
   5:          throw new ArgumentNullException("requestContext");
   6:      }
   7:      if (String.IsNullOrEmpty(controllerName))
   8:      {
   9:          throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
  10:      }
  11:      RequestContext = requestContext;
  12:      Type controllerType = GetControllerType(controllerName);
  13:      IController controller = GetControllerInstance(controllerType);
  14:      return controller;
  15:  }

Initial observation – it’s marked virtual; yay, I can override it.

This method passes the current request context and the controller name taken from the url (‘blah’ in our case). It does some initial checks and in line 12, it tries to find if there IS a controller with that name. Our example comes back with a ‘null’ for the controllerType.

A null gets passed to the GetControllerInstance method in line 13 and this is what that method looks like:

   1:  protected internal virtual IController GetControllerInstance(Type controllerType) {
   2:      if (controllerType == null) {
   3:          throw new HttpException(404,
   4:              String.Format(
   5:                  CultureInfo.CurrentUICulture,
   6:                  MvcResources.DefaultControllerFactory_NoControllerFound,
   7:                  RequestContext.HttpContext.Request.Path));
   8:      }
   9:      ...

There it is coded clearly in plain English:

  • if controllerType is null, throw a 404

(well, not literally, but you get the point).

Here’s one way to get out of this:

  • Create an MVC project and add a class ‘BlahControllerFactory.cs’ (cleverly named huh?) to your project
  • Derive the class from ‘DefaultControllerFactory’
  • Modify the Application_Start (in Global.asax.cs) to the following to make sure the ‘BlahControllerFactory’ gets called and not the DefaultControllerFactory - line 4 does the trick
  •    1:  protected void Application_Start()
       2:  {
       3:      RegisterRoutes(RouteTable.Routes);
       4:      ControllerBuilder.Current.SetControllerFactory(new BlahControllerFactory());
       5:  }
  • Override the CreateControllerFactory method to something like below:
   1:  public override IController CreateController(RequestContext requestContext, string controllerName)
   2:  {
   3:      if (requestContext == null)
   4:      {
   5:          throw new ArgumentNullException("requestContext");
   6:      }
   7:      if (String.IsNullOrEmpty(controllerName))
   8:      {
   9:          throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
  10:      }
  11:      RequestContext = requestContext;
  12:      Type controllerType = GetControllerType(controllerName);
  13:      if (controllerType == null)
  14:      {
  15:          controllerName = "Home";
  16:          controllerType = GetControllerType(controllerName);
  17:          requestContext.RouteData.Values["Controller"] = "Home";
  18:          requestContext.RouteData.Values["action"] = "Index";
  19:      }
  20:      IController controller = GetControllerInstance(controllerType);
  21:      return controller;
  22:  }

Let’s look at  lines 13-19 (new additions).

  • If the controllerType is null, change the controllerName from ‘blah’ to ‘whatever you want’
  • Try finding the controllerType again (this should not fail, unless you intentionally want it to)
  • Set the values for the RouteData.Values dictionary object

and you’re done.

This is how you can decide to call a Custom404Controller or go back to the Home page as I’ve done above. There’s no need to hardcode the values in here, you can read them from your web.config as well. Just make sure you don’t call the keys in the web.config file as ‘DefaultController’, ‘DefaultAction’, since there’s already a default controller and a default action in your global.asax.cs file. Also, this is not a ‘default’ per say, this is something of the order of an ERROR. So I prefer to call them as ‘RedmondWeHaveAProblemControllerName’ and ‘RedmondWeHaveAProblemAction’.

Have fun.

Published Wednesday, October 14, 2009 7:47 PM by nmarun
Filed under: ,

Comments

# re: Custom 404 when no route matches

Thursday, October 15, 2009 10:20 AM by Trevor de Koekkoek

Stop trying to reinvent the wheel.  Use catchall:

routes.MapRoute(

   "CatchAll",

   "{*catchall}",

   new { Controller = "Error", Action = "NotFound" }

);

# re: Custom 404 when no route matches

Thursday, October 15, 2009 1:31 PM by nmarun

Trevor,

It does not work for all cases and to test it I created an mvc project and added the route you mentioned. Ran the application and added '/blog' next to the base url. I get:

Server Error in '/' Application.

The resource cannot be found.

Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable.  Please review the following URL and make sure that it is spelled correctly.

Requested URL: /blog

But if I do '/blog/blog/blog/blog' then the ErrorController gets called, but for just the '/blog', the default route gets mapped and you'll get the 404.

I guess the post is not 'a re-invention of the wheel'.

# re: Custom 404 when no route matches

Thursday, October 15, 2009 1:56 PM by Trevor de Koekkoek

good point.  I'll have to play with some more.  Thanks for pointing that out.

# ASP.NET MVC Archived Blog Posts, Page 1

Tuesday, October 20, 2009 11:27 PM by ASP.NET MVC Archived Blog Posts, Page 1

Pingback from  ASP.NET MVC Archived Blog Posts, Page 1

# ASP.NET MVC ViewData.Eval() method

Thursday, November 26, 2009 2:39 AM by IBloggable - implemented

While digging deeper into MVC Views, I stumbled on this method – ViewData.Eval(). Found it interesting

# ASP.NET MVC ViewData.Eval() method | I love .NET!

Thursday, November 26, 2009 5:28 AM by ASP.NET MVC ViewData.Eval() method | I love .NET!

Pingback from  ASP.NET MVC ViewData.Eval() method | I love .NET!

# ASP.NET MVC 2 throws exception for ‘favicon.ico’

Saturday, March 13, 2010 10:21 PM by IBloggable - implemented

I must be on fire or something – third blog in 2 days… awesome! Before I begin, in case you’re wondering

# re: Custom 404 when no route matches

Sunday, March 14, 2010 11:00 PM by TaoYang

Why not set 404 error page in web.config file?

# re: Custom 404 when no route matches

Saturday, January 29, 2011 8:22 PM by James

Elegant way to perform custom 404 in mvc, thanks for great post.

# re: Custom 404 when no route matches

Monday, January 31, 2011 8:56 AM by nmarun

@@James.. glad it helped you.

Arun

# re: Custom 404 when no route matches

Tuesday, March 27, 2012 11:20 AM by Thomas

So-so. Something was not impressed.

Leave a Comment

(required) 
(required) 
(optional)
(required)