Làm thế nào tôi có thể xử lý đúng 404 trong ASP.NET MVC?


432

Tôi đang sử dụng RC2

Sử dụng định tuyến URL:

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

Ở trên dường như xử lý các yêu cầu như thế này (giả sử thiết lập bảng tuyến đường mặc định theo dự án MVC ban đầu): "/ blah / blah / blah / blah"

Ghi đè Xử lýUn UnknownAction () trong chính bộ điều khiển:

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

Tuy nhiên, các chiến lược trước đó không xử lý yêu cầu đối với bộ điều khiển Bad / Unknown. Ví dụ: tôi không có "/ IDoNotExist", nếu tôi yêu cầu điều này, tôi nhận được trang 404 chung từ máy chủ web chứ không phải 404 của tôi nếu tôi sử dụng định tuyến + ghi đè.

Vì vậy, cuối cùng, câu hỏi của tôi là: Có cách nào để bắt được loại yêu cầu này bằng cách sử dụng một tuyến đường hoặc một cái gì đó khác trong chính khung công tác MVC không?

HOẶC tôi chỉ nên mặc định sử dụng Web.Config customErrors làm trình xử lý 404 của mình và quên tất cả điều này? Tôi giả sử nếu tôi đi với customErrors tôi sẽ phải lưu trữ trang 404 chung bên ngoài / Lượt xem do các hạn chế của Web.Config đối với truy cập trực tiếp.


3
Đó là lỗi 404, tôi sẽ không bận tâm về nó. hãy để nó hiển thị 404. vì chắc chắn người dùng đã nhầm lẫn một cái gì đó. hoặc nếu đó là một cái gì đó được di chuyển thì ứng dụng của bạn sẽ nhận yêu cầu đó và chuyển hướng vĩnh viễn. 404 thuộc về máy chủ web không phải là ứng dụng. bạn luôn có thể tùy chỉnh các trang iis bị lỗi.
mamu

bạn cũng có thể xem giải pháp này cũng như blog.dantup.com/2009/04/ trên
Nhà phát triển

ben.onfabrik.com/posts/aspnet-mvc-custom-error-pages cũng có một số thông tin tốt
Chris S

4
Thật đáng tiếc khi 4 bản phát hành ổn định sau đó và hơn 5 năm, tình huống xử lý 404 trong asp.net MVC + IIS vẫn chưa thực sự được cải thiện và đây vẫn là cách hỏi đáp về cách xử lý.
Joelmdev

Câu trả lời:


271

Mã được lấy từ http://bloss.microsoft.co.il/bloss/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx và hoạt động trong ASP.net MVC 1.0 cũng vậy

Đây là cách tôi xử lý các ngoại lệ 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));
}

23
cập nhật: việc kiểm tra http 404 chắc chắn là bắt buộc, nhưng tôi vẫn không hoàn toàn chắc chắn khi nào bạn nhận được 500. Ngoài ra, bạn cũng cần đặt rõ ràng Phản hồi.StatusCode = 404 hoặc 500 nếu không google sẽ bắt đầu lập chỉ mục các trang này nếu bạn đang trả lại 200 mã trạng thái mà mã này hiện đang làm
Simon_Weaver

6
@Simon_Weaver: đồng ý! Điều này cần phải trả về mã trạng thái 404. Về cơ bản, nó bị hỏng như một giải pháp 404 cho đến khi nó được. Kiểm tra này: codinghorror.com/blog/2007/03/...
Matt Kocaj

1
Có một lỗ hổng cơ bản với toàn bộ đề xuất này - bởi thời gian thực hiện đã nổi lên trên Global.asax, quá nhiều httpContext bị thiếu. Bạn không thể định tuyến lại vào bộ điều khiển của mình như ví dụ gợi ý. Tham khảo các ý kiến ​​trong liên kết blog ở đầu.
Matt Kocaj

3
Như một số ý kiến ​​từ phía trên và bài đăng được liên kết đề cập, điều này dường như không hoạt động. Bộ điều khiển lỗi bị tấn công, nhưng màn hình trống sẽ trả về. (sử dụng mvc 3)
RyanW

4
một cái gì đó không cảm thấy đúng, toàn bộ mục đích của MVC là loại bỏ tất cả sự trừu tượng đó và một lần nữa nó lại ...
Alex Nolasco

255

Yêu cầu đối với 404

Sau đây là những yêu cầu của tôi đối với giải pháp 404 và dưới đây tôi chỉ ra cách tôi triển khai nó:

  • Tôi muốn xử lý các tuyến phù hợp với các hành động xấu
  • Tôi muốn xử lý các tuyến phù hợp với bộ điều khiển xấu
  • Tôi muốn xử lý các tuyến đường không khớp (các url tùy ý mà ứng dụng của tôi không thể hiểu được) - tôi không muốn các tuyến này nổi lên Global.asax hoặc IIS vì sau đó tôi không thể chuyển hướng trở lại vào ứng dụng MVC của mình đúng cách
  • Tôi muốn có cách xử lý theo cách tương tự như trên, 404 tùy chỉnh - như khi ID được gửi cho một đối tượng không tồn tại (có thể bị xóa)
  • Tôi muốn tất cả các 404 của tôi trả về chế độ xem MVC (không phải trang tĩnh) để tôi có thể bơm thêm dữ liệu sau này nếu cần ( thiết kế 404 tốt ) chúng phải trả về mã trạng thái HTTP 404

Giải pháp

Tôi nghĩ bạn nên lưu Application_Errortrong Global.asax cho những thứ cao hơn, như ngoại lệ chưa được xử lý và ghi nhật ký (như chương trình trả lời của Shay Jacoby ) nhưng không xử lý 404. Đây là lý do tại sao đề xuất của tôi giữ nội dung 404 khỏi tệp Global.asax.

Bước 1: Có một vị trí chung cho logic lỗi 404

Đây là một ý tưởng tốt cho khả năng bảo trì. Sử dụng ErrorControll để các cải tiến trong tương lai cho trang 404 được thiết kế tốt của bạn có thể dễ dàng điều chỉnh. Ngoài ra, hãy chắc chắn rằng phản hồi của bạn có mã 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
}

Bước 2: Sử dụng lớp Trình điều khiển cơ sở để bạn có thể dễ dàng gọi hành động 404 tùy chỉnh của mình và nối dây HandleUnknownAction

404 trong ASP.NET MVC cần phải được bắt gặp ở một số nơi. Đầu tiên là HandleUnknownAction.

