如何在ASP.NET MVC中正确处理404?

Bri*_*ian 426 asp.net-mvc http-status-code-404

我正在使用RC2

使用URL路由:

routes.MapRoute(
    "Error",
     "{*url}",
     new { controller = "Errors", action = "NotFound" }  // 404s
);
Run Code Online (Sandbox Code Playgroud)

以上似乎照顾这样的请求(假设默认路由表由初始MVC项目设置):"/ blah/blah/blah/blah"

覆盖控制器本身中的HandleUnknownAction():

// 404s - handle here (bad action requested
protected override void HandleUnknownAction(string actionName) {
    ViewData["actionName"] = actionName;
    View("NotFound").ExecuteResult(this.ControllerContext);
}  
Run Code Online (Sandbox Code Playgroud)

但是,之前的策略不处理对Bad/Unknown控制器的请求.例如,我没有"/ IDoNotExist",如果我请求这个,我从Web服务器获取通用404页面而不是我的404,如果我使用路由+覆盖.

最后,我的问题是: 有没有办法在MVC框架中使用路由或其他东西来捕获这种类型的请求?

或者我应该默认使用Web.Config customErrors作为我的404处理程序并忘记所有这些?我假设如果我使用customErrors,由于Web.Config对直接访问的限制,我必须在/ Views之外存储通用404页面.

小智 264

代码来自http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx并且有效在ASP.net MVC 1.0中也是如此

以下是我处理http异常的方法:

protected void Application_Error(object sender, EventArgs e)
{
   Exception exception = Server.GetLastError();
   // Log the exception.

   ILogger logger = Container.Resolve<ILogger>();
   logger.Error(exception);

   Response.Clear();

   HttpException httpException = exception as HttpException;

   RouteData routeData = new RouteData();
   routeData.Values.Add("controller", "Error");

   if (httpException == null)
   {
       routeData.Values.Add("action", "Index");
   }
   else //It's an Http Exception, Let's handle it.
   {
       switch (httpException.GetHttpCode())
       {
          case 404:
              // Page not found.
              routeData.Values.Add("action", "HttpError404");
              break;
          case 500:
              // Server error.
              routeData.Values.Add("action", "HttpError500");
              break;

           // Here you can handle Views to other error codes.
           // I choose a General error template  
           default:
              routeData.Values.Add("action", "General");
              break;
      }
  }           

  // Pass exception details to the target error View.
  routeData.Values.Add("error", exception);

  // Clear the error on server.
  Server.ClearError();

  // Avoid IIS7 getting in the middle
  Response.TrySkipIisCustomErrors = true; 

  // Call target Controller and pass the routeData.
  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(    
       new HttpContextWrapper(Context), routeData));
}
Run Code Online (Sandbox Code Playgroud)

  • 更新:肯定需要检查http 404,但我仍然不确定你何时获得500.你还需要明确设置Response.StatusCode = 404或500否则google将开始索引这些页面如果您要返回此代码当前执行的200状态代码 (23认同)
  • @Simon_Weaver:同意了!这需要返回404状态代码.它基本上被打破为404解决方案,直到它完成.请查看:http://www.codinghorror.com/blog/2007/03/creating-user-friendly-404-pages.html (6认同)
  • 感觉不对劲,MVC的整个目的是删除所有的抽象,但在这里又是...... (4认同)
  • 正如上面的一些评论和链接的帖子提到的那样,这似乎不起作用.命中errorcontroller,但返回空白屏幕.(使用mvc 3) (3认同)

Mat*_*caj 253

要求404

以下是我对404解决方案的要求,下面我将展示如何实现它:

  • 我想处理带有错误操作的匹配路由
  • 我想处理与坏控制器匹配的路由
  • 我想处理不匹配的路由(我的应用无法理解的任意网址) - 我不希望这些冒泡到Global.asax或IIS因为那时我无法正确地重定向回我的MVC应用程序
  • 我想要一种方法来处理与上面相同的方式,自定义404s - 就像为不存在的对象(可能删除)提交ID
  • 我希望我的所有404都返回一个MVC视图(不是静态页面),如果有必要,我可以在以后提取更多数据(好的404设计),并且它们必须返回HTTP 404状态代码

