asp.net-mvc - 処理 - mvc リソースが見つかりませんでした。




ASP.NET MVCで404を正しく処理するにはどうすればよいですか? (13)

404の要件

以下は404ソリューションのための私の要件であり、私はそれを実装する方法を示しています。

  • 私は悪い行為と一致したルートを扱いたい
  • 私は悪いコントローラと一致したルートを扱いたい
  • 私は不一致のルート(私のアプリケーションが理解できない任意のURL)を処理したい - 私はこれらのバブリングをGlobal.asaxやIISにしたくない。なぜなら私はMVCアプリケーションに正しくリダイレ​​クトできないからだ。
  • 上記と同じようにカスタム404を処理する方法が必要です - 存在しない(削除されたかもしれない)オブジェクトに対してIDが送信されたときのように)
  • 私はすべての私の404をMVCビュー(静的なページではない)に戻して、必要に応じて後でより多くのデータを送ることができるようにしたい( codinghorror.com/blog/2007/03/… 、HTTP 404ステータスコード

溶液

私はあなたが処理できない例外やログ( Shay Jacobyの答えのような)のような高いものについてはApplication_ErrorをGlobal.asaxに保存しなければならないと思うが、404の処理はできないと思う。 これが私の提案が404のものをGlobal.asaxファイルから守る理由です。

ステップ1:404エラーロジックの共通の場所がある

これは保守性のための良い考えです。 ErrorController使って、 codinghorror.com/blog/2007/03/…将来の改善が容易に適応できるようにします。 また、 あなたの応答に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の404は、いくつかの場所で捕らえられる必要があります。 最初はHandleUnknownActionです。

InvokeHttp404メソッドは、 ErrorControllerと新しいHttp404アクションに再ルーティングするための共通の場所を作成します。 DRY考えよう!

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
}

手順3:コントローラ工場で依存性注入を使用し、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;
    }
}

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

私は、彼らの起源に近いところでエラーを捕まえる方が良いと思います。 これが私が上記のApplication_Errorハンドラを好む理由です。

これは404を捕まえる第2の場所です。

ステップ4:NotFoundルートをGlobal.asaxに追加して、アプリケーションに解析されないURLを追加する

このルートは、 Http404アクションを指しているHttp404です。 ルーティングエンジンがここでドメイン部分を削除しているため、 urlパラメータは相対URLになります。 そのため、ステップ1で条件付きのすべてのURLロジックを使用しています。

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

これは、あなたがあなた自身を呼び出さないMVCアプリケーションで404を捕まえるための3番目で最後の場所です。 ここで比類のないルートをキャッチしないと、MVCはASP.NET(Global.asax)まで問題を渡します。この状況では、MVCはその問題を本当に望んでいません。

ステップ5:最後に、アプリが何かを見つけることができないときに404を呼び出す

悪いIDがMy Loansコントローラに送信されたとき( MyControllerから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);
    }

これは、すべてがこれより少ないコードでより少ない場所に繋がることができればうれしいですが、私はこのソリューションがより保守性が高く、よりテスト可能で、かなり実用的だと思います。

これまでのフィードバックに感謝します。 私はもっ​​と欲しいです。

注:これは私のオリジナルの回答から大幅に編集されていますが、目的と要件は同じです - これが新しい回答を追加しなかった理由です

私は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ページを取得します。

最後に、私の質問です: ルートやMVCフレームワーク自体の何かを使用してこのタイプの要求をキャッチする方法はありますか?

私は404ハンドラとしてWeb.ConfigのcustomErrorsを使用し、これをすべて忘れる必要がありますか? 私はcustomErrorsと一緒に行くと仮定しますが、Web.Configの直接アクセスの制限のために、/ Viewsの外に汎用404ページを保存する必要があります。


クイックアンサー/ TL; DR

そこの怠け者のために:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

次に、この行をglobal.asaxから削除しglobal.asax

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

これはIIS7 +とIIS Expressのみに適用されます。

あなたがカッシーニを使っているなら..まあ..ええ..厄介な...

長い、答えを説明した

私はこれが答えられたことを知っている。 しかし答えは本当に簡単です(本当にこれに答えるためにDavid FowlerとDamian Edwardsに感謝します)。

カスタムを行う必要はありません

ASP.NET MVC3では、すべてのビットとピースがそこにあります。

ステップ1 - > web.configを2つの場所に更新します。

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

今私が使用することを決めたルートを注意深くメモしてください。 あなたは何でも使えますが、私のルートは

  • /NotFound < - 404が見つかりませんでした、エラーページ。
  • /ServerError < - 他のエラーの場合は、自分のコードで発生したエラーを含めます。 これは500内部サーバーエラーです

<system.web>最初のセクションにカスタムエントリが1つしかないことを確認してください。 statusCode="404"エントリ? 私は500 Server Error (つまり、あなたのコードにバグがあり、ユーザの要求がクラッシュしたときに起こる厄介なエラー)を含む他のすべてのエラーがあるため、ステータスコードを1つだけリストしました。他のすべてのエラーは、 defaultRedirect="/ServerError" .. 404ページが見つからない場合は、 /ServerErrorルートに移動してください。

