使用Server.TransferRequest()

时间:2016-10-21 03:21:21

标签: c# asp.net .net asp.net-mvc error-handling

我正在尝试微调我的MVC应用程序中的错误处理。

我在 web.config 中启用了自定义错误,并将以下代码添加到Application_Error

Global.asax中

protected void Application_Error(object sender, EventArgs e)
{
    Exception exception = Server.GetLastError() as Exception;
    if (exception != null)
    {
        Context.ClearError();
        Context.Response.TrySkipIisCustomErrors = true;

        string path = (exception is HttpException && (exception as HttpException).GetHttpCode() == 404) ?
            "~/Error/NotFound" :
            "~/Error/Index";
        Context.Server.TransferRequest(path, false);
    }
}

ErrorController.cs

[AllowAnonymous]
public ActionResult Index()
{
    Response.Clear();
    Response.StatusCode = 503;
    Response.TrySkipIisCustomErrors = true;
    return View();
}

[AllowAnonymous]
public ActionResult NotFound()
{
    Response.Clear();
    Response.StatusCode = 404;
    Response.TrySkipIisCustomErrors = true;
    return View();
}

的Web.config

<system.web>
  <customErrors mode="RemoteOnly" defaultRedirect="~/Error">
    <error statusCode="404" redirect="~/Error/NotFound"/>
    <error statusCode="500" redirect="~/Error" />
  </customErrors>
</system.web>

这似乎运作得相当好。但是如何将一些错误细节传递给我的错误控制器?

此外,还有一些关于向控制器中发生异常的错误控制器获取异常详细信息的提示。

注意:我不想在这里使用重定向。这样做会告诉Google这样的抓取工具有关该网址的错误信息。

6 个答案:

答案 0 :(得分:6)

如果要在错误控制器中获取错误详细信息,而不是在Application_Error函数中清除错误详细信息(Context.ClearError())。

进入ErrorController后,Action再次获取最后一个错误,然后将其清除。

HttpContext.Server.GetLastError()

如果您想获取发生异常的控制器和操作名称,可以使用下面的代码来获取详细信息

Request.RequestContext.RouteData.Values["controller"]
Request.RequestContext.RouteData.Values["action"]

此外,如果您想从Application_Error函数运行ErrorController和Specific Action,您可以执行以下操作

   protected void Application_Error()
   {

Exception exception = Server.GetLastError();
var httpException = exception as HttpException;
Response.Clear();
Server.ClearError();
var routeData = new RouteData();
routeData.Values["controller"] = "Errors";
routeData.Values["action"] = "Common";
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;
  case 400:
    routeData.Values["action"] = "Http400";
    break;
  }
}

Response.TrySkipIisCustomErrors = true;
IController errorsController = new ErrorsController();
var rc = new RequestContext(new HttpContextWrapper(Context), routeData);

/* This will run specific action without redirecting */
errorsController.Execute(rc);

}

如果要将错误作为对象传递给错误控制器,则可以添加额外的路由数据,如下所示

routeData.Values["errorDetail"] = httpException;

答案 1 :(得分:3)

添加参数会有帮助吗?

  • public ActionResult Index(string errorMessage)
  • public ActionResult NotFound(string errorMessage)

然后在Application_Error中看起来像 -

protected void Application_Error(object sender, EventArgs e)
{
    Exception exception = Server.GetLastError() as Exception;
    if (exception != null)
    {
        Context.ClearError();
        Context.Response.TrySkipIisCustomErrors = true;

        string path = (exception is HttpException && (exception as HttpException).GetHttpCode() == 404) ?
            "~/Error/NotFound?errorMessage="+exception.Message :
            "~/Error/Index?errorMessage="+exception.Message;
        Context.Server.TransferRequest(path, false);
    }
}

您可以根据自己的要求添加其他参数。 虽然不是最好的方法。

答案 2 :(得分:2)

一种简单的方法是传递Exception或ViewModel,如下所示:

在你的application_error中:

HttpContext.Current.Items["Exception"] = exception;
错误控制器中的

var exception = HttpContext.Current.Items["Exception"] as Exception;

警告:我不喜欢使用HttpContext。

答案 3 :(得分:1)

这是我的设置。它不会重定向,它会在同一个地方处理应用程序和一些已配置的IIS错误。您还可以将任何所需信息传递给错误控制器。

Web.config

<system.web>
  <customErrors mode="Off" />
  ...
</system.web>

<system.webServer>
  <httpErrors errorMode="Custom" existingResponse="Auto">
    <remove statusCode="403" />
    <remove statusCode="404" />
    <remove statusCode="500" />
    <error statusCode="403" responseMode="ExecuteURL" path="/Error/Display/403" />
    <error statusCode="404" responseMode="ExecuteURL" path="/Error/Display/404" />
    <error statusCode="500" responseMode="ExecuteURL" path="/Error/Display/500" />
  </httpErrors>
...
</system.webServer>

ErrorController 中(仅为简洁起见显示方法签名):

// This one gets called from Application_Error
// You can add additional parameters to this action if needed
public ActionResult Index(Exception exception)
{
   ...
}

// This one gets called by IIS (see Web.config)
public ActionResult Display([Bind(Prefix = "id")] HttpStatusCode statusCode)
{
    ...
}

此外,我还有ErrorViewModelIndex视图。

Application_Error