Các InvokeHttp404phương pháp tạo ra một nơi phổ biến để tái định tuyến đến ErrorControllervà mới của chúng tôi Http404hành động. Hãy suy nghĩ KHÔ !

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
}

Bước 3: Sử dụng tính năng tiêm phụ thuộc trong Nhà máy điều khiển của bạn và kết nối 404 httpExceptions

Giống như vậy (không nhất thiết phải là StructMap):

Ví dụ về 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;
    }
}

Ví dụ về 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;
    }

Tôi nghĩ rằng tốt hơn là bắt lỗi gần hơn với nơi chúng bắt nguồn. Đây là lý do tại sao tôi thích ở trên để Application_Errorxử lý.

Đây là nơi thứ hai để bắt 404s.

Bước 4: Thêm tuyến NotFound vào Global.asax cho các url không được phân tích cú pháp vào ứng dụng của bạn

Tuyến đường này nên chỉ ra Http404hành động của chúng tôi . Lưu ý urlparam sẽ là một url tương đối vì công cụ định tuyến đang tước phần miền ở đây? Đó là lý do tại sao chúng ta có tất cả logic url có điều kiện trong Bước 1.

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

Đây là nơi thứ ba và cuối cùng để bắt 404 trong một ứng dụng MVC mà bạn không tự gọi mình. Nếu bạn không bắt được các tuyến chưa từng có ở đây thì MVC sẽ chuyển vấn đề sang ASP.NET (Global.asax) và bạn không thực sự muốn điều đó trong tình huống này.

Bước 5: Cuối cùng, gọi 404 khi ứng dụng của bạn không thể tìm thấy thứ gì đó

Giống như khi ID xấu được gửi tới bộ điều khiển Cho vay của tôi (xuất phát từ 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);
    }

Sẽ thật tuyệt nếu tất cả những điều này có thể được kết nối ở ít nơi hơn với ít mã hơn nhưng tôi nghĩ giải pháp này dễ bảo trì hơn, dễ kiểm tra hơn và khá thực dụng.

Cảm ơn các phản hồi cho đến nay. Tôi muốn nhận được nhiều hơn.

LƯU Ý: Điều này đã được chỉnh sửa đáng kể từ câu trả lời ban đầu của tôi nhưng mục đích / yêu cầu là như nhau - đây là lý do tại sao tôi chưa thêm câu trả lời mới


12
Cảm ơn đã viết đầy đủ. Một bổ sung là khi chạy trong IIS7, bạn cần thêm thuộc tính "TrySkipIisCustomErrors" thành true. Nếu không, IIS sẽ vẫn trả về trang 404 mặc định. Chúng tôi đã thêm Feedback.TrySkipIiisCustomErrors = true; sau dòng trong Bước 5 đặt mã trạng thái. msdn.microsoft.com/en-us/l Library / Mạnh
Rick

1
@Ryan customErrorsPhần của web.config xác định các trang chuyển hướng tĩnh được xử lý ở mức cao trong aspnet nếu không phải IIS. Đây không phải là điều tôi muốn vì tôi cần có kết quả là MVC Views (vì vậy tôi có thể có dữ liệu trong đó, v.v.). Tôi sẽ không nói một cách cụ thể rằng " customErrorsđã lỗi thời trong MVC" nhưng đối với tôi và giải pháp 404 này chắc chắn là như vậy.
Matt Kocaj

1
Ngoài ra, ai đó có thể cập nhật Bước 3 để StructMap không được sử dụng không? Có thể chỉ là một Trình điều khiển chung chung sẽ dễ thực hiện nếu bạn chưa sử dụng Trình điều khiển.
David Murdoch

7
Điều này hoạt động tốt cho MVC3. Tôi đã chuyển sang ObjectFactory.GetInstanceMVC3 DependencyResolver.Current.GetServicethay vì vậy nó chung chung hơn. Tôi đang sử dụng Ninject.
kamranicus

122
Có ai khác thấy nó điên rồ một cách rõ ràng rằng một điều phổ biến như 404 trong một khung web là rất phức tạp.
quentin-starin

235

ASP.NET MVC không hỗ trợ các trang 404 tùy chỉnh rất tốt. Nhà máy điều khiển tùy chỉnh, tuyến đường bắt tất cả, lớp trình điều khiển cơ sở với HandleUnknownAction- argh!

Các trang lỗi tùy chỉnh IIS là sự thay thế tốt hơn cho đến nay:

web.config

<system.webServer>
  <httpErrors errorMode="Custom" existingResponse="Replace">
    <remove statusCode="404" />
    <error statusCode="404" responseMode="ExecuteURL" path="/Error/PageNotFound" />
  </httpErrors>
</system.webServer>

Lỗi kiểm soát

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

Dự án mẫu


38
ĐIỀU NÀY NÊN ĐƯỢC CHẤP NHẬN TRẢ LỜI !!! Hoạt động tuyệt vời trên ASP.NET MVC 3 với IIS Express.
Andrei Rînea

7
Nếu bạn đang sử dụng IIS7 + thì đây chắc chắn là cách tốt nhất. +1!
elo80ka

3
có thể chỉ trả về trạng thái 404 khi bạn đang làm việc trong JSON trong cùng một dự án không?
VinnyG

6
Điều này hoạt động rất tốt trong iis express, nhưng ngay khi tôi triển khai trang web trong sản xuất IIS 7.5, tất cả những gì tôi nhận được là một trang trắng thay vì chế độ xem lỗi.
Moulde

2
Theo thử nghiệm của tôi (với MVC3), điều này phá vỡ customErrors mode="On"cùng với HandleErrorAttributechức năng. Các trang lỗi tùy chỉnh cho các ngoại lệ chưa được xử lý trong các hành động của bộ điều khiển không được cung cấp nữa.
Slauma

153

Trả lời nhanh / TL; DR

nhập mô tả hình ảnh ở đây

Dành cho những người lười biếng ngoài kia:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

Sau đó xóa dòng này khỏi global.asax

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

Và điều này chỉ dành cho IIS7 + và IIS Express.

Nếu bạn đang sử dụng Cassini .. tốt .. um .. er .. vụng về ... vụng về


Long, giải thích câu trả lời

Tôi biết điều này đã được trả lời. Nhưng câu trả lời là THỰC SỰ ĐƠN GIẢN (chúc mừng David FowlerDamian Edwards vì đã thực sự trả lời điều này).

cần phải làm bất cứ điều gì tùy chỉnh .

Dành cho ASP.NET MVC3 , tất cả các bit và miếng ở đó.

