当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《如何在ASP.NETMVC中正确处理404?》经验,为你挑选了7个好方法。

我正在使用RC2

使用URL路由:

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

以上似乎照顾这样的请求(假设默认路由表由初始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);
}  

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

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

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



1> 小智..:

代码来自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();
   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));
}


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

2> Matt Kocaj..:
要求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
}

步骤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();
        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
}

步骤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).InvokeHttp404(RequestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }
}

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).InvokeHttp404(requestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }

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

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

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

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

        routes.MapRoute("NotFound", "{*url}", 
            new { controller = "Error", action = "Http404" });

这是在你自己不调用的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);
    }

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

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

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


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

3> Pavel Chuchu..:

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

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

web.config中

  
    
    
  

ErrorController
public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View();
    }
}
示例项目

GitHub上的Test404

直播网站


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

4> Pure.Krome..:

快速回答/ TL; DR

在此输入图像描述

对于那里的懒人:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

然后从中删除此行 global.asax

GlobalFilters.Filters.Add(new HandleErrorAttribute());

这仅适用于IIS7 +和IIS Express.

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


很久,解释的答案

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

没有必要做任何定制.

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

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


    
      
    


    
      
      
      
      
        

...

...

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

/NotFound < - 找不到404,错误页面.

/ServerError< - 对于任何其他错误,包括我的代码中发生的错误.这是500内部服务器错误

看看第一部分中只有一个自定义条目?这个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}
        );
}

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

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

从中删除此行 global.asax

GlobalFilters.Filters.Add(new HandleErrorAttribute());

第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");
    }
}

好的,我们来看看吧.首先,这里没有 [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影响.啊......时代变了:)
-1,对不起,对我来说任何改变404的网址的解决方案都是错误的.并且使用webconfig在MVC中无法在不更改url的情况下处理它,或者您需要创建静态html文件或aspx(是的,普通的旧aspx文件)才能够执行此操作.如果你喜欢在网址中使用`?aspxerrorpath =/er/not/found`,你的解决方案就没问题了.

5> Marco..:

我调查了很多关于如何在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));
        }
    }
}

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;
    }
}

(可选的)

说明:

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)


    
        
    

此解决方案的问题:

    在情况(1),(4),(6)中不符合目标(1).

    不自动符合目标(2).必须手动编程.

    不符合目标(3).

(2)


    
        
        
    

此解决方案的问题:

    仅适用于IIS 7+.

    在情况(2),(3),(5)中不符合目标(1).

    不自动符合目标(2).必须手动编程.

(3)


    
        
        
    

此解决方案的问题:

    仅适用于IIS 7+.

    不自动符合目标(2).必须手动编程.

    它模糊了应用程序级别的http异常.例如,不能使用customErrors部分,System.Web.Mvc.HandleErrorAttribute等.它不仅可以显示通用错误页面.

(4)


    
        
    


    
        
        
    

此解决方案的问题:

    仅适用于IIS 7+.

    不自动符合目标(2).必须手动编程.

    在情况(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事件将永远不会触发.
使用MVC 4,我总是得到`MissingMethodException:无法在行上创建一个抽象类``c.Execute(new RequestContext(new HttpContextWrapper(Context),rd));`有什么想法吗?

6> 小智..:

我真的很喜欢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();
        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
}

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


这很棒.那些"两次"的案子开始困扰我.*更新了我的答案*

7> Dave K..:

我可以获得@ 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();
            }
            else
                throw ex;
        }
    }
}

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

推荐阅读
mobiledu2402851323
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有