OK。 それは途中です。今、 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}
        );
}

それは2つの無視する経路をリストしています - > axd'sfavicons (あなたのために経路を無視するボーナス!)そして(命令はここでは命令です)、私は2つの明示的なエラー処理ルートを持っています。 この場合、デフォルトのものです。 もちろん、私はもっと多く持っていますが、それは私のウェブサイトにとって特別です。 エラールートがリストの一番上にあることを確認してください。 順序は必須です。

最後に、 global.asaxファイル内にある間に、HandleError属性をグローバルに登録しません。 いいえ、ありません。 ナダ いいえ。 Nien。 負。 Noooooooooo ...

global.asaxからこの行を削除する

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

ステップ3 - アクションメソッドを使用してコントローラを作成する

今、2つのアクションメソッドを持つコントローラを追加します...

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

さて、これをチェックしてみましょう。 まず、ここにNO [HandleError]属性があります。 どうして? ビルドされたASP.NETフレームワークはすでにエラーを処理しており、エラーを処理するために必要なすべての不具合を指定しています。このメソッドにはあります!

次に、2つのアクションメソッドがあります。 そこには厳しいものはありません。 あなたは例外情報を表示したい場合は、 Server.GetLastError()を使用してその情報を取得できます。

ボーナスWTF:はい、エラー処理をテストするために3番目のアクションメソッドを作成しました。

ステップ4 - ビューを作成する

最後に、2つのビューを作成します。 このコントローラのために、通常の視点にemを入れてください。

ボーナスコメント

  • Application_Error(object sender, EventArgs e)は必要ありません。
  • 上記の手順はすべてElmah完全に100%動作しElmah 。 エルマがwroxsを欺く!

そして、私の友人は、それでなければなりません。

さて、このことをたくさん読んで、ユニコーンを賞品にしてくださったことをお祝いします!


1)抽象コントローラクラスを作成する。

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)あなたのすべてのコントローラーでこの抽象クラスから継承を作成する

public class HomeController : MyController
{}  

3)そして、「NotFound」という名前のビューをView-Sharedフォルダに追加します。


MVC4 WebAPI 404では、次のように処理できます。

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

ホームコントローラー

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

VIEW

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

グローバル

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

結果


コメントが長すぎたので、回答を投稿する...

ユニコーン投稿/回答のコメントと質問です。

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

私はそれがシンプルであり、明らかにマイクロソフトの一部の人々が相談されたという事実のため、この回答を他の人よりも好む。私は3つの質問を持っています。答えが得られたら、私はこの答えをASP.NET MVC(x)アプリケーション用のインターウェブ上のすべての404/500エラー回答の聖杯にします。

@ Pure.Krome

  1. あなたはGWBが指摘したコメントからSEOのものとあなたの答えを更新することができます(そこにあなたの答えでこれのいずれかの言及はなかった) - <customErrors mode="On" redirectMode="ResponseRewrite"><httpErrors errorMode="Custom" existingResponse="Replace">

  2. そのようにそれを行うには大丈夫であれば、あなたのASP.NETチームの友人を尋ねることができます-いくつかの確認を持っていいだろう-多分変更することが大きなノーノーませんredirectModeし、existingResponseSEOとうまくプレーできるようにするには、このように!

  3. マイクロソフトの友人と話した後customErrors redirectMode="ResponseRewrite"customErrors redirectMode="ResponseRedirect"、そのようなものすべてを取り巻く明確化を加えてもいいですか(httpErrors errorMode="Custom" existingResponse="Replace"、、、customErrors誰かが提案したように完全に削除しますか?

私が言っていたように; あなたの回答をより完全にすることができれば、スーパーナイスになるでしょう。これは54,000以上の見解でかなり人気のある質問のようです。

更新:Unicornの回答は302 Foundと200 OKを行い、ルートを使用して404を返すように変更することはできません。あまりMVCではない物理的なファイルでなければなりません:ish。そこで、別の解決策に移ります。これは究極のMVCであるように思われたので、あまりにも悪いことです。


コードは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));
}

無効なコントローラのために@ 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;
        }
    }
}

私はMVC 2.0を使用していることを言及する必要があります。


私のソリューションは、Herman Kan'sとほぼ同じですが、小さなシワで私のプロジェクトでも使えるようになりました。

カスタムエラーコントローラを作成する:

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

次に、カスタムコントローラファクトリを作成します。

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

最後に、カスタムエラーコントローラにオーバーライドを追加します。

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

以上です。 Web.configの変更は必要ありません。


私はすべての記事を通過しましたが、何も私のために働いています:私の要件のユーザーのタイプはあなたのURLカスタム404ページに表示する必要があります。私はそれが非常にまっすぐ進むと思った。しかし404の処理を正しく理解する必要があります:

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

この記事は非常に参考になりました。一度に読むべきです。Customeエラーページ - Ben Foster


私は本当にコッツァクの解決が好きであり、とても明確に説明されていると思います。 私の唯一の追加は、次のようにステップ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
}

