ASP.NET MVC 작업에서 HTTP 404 응답을 보내는 적절한 방법은 무엇입니까?
경로가 주어진 경우 :
{FeedName} / {ItemPermalink}
예 : / Blog / Hello-World
항목이없는 경우 404를 반환하고 싶습니다. ASP.NET MVC에서이 작업을 수행하는 올바른 방법은 무엇입니까?
엉덩이에서 촬영 (카우보이 코딩 ;-)), 다음과 같이 제안합니다.
제어 장치:
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가 어떻게 달성되는지 확인할 수 있습니다.이 방법이 누락 된 것이 있는지 알 수 없기 때문입니다 (거의 너무 간단 해 보입니다).
I did use reflector to take a look at the HttpUnauthorizedResult just now. Seems they're setting the StatusCode on the response to 0x191 (401). Although this works for 401, using 404 as the new value I seem to be getting just a blank page in Firefox. Internet Explorer shows a default 404 though (not the ASP.NET version). Using the webdeveloper toolbar I inspected the headers in FF, which DO show a 404 Not Found response. Could be simply something I misconfigured in FF.
This being said, I think Jeff's approach is a fine example of KISS. If you don't really need the verbosity in this sample, his method works fine as well.
We do it like so; this code is found in BaseController
/// <summary>
/// returns our standard page not found view
/// </summary>
protected ViewResult PageNotFound()
{
Response.StatusCode = 404;
return View("PageNotFound");
}
called like so
public ActionResult ShowUserDetails(int? id)
{
// make sure we have a valid ID
if (!id.HasValue) return PageNotFound();
throw new HttpException(404, "Are you sure you're in the right place?");
The HttpNotFoundResult is a great first step to what I am using. Returning an HttpNotFoundResult is good. Then the question is, what's next?
I created an action filter called HandleNotFoundAttribute that then shows a 404 error page. Since it returns a view, you can create a special 404 view per controller, or let is use a default shared 404 view. This will even be called when a controller doesn't have the specified action present, because the framework throws an HttpException with a status code of 404.
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
};
}
}
}
Note that as of MVC3, you can just use HttpStatusCodeResult
.
Using ActionFilter is hard to maintain because whenever we throw an error the filter need to be set in the attribute. What if we forget to set it? One way is deriving OnException
on base controller. You need to define a BaseController
derived from Controller
and all your controllers must derive from BaseController
. It is a best practise to have a base controller.
Note if using Exception
the response status code is 500, so we need to change it to 404 for Not Found and 401 for Unauthorized. Just like I mention above, use OnException
overrides on BaseController
to avoid using filter attribute.
The new MVC 3 also make more troublesome by returning an empty view to browser. The best solution after some research is based on my answer here How to return a view for HttpNotFound() in ASP.Net MVC 3?
To make more convinience I paste it here:
After some study. The workaround for MVC 3 here is to derive all HttpNotFoundResult
, HttpUnauthorizedResult
, HttpStatusCodeResult
classes and implement new (overriding it) HttpNotFound
() method in BaseController
.
It is best practise to use base Controller so you have 'control' over all derived Controllers.
I create new HttpStatusCodeResult
class, not to derive from ActionResult
but from ViewResult
to render the view or any View
you want by specifying the ViewName
property. I follow the original HttpStatusCodeResult
to set the HttpContext.Response.StatusCode
and HttpContext.Response.StatusDescription
but then base.ExecuteResult(context)
will render the suitable view because again I derive from ViewResult
. Simple enough is it? Hope this will be implemented in the MVC core.
See my BaseController
bellow:
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);
}
}
}
}
To use in your action like this:
public ActionResult Index()
{
// Some processing
if (...)
return HttpNotFound();
// Other processing
}
And in _Layout.cshtml (like master page)
<div class="content">
@if (ViewBag.Message != null)
{
<div class="inlineMsg"><p>@ViewBag.Message</p></div>
}
@RenderBody()
</div>
Additionally you can use a custom view like Error.shtml
or create new NotFound.cshtml
like I commented in the code and you may define a view model for the status description and other explanations.
'development' 카테고리의 다른 글
투명한 배경으로 matplotlib에서 플롯을 내보내는 방법은 무엇입니까? (0) | 2020.09.05 |
---|---|
소스 제어에서 구성 파일을 어떻게 처리합니까? (0) | 2020.09.05 |
간단한 Java 인 메모리 캐시를 찾고 있습니다. (0) | 2020.09.05 |
app.config에서 사용자 지정 구성 섹션을 만드는 방법은 무엇입니까? (0) | 2020.09.05 |
Xcode에서 Git을 올바르게 사용하는 방법은 무엇입니까? (0) | 2020.09.05 |