Bước 1 -> Cập nhật web.config của bạn trong HAI điểm.

<system.web>
    <customErrors mode="On" defaultRedirect="/ServerError">
      <error statusCode="404" redirect="/NotFound" />
    </customErrors>

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

Bây giờ hãy lưu ý cẩn thận về ROUTES tôi đã quyết định sử dụng. Bạn có thể sử dụng bất cứ thứ gì, nhưng các tuyến đường của tôi là

  • /NotFound <- đối với 404 không tìm thấy, trang lỗi.
  • /ServerError<- đối với bất kỳ lỗi nào khác, bao gồm các lỗi xảy ra trong mã của tôi. đây là lỗi 500 máy chủ nội bộ

Xem làm thế nào phần đầu tiên <system.web>chỉ có một mục tùy chỉnh? Các statusCode="404"mục? Tôi chỉ liệt kê một mã trạng thái vì tất cả các lỗi khác, bao gồm 500 Server Error(ví dụ: những lỗi đáng tiếc đó xảy ra khi mã của bạn có lỗi và làm hỏng yêu cầu của người dùng) .. tất cả các lỗi khác được xử lý bởi cài đặt defaultRedirect="/ServerError".. có nói , nếu bạn không tìm thấy trang 404, thì vui lòng xem tuyến đường/ServerError .

Đồng ý. đó là ngoài đường .. bây giờ đến các tuyến đường của tôi được liệt kê trongglobal.asax

Bước 2 - Tạo các tuyến đường trong Global.asax

Đây là phần lộ trình đầy đủ của tôi ..

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

Trong đó liệt kê hai tuyến bỏ qua -> axd'sfavicons(ooo! Phần thưởng bỏ qua tuyến đường, dành cho bạn!) Sau đó (và thứ tự là MỆNH LỆNH TẠI ĐÂY), tôi có hai tuyến xử lý lỗi rõ ràng của mình .. tiếp theo là bất kỳ tuyến nào khác. Trong trường hợp này, một mặc định. Tất nhiên, tôi có nhiều hơn, nhưng đó là đặc biệt cho trang web của tôi. Chỉ cần đảm bảo các tuyến lỗi nằm ở đầu danh sách. Trật tự là bắt buộc .

Cuối cùng, trong khi chúng tôi ở trong global.asaxtệp của mình, chúng tôi KHÔNG đăng ký toàn cầu thuộc tính HandleError. Không, không, không thưa ngài. Nadda. Không. Niên. Tiêu cực. Không ...

Xóa dòng này khỏi global.asax

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

Bước 3 - Tạo bộ điều khiển với các phương thức hành động

Bây giờ .. chúng tôi thêm một bộ điều khiển với hai phương thức hành động ...

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

Ok, hãy kiểm tra cái này Trước hết, KHÔNG có [HandleError] thuộc tính ở đây. Tại sao? Bởi vì được xây dựng trongASP.NET sẵn đã xử lý lỗi VÀ chúng tôi đã chỉ định tất cả các shit chúng tôi cần làm để xử lý lỗi :) Đó là trong phương pháp này!

Tiếp theo, tôi có hai phương thức hành động. Không có gì khó khăn ở đó. Nếu bạn muốn hiển thị bất kỳ thông tin ngoại lệ nào, thì bạn có thể sử dụng Server.GetLastError()để có được thông tin đó.

Phần thưởng WTF: Có, tôi đã thực hiện phương pháp hành động thứ ba, để kiểm tra xử lý lỗi.

Bước 4 - Tạo Chế độ xem

Và cuối cùng, tạo ra hai quan điểm. Đặt em vào vị trí xem bình thường, cho bộ điều khiển này.

nhập mô tả hình ảnh ở đây

Thưởng bình luận

  • Bạn không cần một Application_Error(object sender, EventArgs e)
  • Các bước trên đều hoạt động hoàn hảo 100% với Elmah . Elmah làm phiền wroxs!

Và đó, các bạn của tôi, nên là nó.

Bây giờ, chúc mừng bạn đã đọc nhiều và có một Kỳ lân như một giải thưởng!

nhập mô tả hình ảnh ở đây


Vì vậy, tôi đã cố gắng thực hiện điều này nhưng một vài vấn đề ... đầu tiên, Bạn cần một ~ trước đường dẫn trong weeb.config hoặc nó không hoạt động cho các thư mục ảo. 2-Nếu lỗi tùy chỉnh IIS kích hoạt và chế độ xem đang sử dụng bố cục thì nó hoàn toàn không hiển thị, chỉ là một trang trắng. Tôi đã giải quyết điều đó bằng cách thêm dòng này vào bộ điều khiển "Feedback.TrySkipIisCustomErrors = true;" . Tuy nhiên, nó vẫn không hoạt động nếu bạn truy cập một url là một tệp nhưng 404 .. như mysite / anything / fake.html có một trang trắng.
Robert Noack

3
-1, xin lỗi, đối với tôi, bất kỳ giải pháp nào thay đổi url cho 404 đều sai. và với webconfig, không có cách nào trong MVC mà bạn có thể xử lý nó mà không thay đổi url hoặc bạn cần tạo các tệp html tĩnh hoặc aspx (vâng, các tệp aspx cũ đơn giản) để có thể làm điều đó. giải pháp của bạn là tốt nếu bạn muốn ?aspxerrorpath=/er/not/foundcó trong url.
Gutek

7
Điều này nghe có vẻ kỳ lạ - nhưng câu trả lời của tôi đã được cung cấp từ lâu và tôi đồng ý với @Gutek của bạn, tôi không muốn chuyển hướng đến trang lỗi nữa . Tôi đã từng (tham khảo câu trả lời của tôi: P). Nếu xảy ra lỗi trên / some / resource .. thì tài nguyên đó sẽ trả về 404 hoặc 500, v.v. MASSIVE SEO có nghĩa khác. À .. thời gian thay đổi như thế nào :)
Pure.Krom

@Gutek Bạn có biết về customErrors redirectMode = "FeedbackRewrite" không? Và trả về 404 không phải là lý tưởng từ góc độ bảo mật
Jowen

1
@Chris <chèn vị thần yêu thích của bạn vào đây> chết tiệt. Tôi thậm chí không thể nhớ những gì bây giờ. Chà, bộ sưu tập meme của tôi để giải cứu ... và .. đã sửa.
Pure.Krom

86

Tôi đã điều tra RẤT NHIỀU cách quản lý 404s trong MVC (cụ thể là MVC3) và điều này, IMHO là giải pháp tốt nhất tôi nghĩ ra:

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

