ASP.NET MVCアクションからHTTP 404応答を送信する適切な方法は何ですか?


92

ルートが与えられた場合:

{FeedName} / {ItemPermalink}

例:/ Blog / Hello-World

アイテムが存在しない場合、404を返します。ASP.NETMVCでこれを行う正しい方法は何ですか?


ところでこの質問をしてくれてありがとう。これは私の標準的なプロジェクトの追加で行われます:D
Erik van Brakel

回答:


69

ヒップから撃つ(カウボーイコーディング;-))、私はこのようなものをお勧めします:

コントローラ:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return new HttpNotFoundResult("This doesn't exist");
    }
}

HttpNotFoundResult:

using System;
using System.Net;
using System.Web;
using System.Web.Mvc;

namespace YourNamespaceHere
{
    /// <summary>An implementation of <see cref="ActionResult" /> that throws an <see cref="HttpException" />.</summary>
    public class HttpNotFoundResult : ActionResult
    {
        /// <summary>Initializes a new instance of <see cref="HttpNotFoundResult" /> with the specified <paramref name="message"/>.</summary>
        /// <param name="message"></param>
        public HttpNotFoundResult(String message)
        {
            this.Message = message;
        }

        /// <summary>Initializes a new instance of <see cref="HttpNotFoundResult" /> with an empty message.</summary>
        public HttpNotFoundResult()
            : this(String.Empty) { }

        /// <summary>Gets or sets the message that will be passed to the thrown <see cref="HttpException" />.</summary>
        public String Message { get; set; }

        /// <summary>Overrides the base <see cref="ActionResult.ExecuteResult" /> functionality to throw an <see cref="HttpException" />.</summary>
        public override void ExecuteResult(ControllerContext context)
        {
            throw new HttpException((Int32)HttpStatusCode.NotFound, this.Message);
        }
    }
}
// By Erik van Brakel, with edits from Daniel Schaffer :)

このアプローチを使用すると、フレームワーク標準に​​準拠します。すでにHttpUnauthorizedResultがそこにあるので、これは、後でコードを保守している別の開発者(あなたが知っている、あなたが住んでいる場所を知っている心理学者)の目の前でフレームワークを拡張するだけです。

リフレクターを使用してアセンブリを調べ、HttpUnauthorizedResultがどのように実現されるかを確認できます。これは、このアプローチが何かを見逃しているかどうかがわからないためです(ほとんど単純すぎるようです)。


リフレクターを使用してHttpUnauthorizedResultを今すぐ確認しました。応答のStatusCodeを0x191(401)に設定しているようです。これは401で機能しますが、404を新しい値として使用しているので、Firefoxで空白のページしか表示されないようです。ただし、Internet Explorerではデフォルトの404が表示されます(ASP.NETバージョンでは表示されません)。webdeveloperツールバーを使用して、FFのヘッダーを検査しましたが、404 Not Found応答を示しています。FFで構成を誤っただけかもしれません。


そうは言っても、ジェフのアプローチはKISSの良い例だと思います。このサンプルの冗長性が本当に必要ない場合は、彼の方法も問題なく機能します。


ええ、私は列挙型にも気づきました。私が言ったように、それは単なる大まかな例です、それを自由に改善してください。結局のところ、これはナレッジベースになるはずです;-)
Erik van Brakel

私は少し船外に行ったと思います...お楽しみください:D
ダニエルシャファー

FWIW、ジェフの例でも、カスタム404ページが必要です。
ダニエルシャファー、

2
HttpContext.Response.StatusCode = 404を設定するだけでなくHttpExceptionをスローすることに関する1つの問題は、OnException Controllerハンドラー(私が行うように)を使用すると、HttpExceptionsもキャッチすることです。したがって、StatusCodeを設定するだけの方が良い方法だと思います。
Igor Brejc 09

