tags:

views:

161

answers:

4

I have a Controller that pulls images from a database, re-sizes them, caches the result on disk, and spits the image out as a Content() result.

Recent I have added support for a "Scrape-buster" code on my site. That is, I take a hash of a unique code attached to each image plus some salt, and pass the first few characters of that hash to the user, for confirmation during retrieval. This allows me to prevent people from scraping every image off of the site. (Without being logged in, and scraping the HTML as well, that is.)

Anyways, If the ScrapeBuster code is incorrect, I would like to return a 404 error from my controller. Is there a built-in way to do this, or am I looking at building a custom ActionResult?

+3  A: 

The simplest (probably not the cleanest) way is to throw an HttpException:

public static void Throw404()
{
    throw new HttpException(404, "The resource cannot be found");
}

You should make sure no one in the call stack is catching the exception. This way, output will look like the normal ASP.NET Yellow Screen of Death 404.

The cleaner way is to return a custom result:

public sealed class HttpNotFoundResult : ActionResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        context.HttpContext.Response.StatusCode = 404;
    }
}
Mehrdad Afshari
+2  A: 

You could send a 403 by returning an HttpUnauthorizedResult. That's arguably a more correct response since the URL is valid, but doesn't contain the "authorization" information. It would, if your site is configured correctly, also have the added benefit of redirecting them to your login page.

tvanfosson
+1  A: 
public class NotFoundViewResult : ViewResult
{
    public ViewResultNotFound()
    {
        ViewName = "NotFound";
    }

    public override void ExecuteResult(ControllerContext context)
    {
        context.HttpContext.Response.TrySkipIisCustomErrors = true;
        context.HttpContext.Response.StatusCode = 404;

        base.ExecuteResult(context);
    }
}

And create NotFound View in ~/Views/Shared folder

eu-ge-ne
A: 

Thanks everyone, here is what I came up with from your answers:

public static class HttpErrorResultHelper
{
    public static HttpErrorResult HttpError(this Controller controller, int statusCode)
    {
        return new HttpErrorResult
        {
            StatusCode = statusCode
        };
    }

    public static HttpErrorResult HttpError(this Controller controller, int statusCode, ActionResult chainedAction)
    {
        return new HttpErrorResult
        {
            StatusCode = statusCode,
            ChainedAction = chainedAction,
        };
    }
}

public sealed class HttpErrorResult : ActionResult
{
    public int StatusCode { get; set; }

    public ActionResult ChainedAction { get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        context.HttpContext.Response.TrySkipIisCustomErrors = true;
        context.HttpContext.Response.StatusCode = this.StatusCode;

        if (this.ChainedAction != null)
        {
            ChainedAction.ExecuteResult(context);
        }
    }
}

What this does is prepends the status code to the front of the response, while still allowing you to send along an actual view result.

this.HttpError(404, this.View("NotFound"));
John Gietzen