views:

1648

answers:

4

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?

+5  A: 
throw new HttpException(404, "Are you sure you're in the right place?");
Justice
+15  A: 

Shooting from the hip (cowboy coding ;-)), I'd suggest something like this:

Controller:

public class HomeController : Controller
{
 public ActionResult Index()
 {
  return new HttpNotFoundResult("This doesn't exist");
 }
}


HttpNotFoundResult:

using System;
using System.Net;
using System.Web;
using System.Web.Mvc;

namespace YourNamespaceHere
{
    /// <summary>An implementation of <see cref="ActionResult" /> that throws an <see cref="HttpException" />.</summary>
    public class HttpNotFoundResult : ActionResult
    {
        /// <summary>Initializes a new instance of <see cref="HttpNotFoundResult" /> with the specified <paramref name="message"/>.</summary>
        /// <param name="message"></param>
        public HttpNotFoundResult(String message)
        {
            this.Message = message;
        }

        /// <summary>Initializes a new instance of <see cref="HttpNotFoundResult" /> with an empty message.</summary>
        public HttpNotFoundResult()
            : this(String.Empty) { }

        /// <summary>Gets or sets the message that will be passed to the thrown <see cref="HttpException" />.</summary>
        public String Message { get; set; }

        /// <summary>Overrides the base <see cref="ActionResult.ExecuteResult" /> functionality to throw an <see cref="HttpException" />.</summary>
        public override void ExecuteResult(ControllerContext context)
        {
            throw new HttpException((Int32)HttpStatusCode.NotFound, this.Message);
        }
    }
}
// By Erik van Brakel, with edits from Daniel Schaffer :)

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.

Erik van Brakel
Hope you don't mind, I added a bit to your class :)
Daniel Schaffer
Yeah, I noticed the Enum as well. As I said, it's just a crude example, feel free to improve upon it. This is supposed to be a knowledgebase after all ;-)
Erik van Brakel
I think I went a bit overboard... enjoy :D
Daniel Schaffer
FWIW, Jeff's example also requires that you have a custom 404 page.
Daniel Schaffer
One problem with throwing HttpException instead of just setting the HttpContext.Response.StatusCode = 404 is if you use OnException Controller handler (as I do), it will catch the HttpExceptions too. So I think just setting the StatusCode is a better approach.
Igor Brejc
+10  A: 

We do it like so; this code is found in BaseController

/// <summary>
/// returns our standard page not found view
/// </summary>
protected ViewResult PageNotFound()
{
    Response.StatusCode = 404;
    return View("PageNotFound");
}

called like so

public ActionResult ShowUserDetails(int? id)
{        
    // make sure we have a valid ID
    if (!id.HasValue) return PageNotFound();
Jeff Atwood
is this action then wired up to a default route? Can't see how it gets to get executed.
Christian Dalager
I think he's got a custom error view or something.
Daniel Schaffer
Could be running it like this: protected override void HandleUnknownAction(string actionName){ PageNotFound().ExecuteResult(this.ControllerContext);}
TreeUK
I used to do it that way, but found that splitting the result and the view displayed was a better approach. Check out my answer below.
Brian Vallelunga
+1  A: 

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.

public class HandleNotFoundAttribute : ActionFilterAttribute, IExceptionFilter
{
    public void OnException(ExceptionContext filterContext)
    {
        var httpException = filterContext.Exception.GetBaseException() as HttpException;
        if (httpException != null && httpException.GetHttpCode() == (int)HttpStatusCode.NotFound)
        {
            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; // Prevents IIS from intercepting the error and displaying its own content.
            filterContext.ExceptionHandled = true;
            filterContext.HttpContext.Response.StatusCode = (int) HttpStatusCode.NotFound;
            filterContext.Result = new ViewResult
                                        {
                                            ViewName = "404",
                                            ViewData = filterContext.Controller.ViewData,
                                            TempData = filterContext.Controller.TempData
                                        };
        }
    }
}
Brian Vallelunga