4
MVC3のHttpExceptionまたはHttpNotFoundResultは、多くの点で役立ちます。@Igor Brejcの場合は、OnExceptionのifステートメントを使用して、見つからないエラーを除外します。
CallMeLaNN 2011年

46

私たちはそのようにします。このコードはBaseController

/// <summary>
/// returns our standard page not found view
/// </summary>
protected ViewResult PageNotFound()
{
    Response.StatusCode = 404;
    return View("PageNotFound");
}

そのように呼ばれた

public ActionResult ShowUserDetails(int? id)
{        
    // make sure we have a valid ID
    if (!id.HasValue) return PageNotFound();

このアクションはデフォルトのルートに配線されていますか?どのように実行されるかわかりません。
Christian Dalager、2009

2
次のように実行している可能性があります:protected override void HandleUnknownAction(string actionName){PageNotFound()。ExecuteResult(this.ControllerContext); }
Tristan Warner-Smith、

以前はその方法で行っていましたが、結果と表示されるビューを分割する方がより良いアプローチであることがわかりました。以下の私の答えをチェックしてください。
Brian Vallelunga

19
throw new HttpException(404, "Are you sure you're in the right place?");

これは、で設定されたカスタムエラーページに従っているので、気に入っていますweb.config
Mike Cole

7

HttpNotFoundResultは、私が使用しているものへの素晴らしい最初のステップです。HttpNotFoundResultを返すのは良いことです。次に問題は、次は何ですか?

HandleNotFoundAttributeと呼ばれるアクションフィルターを作成し、404エラーページを表示しました。ビューを返すため、コントローラーごとに特別な404ビューを作成するか、デフォルトの共有404ビューを使用できます。フレームワークがステータスコード404のHttpExceptionをスローするため、指定されたアクションがコントローラーに存在しない場合でも、これが呼び出されます。

public class HandleNotFoundAttribute : ActionFilterAttribute, IExceptionFilter
{
    public void OnException(ExceptionContext filterContext)
    {
        var httpException = filterContext.Exception.GetBaseException() as HttpException;
        if (httpException != null && httpException.GetHttpCode() == (int)HttpStatusCode.NotFound)
        {
            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; // Prevents IIS from intercepting the error and displaying its own content.
            filterContext.ExceptionHandled = true;
            filterContext.HttpContext.Response.StatusCode = (int) HttpStatusCode.NotFound;
            filterContext.Result = new ViewResult
                                        {
                                            ViewName = "404",
                                            ViewData = filterContext.Controller.ViewData,
                                            TempData = filterContext.Controller.TempData
                                        };
        }
    }
}


6

エラーをスローするたびに属性にフィルターを設定する必要があるため、ActionFilterの使用は維持困難です。設定を忘れた場合はどうなりますか?1つの方法はOnException、ベースコントローラでの派生です。BaseController派生元を定義する必要がありController、すべてのコントローラーはから派生する必要がありますBaseController。ベースコントローラを使用することをお勧めします。

Exception応答ステータスコードの使用が500である場合は、Not Foundの場合は404に、Unauthorizedの場合は401に変更する必要があることに注意してください。上記で述べたように、フィルター属性の使用を避けるためにOnExceptionオーバーライドを使用しますBaseController

新しいMVC 3は、空のビューをブラウザーに返すことで、さらに面倒になります。いくつかの調査の後の最善の解決策は、ASP.Net MVC 3でHttpNotFound()のビューを返す方法についての私の回答に基づいています

より便利にするために、ここに貼り付けます。


いくつかの研究の後。ここでのMVC 3の回避策は、すべての、、クラスを派生させHttpNotFoundResultHttpUnauthorizedResultnew(オーバーライド)()メソッドHttpStatusCodeResultを実装することです。HttpNotFoundBaseController

すべての派生コントローラーを「制御」できるように、ベースコントローラーを使用することをお勧めします。

私は新しい作成しHttpStatusCodeResultていないから導き出すために、クラスをActionResultしかしからViewResultビューまたはレンダリングするためにView指定することにより、必要なViewNameプロパティを。私は、元従うHttpStatusCodeResult設定するHttpContext.Response.StatusCodeとしHttpContext.Response.StatusDescriptionたが、その後base.ExecuteResult(context)、再び、私はから派生しているため、適切なビューをレンダリングしますViewResult。十分簡単ですか?これがMVCコアに実装されることを願っています。

私のBaseControllerうなり声を見てください:

using System.Web;
using System.Web.Mvc;

namespace YourNamespace.Controllers
{
    public class BaseController : Controller
    {
        public BaseController()
        {
            ViewBag.MetaDescription = Settings.metaDescription;
            ViewBag.MetaKeywords = Settings.metaKeywords;
        }

        protected new HttpNotFoundResult HttpNotFound(string statusDescription = null)
        {
            return new HttpNotFoundResult(statusDescription);
        }

        protected HttpUnauthorizedResult HttpUnauthorized(string statusDescription = null)
        {
            return new HttpUnauthorizedResult(statusDescription);
        }

        protected class HttpNotFoundResult : HttpStatusCodeResult
        {
            public HttpNotFoundResult() : this(null) { }

            public HttpNotFoundResult(string statusDescription) : base(404, statusDescription) { }

        }

        protected class HttpUnauthorizedResult : HttpStatusCodeResult
        {
            public HttpUnauthorizedResult(string statusDescription) : base(401, statusDescription) { }
        }

        protected class HttpStatusCodeResult : ViewResult
        {
            public int StatusCode { get; private set; }
            public string StatusDescription { get; private set; }

            public HttpStatusCodeResult(int statusCode) : this(statusCode, null) { }

            public HttpStatusCodeResult(int statusCode, string statusDescription)
            {
                this.StatusCode = statusCode;
                this.StatusDescription = statusDescription;
            }

            public override void ExecuteResult(ControllerContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException("context");
                }

                context.HttpContext.Response.StatusCode = this.StatusCode;
                if (this.StatusDescription != null)
                {
                    context.HttpContext.Response.StatusDescription = this.StatusDescription;
                }
                // 1. Uncomment this to use the existing Error.ascx / Error.cshtml to view as an error or
                // 2. Uncomment this and change to any custom view and set the name here or simply
                // 3. (Recommended) Let it commented and the ViewName will be the current controller view action and on your view (or layout view even better) show the @ViewBag.Message to produce an inline message that tell the Not Found or Unauthorized
                //this.ViewName = "Error";
                this.ViewBag.Message = context.HttpContext.Response.StatusDescription;
                base.ExecuteResult(context);
            }
        }
    }
}

このようなアクションで使用するには:

public ActionResult Index()
{
    // Some processing
    if (...)
        return HttpNotFound();
    // Other processing
}

_Layout.cshtml(マスターページと同様)

<div class="content">
    @if (ViewBag.Message != null)
    {
        <div class="inlineMsg"><p>@ViewBag.Message</p></div>
    }
    @RenderBody()
</div>

さらに、カスタムビューを使用しError.shtmlたりNotFound.cshtml、コードでコメントしたような新しいビューを作成したり、ステータスの説明やその他の説明のビューモデルを定義したりできます。


ベースコントローラーを使用するには覚えておく必要があるため、ベースコントローラーに勝るグローバルフィルターをいつでも登録できます。
John Culviner 2013

:)これもMVC4の問題であるかどうかは不明です。その時私が意味しているのは、誰かが答えたHandleNotFoundAttributeフィルターです。アクションごとに適用する必要はありません。たとえば、id paramがあり、Index()アクションがないアクションにのみ適しています。HandleNotFoundAttributeではなく、カスタムHandleErrorAttributeのグローバルフィルターに同意しました。
CallMeLaNN 2013

MVC3にもあると思いましたが、よくわかりません。答えに出くわす可能性がある他の人に関係なく良い議論
John Culviner 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.