Trình điều khiển lỗi:

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

(Không bắt buộc)

Giải trình:

AFAIK, có 6 trường hợp khác nhau mà ứng dụng ASP.NET MVC3 có thể tạo 404s.

(Được tạo tự động bởi ASP.NET Framework :)

(1) Một URL không tìm thấy sự trùng khớp trong bảng lộ trình.

(Được tạo tự động bởi ASP.NET MVC Framework :)

(2) Một URL tìm thấy sự trùng khớp trong bảng tuyến đường, nhưng chỉ định một bộ điều khiển không tồn tại.

(3) Một URL tìm thấy sự trùng khớp trong bảng tuyến đường, nhưng chỉ định một hành động không tồn tại.

(Được tạo thủ công :)

(4) Một hành động trả về một httpNotFoundResult bằng cách sử dụng phương thức HttpNotFound ().

(5) Một hành động ném một HTTPException với mã trạng thái 404.

(6) Một hành động sửa đổi thủ công thuộc tính Feedback.StatusCode thành 404.

Thông thường, bạn muốn hoàn thành 3 mục tiêu:

(1) Hiển thị trang lỗi 404 tùy chỉnh cho người dùng.

(2) Duy trì mã trạng thái 404 trên phản hồi của khách hàng (đặc biệt quan trọng đối với SEO).

(3) Gửi phản hồi trực tiếp, không liên quan đến chuyển hướng 302.

Có nhiều cách khác nhau để cố gắng thực hiện điều này:

(1)

<system.web>
    <customErrors mode="On">
        <error statusCode="404" redirect="~/Errors/NotFound"/>
    </customError>
</system.web>

Các vấn đề với giải pháp này:

  1. Không tuân thủ mục tiêu (1) trong các trường hợp (1), (4), (6).
  2. Không tuân thủ mục tiêu (2) tự động. Nó phải được lập trình bằng tay.
  3. Không tuân thủ mục tiêu (3).

(2)

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Các vấn đề với giải pháp này:

  1. Chỉ hoạt động trên IIS 7+.
  2. Không tuân thủ mục tiêu (1) trong các trường hợp (2), (3), (5).
  3. Không tuân thủ mục tiêu (2) tự động. Nó phải được lập trình bằng tay.

(3)

<system.webServer>
    <httpErrors errorMode="Custom" existingResponse="Replace">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Các vấn đề với giải pháp này:

  1. Chỉ hoạt động trên IIS 7+.
  2. Không tuân thủ mục tiêu (2) tự động. Nó phải được lập trình bằng tay.
  3. Nó che khuất các trường hợp ngoại lệ http cấp ứng dụng. Ví dụ: không thể sử dụng phần customErrors, System.Web.Mvc.HandleErrorAttribution, v.v. Nó không thể chỉ hiển thị các trang lỗi chung.

(4)

<system.web>
    <customErrors mode="On">
        <error statusCode="404" redirect="~/Errors/NotFound"/>
    </customError>
</system.web>

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Các vấn đề với giải pháp này:

  1. Chỉ hoạt động trên IIS 7+.
  2. Không tuân thủ mục tiêu (2) tự động. Nó phải được lập trình bằng tay.
  3. Không tuân thủ mục tiêu (3) trong các trường hợp (2), (3), (5).

Những người đã gặp rắc rối với điều này trước khi thậm chí đã cố gắng tạo thư viện của riêng họ (xem http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html ). Nhưng giải pháp trước đây dường như bao gồm tất cả các trường hợp mà không phức tạp khi sử dụng thư viện bên ngoài.


Câu trả lời chính xác. Đáng giá hơn nhiều upvote. Tại sao mã global.asax của bạn không hoạt động / thuộc về Application_Error.
NinjaNye

7
Cảm ơn! Không thể thực hiện được trong Application_Error, vì 404 rõ ràng được ném từ bộ điều khiển không được coi là lỗi trên ASP.NET. Nếu bạn trả về một httpNotFound () từ bộ điều khiển, sự kiện Application_Error sẽ không bao giờ kích hoạt.
Marco

1
Tôi nghĩ rằng bạn đã quên public ActionResult NotFound() {}trong Trình điều khiển lỗi của bạn. Ngoài ra, bạn có thể giải thích _NotFoundmột phần của bạn sẽ trông như thế nào đối với các yêu cầu AJAX không?
d4n3

2
Với MVC 4 Tôi luôn luôn có được MissingMethodException: Cannot create an abstract classtrên dòng c.Execute(new RequestContext(new HttpContextWrapper(Context), rd)); Bất kỳ ý tưởng?
TiếtBio

1
Nếu URL "không tìm thấy" bao gồm một dấu chấm trong đường dẫn (ví dụ: ví dụ.com / hi.bob ) thì Application_EndRequest hoàn toàn không kích hoạt và tôi nhận được trang 404 chung của IE.
Bob.at.Ấn Độ. Sức khỏe

13

Tôi thực sự thích giải pháp cottsaks và nghĩ rằng nó được giải thích rất rõ ràng. bổ sung duy nhất của tôi là thay đổi bước 2 như sau

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
}

Về cơ bản, điều này ngăn các url chứa các hành động VÀ bộ điều khiển không hợp lệ kích hoạt thói quen ngoại lệ hai lần. ví dụ: đối với các url như asdfsdf / dfgdfgd


4
Thật tuyệt vời. Những trường hợp "hai lần" đó đã bắt đầu làm phiền tôi. đã cập nhật câu trả lời của tôi
Matt Kocaj

giải pháp trên có hoạt động được không nếu người dùng nhập sai bộ điều khiển và tên hành động?
Monojit Sarkar

6

Cách duy nhất tôi có thể khiến phương thức của @ cottsak hoạt động đối với các bộ điều khiển không hợp lệ là sửa đổi yêu cầu tuyến hiện có trong CustomContoderFactory, như vậy:

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

Tôi nên đề cập đến việc tôi đang sử dụng MVC 2.0.


Bạn có biết tại sao? (Cụ thể là MVC2?)
Matt Kocaj

Tôi nghĩ rằng chìa khóa là để sửa đổi yêu cầu hiện có thay vì thực hiện một yêu cầu mới, nhưng tôi đã làm điều này một chút trước đây vì vậy tôi không chắc chắn đó là nó. "InvokeHttp404" không hoạt động từ nhà máy điều khiển.
Dave K

