【问题标题】:Passing a view model with Server.TransferRequest()使用 Server.TransferRequest() 传递视图模型
【发布时间】:2017-03-03 05:52:36
【问题描述】:

我正在尝试微调我的 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 这样的抓取工具有关该 URL 的错误信息。

【问题讨论】:

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


    【解决方案1】:

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

    一旦你在 ErrorController Action 中再次获取最后一个错误然后清除它。

    HttpContext.Server.GetLastError()
    

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

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

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

       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;

    【讨论】:

      【解决方案2】:

      添加参数会有帮助吗?

      • 公共 ActionResult 索引(字符串错误消息)
      • 公共 ActionResult NotFound(字符串错误消息)

      然后在 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);
          }
      }
      

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

      【讨论】:

      • 这是一种方法,但肯定不太理想。例如,某些异常具有内部异常。它们还包含其他信息。
      【解决方案3】:

      一个简单的方法是像这样传递 Exception 或 ViewModel:

      在您的 application_error 中:

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

      在您的错误控制器中:

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

      警告:我不喜欢使用 HttpContext。

      【讨论】:

      • 为什么不喜欢使用HttpContext
      • System.Web 是一个有问题的单体 API,正在被替换,但更重要的是 HttpContext 是一个很难测试的单例/全局状态
      【解决方案4】:

      这是我的设置。它不会重定向,它会在同一个地方同时处理应用程序和一些配置的 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)
      {
          ...
      }
      

      另外,我有一个ErrorViewModel 和一个Index 视图。

      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 中,我分别处理这个异常,从这个DisplayableException 设置ErrorViewModelTitleText 属性。

      【讨论】:

      • 似乎您的代码需要更多的错误检查,例如在几个地方检查null。一个你不想引入新异常的地方是你的错误处理代码。
      • 我完全同意我们不想在异常处理代码中获取异常。然而,回顾我的代码,我认为唯一可能产生问题的两个地方是ExecuteErrorAction 方法(在 HttpModule 中)或CreateErrorModel(DisplayableException) 重载(在ErrorController 中)。在第一种情况下,如果无法创建 ErrorController 的新实例,则可能会出现问题(我认为这不太可能,并且在开发过程中会立即显现出来)。第二个将因不太可能的null 参数而失败。你怎么看?
      • 我知道“不太可能”通常不是不做某事的好理由,但对于ExecuteErrorAction 的情况,我认为假设将创建一个实例是足够安全的。对于第二种情况(CreateErrorModel(DisplayableException)),好吧,它更有可能发生。我说“不太可能”是因为在这种特殊情况下调用该方法的方式。但是,我知道它不能作为一个好的论据,因为方法不应该知道它是如何被调用的。这也适用于ExecuteErrorAction。我将更新代码以反映这一点。
      • 我删除了ExecuteErrorAction 并将代码移到了Applicaiton_Error 中(无论如何,该方法有点毫无意义)。我还在CreateErrorModel 中为异常添加了一个空检查。如果它为空,我将返回一个新的空模型。
      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        这样试试;

                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 ... 否则 ...)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-02-20
          • 1970-01-01
          • 2017-10-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-09-27
          • 2016-12-17
          相关资源
          最近更新 更多