我认为你应该Application_Error在Global.asax中保存更高级的东西,比如未处理的异常和日志记录(比如Shay Jacoby的答案显示),但不是404处理.这就是为什么我的建议将404内容保留在Global.asax文件之外的原因.

第1步:为404错误逻辑提供一个共同的位置

这是可维护性的好主意.使用ErrorController,以便您对精心设计的404页面的未来改进可以轻松适应.另外,请确保您的回复包含404代码!

public class ErrorController : MyController
{
    #region Http404

    public ActionResult Http404(string url)
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        var model = new NotFoundViewModel();
        // If the url is relative ('NotFound' route) then replace with Requested path
        model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ?
            Request.Url.OriginalString : url;
        // Dont get the user stuck in a 'retry loop' by
        // allowing the Referrer to be the same as the Request
        model.ReferrerUrl = Request.UrlReferrer != null &&
            Request.UrlReferrer.OriginalString != model.RequestedUrl ?
            Request.UrlReferrer.OriginalString : null;

        // TODO: insert ILogger here

        return View("NotFound", model);
    }
    public class NotFoundViewModel
    {
        public string RequestedUrl { get; set; }
        public string ReferrerUrl { get; set; }
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

步骤2:使用基本Controller类,以便您可以轻松调用自定义404操作并连接 HandleUnknownAction

ASP.NET MVC中的404s需要在很多地方被捕获.首先是HandleUnknownAction.

InvokeHttp404方法为重新路由到ErrorController我们的新Http404操作创建了一个公共位置.想想!

public abstract class MyController : Controller
{
    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        // If controller is ErrorController dont 'nest' exceptions
        if (this.GetType() != typeof(ErrorController))
            this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

步骤3:在Controller Factory中使用依赖注入并连接404 HttpExceptions

像这样(它不一定是StructureMap):

MVC1.0示例:

public class StructureMapControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(RequestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }
}
Run Code Online (Sandbox Code Playgroud)

MVC2.0示例:

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == 404)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(requestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }
Run Code Online (Sandbox Code Playgroud)

我认为更好地捕捉到更接近它们起源的错误.这就是为什么我更喜欢上面的Application_Error处理程序.

这是第二个捕获404s的地方.

步骤4:向Global.asax添加NotFound路由,以获取无法解析到您的应用中的网址

这条路线应该指向我们的Http404行动.请注意,urlparam将是一个相对URL,因为路由引擎正在剥离域部分?这就是我们在步骤1中拥有所有条件URL逻辑的原因.

        routes.MapRoute("NotFound", "{*url}", 
            new { controller = "Error", action = "Http404" });
Run Code Online (Sandbox Code Playgroud)

这是在你自己不调用的MVC应用程序中捕获404s的第三个也是最后一个.如果你没有在这里捕获不匹配的路由,那么MVC会将问题传递给ASP.NET(Global.asax),在这种情况下你真的不想要它.

第5步:最后,当您的应用无法找到某些内容时调用404

就像将错误的ID提交给我的Loans控制器(源自MyController):

    //
    // GET: /Detail/ID

    public ActionResult Detail(int ID)
    {
        Loan loan = this._svc.GetLoans().WithID(ID);
        if (loan == null)
            return this.InvokeHttp404(HttpContext);
        else
            return View(loan);
    }
Run Code Online (Sandbox Code Playgroud)

如果所有这些都可以用更少的代码连接在更少的地方,那将是很好的,但我认为这个解决方案更易于维护,更可测试且相当实用.

感谢您的反馈到目前为止.我想要得到更多.

注意:这已经根据我的原始答案进行了大量编辑,但目的/要求是相同的 - 这就是我没有添加新答案的原因

  • 是否有人发现它显然是疯狂的,因为在Web框架中404这样的常见事情是如此血腥复杂. (119认同)
  • 感谢您的完整报道.另外一个问题是,当在IIS7下运行时,您需要将属性"TrySkipIisCustomErrors"添加到true.否则IIS仍将返回默认的404页面.我们添加了Response.TrySkipIiisCustomErrors = true; 在步骤5中设置状态代码的行之后.http://msdn.microsoft.com/en-us/library/system.web.httpresponse.tryskipiiscustomerrors.aspx (12认同)
  • 这适用于MVC3.我将`ObjectFactory.GetInstance`切换到MVC3的`DependencyResolver.Current.GetService`,所以它更通用.我正在使用Ninject. (7认同)