Tôi đã cập nhật câu trả lời của tôi hôm nay với một số chi tiết cụ thể về MVC2. Bạn có thể vui lòng cho tôi biết nếu giải pháp của tôi như chi tiết ở trên vẫn không hiệu quả với bạn?
Matt Kocaj

4

Đây là một phương pháp khác sử dụng các công cụ MVC mà bạn có thể xử lý các yêu cầu đối với tên trình điều khiển xấu, tên tuyến đường xấu và bất kỳ tiêu chí nào khác mà bạn thấy phù hợp bên trong phương thức Hành động. Cá nhân, tôi thích tránh càng nhiều cài đặt web.config càng tốt, vì chúng thực hiện chuyển hướng 302/200 và không hỗ trợ FeedbackRewrite ( Server.Transfer) bằng cách sử dụng chế độ xem Dao cạo. Tôi muốn trả lại 404 với trang lỗi tùy chỉnh vì lý do SEO.

Một số điều này là mới về kỹ thuật của cottsak ở trên.

Thay vào đó, giải pháp này cũng sử dụng các cài đặt web.config tối thiểu có lợi cho Bộ lọc lỗi MVC 3.

Sử dụng

Chỉ cần ném một HTTPException từ một hành động hoặc ActionFilterAttribution tùy chỉnh.

Throw New HttpException(HttpStatusCode.NotFound, "[Custom Exception Message Here]")

Bước 1

Thêm cài đặt sau vào web.config. Điều này là bắt buộc để sử dụng HandErrorAttribution của MVC.

<customErrors mode="On" redirectMode="ResponseRedirect" />

Bước 2

Thêm một Xử lý tùy chỉnhHttpErrorAttribution tương tự như HandErrorAttribution của khung công tác MVC, ngoại trừ các lỗi 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

Bước 3

Thêm bộ lọc vào GlobalFilterCollection ( GlobalFilters.Filters) trong Global.asax. Ví dụ này sẽ định tuyến tất cả các lỗi InternalServerError (500) đến chế độ xem chia sẻ Lỗi ( Views/Shared/Error.vbhtml). Lỗi NotFound (404) cũng sẽ được gửi đến ErrorHttp404.vbhtml trong các chế độ xem được chia sẻ. Tôi đã thêm một lỗi 401 ở đây để cho bạn thấy làm thế nào điều này có thể được mở rộng cho các mã lỗi HTTP bổ sung. Lưu ý rằng đây phải là các khung nhìn được chia sẻ và tất cả chúng đều sử dụng System.Web.Mvc.HandleErrorInfođối tượng làm mô hình.

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

Bước 4

Tạo một lớp trình điều khiển cơ sở và kế thừa từ nó trong các trình điều khiển của bạn. Bước này cho phép chúng tôi xử lý các tên hành động không xác định và nâng lỗi HTTP 404 lên HandHttpErrorAttribution của chúng tôi.

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

Bước 5

Tạo ghi đè ControlFactory và ghi đè lên tệp Global.asax của bạn trong Application_Start. Bước này cho phép chúng tôi tăng ngoại lệ HTTP 404 khi tên bộ điều khiển không hợp lệ đã được chỉ định.

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)

Bước 6

Bao gồm một tuyến đường đặc biệt trong RoutTable.Routes cho hành động BaseContoder Unknown. Điều này sẽ giúp chúng tôi tăng 404 trong trường hợp người dùng truy cập bộ điều khiển không xác định hoặc hành động không xác định.

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

Tóm lược

Ví dụ này đã trình bày cách người ta có thể sử dụng khung MVC để trả về Mã lỗi 404 HTTP cho trình duyệt mà không cần chuyển hướng bằng các thuộc tính bộ lọc và chế độ xem lỗi được chia sẻ. Nó cũng cho thấy hiển thị cùng một trang lỗi tùy chỉnh khi tên hành động và tên hành động không hợp lệ được chỉ định.

Tôi sẽ thêm ảnh chụp màn hình tên bộ điều khiển không hợp lệ, tên hành động và 404 tùy chỉnh được nêu lên từ hành động Trang chủ / TriggerNotFound nếu tôi nhận đủ số phiếu để đăng một =). Fiddler trả về tin nhắn 404 khi tôi truy cập các URL sau bằng giải pháp này:

/InvalidController
/Home/InvalidRoute
/InvalidController/InvalidRoute
/Home/TriggerNotFound

bài viết của cottsak ở trên và những bài viết này là tài liệu tham khảo tốt.


Hmm, tôi không thể làm điều này hoạt động: The IControllerFactory 'aaa.bbb.CustomControllerFactory' did not return a controller for the name '123'.- có ý tưởng nào tại sao tôi lại có được điều đó không?
enashnash

redirectMode = "FeedbackRedirect". Điều này sẽ trả về 302 Found + 200 OK, điều này không tốt cho SEO!
PussInBoots

4

Giải pháp rút gọn của tôi hoạt động với các khu vực, bộ điều khiển và hành động chưa được xử lý:

  1. Tạo chế độ xem 404.cshtml.

  2. Tạo một lớp cơ sở cho bộ điều khiển của bạn:

    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");
        }
    }
  3. Tạo một nhà máy điều khiển tùy chỉnh trả về bộ điều khiển cơ sở của bạn dưới dạng dự phòng:

    public class ControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType != null)
                return base.GetControllerInstance(requestContext, controllerType);
    
            return new Controller();
        }
    }
  4. Thêm vào Application_Start()dòng sau:

    ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));

3

Trong MVC4 WebAPI 404 có thể được xử lý theo cách sau,

KHÓA HỌC APICONTROLLER

    // GET /api/courses/5
    public HttpResponseMessage<Courses> Get(int id)
    {
        HttpResponseMessage<Courses> resp = null;

        var aCourse = _courses.Where(c => c.Id == id).FirstOrDefault();

        resp = aCourse == null ? new HttpResponseMessage<Courses>(System.Net.HttpStatusCode.NotFound) : new HttpResponseMessage<Courses>(aCourse);

        return resp;
    }

KIỂM SOÁT NHÀ

public ActionResult Course(int id)
{
    return View(id);
}

LƯỢT XEM

<div id="course"></div>
<script type="text/javascript">
    var id = @Model;
    var course = $('#course');
    $.ajax({    
        url: '/api/courses/' + id,
        success: function (data) {
            course.text(data.Name);
        },
        statusCode: {
            404: function() 
            {
                course.text('Course not available!');    
            }
        }
    });
</script>

TOÀN CẦU

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

CÁC KẾT QUẢ

