If given the route:
{FeedName}/{ItemPermalink}
ex: /Blog/Hello-World
If the item doesn't exist, I want to return a 404. What is the right way to do this in ASP.NET MVC?
If given the route:
{FeedName}/{ItemPermalink}
ex: /Blog/Hello-World
If the item doesn't exist, I want to return a 404. What is the right way to do this in ASP.NET MVC?
We do it like so; this code is found in
BaseController
called like so
Shooting from the hip (cowboy coding ;-)), I'd suggest something like this:
Controller:
HttpNotFoundResult:
Using this approach you comply to the framework standards. There already is a HttpUnauthorizedResult in there, so this would simply extend the framework in the eyes of another developer maintaining your code later on (you know, the psycho who knows where you live).
You could use reflector to take a look into the assembly to see how the HttpUnauthorizedResult is achieved, because I don't know if this approach misses anything (it seems too simple almost).
I did use reflector to take a look at the HttpUnauthorizedResult just now. Seems they're setting the StatusCode on the response to 0x191 (401). Although this works for 401, using 404 as the new value I seem to be getting just a blank page in Firefox. Internet Explorer shows a default 404 though (not the ASP.NET version). Using the webdeveloper toolbar I inspected the headers in FF, which DO show a 404 Not Found response. Could be simply something I misconfigured in FF.
This being said, I think Jeff's approach is a fine example of KISS. If you don't really need the verbosity in this sample, his method works fine as well.
Note that as of MVC3, you can just use
HttpStatusCodeResult
.The HttpNotFoundResult is a great first step to what I am using. Returning an HttpNotFoundResult is good. Then the question is, what's next?
I created an action filter called HandleNotFoundAttribute that then shows a 404 error page. Since it returns a view, you can create a special 404 view per controller, or let is use a default shared 404 view. This will even be called when a controller doesn't have the specified action present, because the framework throws an HttpException with a status code of 404.
Using ActionFilter is hard to maintain because whenever we throw an error the filter need to be set in the attribute. What if we forget to set it? One way is deriving
OnException
on base controller. You need to define aBaseController
derived fromController
and all your controllers must derive fromBaseController
. It is a best practise to have a base controller.Note if using
Exception
the response status code is 500, so we need to change it to 404 for Not Found and 401 for Unauthorized. Just like I mention above, useOnException
overrides onBaseController
to avoid using filter attribute.The new MVC 3 also make more troublesome by returning an empty view to browser. The best solution after some research is based on my answer here How to return a view for HttpNotFound() in ASP.Net MVC 3?
To make more convinience I paste it here:
After some study. The workaround for MVC 3 here is to derive all
HttpNotFoundResult
,HttpUnauthorizedResult
,HttpStatusCodeResult
classes and implement new (overriding it)HttpNotFound
() method inBaseController
.It is best practise to use base Controller so you have 'control' over all derived Controllers.
I create new
HttpStatusCodeResult
class, not to derive fromActionResult
but fromViewResult
to render the view or anyView
you want by specifying theViewName
property. I follow the originalHttpStatusCodeResult
to set theHttpContext.Response.StatusCode
andHttpContext.Response.StatusDescription
but thenbase.ExecuteResult(context)
will render the suitable view because again I derive fromViewResult
. Simple enough is it? Hope this will be implemented in the MVC core.See my
BaseController
bellow:To use in your action like this:
And in _Layout.cshtml (like master page)
Additionally you can use a custom view like
Error.shtml
or create newNotFound.cshtml
like I commented in the code and you may define a view model for the status description and other explanations.