如何正确处理ASP中的404。净MVC吗?

我用的是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);
}

但是,前面的策略不处理对坏/未知控制器的请求。例如,我没有“/IDoNotExist”,如果我请求这个,我从web服务器得到通用404页面,而不是我的404,如果我使用路由+覆盖。

最后,我的问题是:是否有任何方法来捕捉这种类型的请求使用路由或MVC框架本身的其他东西?

或者我应该默认使用Web。配置customErrors作为我的404处理程序,忘记这一切?我假设如果我使用customErrors,由于Web的原因,我将不得不在/Views之外存储通用404页面。配置直接访问限制。

157437 次浏览

代码取自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));
}

404要求

以下是我对404解决方案的要求,下面我展示了我如何实现它:

  • 我想处理匹配的路由与坏的行为
  • 我想处理与坏控制器匹配的路由
  • 我想处理不匹配的路由(任意url,我的应用程序不能理解)-我不希望这些冒泡到全局。asax或IIS,因为我不能重定向回到我的MVC应用程序正确
  • 我想要一种与上面相同的方式处理的方法,自定义404 -就像当一个ID被提交给一个不存在的对象(可能已经删除)。
  • 我想我所有的404返回一个MVC视图(不是一个静态页面),我可以泵更多的数据后,如有必要(好的404设计) 而且他们必须返回HTTP 404状态代码

解决方案

我认为你应该保存Application_Error在全局。asax用于更高级的事情,如未处理的异常和日志(如谢伊·雅各比的回答所示),但不用于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被提交给我的Loans控制器(来自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);
}

如果所有这些都能在更少的地方用更少的代码连接起来,那就太好了,但我认为这种解决方案更可维护,更可测试,而且相当实用。

谢谢你的反馈。我想要更多。

注:这已经从我原来的答案编辑显著,但目的/要求是相同的-这就是为什么我没有添加一个新的答案

我真的很喜欢cottsaks的解决方案,认为它解释得非常清楚。我唯一增加的是修改步骤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两次触发异常例程。Eg的url,如asdfsdf/dfgdfgd

我能让@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。

ASP。NET MVC不太支持自定义404页面。自定义控制器工厂,捕获所有路由,带有HandleUnknownAction的基控制器类-啊哈!

到目前为止,IIS自定义错误页面是更好的选择:

. config

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

ErrorController

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

示例项目

快速回答/ TL

enter image description here

对于那些懒惰的人:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

然后从global.asax中删除这一行

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

这仅适用于IIS7+和IIS Express。

如果你使用卡西尼号..嗯. .嗯. .呃. .尴尬…… < img src = " https://i.imgur.com/g1GTW3v.gif " alt = "尴尬" / > < / p >


冗长的解释

我知道这个问题已经得到了回答。但答案真的很简单(为大卫·弗勒Damian爱德华兹真正回答了这个问题而欢呼)。

不需要做任何定制

对于ASP.NET MVC3,所有的比特和碎片都在那里。

步骤1 ->更新你的网页。在两个点配置。

<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>中的第一部分只有一个自定义条目了吗?statusCode="404"条目?我只列出了一个状态码,因为所有其他错误,包括500 Server Error(即。当你的代码有bug并使用户的请求崩溃时发生的那些讨厌的错误)。所有其他错误由设置defaultRedirect="/ServerError" ..处理。上面写着,如果你的404页面没有找到,那么请转到路径/ServerError

好的。那太离谱了。现在到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}
);
}

列出两条忽略路由->axd'sfavicons (ooo!奖励忽略路线,为你!) 然后(这里的顺序是IMPERATIVE),我有两个显式的错误处理路由..然后是其他路线。在本例中,是默认值。当然,我还有更多,但那是我网站的特色。只要确保错误路由位于列表的顶部即可。秩序是必须的 . < / p >

最后,当我们在global.asax文件中时,我们没有全局注册HandleError属性。不,不,不,先生。没有。年兽。负的。Noooooooooo……

global.asax中删除这一行

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

步骤3 -用动作方法创建控制器

现在. .我们添加了一个带有两个动作方法的控制器…

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

好的,我们来看看这个。首先,这里有没有 [HandleError]属性。为什么?因为内置的ASP.NET框架已经在处理错误,而且我们已经指定了处理错误所需做的所有事情:)它就在这个方法中!

接下来,我有两个动作方法。没什么难的。如果你想显示任何异常信息,那么你可以使用Server.GetLastError()来获取该信息。

奖励WTF:是的,我做了第三个动作方法,以测试错误处理。

步骤4 -创建视图

最后,创建两个视图。把它们放到这个控制器的正常视图点。

enter image description here

奖金的评论

  • 你不需要Application_Error(object sender, EventArgs e)
  • 以上步骤在Elmah中都100%完美地工作。该死的埃尔玛!

朋友们,就这样吧。

现在,祝贺你读了这么多,并有一个独角兽作为奖品!