nhập mô tả hình ảnh ở đây


2

Hãy thử NotFoundMVC trên nuget. Nó hoạt động, không cần thiết lập.


http://localhost/Views/Shared/NotFound.cshtmlkhông dẫn đến một trang 404 tùy chỉnh.
Dan Friedman

Nó rất dễ dàng để tùy chỉnh. Bạn có quyền truy cập vào url được yêu cầu và người giới thiệu, vì vậy bạn có thể làm những gì bạn thích. Tôi sử dụng gói này, và nó hoạt động thực sự tốt.
Avrohom Yisroel

Đây là một gói tuyệt vời, cung cấp cho bạn sẽ không sử dụng các hành động async Nhiệm vụ <ActionResult> (hoặc các hoạt động không đồng bộ tương tự khác). Trên MVC 5 đây là một kịch bản bị hỏng. Có một ngã ba trên GitHub để phá vỡ điều đó, nhưng đối với tôi đó là không, không.
Stargazer

2

Giải pháp của tôi, trong trường hợp ai đó thấy nó hữu ích.

Trong Web.config:

<system.web>
    <customErrors mode="On" defaultRedirect="Error" >
      <error statusCode="404" redirect="~/Error/PageNotFound"/>
    </customErrors>
    ...
</system.web>

Trong Controllers/ErrorController.cs:

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        if(Request.IsAjaxRequest()) {
            Response.StatusCode = (int)HttpStatusCode.NotFound;
            return Content("Not Found", "text/plain");
        }

        return View();
    }
}

Thêm một PageNotFound.cshtmltrong Sharedthư mục, và đó là nó.


2
Không phải vấn đề này chuyển hướng 302 sau đó là trạng thái 200 (OK) cho khách hàng? Họ có nên vẫn nhận được trạng thái 404 không?
Sam

@Konamiman Bạn có chắc chắn dòng trong mã của bạn nên đọc model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ? Request.Url.OriginalString : url;và không model.RequestedUrl = Request.Url.OriginalString.Contains(url) && Request.Url.OriginalString != url ? Request.Url.OriginalString : url;(& thay vì &&)?
Jean-François Beauchamp

2

Dường như với tôi rằng CustomErrorscấu hình tiêu chuẩn chỉ nên hoạt động , do sự phụ thuộc vào Server.Transfernó dường như là việc thực hiện bên trong củaResponseRewrite không tương thích với MVC.

Cảm giác này giống như một lỗ hổng chức năng rõ ràng đối với tôi, vì vậy tôi quyết định triển khai lại tính năng này bằng mô-đun HTTP. Giải pháp bên dưới cho phép bạn xử lý bất kỳ mã trạng thái HTTP nào (bao gồm 404) bằng cách chuyển hướng đến bất kỳ tuyến MVC hợp lệ nào giống như bạn thường làm.

<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite">
    <error statusCode="404" redirect="404.aspx" />
    <error statusCode="500" redirect="~/MVCErrorPage" />
</customErrors>

Điều này đã được thử nghiệm trên các nền tảng sau đây;

  • MVC4 trong Chế độ đường ống tích hợp (IIS Express 8)
  • MVC4 trong Chế độ cổ điển (Máy chủ phát triển VS, Cassini)
  • MVC4 trong Chế độ cổ điển (IIS6)

Những lợi ích

  • Giải pháp chung có thể được thả vào bất kỳ dự án MVC nào
  • Cho phép hỗ trợ cho cấu hình lỗi tùy chỉnh truyền thống
  • Hoạt động ở cả hai chế độ Đường ống tích hợp và Cổ điển

Giải pháp

namespace Foo.Bar.Modules {

    /// <summary>
    /// Enables support for CustomErrors ResponseRewrite mode in MVC.
    /// </summary>
    public class ErrorHandler : IHttpModule {

        private HttpContext HttpContext { get { return HttpContext.Current; } }
        private CustomErrorsSection CustomErrors { get; set; }

        public void Init(HttpApplication application) {
            System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("~");
            CustomErrors = (CustomErrorsSection)configuration.GetSection("system.web/customErrors");

            application.EndRequest += Application_EndRequest;
        }

        protected void Application_EndRequest(object sender, EventArgs e) {

            // only handle rewrite mode, ignore redirect configuration (if it ain't broke don't re-implement it)
            if (CustomErrors.RedirectMode == CustomErrorsRedirectMode.ResponseRewrite && HttpContext.IsCustomErrorEnabled) {

                int statusCode = HttpContext.Response.StatusCode;

                // if this request has thrown an exception then find the real status code
                Exception exception = HttpContext.Error;
                if (exception != null) {
                    // set default error status code for application exceptions
                    statusCode = (int)HttpStatusCode.InternalServerError;
                }

                HttpException httpException = exception as HttpException;
                if (httpException != null) {
                    statusCode = httpException.GetHttpCode();
                }

                if ((HttpStatusCode)statusCode != HttpStatusCode.OK) {

                    Dictionary<int, string> errorPaths = new Dictionary<int, string>();

                    foreach (CustomError error in CustomErrors.Errors) {
                        errorPaths.Add(error.StatusCode, error.Redirect);
                    }

                    // find a custom error path for this status code
                    if (errorPaths.Keys.Contains(statusCode)) {
                        string url = errorPaths[statusCode];

                        // avoid circular redirects
                        if (!HttpContext.Request.Url.AbsolutePath.Equals(VirtualPathUtility.ToAbsolute(url))) {

                            HttpContext.Response.Clear();
                            HttpContext.Response.TrySkipIisCustomErrors = true;

                            HttpContext.Server.ClearError();

                            // do the redirect here
                            if (HttpRuntime.UsingIntegratedPipeline) {
                                HttpContext.Server.TransferRequest(url, true);
                            }
                            else {
                                HttpContext.RewritePath(url, false);

                                IHttpHandler httpHandler = new MvcHttpHandler();
                                httpHandler.ProcessRequest(HttpContext);
                            }

                            // return the original status code to the client
                            // (this won't work in integrated pipleline mode)
                            HttpContext.Response.StatusCode = statusCode;

                        }
                    }

                }

            }

        }

        public void Dispose() {

        }


    }

}

Sử dụng

Bao gồm điều này như mô-đun HTTP cuối cùng trong web.config của bạn

  <system.web>
    <httpModules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </httpModules>
  </system.web>

  <!-- IIS7+ -->
  <system.webServer>
    <modules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </modules>
  </system.webServer>