Pav*_*uva 232

ASP.NET MVC不能很好地支持自定义404页面.定制控制器工厂,全能路线,基础控制器类HandleUnknownAction- 唉!

到目前为止,IIS自定义错误页面是更好的选择

web.config中

<system.webServer>
  <httpErrors errorMode="Custom" existingResponse="Replace">
    <remove statusCode="404" />
    <error statusCode="404" responseMode="ExecuteURL" path="/Error/PageNotFound" />
  </httpErrors>
</system.webServer>
Run Code Online (Sandbox Code Playgroud)

ErrorController

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View();
    }
}
Run Code Online (Sandbox Code Playgroud)

示例项目

  • 这应该是接受的答案!使用IIS Express在ASP.NET MVC 3上运行良好. (38认同)
  • 如果您使用的是IIS7 +,这绝对是您的选择.+1! (7认同)
  • 这在iis express中运行良好,但是一旦我在生产IIS 7.5中部署该站点,我得到的只是一个白页而不是错误视图. (6认同)
  • 当您在同一个项目中使用JSON时,是否可以仅返回状态404? (3认同)
  • 根据我的测试(使用MVC3),这会将`customErrors mode ="On"`与`HandleErrorAttribute`一起打破.控制器操作中未处理的异常的自定义错误页面不再提供. (2认同)
  • @AndreiRinea这种方法效果很好,但只有你想要所有404的单一捕获.例如,如果您想要404用于静态资源(例如在/ images下),那么您可以避免为不良资源请求重新呈现自定义404页面(带有布局,图像等)的开销. (2认同)

Pur*_*ome 153

快速回答/ TL; DR

在此输入图像描述

对于那里的懒人:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0
Run Code Online (Sandbox Code Playgroud)

然后从中删除此行 global.asax

GlobalFilters.Filters.Add(new HandleErrorAttribute());
Run Code Online (Sandbox Code Playgroud)

这仅适用于IIS7 +和IIS Express.

如果你正在使用卡西尼......那么......嗯..呃......尴尬...... 尴尬


很久,解释的答案

我知道这已经回答了.但答案真的很简单(为David FowlerDamian Edwards欢呼,真的回答这个问题).

没有必要做任何定制.

因为ASP.NET MVC3,所有的零碎都在那里.

第1步 - >在两个位置更新您的web.config.

<system.web>
    <customErrors mode="On" defaultRedirect="/ServerError">
      <error statusCode="404" redirect="/NotFound" />
    </customErrors>
Run Code Online (Sandbox Code Playgroud)

<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404" subStatusCode="-1" />
      <error statusCode="404" path="/NotFound" responseMode="ExecuteURL" />
      <remove statusCode="500" subStatusCode="-1" />
      <error statusCode="500" path="/ServerError" responseMode="ExecuteURL" />
    </httpErrors>    

...
<system.webServer>
...
</system.web>
Run Code Online (Sandbox Code Playgroud)

现在请仔细注意我决定使用的路线.你可以使用任何东西,但我的路线是

  • /NotFound < - 找不到404,错误页面.
  • /ServerError< - 对于任何其他错误,包括我的代码中发生的错误.这是500内部服务器错误

看看第一部分中<system.web>只有一个自定义条目?这个statusCode="404"条目?我只列出了一个状态代码,因为所有其他错误,包括500 Server Error(即,当您的代码有错误并崩溃用户的请求时发生的那些讨厌的错误)..所有其他错误由设置处理defaultRedirect="/ServerError"... ,如果您没有找到404页面,请转到该路线/ServerError.

好.这是不合适的..现在我的路线列出来了global.asax

第2步 - 在Global.asax中创建路由