enter image description here

下面是另一个使用MVC工具的方法,你可以处理对坏控制器名、坏路由名的请求,以及任何其他你认为在Action方法中合适的条件。就我个人而言,我倾向于尽量避免使用网络。配置设置,因为他们做302 / 200重定向,不支持ResponseRewrite (Server.Transfer)使用Razor视图。出于搜索引擎优化的原因,我更喜欢返回带有自定义错误页面的404。

其中一些是对cottsak上面的技术的新采取。

这个解决方案也使用最少的网络。配置设置偏向MVC 3错误过滤器。

使用

只需从动作或自定义ActionFilterAttribute抛出一个HttpException。

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

步骤1

将以下设置添加到您的web.config。这是使用MVC的HandleErrorAttribute所必需的。

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

步骤2

添加一个自定义的HandleHttpErrorAttribute,类似于MVC框架的HandleErrorAttribute,除了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

步骤3

Global.asax中的GlobalFilterCollection (GlobalFilters.Filters)添加过滤器。这个例子将所有InternalServerError(500)错误路由到Error共享视图(Views/Shared/Error.vbhtml)。NotFound(404)错误将被发送到ErrorHttp404。共享视图中的VBHTML。我在这里添加了一个401错误,以向您展示如何将其扩展为其他HTTP错误代码。注意,这些必须是共享视图,并且它们都使用System.Web.Mvc.HandleErrorInfo对象作为模型。

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

步骤4

创建一个基控制器类,并在控制器中继承它。这一步允许我们处理未知的动作名称,并向我们的HandleHttpErrorAttribute抛出HTTP 404错误。

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

步骤5

创建一个ControllerFactory重写,并在全局中重写它。asax文件在Application_Start。这个步骤允许我们在指定了无效的控制器名时引发HTTP 404异常。

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)

步骤6

在你的路由表中包含一个特殊的路由。BaseController Unknown动作的路由。这将帮助我们在用户访问未知控制器或未知操作的情况下引发404。

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

总结

这个例子演示了如何使用MVC框架向浏览器返回404 Http错误代码,而不需要使用过滤器属性和共享错误视图进行重定向。它还演示了当指定无效的控制器名和动作名时显示相同的自定义错误页面。

我将添加一个无效的控制器名,动作名的截图,以及一个自定义404从Home/TriggerNotFound动作引发,如果我得到足够的投票,张贴一个=)。提琴手返回404消息时,我访问以下url使用这个解决方案:

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

Cottsak上面的文章和这些文章都是很好的参考。

我已经调查了很多如何正确地管理MVC (特别是MVC3)中的404,这,IMHO是我提出的最好的解决方案:

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

ErrorsController:

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

(可选)

解释:

AFAIK,有6种不同的情况,ASP。NET MVC3应用程序可以生成404。

(由ASP自动生成。净框架:)

(1) URL在路由表中找不到匹配。

(由ASP自动生成。NET MVC框架:)

(2) URL在路由表中找到匹配,但指定了一个不存在的控制器。

(3) URL在路由表中找到匹配,但指定了一个不存在的动作。

(手动生成:)

一个动作通过使用HttpNotFound()方法返回一个HttpNotFoundResult。

动作抛出一个状态代码为404的HttpException。

(6)一个动作手动修改响应。StatusCode属性变为404。

通常情况下,你需要完成3个目标:

(1)向用户显示自定义404错误页面。

维护客户端响应上的404状态代码(对SEO特别重要)。

直接发送响应,不涉及302重定向。

有很多方法可以做到这一点:

(1)

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

此解决方案存在的问题:

  1. 在情况(1)、(4)、(6)中不符合目标(1)。
  2. 不自动符合目标(2)。它必须手动编程。
  3. 不符合目标(3)。

(2)

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

此解决方案存在的问题:

  1. 仅适用于iis7 +。
  2. 在情况(2)、(3)、(5)中不符合目标(1)。
  3. 不自动符合目标(2)。它必须手动编程。

(3)

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

此解决方案存在的问题:

  1. 仅适用于iis7 +。
  2. 不自动符合目标(2)。它必须手动编程。
  3. 它模糊了应用程序级别的http异常。例如,不能使用customErrors部分,System.Web.Mvc。HandleErrorAttribute等等。它不能只显示一般的错误页面。

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

此解决方案存在的问题:

  1. 仅适用于iis7 +。
  2. 不自动符合目标(2)。它必须手动编程。
  3. 在情况(2)、(3)、(5)中不符合目标(3)。

在此之前遇到麻烦的人甚至尝试创建自己的库(参见http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html)。但是前面的解决方案似乎涵盖了所有的情况,而没有使用外部库的复杂性。

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

视图

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

结果

enter image description here

试试nuget上的NotFoundMVC。它可以工作,不需要设置。

我的解决方案,以防有人觉得有用。

在web . config:

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

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