Đối với những người bạn chú ý, bạn sẽ nhận thấy rằng trong chế độ Đường ống tích hợp, điều này sẽ luôn phản hồi với HTTP 200 do cách thức Server.TransferRequesthoạt động. Để trả về mã lỗi thích hợp, tôi sử dụng bộ điều khiển lỗi sau.

public class ErrorController : Controller {

    public ErrorController() { }

    public ActionResult Index(int id) {
        // pass real error code to client
        HttpContext.Response.StatusCode = id;
        HttpContext.Response.TrySkipIisCustomErrors = true;

        return View("Errors/" + id.ToString());
    }

}

2

Xử lý các lỗi trong ASP.NET MVC chỉ là một nỗi đau ở mông. Tôi đã thử rất nhiều đề xuất trên trang này và các câu hỏi và trang web khác và không có gì hoạt động tốt. Một đề xuất là xử lý lỗi trên web.config bên trong system.webserver nhưng điều đó chỉ trả về các trang trống .

Mục tiêu của tôi khi đưa ra giải pháp này là;

  • KHÔNG GIẢM
  • Trả về MÃ SỐ TÌNH TRẠNG PROPER không 200 / Ok như xử lý lỗi mặc định

Đây là giải pháp của tôi.

1 .Add sau đây để system.web phần

   <system.web>
     <customErrors mode="On" redirectMode="ResponseRewrite">
      <error statusCode="404"  redirect="~/Error/404.aspx" />
      <error statusCode="500" redirect="~/Error/500.aspx" />
     </customErrors>
    <system.web>

Ở trên xử lý bất kỳ url nào không được xử lý bởi Rout.config và các ngoại lệ chưa được xử lý , đặc biệt là các trường hợp gặp phải trên các khung nhìn. Lưu ý tôi đã sử dụng aspx không phải html . Điều này là để tôi có thể thêm mã phản hồi vào mã phía sau.

2 . Tạo một thư mục có tên Lỗi (hoặc bất cứ điều gì bạn thích) ở thư mục gốc của dự án và thêm hai biểu mẫu web. Dưới đây là trang 404 của tôi;

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="404.aspx.cs" Inherits="Myapp.Error._404" %>

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title >Page Not found</title>
    <link href="<%=ResolveUrl("~/Content/myapp.css")%>" rel="stylesheet" />
</head>
<body>
    <div class="top-nav">
      <a runat="server" class="company-logo" href="~/"></a>
    </div>
    <div>
        <h1>404 - Page Not found</h1>
        <p>The page you are looking for cannot be found.</p>
        <hr />
        <footer></footer>
    </div>
</body>
</html>

Và trên mã phía sau tôi đặt mã phản hồi

protected void Page_Load(object sender, EventArgs e)
{
    Response.StatusCode = 404;
}

Làm tương tự cho 500 trang

3. Để xử lý lỗi trong bộ điều khiển. Có nhiều cách để làm điều đó. Đây là những gì làm việc cho tôi. Tất cả các bộ điều khiển của tôi kế thừa từ một bộ điều khiển cơ sở. Trong bộ điều khiển cơ sở, tôi có các phương thức sau

protected ActionResult ShowNotFound()
{
    return ShowNotFound("Page not found....");
}

protected ActionResult ShowNotFound(string message)
{
    return ShowCustomError(HttpStatusCode.NotFound, message);
}

protected ActionResult ShowServerError()
{
    return ShowServerError("Application error....");
}

protected ActionResult ShowServerError(string message)
{
    return ShowCustomError(HttpStatusCode.InternalServerError, message);
}

protected ActionResult ShowNotAuthorized()
{
    return ShowNotAuthorized("You are not allowed ....");

}

protected ActionResult ShowNotAuthorized(string message)
{
    return ShowCustomError(HttpStatusCode.Forbidden, message);
}

protected ActionResult ShowCustomError(HttpStatusCode statusCode, string message)
{
    Response.StatusCode = (int)statusCode;
    string title = "";
    switch (statusCode)
    {
        case HttpStatusCode.NotFound:
            title = "404 - Not found";
            break;
        case HttpStatusCode.Forbidden:
            title = "403 - Access Denied";
            break;
        default:
            title = "500 - Application Error";
            break;
    }
    ViewBag.Title = title;
    ViewBag.Message = message;
    return View("CustomError");
}

4 .Add các CustomError.cshtml để bạn chia sẻ quan điểm thư mục. Dưới đây là của tôi;

<h1>@ViewBag.Title</h1>
<br />
<p>@ViewBag.Message</p>

Bây giờ trong bộ điều khiển ứng dụng của bạn, bạn có thể làm một cái gì đó như thế này;

public class WidgetsController : ControllerBase
{
  [HttpGet]
  public ActionResult Edit(int id)
  {
    Try
    {
       var widget = db.getWidgetById(id);
       if(widget == null)
          return ShowNotFound();
          //or return ShowNotFound("Invalid widget!");
       return View(widget);
    }
    catch(Exception ex)
    {
       //log error
       logger.Error(ex)
       return ShowServerError();
    }
  }
}

Bây giờ cho cảnh báo . Nó sẽ không xử lý lỗi tập tin tĩnh. Vì vậy, nếu bạn có một tuyến đường như example.com/widgets và người dùng thay đổi nó thành example.com/widgets.html , họ sẽ nhận được trang lỗi mặc định IIS để bạn phải xử lý lỗi cấp độ IIS theo một cách khác.


1

Đăng một câu trả lời vì nhận xét của tôi quá dài ...

Đó là cả một bình luận và câu hỏi cho bài đăng / câu trả lời kỳ lân:

https://stackoverflow.com/a/7499406/687549

Tôi thích câu trả lời này hơn những câu hỏi khác vì nó đơn giản và thực tế là rõ ràng một số người ở Microsoft đã được hỏi ý kiến. Tuy nhiên, tôi đã nhận được ba câu hỏi và nếu chúng có thể được trả lời thì tôi sẽ gọi câu trả lời này là chén thánh của tất cả các câu trả lời lỗi 404/500 trên các interwebs cho một ứng dụng ASP.NET MVC (x).

