【问题标题】:ASP.NET MVC app custom error pages not displaying in shared hosting environmentASP.NET MVC 应用程序自定义错误页面未显示在共享托管环境中
【发布时间】:2010-12-14 23:36:11
【问题描述】:

我在共享主机上部署的 ASP.NET MVC 应用程序中遇到自定义错误问题。我创建了一个 ErrorController 并将以下代码添加到 Global.asax 以捕获未处理的异常,记录它们,然后将控制权转移到 ErrorController 以显示自定义错误。此代码取自here

protected void Application_Error(object sender, EventArgs e)
{
    Exception ex = Server.GetLastError();
    Response.Clear();

    HttpException httpEx = ex as HttpException;
    RouteData routeData = new RouteData();
    routeData.Values.Add("controller", "Error");

    if (httpEx == null)
    {
        routeData.Values.Add("action", "Index");
    }
    else
    {
        switch (httpEx.GetHttpCode())
        {
            case 404:
                routeData.Values.Add("action", "HttpError404");
                break;
            case 500:
                routeData.Values.Add("action", "HttpError500");
                break;
            case 503:
                routeData.Values.Add("action", "HttpError503");
                break;
            default:
                routeData.Values.Add("action", "Index");
                break;
        }
    }

    ExceptionLogger.LogException(ex); // <- This is working. Errors get logged

    routeData.Values.Add("error", ex);
    Server.ClearError();
    IController controller = new ErrorController();
    // The next line doesn't seem to be working
    controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}

Application_Error 肯定会触发,因为日志记录工作正常,但我没有显示我的自定义错误页面,而是获得了 Go Daddy 通用错误页面。从博客文章的标题中,我注意到它使用了 MVC 框架的 Release Candidate 2。 1.0 中是否发生了一些变化,导致最后一行代码不起作用?像往常一样works great on my machine

任何建议将不胜感激。

编辑:忘了提到我已经尝试了 Web.config 中 customErrors 模式的所有 3 种可能性(Off、On 和 RemoteOnly)。无论此设置如何,结果都相同。

编辑 2:我也尝试过在控制器类上使用和不使用 [HandleError] 装饰。

更新:我已经发现并修复了 404。 Go Daddy 的主机控制中心的设置面板中有一个部分可以控制 404 行为,默认是显示它们的通用页面,显然这会覆盖任何 Web.config 设置。所以我的自定义 404 页面现在按预期显示。但是,500s 和 503s 仍然无法正常工作。如果 Sql Server 抛出异常,我在 HomeController 中有代码来获取内容的静态文本版本,如下所示:

public ActionResult Index()
{
    CcmDataClassesDataContext dc = new CcmDataClassesDataContext();

    // This might generate an exception which will be handled in the OnException override
    HomeContent hc = dc.HomeContents.GetCurrentContent();

    ViewData["bodyId"] = "home";
    return View(hc);
}

protected override void OnException(ExceptionContext filterContext)
{
    // Only concerned here with SqlExceptions so an HTTP 503 message can
    // be displayed in the Home View. All others will bubble up to the
    // Global.asax.cs and be handled/logged there.
    System.Data.SqlClient.SqlException sqlEx =
        filterContext.Exception as System.Data.SqlClient.SqlException;
    if (sqlEx != null)
    {
        try
        {
            ExceptionLogger.LogException(sqlEx);
        }
        catch
        {
            // couldn't log exception, continue without crashing
        }

        ViewData["bodyId"] = "home";
        filterContext.ExceptionHandled = true;
        HomeContent hc = ContentHelper.GetStaticContent();
        if (hc == null)
        {
            // Couldn't get static content. Display friendly message on Home View.
            Response.StatusCode = 503;
            this.View("ContentError").ExecuteResult(this.ControllerContext);
        }
        else
        {
            // Pass the static content to the regular Home View
            this.View("Index", hc).ExecuteResult(this.ControllerContext);
        }
    }
}

这是尝试获取静态内容的代码:

public static HomeContent GetStaticContent()
{
    HomeContent hc;

    try
    {
        string path = Configuration.CcmConfigSection.Config.Content.PathToStaticContent;
        string fileText = File.ReadAllText(path);
        string regex = @"^[^#]([^\r\n]*)";
        MatchCollection matches = Regex.Matches(fileText, regex, RegexOptions.Multiline);
        hc = new HomeContent
            {
                ID = Convert.ToInt32(matches[0].Value),
                Title = matches[1].Value,
                DateAdded = DateTime.Parse(matches[2].Value),
                Body = matches[3].Value,
                IsCurrent = true
            };
    }
    catch (Exception ex)
    {
        try
        {
            ExceptionLogger.LogException(ex);
        }
        catch
        {
            // couldn't log exception, continue without crashing
        }
        hc = null;
    }

    return hc;
}

我已经验证,如果我更改连接字符串以生成 SqlException,代码会正确记录错误,然后抓取并显示静态内容。但是,如果我还更改了 Web.config 中静态文本文件的路径以测试 503 版本的主视图,那么我得到的是一个除了“服务不可用”之外什么都没有的页面。而已。没有具有网站外观的自定义 503 消息。

有没有人对改进代码有任何可能有帮助的建议?向 HttpResponse 添加不同的标头会有所帮助吗?还是 Go Daddy 重手劫持 503?

