404要求
以下是我对404解决方案的要求,下面我展示了我如何实现它:
I want to handle matched routes with bad actions
I want to handle matched routes with bad controllers
I want to handle un-matched routes (arbitrary urls that my app can't understand) - i don't want these bubbling up to the Global.asax or IIS because then i can't redirect back into my MVC app properly
I want a way to handle in the same manner as above, custom 404s - like when an ID is submitted for an object that does not exist (maybe deleted)
I want all my 404s to return an MVC view (not a static page) to which i can pump more data later if necessary (good 404 designs) and they must return the HTTP 404 status code
解决方案
我认为你应该在全局中保存Application_Error。asax用于高级的事情,如未处理的异常和日志记录(如Shay Jacoby的回答所示),但不用于404处理。这就是为什么我的建议将404内容排除在全局中。asax文件。
步骤1:为404错误逻辑设置一个公共位置
这对于可维护性来说是个好主意。使用ErrorController,以便将来对精心设计的404页面的改进可以很容易地适应。此外,确保您的响应有404代码!
public class ErrorController : MyController
{
#region Http404
public ActionResult Http404(string url)
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
var model = new NotFoundViewModel();
// If the url is relative ('NotFound' route) then replace with Requested path
model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ?
Request.Url.OriginalString : url;
// Dont get the user stuck in a 'retry loop' by
// allowing the Referrer to be the same as the Request
model.ReferrerUrl = Request.UrlReferrer != null &&
Request.UrlReferrer.OriginalString != model.RequestedUrl ?
Request.UrlReferrer.OriginalString : null;
// TODO: insert ILogger here
return View("NotFound", model);
}
public class NotFoundViewModel
{
public string RequestedUrl { get; set; }
public string ReferrerUrl { get; set; }
}
#endregion
}
步骤2:使用一个基本的Controller类,这样就可以轻松地调用自定义404动作并连接HandleUnknownAction
404在ASP。NET MVC需要在许多地方被捕获。第一个是HandleUnknownAction。
InvokeHttp404方法为重新路由到ErrorController和新的Http404动作创建了一个公共位置。想干!
public abstract class MyController : Controller
{
#region Http404 handling
protected override void HandleUnknownAction(string actionName)
{
// If controller is ErrorController dont 'nest' exceptions
if (this.GetType() != typeof(ErrorController))
this.InvokeHttp404(HttpContext);
}
public ActionResult InvokeHttp404(HttpContextBase httpContext)
{
IController errorController = ObjectFactory.GetInstance<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 httpexception
就像这样(它不一定是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的地方。
步骤4:添加一个NotFound路由到全局。Asax用于无法解析到应用程序中的url
这个路由应该指向Http404操作。注意url参数将是一个相对url,因为路由引擎在这里剥离域部分?这就是为什么我们在步骤1中有所有的条件url逻辑。
routes.MapRoute("NotFound", "{*url}",
new { controller = "Error", action = "Http404" });
这是在MVC应用程序中捕获不是自己调用的404的第三个也是最后一个地方。如果你在这里没有捕捉到不匹配的路由,那么MVC就会把这个问题传递给ASP。NET (global。asax)在这种情况下你不希望那样。
第五步:最后,当你的应用找不到东西时调用404
就像当一个坏的ID提交给我的贷款控制器(来源于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);
}
如果所有这些都能在更少的地方用更少的代码连接起来,那就太好了,但我认为这种解决方案更可维护,更可测试,而且相当实用。
谢谢你的反馈。我想要更多。
注:这已经从我原来的答案编辑显著,但目的/要求是相同的-这就是为什么我没有添加一个新的答案