Earlz
Earlz

Reputation: 63835

Throwing an HttpException always sends back HTTP 500 error?

I'm trying to throw an HTTP 403 error code back at the client. I've read that HttpException is the cleanest way to accomplish this, but it's not working for me. I throw the exception from within a page like this:

throw new HttpException(403,"You must be logged in to access this resource.");

However, this will only give a standard ASP.Net stack trace(with 500 error) when CustomErrors is off. If CustomErrors is on, then this will not redirect to the page I have setup to be displayed when a 403 error occurs. Should I forget about HttpException and instead set all the HTTP codes myself? How do I fix this?

The custom errors part of my Web.Config is this:

<customErrors mode="On" defaultRedirect="GenericErrorPage.html">
      <error statusCode="403" redirect="Forbidden.html" />
</customErrors>

Instead of getting Forbidden.html, I'll get GenericErrorPage.html

Upvotes: 14

Views: 42499

Answers (4)

bytesnz
bytesnz

Reputation: 417

We're using System.Web.HttpApplication for an app and this simplest way I've found is to create a System.Net.Http.HttpResponseMessage (specifying the System.Net.HttpStatusCode and optionally content like System.Net.Http.StringContent) and throw it in a System.Web.Http.HttpResponseException.

For example

using System.Net;
using System.Net.Http;
using System.Web.Http;
...

public class AController : ApiController
{
  ...
  public MyObject GetObject(int Id)
  {
    HttpResponseMessage resp;

    if (Id == null)
    {
      resp = new HttpResponseMessage(HttpStatusCode.BadRequest)
      {
        Content = new StringContent("An ID is required")
      };
      throw new HttpResponseException(resp);
    }

    var result = GetObject(Id);

    if (result == null)
    {
      resp = new HttpResponseMessage(HttpStatusCode.NotFound);
      throw new HttpResponseException(resp);
    }

    return result;
  }
  ...
}

Upvotes: 0

OscarVGG
OscarVGG

Reputation: 2670

You need to override Application error like this:

    protected void Application_Error()
    {
        var exception = Server.GetLastError();
        var httpException = exception as HttpException;
        Response.Clear();
        Server.ClearError();
        
        var routeData = new RouteData();
        routeData.Values["controller"] = "Errors";
        routeData.Values["action"] = "General";
        routeData.Values["exception"] = exception;
        Response.StatusCode = 500;

        if (httpException != null)
        {
            Response.StatusCode = httpException.GetHttpCode();
            switch (Response.StatusCode)
            {
                case 403:
                    routeData.Values["action"] = "Http403";
                    break;
                case 404:
                    routeData.Values["action"] = "Http404";
                    break;
            }
        }

        IController errorsController = new ErrorsController();
        var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
        errorsController.Execute(rc);
    }

Then you've got to add the errorsController:

public class ErrorsController : Controller
{
    public ActionResult General(Exception exception)
    {
        ViewBag.ErrorCode = Response.StatusCode;
        ViewBag.Message = "Error Happened";

        //you should log your exception here

        return View("Index");
    }

    public ActionResult Http404()
    {
        ViewBag.ErrorCode = Response.StatusCode;
        ViewBag.Message = "Not Found";
        return View("Index");
    }

    public ActionResult Http403()
    {
        ViewBag.Message = Response.StatusCode;
        ViewBag.Message = "Forbidden";
        return View("Index");
    }

}

And last create a view in for errorsController. I created just one view called index in Views/Errors/.

Upvotes: 8

Earlz
Earlz

Reputation: 63835

I've actually ended up making my own nifty little class to fix this problem. It doesn't handle everything and I'm not sure it plays nice with MVC, but it works for my use. Basically, instead of relying on ASP.Net to output the correct error page and error code, it will clear the error and then do a server-side transfer and display the appropriate Web.Config error page. It also recognizes the customErrors mode and reacts accordingly.

public static class CustomErrorsFixer
{
    static public void HandleErrors(HttpContext Context)
    {
        if(RunIt(Context)==false){
            return;
        }
        HttpException ex = Context.Error as HttpException;
        if(ex==null){
            try{
                ex=Context.Error.InnerException as HttpException;
            }catch{
                ex=null;
            }
        }

        if (ex != null){
            Context.Response.StatusCode = ex.GetHttpCode();
        }else{
            Context.Response.StatusCode = 500;
        }
        Context.ClearError();

        Context.Server.Transfer(GetCustomError(Context.Response.StatusCode.ToString()));
        HttpContext.Current.Response.End();
    }
    static protected string GetCustomError(string code)
    {
        CustomErrorsSection section = ConfigurationManager.GetSection("system.web/customErrors") as CustomErrorsSection;

        if (section != null)
        {
            CustomError page = section.Errors[code];

            if (page != null)
            {
                return page.Redirect;
            }
        }
        return section.DefaultRedirect;
    }
    static protected bool RunIt(HttpContext context){
        CustomErrorsSection section = ConfigurationManager.GetSection("system.web/customErrors") as CustomErrorsSection;
        switch(section.Mode){
            case CustomErrorsMode.Off:
                return false;
            case CustomErrorsMode.On:
                return true;
            case CustomErrorsMode.RemoteOnly:
                return !(context.Request.UserHostAddress=="127.0.0.1");
            default:
                return true;
        }
    }

}

And then to activate it, just add a small thing to Global.asax

    protected virtual void Application_Error (Object sender, EventArgs e)
    {
        CustomErrorsFixer.HandleErrors(Context);
    }

Upvotes: 1

Binus
Binus

Reputation: 1075

With this code included in the element configuration/system.web of Web.config file:

  <customErrors mode="On">
    <error statusCode="403" redirect="~/errors/Forbidden.aspx"/>
  </customErrors>

I managed it to work as expected.

You can find a good tutorial with examples (example 3 is the right one) here: http://aspnetresources.com/articles/CustomErrorPages

Or you may use Response.Status to do so: Asp Classic return specific http status code

Upvotes: 3

Related Questions