这是我的完整路线部分..

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("{*favicon}", new {favicon = @"(.*/)?favicon.ico(/.*)?"});

    routes.MapRoute(
        "Error - 404",
        "NotFound",
        new { controller = "Error", action = "NotFound" }
        );

    routes.MapRoute(
        "Error - 500",
        "ServerError",
        new { controller = "Error", action = "ServerError"}
        );

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new {controller = "Home", action = "Index", id = UrlParameter.Optional}
        );
}
Run Code Online (Sandbox Code Playgroud)

这列出了两个忽略路由 - > axd'sfavicons(ooo!bonus ignore route,for you!)然后(并且命令是IMPERATIVE HERE),我有两个显式的错误处理路由..后跟任何其他路由.在这种情况下,默认值为1.当然,我有更多,但这对我的网站来说很特别.只需确保错误路由位于列表顶部.秩序是必要的.

最后,当我们在global.asax文件中时,我们不会全局注册HandleError属性.不,不,不,先生.Nadda.不.粘.负.Noooooooooo ...

从中删除此行 global.asax

GlobalFilters.Filters.Add(new HandleErrorAttribute());
Run Code Online (Sandbox Code Playgroud)

第3步 - 使用操作方法创建控制器

现在..我们添加一个带有两种动作方法的控制器......

public class ErrorController : Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        return View();
    }

    public ActionResult ServerError()
    {
        Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        // Todo: Pass the exception into the view model, which you can make.
        //       That's an exercise, dear reader, for -you-.
        //       In case u want to pass it to the view, if you're admin, etc.
        // if (User.IsAdmin) // <-- I just made that up :) U get the idea...
        // {
        //     var exception = Server.GetLastError();
        //     // etc..
        // }

        return View();
    }

    // Shhh .. secret test method .. ooOOooOooOOOooohhhhhhhh
    public ActionResult ThrowError()
    {
        throw new NotImplementedException("Pew ^ Pew");
    }
}
Run Code Online (Sandbox Code Playgroud)

好的,我们来看看吧.首先,这里没有 [HandleError]属性.为什么?因为内置ASP.NET框架已经处理错误而且我们已经指定了我们需要做的所有狗屎来处理错误:)这是在这个方法中!

接下来,我有两个动作方法.那里没什么难的.如果您希望显示任何异常信息,那么您可以使用Server.GetLastError()获取该信息.

奖金WTF:是的,我做了第三个动作方法,测试错误处理.

第4步 - 创建视图

最后,创建两个视图.将em放在普通视点中,对于此控制器.

在此输入图像描述

奖金评论

  • 你不需要 Application_Error(object sender, EventArgs e)
  • 以上步骤与Elmah完全一致.Elmah fraking wroxs!

那个,我的朋友,应该是它.

现在,恭喜阅读这篇文章并获得独角兽作为奖品!

在此输入图像描述

  • 这可能听起来很奇怪 - 但我的回答是在很久以前提供的,我同意你的@Gutek,我不喜欢重定向到错误页面*了*.我曾经(参考我的答案:P).如果错误发生在/ some/resource ..那么那个资源应该返回404或500等等.否则就会产生大规模的SEO影响.啊......时代变了:) (7认同)
  • -1,对不起,对我来说任何改变404的网址的解决方案都是错误的.并且使用webconfig在MVC中无法在不更改url的情况下处理它,或者您需要创建静态html文件或aspx(是的,普通的旧aspx文件)才能够执行此操作.如果你喜欢在网址中使用`?aspxerrorpath =/er/not/found`,你的解决方案就没问题了. (3认同)

Mar*_*rco 85

我调查了很多关于如何在MVC妥善管理404 (具体MVC3) ,这一点,恕我直言,是最好的解决方案,我想出来的:

在global.asax中:

public class MvcApplication : HttpApplication
{
    protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 404)
        {
            Response.Clear();

            var rd = new RouteData();
            rd.DataTokens["area"] = "AreaName"; // In case controller is in another area
            rd.Values["controller"] = "Errors";
            rd.Values["action"] = "NotFound";

            IController c = new ErrorsController();
            c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

ErrorsController:

public sealed class ErrorsController : Controller
{
    public ActionResult NotFound()
    {
        ActionResult result;

        object model = Request.Url.PathAndQuery;

        if (!Request.IsAjaxRequest())
            result = View(model);
        else
            result = PartialView("_NotFound", model);

        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

(可选的)

说明:

AFAIK,ASP.NET MVC3应用程序可以生成404的6种不同情况.

(由ASP.NET Framework自动生成:)

(1) URL在路由表中找不到匹配项.

(由ASP.NET MVC框架自动生成:)

(2) URL在路由表中查找匹配项,但指定不存在的控制器.

(3) URL在路由表中查找匹配项,但指定不存在的操作.

(手动生成:)

(4)动作使用方法HttpNotFound()返回HttpNotFoundResult.

(5)一个动作抛出一个状态码为404的HttpException.

(6)动作手动将Response.StatusCode属性修改为404.

通常,您希望实现3个目标:

(1)向用户显示自定义404错误页面.

(2)维护客户端响应的404状态代码(对SEO特别重要).

(3)直接发送响应,不涉及302重定向.

有多种方法可以尝试实现此目的:

(1)

<system.web>
    <customErrors mode="On">
        <error statusCode="404" redirect="~/Errors/NotFound"/>
    </customError>
</system.web>
Run Code Online (Sandbox Code Playgroud)

此解决方案的问题:

  1. 在情况(1),(4),(6)中不符合目标(1).
  2. 不自动符合目标(2).必须手动编程.
  3. 不符合目标(3).

(2)

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>
Run Code Online (Sandbox Code Playgroud)

此解决方案的问题:

  1. 仅适用于IIS 7+.
  2. 在情况(2),(3),(5)中不符合目标(1).
  3. 不自动符合目标(2).必须手动编程.

(3)

<system.webServer>
    <httpErrors errorMode="Custom" existingResponse="Replace">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>
Run Code Online (Sandbox Code Playgroud)

此解决方案的问题:

  1. 仅适用于IIS 7+.
  2. 不自动符合目标(2).必须手动编程.
  3. 它模糊了应用程序级别的http异常.例如,不能使用customErrors部分,System.Web.Mvc.HandleErrorAttribute等.它不仅可以显示通用错误页面.

(4)

<system.web>
    <customErrors mode="On">
        <error statusCode="404" redirect="~/Errors/NotFound"/>
    </customError>
</system.web>
Run Code Online (Sandbox Code Playgroud)

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>
Run Code Online (Sandbox Code Playgroud)

此解决方案的问题:

  1. 仅适用于IIS 7+.
  2. 不自动符合目标(2).必须手动编程.
  3. 在情况(2),(3),(5)中不符合目标(3).

在尝试创建自己的库之前,对此感到困扰的人们(请参阅http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html).但是之前的解决方案似乎涵盖了所有情况,而没有使用外部库的复杂性.

  • 谢谢!它不能在Application_Error下完成,因为从控制器抛出的显式404不会被视为ASP.NET上的错误.如果从控制器返回HttpNotFound(),则Application_Error事件将永远不会触发. (7认同)
  • 使用MVC 4,我总是得到`MissingMethodException:无法在行上创建一个抽象类``c.Execute(new RequestContext(new HttpContextWrapper(Context),rd));`有什么想法吗? (2认同)

Dav*_*owe 13

我真的很喜欢cottsaks解决方案,并认为它非常清楚地解释.我唯一的补充是如下改变第2步

public abstract class MyController : Controller
{

    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        //if controller is ErrorController dont 'nest' exceptions
        if(this.GetType() != typeof(ErrorController))
        this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

基本上,这会阻止包含无效操作和控制器的URL触发异常例程两次.例如,对于诸如asdfsdf/dfgdfgd之类的URL

  • 这很棒.那些"两次"的案子开始困扰我.*更新了我的答案* (4认同)

Dav*_*e K 6

我可以获得@ cottsak的方法来处理无效控制器的唯一方法是修改CustomControllerFactory中的现有路由请求,如下所示:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType); 
            else
                return ObjectFactory.GetInstance(controllerType) as Controller;
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                requestContext.RouteData.Values["controller"] = "Error";
                requestContext.RouteData.Values["action"] = "Http404";
                requestContext.RouteData.Values.Add("url", requestContext.HttpContext.Request.Url.OriginalString);

                return ObjectFactory.GetInstance<ErrorController>();
            }
            else
                throw ex;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我应该提到我正在使用MVC 2.0.


sky*_*dev 5

这是使用 MVC 工具的另一种方法,您可以处理对错误控制器名称、错误路由名称以及您认为适合 Action 方法内部的任何其他条件的请求。就我个人而言,我更喜欢避免尽可能多的 web.config 设置,因为它们执行 302 / 200 重定向,并且不支持Server.Transfer使用 Razor 视图的 ResponseRewrite ( )。出于 SEO 原因,我更愿意返回带有自定义错误页面的 404。

其中一些是对上述 cottsak 技术的新运用。

此解决方案还使用有利于 MVC 3 错误过滤器的最小 web.config 设置。

用法

只需从操作或自定义 ActionFilterAttribute 抛出 HttpException。

Throw New HttpException(HttpStatusCode.NotFound, "[Custom Exception Message Here]")
Run Code Online (Sandbox Code Playgroud)

步骤1

将以下设置添加到您的 web.config。这是使用 MVC 的 HandleErrorAttribute 所必需的。

<customErrors mode="On" redirectMode="ResponseRedirect" />
Run Code Online (Sandbox Code Playgroud)

第2步

添加与 MVC 框架的 HandleErrorAttribute 类似的自定义 HandleHttpErrorAttribute,但 HTTP 错误除外:

<AttributeUsage(AttributeTargets.All, AllowMultiple:=True)>
Public Class HandleHttpErrorAttribute
    Inherits FilterAttribute
    Implements IExceptionFilter

    Private Const m_DefaultViewFormat As String = "ErrorHttp{0}"

    Private m_HttpCode As HttpStatusCode
    Private m_Master As String
    Private m_View As String

    Public Property HttpCode As HttpStatusCode
        Get
            If m_HttpCode = 0 Then
                Return HttpStatusCode.NotFound
            End If
            Return m_HttpCode
        End Get
        Set(value As HttpStatusCode)
            m_HttpCode = value
        End Set
    End Property

    Public Property Master As String
        Get
            Return If(m_Master, String.Empty)
        End Get
        Set(value As String)
            m_Master = value
        End Set
    End Property

    Public Property View As String
        Get
            If String.IsNullOrEmpty(m_View) Then
                Return String.Format(m_DefaultViewFormat, Me.HttpCode)
            End If
            Return m_View
        End Get
        Set(value As String)
            m_View = value
        End Set
    End Property

    Public Sub OnException(filterContext As System.Web.Mvc.ExceptionContext) Implements System.Web.Mvc.IExceptionFilter.OnException
        If filterContext Is Nothing Then Throw New ArgumentException("filterContext")

        If filterContext.IsChildAction Then
            Return
        End If

        If filterContext.ExceptionHandled OrElse Not filterContext.HttpContext.IsCustomErrorEnabled Then
            Return
        End If

        Dim ex As HttpException = TryCast(filterContext.Exception, HttpException)
        If ex Is Nothing OrElse ex.GetHttpCode = HttpStatusCode.InternalServerError Then
            Return
        End If

        If ex.GetHttpCode <> Me.HttpCode Then
            Return
        End If

        Dim controllerName As String = filterContext.RouteData.Values("controller")
        Dim actionName As String = filterContext.RouteData.Values("action")
        Dim model As New HandleErrorInfo(filterContext.Exception, controllerName, actionName)

        filterContext.Result = New ViewResult With {
            .ViewName = Me.View,
            .MasterName = Me.Master,
            .ViewData = New ViewDataDictionary(Of HandleErrorInfo)(model),
            .TempData = filterContext.Controller.TempData
        }
        filterContext.ExceptionHandled = True
        filterContext.HttpContext.Response.Clear()
        filterContext.HttpContext.Response.StatusCode = Me.HttpCode
        filterContext.HttpContext.Response.TrySkipIisCustomErrors = True
    End Sub
End Class
Run Code Online (Sandbox Code Playgroud)

步骤3

将过滤器添加到 中的 GlobalFilterCollection( GlobalFilters.Filters) 中Global.asax。此示例会将所有 InternalServerError (500) 错误路由到错误共享视图 ( Views/Shared/Error.vbhtml)。NotFound (404) 错误也将发送到共享视图中的 ErrorHttp404.vbhtml。我在此处添加了 401 错误,以向您展示如何针对其他 HTTP 错误代码进行扩展。请注意,这些必须是共享视图,并且它们都使用System.Web.Mvc.HandleErrorInfo对象作为模型。

filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp401", .HttpCode = HttpStatusCode.Unauthorized})
filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp404", .HttpCode = HttpStatusCode.NotFound})
filters.Add(New HandleErrorAttribute With {.View = "Error"})
Run Code Online (Sandbox Code Playgroud)

步骤4

创建一个基本控制器类并在控制器中继承它。此步骤允许我们处理未知的操作名称并向我们的 HandleHttpErrorAttribute 引发 HTTP 404 错误。

Public Class BaseController
    Inherits System.Web.Mvc.Controller

    Protected Overrides Sub HandleUnknownAction(actionName As String)
        Me.ActionInvoker.InvokeAction(Me.ControllerContext, "Unknown")
    End Sub

    Public Function Unknown() As ActionResult
        Throw New HttpException(HttpStatusCode.NotFound, "The specified controller or action does not exist.")
        Return New EmptyResult
    End Function
End Class
Run Code Online (Sandbox Code Playgroud)

步骤5

创建一个 ControllerFactory 覆盖,并在 Application_Start 的 Global.asax 文件中覆盖它。此步骤允许我们在指定无效的控制器名称时引发 HTTP 404 异常。

Public Class MyControllerFactory
    Inherits DefaultControllerFactory

    Protected Overrides Function GetControllerInstance(requestContext As System.Web.Routing.RequestContext, controllerType As System.Type) As System.Web.Mvc.IController
        Try
            Return MyBase.GetControllerInstance(requestContext, controllerType)
        Catch ex As HttpException
            Return DependencyResolver.Current.GetService(Of BaseController)()
        End Try
    End Function
End Class

'In Global.asax.vb Application_Start:

controllerBuilder.Current.SetControllerFactory(New MyControllerFactory)
Run Code Online (Sandbox Code Playgroud)

步骤6

在 RoutTable.Routes 中包含一条用于 BaseController Unknown 操作的特殊路由。这将帮助我们在用户访问未知控制器或未知操作的情况下引发 404。

'BaseController
routes.MapRoute( _
    "Unknown", "BaseController/{action}/{id}", _
    New With {.controller = "BaseController", .action = "Unknown", .id = UrlParameter.Optional} _
)
Run Code Online (Sandbox Code Playgroud)

概括

此示例演示了如何使用 MVC 框架将 404 Http 错误代码返回到浏览器,而无需使用筛选器属性和共享错误视图进行重定向。它还演示了在指定无效的控制器名称和操作名称时显示相同的自定义错误页面。

如果我获得足够的票数来发布一个,我将添加无效控制器名称、操作名称和从 Home/TriggerNotFound 操作引发的自定义 404 的屏幕截图 =)。当我使用此解决方案访问以下 URL 时,Fiddler 返回 404 消息:

/InvalidController
/Home/InvalidRoute
/InvalidController/InvalidRoute
/Home/TriggerNotFound
Run Code Online (Sandbox Code Playgroud)

cottsak 的上述帖子和这些文章都是很好的参考。


Her*_*Kan 5

我的缩短解决方案适用于未处理的区域、控制器和操作:

  1. 创建视图404.cshtml。

  2. 为您的控制器创建一个基类:

    public class Controller : System.Web.Mvc.Controller
    {
        protected override void HandleUnknownAction(string actionName)
        {
            Http404().ExecuteResult(ControllerContext);
        }
    
        protected virtual ViewResult Http404()
        {
            Response.StatusCode = (int)HttpStatusCode.NotFound;
            return View("404");
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 创建一个自定义控制器工厂,返回您的基本控制器作为后备:

    public class ControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType != null)
                return base.GetControllerInstance(requestContext, controllerType);
    
            return new Controller();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 添加到Application_Start()以下行:

    ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
    
    Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

132488 次

最近记录:

7 年,4 月 前