@ Pure.Krom

  1. Bạn có thể cập nhật câu trả lời của mình với nội dung SEO từ các nhận xét được chỉ ra bởi GWB (không bao giờ có bất kỳ đề cập nào về câu trả lời này trong câu trả lời của bạn) - <customErrors mode="On" redirectMode="ResponseRewrite"><httpErrors errorMode="Custom" existingResponse="Replace">?

  2. Bạn có thể hỏi bạn bè trong nhóm ASP.NET của mình xem có ổn không khi làm như vậy - sẽ rất tốt nếu có một số xác nhận - có thể đó là một điều không nên thay đổi redirectModeexistingResponsetheo cách này để có thể chơi tốt với SEO?!

  3. Bạn có thể thêm một số làm rõ xung quanh tất cả những thứ đó ( customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", loại bỏ customErrorshoàn toàn như một người nào đó đề xuất) sau khi nói chuyện với bạn bè tại Microsoft?

Như tôi đã nói; Sẽ thật tuyệt vời nếu chúng tôi có thể làm cho câu trả lời của bạn đầy đủ hơn vì đây dường như là một câu hỏi khá phổ biến với hơn 54 000 lượt xem.

Cập nhật : Câu trả lời Unicorn thực hiện 302 Tìm thấy và 200 OK và không thể thay đổi để chỉ trả về 404 bằng cách sử dụng tuyến đường. Nó phải là một tệp vật lý không phải là MVC: ish. Vì vậy, chuyển sang một giải pháp khác. Quá tệ bởi vì đây dường như là MVC cuối cùng: trả lời cho đến nay.


1

Thêm giải pháp của tôi, gần giống với giải pháp của Herman Kan, với một nếp nhăn nhỏ để cho phép nó hoạt động cho dự án của tôi.

Tạo bộ điều khiển lỗi tùy chỉnh:

public class Error404Controller : BaseController
{
    [HttpGet]
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View("404");
    }
}

Sau đó tạo một nhà máy điều khiển tùy chỉnh:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        return controllerType == null ? new Error404Controller() : base.GetControllerInstance(requestContext, controllerType);
    }
}

Cuối cùng, thêm ghi đè vào bộ điều khiển lỗi tùy chỉnh:

protected override void HandleUnknownAction(string actionName)
{
    var errorRoute = new RouteData();
    errorRoute.Values.Add("controller", "Error404");
    errorRoute.Values.Add("action", "PageNotFound");
    new Error404Controller().Execute(new RequestContext(HttpContext, errorRoute));
}

Và đó là nó. Không cần thay đổi Web.config.


1

1) Tạo lớp Trình điều khiển trừu tượng.

public abstract class MyController:Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = 404;
        return View("NotFound");
    }

    protected override void HandleUnknownAction(string actionName)
    {
        this.ActionInvoker.InvokeAction(this.ControllerContext, "NotFound");
    }
    protected override void OnAuthorization(AuthorizationContext filterContext) { }
}  

2) Tạo sự kế thừa từ lớp trừu tượng này trong tất cả các bộ điều khiển của bạn

public class HomeController : MyController
{}  

3) Và thêm chế độ xem có tên "NotFound" trong thư mục Chế độ xem chung.


0

Tôi đã đi qua hầu hết các giải pháp được đăng trên chủ đề này. Mặc dù câu hỏi này có thể đã cũ, nhưng nó vẫn rất phù hợp với các dự án mới ngay cả bây giờ, vì vậy tôi đã dành khá nhiều thời gian để đọc các câu trả lời được trình bày ở đây cũng như các nơi khác.

Như @Marco đã chỉ ra các trường hợp khác nhau theo đó 404 có thể xảy ra, tôi đã kiểm tra giải pháp tôi đã biên soạn cùng với danh sách đó. Ngoài danh sách yêu cầu của anh ấy, tôi cũng thêm một yêu cầu nữa.

  • Giải pháp sẽ có thể xử lý các cuộc gọi MVC cũng như AJAX / WebAPI theo cách phù hợp nhất. (tức là nếu 404 xảy ra trong MVC, nó sẽ hiển thị trang Không tìm thấy và nếu 404 xảy ra trong WebAPI, thì nó không nên chiếm quyền phản hồi XML / JSON để Javascript tiêu thụ có thể phân tích cú pháp dễ dàng).

Giải pháp này là 2 lần:

Phần đầu tiên của nó đến từ @Guillaume tại https://stackoverflow.com/a/27354140/2 310818 . Giải pháp của họ xử lý bất kỳ 404 nào đã gây ra do tuyến không hợp lệ, bộ điều khiển không hợp lệ và hành động không hợp lệ.

Ý tưởng là tạo một WebForm và sau đó gọi nó là hành động NotFound của Trình điều khiển lỗi MVC của bạn. Nó thực hiện tất cả những điều này mà không có bất kỳ chuyển hướng nào, do đó bạn sẽ không thấy một 302 nào trong Fiddler. URL gốc cũng được bảo tồn, điều này làm cho giải pháp này trở nên tuyệt vời!


Phần thứ hai của nó đến từ @ Germán tại https://stackoverflow.com/a/5536676/2 310818 . Giải pháp của họ xử lý bất kỳ 404 nào được trả về bởi các hành động của bạn dưới dạng httpNotFoundResult () hoặc ném httpException () mới!

Ý tưởng là để có một bộ lọc xem phản hồi cũng như ngoại lệ được ném bởi bộ điều khiển MVC của bạn và gọi hành động thích hợp trong Bộ điều khiển lỗi của bạn. Một lần nữa giải pháp này hoạt động mà không có bất kỳ chuyển hướng nào và url gốc được bảo tồn!


Như bạn có thể thấy, cả hai giải pháp này cùng nhau cung cấp một cơ chế xử lý lỗi rất mạnh mẽ và chúng đạt được tất cả các yêu cầu được liệt kê bởi @Marco cũng như các yêu cầu của tôi. Nếu bạn muốn xem một mẫu làm việc hoặc bản demo của giải pháp này, vui lòng để lại trong phần bình luận và tôi sẽ rất vui khi kết hợp nó lại với nhau.


0

Tôi đã xem qua tất cả các bài viết nhưng không có gì hiệu quả đối với tôi: Người dùng yêu cầu của tôi nhập bất cứ điều gì vào trang 404 tùy chỉnh url của bạn sẽ hiển thị. Tôi nghĩ rằng nó rất đơn giản. Nhưng bạn nên hiểu cách xử lý 404 đúng cách:

 <system.web>
    <customErrors mode="On" redirectMode="ResponseRewrite">
      <error statusCode="404" redirect="~/PageNotFound.aspx"/>
    </customErrors>
  </system.web>
<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404"/>
      <error statusCode="404" path="/PageNotFound.html" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Tôi thấy bài viết này rất hữu ích. Nên đọc ngay lập tức. Trang lỗi Custome-Ben Foster

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.