Shared文件夹中添加PageNotFound.cshtml,就完成了。

因为我的评论太长了,所以发表了一个答案…

这既是对独角兽帖子/答案的评论,也是对它的问题:

https://stackoverflow.com/a/7499406/687549 < a href = " https://stackoverflow.com/a/7499406/687549 " > < / >

比起其他答案,我更喜欢这个答案,因为它很简单,而且事实是,显然微软的一些人被咨询了。然而,我有三个问题,如果他们可以回答,那么我将把这个答案称为互联网上所有404/500个错误答案的圣杯。NET MVC (x)应用程序。

@Pure。Krome

  1. 你能更新你的答案,从GWB指出的评论SEO的东西(在你的回答中从来没有提到过这一点)- <customErrors mode="On" redirectMode="ResponseRewrite"><httpErrors errorMode="Custom" existingResponse="Replace">?

  2. 你能问问你的ASP吗?NET团队的朋友们,如果这样做是可以的——得到一些确认就好了——也许以这种方式改变redirectModeexistingResponse是一个大禁忌,以便能够很好地发挥SEO?!

  3. 在与你在微软的朋友交谈后,你能补充一些关于所有这些东西的澄清吗(customErrors redirectMode="ResponseRewrite"customErrors redirectMode="ResponseRedirect"httpErrors errorMode="Custom" existingResponse="Replace",完全删除customErrors,就像有人建议的那样)?

就像我说的;如果我们能让你的回答更完整就太好了,因为这似乎是一个相当受欢迎的问题,有54000 +的浏览量。

更新: Unicorn回答302 Found和200 OK,不能使用路由更改为只返回404。它必须是一个物理文件,不是很MVC:ish。我们来看另一个解。太糟糕了,因为这似乎是到目前为止的终极MVC:ish答案。

我对未处理区域、控制器和动作的简化解决方案:

  1. 创建视图404.cshtml。

  2. 为控制器创建一个基类:

    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. Create a custom controller factory returning your base controller as a fallback:

    public class ControllerFactory : DefaultControllerFactory
    {
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
    if (controllerType != null)
    return base.GetControllerInstance(requestContext, controllerType);
    
    
    return new Controller();
    }
    }
    
  4. Add to Application_Start() the following line:

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

在我看来,标准的CustomErrors配置应该可以工作然而,由于对Server.Transfer的依赖,ResponseRewrite的内部实现似乎与MVC不兼容。

我觉得这是一个明显的功能漏洞,所以我决定使用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>

如果你注意的话,你会注意到在集成管道模式下,由于Server.TransferRequest的工作方式,它总是以HTTP 200响应。为了返回正确的错误代码,我使用以下错误控制器。

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 at https://stackoverflow.com/a/27354140/2310818。他们的解决方案处理了由于无效路由、无效控制器和无效动作而导致的任何404。

这个想法是创建一个WebForm,然后让它调用MVC错误控制器的NotFound动作。它做所有这些没有任何重定向,所以你不会看到一个302在小提琴手。原始URL也被保留,这使得这个解决方案非常棒!


它的第二部分来自@Germán在https://stackoverflow.com/a/5536676/2310818。他们的解决方案照顾任何404返回你的行为在HttpNotFoundResult()或抛出新的HttpException()的形式!

其思想是让一个过滤器查看由MVC控制器抛出的响应和异常,并在错误控制器中调用适当的操作。再次,这个解决方案的工作没有任何重定向和原始url被保留!


正如您所看到的,这两个解决方案一起提供了一个非常健壮的错误处理机制,它们实现了@Marco列出的所有需求以及我的需求。如果你想看这个解决方案的工作示例或演示,请在评论中留下,我很乐意把它放在一起。

我已经看了所有的文章,但没有一篇对我有用: 我的要求用户键入任何在您的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>

我发现这篇文章很有帮助。__abc0

处理ASP中的错误。NET MVC就是个麻烦。我在这个页面和其他问题和网站上尝试了很多建议,都没有什么效果。一个建议是在system.webserver内部处理. config上的错误,但仅限于返回空白页

当我想出这个解决方案时,我的目标是;

  • 不重定向
  • 返回正确的STATUS CODES,而不是像默认的错误处理那样返回200/Ok

这是我的解决方案。

1。将以下内容添加到包含部分

   <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和未处理的异常,特别是在视图中遇到的异常。注意,我使用的是aspx而不是超文本标记语言。这样我就可以在后面的代码中添加响应代码

2。在项目的根目录下创建一个名为错误的文件夹(或者其他你喜欢的文件夹),并添加这两个webform。下面是我的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到你的共享 views文件夹。下面是我的;

<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级别的错误

加上我的解决方案,这几乎是相同的Herman Kan的,有一个小皱纹,让它工作在我的项目。

创建一个自定义错误控制器:

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

就是这样。不需要网络。配置更改。

1)创建抽象的Controller类。

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”的视图。