基本的に、無効なアクションを含むURLとコントローラが例外ルーチンを2回トリガするのを止めます。 たとえば、asdfsdf / dfgdfgdのようなURLの場合


ASP.NET MVCのエラーを処理することは、バットの痛みです。私はこのページや他の質問やサイトでたくさんの提案を試みましたが、何もうまくいきませんでした。1つの提案は、system.webserver内のweb.configのエラーを処理することでしたが、空白のページを返すだけでした。

私の目標は、この解決策を考え出すときでした。

  • リダイレクトしない
  • デフォルトのエラー処理のように200 / OKでないPROPER STATUS CODESを返す

ここに私の解決策があります。

1. system.webセクションに以下を追加します。

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

上記は、routes.configで処理されなかったURL や、特にビューで発生した未処理の例外を処理します。注意私はHTMLではなくaspxを使用しました。これは、コードの後ろに応答コードを追加できるようにするためです。

2 。プロジェクトのルートにErrorというフォルダを作成し、2つのWebフォームを追加します。以下は私の404ページです。

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

また、コードの背後に私は応答コードを設定した

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

500ページで同じことをする

3.コントローラ内のエラーを処理する。それを行うには多くの方法があります。これは私のために働いたものです。私のすべてのコントローラは、ベースコントローラから継承します。ベースコントローラーでは、以下のメソッドがあります

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. CustomError.cshtmlを共有ビューフォルダに追加します。以下は私のものです。

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

今度はあなたのアプリケーションコントローラで、このようなことをすることができます。

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

今すぐ警告する。静的ファイルエラーは処理されません。したがって、example.com / widgetsなどのルートがあり、ユーザーがexample.com/widgets.htmlに変更すると、IISのデフォルトエラーページが表示され、IISレベルのエラーを処理する必要があります。


それは私には標準的なCustomErrors設定が動作するはずですが、その依存関係Server.Transferは内部実装がResponseRewriteMVCと互換性がないように思われるためです。

これは私には目障りな機能ホールのように感じるので、私はHTTPモジュールを使ってこの機能を再実装することに決めました。以下の解決策では、通常の場合と同じように、有効なMVCルートにリダイレクトすることで、HTTPステータスコード(404も含む)を処理できます。

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

これは以下のプラットフォームでテストされています。

  • 統合パイプラインモードのMVC4(IIS Express 8)
  • クラシックモードのMVC4(VS Development Server、Cassini)
  • クラシックモードでのMVC4(IIS6)

利点

  • 任意のMVCプロジェクトにドロップできる汎用ソリューション
  • 従来のカスタムエラー設定のサポートを有効にする
  • 統合パイプラインモードとクラシックモードの両方で動作

ソリューション

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() {

        }


    }

}

使用法

web.configの最後のHTTPモジュールとしてこれを含めます

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

あなたが気を配っている人には、統合パイプラインモードでは、動作するために常にHTTP 200で応答することに気づくでしょうServer.TransferRequest。適切なエラーコードを返すために、私は次のエラーコントローラを使用します。

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

}

私はこのスレッドに投稿されたソリューションのほとんどを調べました。この質問は古いかもしれませんが、今でも新しいプロジェクトにはまだまだ適用されるので、ここで提示された回答だけでなく、どこで答えを読んでいるか、かなりの時間を費やしました。

@Marcoは、404が起こる可能性のあるさまざまなケースを指摘したので、私はそのリストに対してまとめた解決策をチェックしました。彼の要件リストに加えて、もう一つ追加しました。

  • ソリューションは、MVCとAJAX / WebAPI呼び出しを最も適切な方法で処理できる必要があります。(つまり、MVCで404が発生した場合、Not Foundページが表示され、WebAPIで404が発生した場合、XML / JSONレスポンスをハイジャックして、消費するJavascriptで簡単に解析できます)。

この溶液は2倍である:

その最初の部分は、@ Guillaume(https://.com/a/27354140/2310818からのものです。彼らの解決策は、無効なルート、無効なコントローラ、無効なアクションによって引き起こされた404件を処理します。

アイデアは、Webフォームを作成し、MVCエラーコントローラのNotFoundアクションを呼び出させることです。これはリダイレクトなしですべてのことを行いますので、Fiddlerで単一の302が表示されません。元のURLも保存されているため、このソリューションは素晴らしいものになります。

2番目の部分は、@Germán(https://.com/a/5536676/2310818です。彼らの解決策は、HttpNotFoundResult()の形式であなたのアクションによって返された404を処理したり、新しいHttpException()をスローします!

考え方は、MVCコントローラによってスローされた例外と同様に応答をフィルタで調べ、エラーコントローラで適切なアクションを呼び出すことです。このソリューションはリダイレクトなしで動作し、元のURLは保持されます。

ご覧のように、これらのソリューションはともに、非常に堅牢なエラー処理メカニズムを提供し、@Marcoに記載されているすべての要件と要件を満たしています。このソリューションの実例やデモをご覧になりたい場合は、コメントを残しておいてください。私はそれをまとめておきます。





http-status-code-404