为什么关于带有尝试错误处理的模糊控制器的异常会弄乱管道?

Why does an exception about ambigious controllers with attempted errorhandling mess up the pipeline?

我正在试验一些错误处理,这个特定错误似乎把事情搞砸了。

我有一个订阅 HttpApplication.Error 事件处理程序的 HttpModule(见下文),我在其中清除错误并写入一些虚拟响应内容。

这适用于 "normal" 应用程序错误,但是当我收到有关不明确控制器的错误时,它会像往常一样进入处理程序,但更改的响应被完全忽略,并且管道中的执行流程似乎有一些小故障,创建一个 IIS 子请求到 MVC 之外的相同资源,导致 IIS 错误页面关于它无法列出目录,或者文件不存在,具体取决于原始路径请求。

重现:

  1. 创建一个新的模板化 MVC 项目。
  2. 添加新区域并创建HomeController(这会与默认的HomeController冲突)

运行 应用程序,您应该会看到一个 ASP.NET 关于模糊控制器的错误页面。

  1. 添加下面提供的 HttpModule

运行 应用程序,这将导致来自 DirectoryListingModule 的 403.14 IIS 错误页面。

  1. 删除或重命名新区域中不明确的控制器,但在默认 HomeController 的索引操作中创建运行时错误(例如 DivideByZero)。

运行 应用程序,这将导致 200 响应 "Hello world",正如预期的那样。

public class HttpModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.Error += HandleApplicationError;
    }

    private void HandleApplicationError(object sender, EventArgs e)
    {
        var response = HttpContext.Current.Response;
        var server = ((HttpApplication)sender).Server;
        server.ClearError();
        response.Clear();
        response.Write("Hello world");
    }

    public void Dispose()
    {
    }
}

这里发生了什么?为什么当我尝试清除错误并更改响应时出现关于不明确控制器的异常?

行为取决于抛出请求处理管道异常的位置。

  • 在控制器不明确的情况下,路由系统会找到一条与 url 匹配的路由,并尝试获取将处理该请求的 IHttpHandler。但是在尝试获取 IHttpHandler 时会抛出异常,请检查此堆栈跟踪:

    at System.Web.Mvc.DefaultControllerFactory.GetControllerTypeWithinNamespaces(RouteBase route, String controllerName, HashSet`1 namespaces)
    at System.Web.Mvc.DefaultControllerFactory.GetControllerType(RequestContext requestContext, String controllerName)
    at System.Web.Mvc.DefaultControllerFactory.System.Web.Mvc.IControllerFactory.GetControllerSessionBehavior(RequestContext requestContext, String controllerName)
    at System.Web.Mvc.MvcRouteHandler.GetSessionStateBehavior(RequestContext requestContext)
    at System.Web.Mvc.MvcRouteHandler.GetHttpHandler(RequestContext requestContext)
    at System.Web.Mvc.MvcRouteHandler.System.Web.Routing.IRouteHandler.GetHttpHandler(RequestContext requestContext)
    at System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context)
    at System.Web.Routing.UrlRoutingModule.OnApplicationPostResolveRequestCache(Object s atder, Ev attArgs e)
    at System.Web.HttpApplication.SyncEv attExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
    at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
    

    由于路由模块未能获得处理程序,IIS 将尝试与其他模块一起处理。这意味着它将尝试使用 IIS 默认启用的 DirectoryListingModuleStaticFileModule 来处理请求。

  • 当在控制器操作中抛出异常时,请求已经分配了一个正在处理请求的 IHttpHandler。检查不同的堆栈跟踪:

    at WebApplication3.Controllers.HomeController.ThrowError() at g:\Documents\Visual Studio 2012\Projects\WebApplication3\WebApplication3\Controllers\HomeController.cs:line 32
    at lambda_method(Closure , ControllerBase , Object[] )
    at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
    at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
    at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.ActionInvocation.InvokeSynchronousActionMethod()
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<BeginInvokeSynchronousActionMethod>b__36(IAsyncResult asyncResult, ActionInvocation innerInvokeState)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`2.CallEndDelegate(IAsyncResult asyncResult)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.End()
    at System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult)
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3c()
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass45.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3e()
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass30.<BeginInvokeActionMethodWithFilters>b__2f(IAsyncResult asyncResult)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.CallEndDelegate(IAsyncResult asyncResult)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.End()
    at System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult)
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass1e.<>c__DisplayClass28.<BeginInvokeAction>b__19()
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass1e.<BeginInvokeAction>b__1b(IAsyncResult asyncResult)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.CallEndDelegate(IAsyncResult asyncResult)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.End()
    at System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult)
    at System.Web.Mvc.Controller.<BeginExecuteCore>b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.End()
    at System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
    at System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag)
    at System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult)
    at System.Web.Mvc.Controller.<BeginExecute>b__15(IAsyncResult asyncResult, Controller controller)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.End()
    at System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
    at System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag)
    at System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult)
    at System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult)
    at System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__4(IAsyncResult asyncResult, ProcessRequestState innerState)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult)
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.End()
    at System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
    at System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag)
    at System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult)
    at System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
    at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
    at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
    

    所以在这种情况下,如果抛出异常,系统将不会尝试使用其他模块(如 DirectoryListingModuleStaticFileModule

  • 来处理请求

关于您的错误处理程序,在不明确控制器的情况下,您需要一种方法来防止系统找到另一个将处理请求的 IHttpHandler(因为在尝试获取 MvcHandler 时引发错误)。 也有人可能会争辩说,这样的错误(控制器名称不明确)是一个严重的错误,应该在开发时发现,永远不应该将其投入生产。

无论如何,有一些替代方法,例如转移到静态 html 文件 server.Transfer("Error500.html"); 或蛮力方法,例如在错误处理程序结束时完成请求 ((HttpApplication)sender).CompleteRequest();

因此,例如,当当前 IHttpHandler 为空时,一个选项可能会传输到静态 500 错误文件,否则保留当前处理程序代码:

private void HandleApplicationError(object sender, EventArgs e)
{
    var application = sender as HttpApplication;        
    var server = application.Server;

    if (application.Context.CurrentHandler == null)
    {
        server.Transfer("Error500.html");
    }
    else
    {
        var response = HttpContext.Current.Response;
        server.ClearError();
        response.Clear();
        response.Write("Hello world");
    }
}