【问题讨论】:

    标签: asp.net-mvc error-handling global-asax shared-hosting


    【解决方案1】:

    我在 GoDaddy 上托管了一个 ASP.NET MVC 站点,并且还遇到了处理自定义错误页面的问题。通过反复试验,我发现 GoDaddy 在 HTTP 级别拦截错误。

    例如,任何返回 HTTP 状态代码 404 的页面都会导致 GoDaddy 的自定义错误页面接管。最终,我更改了自定义错误页面以返回 200 状态,并且与 404 相关的问题消失了。我的 HTML 是一样的,只是需要更改 HTTP 状态。

    诚然,我从未尝试过对 503 状态响应做同样的事情,但同样的缓解措施可能会奏效。如果您从返回 503 状态更改为返回 200 状态,问题会消失吗?

    请注意,如果您采用这种变通方法,您将希望阻止搜索引擎将您的错误页面编入索引,一旦返回 200 状态,将与常规页面无法区分(从搜索引擎的角度来看)。因此,请确保添加 META ROBOTS 标记以防止将您的错误页面编入索引,例如

    <META NAME="ROBOTS" CONTENT="NOINDEX">
    

    这种方法的缺点可能是您的页面可能会从 Google 中删除,这绝对不是一件好事!

    更新:所以,另外,你还可以检测用户代理是否是爬虫,如果是爬虫返回503,如果不是爬虫返回200。有关如何检测爬虫的信息,请参阅this blog post。是的,我知道将不同的内容返回给爬虫和用户是一个 SEO 禁忌,但我已经在几个网站上这样做了,到目前为止没有任何不良影响,所以我不确定这是一个多大的问题。

    同时使用两种方法(META ROBOTS 和机器人检测)可能是您最好的选择,以防任何古怪的爬虫从机器人检测器中溜走。

    【讨论】:

    • 谢谢贾斯汀。我终于想出了如何修复 404,请参阅上面的更新。在我的情况下,这种方法的问题是某些 503 将在内容本来所在的实际页面上显示友好消息。如果我返回 200 而不是 503 并且不幸让任何爬虫在当天进行索引,他们将索引我的友好错误消息而不是正确的内容。所以这在我的特殊情况下不起作用。不过感谢您的回复。
    • 是的,要小心爬虫。我在原始帖子中指出,您可以使用 ROBOTS META 标签来解决这个问题,但我也添加了更多细节。见上文。
    • 您可能没有看到任何不良影响,但没有办法知道我和我的客户也会如此 :-)。我犹豫是否考虑您的方法,因为它可能会导致我的客户以及我的客户被从搜索引擎索引中删除和/或禁止。
    • 我认为在这种情况下这不是问题。原因如下:错误应该是不寻常且短暂的,否则您的客户会在 Google 之前解雇您! :-) 并且 503 HTTP 响应是专门定义的,因此用户代理(如搜索引擎)可以知道网站不可用,他们应该稍后再回来查看。因此,搜索引擎期望有时会看到来自同一网站的 503,有时会看到 200(和不同的内容)。从搜索引擎的角度来看,我向您推荐的内容与正常的网络应用程序行为没有区别。
    • 坚持是关键。我终于解决了这个问题,刚刚发布了解决方案。感谢您的建议。在箭筒中使用多个箭头来解决给定的问题总是好的。
    【解决方案2】:

    我找到了解决方案,而且非常简单。原来问题实际上出在 IIS7 中。在 Visual Studio 中调试此问题时,我看到了 HttpResponse 对象的一个​​我以前没有注意到的属性:

    public bool TrySkipIisCustomErrors { get; set; }
    

    这将我带到离我最近的搜索引擎,该搜索引擎出现了 great blog post by Rick Strahlangrypets.com 以及 this question here on SO 上的另一个。这些链接比我能更好地解释血淋淋的细节,但是 Rick 的帖子中的这句话很好地捕捉到了它:

    这里真正的混淆是因为错误被困在 ASP.NET,但最终仍由 IIS 处理,它着眼于 500 状态码并返回常用的 IIS 错误页面。

    这种行为似乎也特定于集成模式下的 IIS7。来自msdn

    在 IIS 7.0 中以经典模式运行时,TrySkipIisCustomErrors 属性默认值为 true。在集成模式下运行时, TrySkipIisCustomErrors 属性默认值为 false。

    所以基本上我最终要做的就是在任何将Response.StatusCode 设置为500 或503 的代码之后添加Response.TrySkipIisCustomErrors = true;,现在一切都按设计运行。

    【讨论】:

    • +1 谢谢,布莱恩。经过 2 个多小时的搜索,您给了我所需的答案。它是如此简单。我试图设置一个 404 错误代码,这给了我一个安全异常和“Response.TrySkipIisCustomErrors = true;”修好了。
    • 这让我快疯了。太感谢了。放置“Response.TrySkipIisCustomErrors = true;”在我的错误控制器中设置响应代码后立即对我有用。这是一种误导,因为我的本地 IIS 7 实例正在呈现我的自定义错误视图,但远程登台服务器却没有。 =P
    • 在应用此答案中提供的内容后,我在同一条船上:stackoverflow.com/a/7499406/114029Response.TrySkipIisCustomErrors = true;来自天堂! :)
    • 您链接讨论 IIS7 的文章,我在 IIS7.5 中遇到了这个问题,您的解决方案也为我解决了。
    猜你喜欢
    • 1970-01-01
    • 2018-12-20
    • 2012-06-29
    • 1970-01-01
    • 1970-01-01
    • 2011-10-07
    • 2016-08-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多