protected void Application_Error(object sender, EventArgs e)
{
    var exception = Server.GetLastError();

    var httpContext = new HttpContextWrapper(Context);

    httpContext.ClearError();

    var routeData = new RouteData();
    routeData.Values["controller"] = "Error";
    routeData.Values["action"] = "Index";
    routeData.Values["exception"] = exception;
    // Here you can add additional route values as necessary.
    // Make sure you add them as parameters to the action you're executing

    IController errorController = DependencyResolver.Current.GetService<ErrorController>();
    var context = new RequestContext(httpContext, routeData);
    errorController.Execute(context);
}

到目前为止,这是我的基本设置。这不会执行重定向(错误控制器操作从Application_Error执行),它处理控制器异常以及IIS 404(例如yourwebsite.com/blah.html)。

从现在开始,ErrorController内发生的任何事情都将取决于您的需求。

作为一个例子,我将添加一些我的实现的额外细节。正如我所说,我有ErrorViewModel

我的 ErrorViewModel

public class ErrorViewModel
{
    public string Title { get; set; }

    public string Text { get; set; }

    // This is only relevant to my business needs
    public string ContentResourceKey { get; set; }

    // I am including the actual exception in here so that in the view,
    // when the request is local, I am displaying the exception for
    // debugging purposes.
    public Exception Exception { get; set; }
}

我的 ErrorController (相关部分):

public ActionResult Index(Exception exception)
{
    ErrorViewModel model;

    var statusCode = HttpStatusCode.InternalServerError;

    if (exception is HttpException)
    {
        statusCode = (HttpStatusCode)(exception as HttpException).GetHttpCode();

        // More details on this below
        if (exception is DisplayableException)
        {
            model = CreateErrorModel(exception as DisplayableException);
        }
        else
        {
            model = CreateErrorModel(statusCode);
            model.Exception = exception;
        }
    }
    else
    {
        model = new ErrorViewModel { Exception = exception };
    }

    return ErrorResult(model, statusCode);
}

public ActionResult Display([Bind(Prefix = "id")] HttpStatusCode statusCode)
{
    var model = CreateErrorModel(statusCode);

    return ErrorResult(model, statusCode);
}

private ErrorViewModel CreateErrorModel(HttpStatusCode statusCode)
{
    var model = new ErrorViewModel();

    switch (statusCode)
    {
        case HttpStatusCode.NotFound:
            // Again, this is only relevant to my business logic.
            // You can do whatever you want here
            model.ContentResourceKey = "error-page-404";
            break;
        case HttpStatusCode.Forbidden:
            model.Title = "Unauthorised.";
            model.Text = "Your are not authorised to access this resource.";
            break;

        // etc...
    }

    return model;
}


private ErrorViewModel CreateErrorModel(DisplayableException exception)
{
    if (exception == null)
    {
        return new ErrorViewModel();
    }

    return new ErrorViewModel
    {
        Title = exception.DisplayTitle,
        Text = exception.DisplayDescription,
        Exception = exception.InnerException
    };
}

private ActionResult ErrorResult(ErrorViewModel model, HttpStatusCode statusCode)
{
    HttpContext.Response.Clear();
    HttpContext.Response.StatusCode = (int)statusCode;
    HttpContext.Response.TrySkipIisCustomErrors = true;

    return View("Index", model);
}

在某些情况下,我需要在发生错误时显示自定义消息。我为此目的有一个自定义异常:

[Serializable]
public class DisplayableException : HttpException
{
    public string DisplayTitle { get; set; }

    public string DisplayDescription { get; set; }

    public DisplayableException(string title, string description)
        : this(title, description, HttpStatusCode.InternalServerError, null, null)
    {
    }

    public DisplayableException(string title, string description, Exception exception)
        : this(title, description, HttpStatusCode.InternalServerError, null, exception)
    {
    }

    public DisplayableException(string title, string description, string message, Exception exception)
        : this(title, description, HttpStatusCode.InternalServerError, message, exception)
    {
    }

    public DisplayableException(string title, string description, HttpStatusCode statusCode, string message, Exception inner)
        : base((int)statusCode, message, inner)
    {
        DisplayTitle = title;
        DisplayDescription = description;
    }
}

然后我像这样使用它:

catch(SomeException ex)
{
    throw new DisplayableException("My Title", "My  custom display message", "An error occurred and I must display something", ex)
}

在我的ErrorController我单独处理此例外,从ErrorViewModel设置Title&#39 {s} TextDisplayableException属性。

答案 4 :(得分:0)

您可以使用Session["AppError"]=exception之类的Session对象吗?然后,您可以在错误控制器中检索它。请记住,异常不是可序列化的,但您可以使用其他技巧。其中几个人在这里:How to serialize an Exception object in C#?What is the correct way to make a custom .NET Exception serializable?

答案 5 :(得分:0)

试试这样;

        protected void Application_Error(Object sender, EventArgs e)
    {
        var exception = Server.GetLastError();
        var statusCode = exception.GetType() == typeof (HttpException) ? ((HttpException) exception).GetHttpCode() : 500;
        var routeData = new RouteData
        {
            Values =
            {
                {"controller", "Error"},
                {"action", "Index"},
                {"statusCode", statusCode},
                {"exception", exception}
            }
        };

        Server.ClearError();
        Response.TrySkipIisCustomErrors = true;

        IController errorController = new ErrorController();
        errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));

        Response.End();
    }
}

然后在ErrorController中的Index方法中编写一些业务代码。 (如果StatusCode == 400 ...... else ......)