Comment puis-je manipuler correctement 404 en ASP.NET MVC?
j'utilise RC2
utilisant le routage D'URL:
routes.MapRoute(
"Error",
"{*url}",
new { controller = "Errors", action = "NotFound" } // 404s
);
ce qui précède semble prendre en compte des requêtes comme celle-ci (en supposant des tables de route par défaut configurées par le projet initial MVC):"/blah/blah/blah / blah "
action de commande supérieure () dans le contrôleur lui-même:
// 404s - handle here (bad action requested
protected override void HandleUnknownAction(string actionName) {
ViewData["actionName"] = actionName;
View("NotFound").ExecuteResult(this.ControllerContext);
}
cependant les stratégies précédentes ne traitent pas une demande à un Mauvais contrôleur / inconnu. Par exemple, je n'ai pas de "/IDoNotExist", si je le demande je reçois la page generic 404 du serveur web et pas mon 404 si j'utilise routing + override.
donc finalement, ma question Est: y a-t-il un moyen d'attraper ce type de requête en utilisant une route ou quelque chose d'autre dans le cadre MVC lui-même?
ou devrais-je utiliser Web par défaut.Config customErrors comme mon gestionnaire 404 et tout oublier? Je suppose que si je vais avec customErrors je vais devoir stocker la page 404 Générique En dehors de / vues en raison du Web.Restrictions de configuration sur l'accès direct.
19 réponses
le code est tiré de http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx et travaille en ASP.net MVC 1.0 ainsi que
Voici comment je gère les exceptions 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));
}
Exigences pour 404
voici mes exigences pour une solution 404 et ci-dessous je montre comment je l'implémente:
- je veux gérer des itinéraires correspondant à de mauvaises actions
- je veux gérer les routes appariées avec de mauvais contrôleurs
- je veux gérer les routes non-assorties (urls arbitraires que mon application ne peut pas comprendre) - Je ne veux pas que ces bulles jusqu'au Global.asax ou IIS car alors Je ne peux pas rediriger correctement dans mon application MVC
- je veux un moyen de gérer de la même manière que ci-dessus, custom 404s-like quand un ID est soumis pour un objet qui n'existe pas (peut-être supprimé)
- je veux que tous mes 404s pour retourner une vue MVC (pas une page statique) à laquelle je peux pomper plus de données plus tard si nécessaire ( good 404 designs ) et ils doit retourner le code de statut HTTP 404
Solution
je pense que vous devriez sauver Application_Error
dans le Global.asax pour les choses plus élevées,comme les exceptions et l'exploitation forestière (comme la réponse de Shay Jacoby montre) mais pas la manipulation 404. C'est pourquoi ma suggestion garde les 404 trucs en dehors du Global.fichier asax.
Étape 1: Avoir un lieu commun pour erreur 404 logique
C'est une bonne idée pour la maintenabilité. Utilisez un ErrorController de sorte que les améliorations futures à votre bien conçu page 404 peut s'adapter facilement. Aussi, assurez-vous que votre réponse a le code 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
}
Étape 2: Utilisez une classe de contrôleur de base de sorte que vous pouvez facilement invoquer votre action 404 personnalisé et brancher HandleUnknownAction
404s en ASP.NET les MVC doivent: être pris à un certain nombre d'endroits. La première est HandleUnknownAction
.
la méthode InvokeHttp404
crée un lieu commun pour le réacheminement vers l'action ErrorController
et notre nouvelle action Http404
. Pensez sec !
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
}
Étape 3: Utilisez L'Injection de dépendance dans votre usine de contrôleur et branchez 404 HttpExceptions
comme cela (il ne doit pas être StructureMap):
MVC1.0 exemple:
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 exemple:
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;
}
je pense qu'il est préférable d'attraper les erreurs plus près de leur origine. C'est pourquoi je préfère ce qui précède au handler Application_Error
.
C'est le deuxième endroit pour attraper des 404s.
Étape 4: Ajouter une route NotFound à Global.asax pour les URL qui ne sont pas utilisées dans votre application
cette route devrait pointer vers notre action Http404
. Notez que le param url
sera une url relative parce que le moteur de routage supprime la partie Domaine ici? C'est pourquoi nous avons toute cette logique d'url conditionnelle à L'Étape 1.
routes.MapRoute("NotFound", "{*url}",
new { controller = "Error", action = "Http404" });
C'est le troisième et dernier endroit pour attraper des 404s dans une application MVC que vous n'invoquez pas vous-même. Si vous n'attrapez pas les routes non appariées ici alors MVC passera le problème jusqu'à ASP.NET (Mondial.asax) et vous ne voulez pas vraiment que dans cette situation.
Étape 5: Enfin, invoquer 404s lorsque votre application ne peut pas trouver quelque chose
comme quand une mauvaise carte D'identité est soumise à mon contrôleur de prêts (dérive de 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);
}
ce serait bien si tout cela pouvait être raccordé dans moins d'endroits avec moins de code mais je pense que cette solution est plus supportable, plus testable et assez pragmatique.
Merci pour les commentaires jusqu'à présent. J'aimerais obtenir plus.
NOTE: Cette réponse a été modifiée de façon significative par rapport à ma réponse originale, mais le but/les exigences sont les mêmes - c'est pourquoi je n'ai pas ajouté de nouvelle réponse
ASP.NET MVC ne supporte pas très bien les pages 404 personnalisées. Usine de contrôleur personnalisé, route catch-all, classe de contrôleur de base avec HandleUnknownAction
- argh!
IIS pages d'erreur personnalisées sont la meilleure alternative pour l'instant:
web.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();
}
}
Exemple De Projet
Quick Answer/TL; DR
Pour les paresseux:
Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0
puis supprimer cette ligne de global.asax
GlobalFilters.Filters.Add(new HandleErrorAttribute());
et ce uniquement pour IIS7+ et IIS Express.
si vous utilisez Cassini .. bien. . la messagerie unifiée .. er.. maladroit. ..
Long, réponse expliquée
je sais que cela a été répondu. Mais la réponse est très SIMPLE (on applaudit David Fowler et Damian Edwards pour avoir vraiment répondu à cette question).
Il y a pas besoin de faire quelque chose de personnalisé .
Pour ASP.NET MVC3
, tous les morceaux sont là.
Step 1 -> mise à Jour de votre site web.config en DEUX endroits.
<system.web>
<customErrors mode="On" defaultRedirect="/ServerError">
<error statusCode="404" redirect="/NotFound" />
</customErrors>
et
<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>
maintenant, notez attentivement les ROUTES que j'ai décidé d'utiliser. Vous pouvez utiliser n'importe quoi, mais mes routes sont
-
/NotFound
<- pour un 404 non trouvé, page d'erreur. -
/ServerError
< - pour toute autre erreur, incluez les erreurs qui se produisent dans mon code. il s'agit d'une erreur de serveur interne de 500
voir comment la première section dans <system.web>
a seulement un entrée sur les douanes? La mention statusCode="404"
? Je n'ai énuméré qu'un seul code de statut parce que toutes les autres erreurs, y compris le 500 Server Error
(c.-à-d. cette erreur pénible qui se produit lorsque votre code a un bug et bloque la requête de l'utilisateur) .. toutes les autres erreurs sont gérées par le paramètre defaultRedirect="/ServerError"
.. qui dit, si vous n'êtes pas une page 404 pas trouvé, alors s'il vous plaît aller à la route /ServerError
.
Ok. c'est hors de la voie.. maintenant à mes routes énumérées dans global.asax
Étape 2 - Création d'itinéraires Mondiale.asax 1519260920"
Voici mon itinéraire complet..
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}
);
}
qui liste deux routes ignorées - > axd's
et favicons
(ooo! bonus ignorer route, pour vous!)
Ensuite (et L'ordre est impératif ici), j'ai mes deux routes explicites de traitement des erreurs .. suivie par tous les autres routes. Dans ce cas, l' la valeur par défaut. Bien sûr, j'en ai plus, mais c'est spécial pour mon site web. assurez-vous que les routes d'erreur sont en haut de la liste. L'ordre est impératif .
enfin, alors que nous sommes dans notre fichier global.asax
, nous N'enregistrons pas globalement L'attribut HandleError. Non, non, non, monsieur. Nadda. Nope. Nien. Négatif. Noooooooooo...
supprimer cette ligne de global.asax
GlobalFilters.Filters.Add(new HandleErrorAttribute());
Étape 3 - Créer le contrôleur avec les méthodes d'action
maintenant .. nous ajoutons un contrôleur avec deux méthodes d'action ...
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");
}
}
OK, voyons ça. Tout d'abord, il ya no [HandleError]
attribut ici. Pourquoi? Parce que le framework intégré dans ASP.NET
gère déjà les erreurs et nous avons spécifié toute la merde que nous devons faire pour gérer une erreur :) c'est dans cette méthode!
ensuite, j'ai les deux méthodes d'action. Rien de difficile. Si vous souhaitez afficher des informations d'exception, alors vous pouvez utiliser Server.GetLastError()
pour obtenir ces informations.
Bonus WTF: Oui, j'ai fait une troisième méthode d'action, pour tester le traitement des erreurs.
Étape 4-Créer les vues
et enfin, créer deux vues. Mettez em dans le champ de vision normal, pour ce contrôleur.
Bonus commentaires
- vous n'avez pas besoin d'un
Application_Error(object sender, EventArgs e)
- Les étapes ci-dessus tous les travaux 100% parfaitement avec Elmah . Elmah fraking wroxs!
et ça, mes amis, ça devrait être ça.
maintenant, félicitations pour avoir lu autant et avoir une licorne comme prix!
j'ai étudié beaucoup sur la façon de gérer correctement les 404s dans MVC (spécifiquement MVC3) , et ceci, IMHO est la meilleure solution que j'ai trouvé:
dans 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;
}
}
(facultatif)
explication:
AFAIK, il y a 6 cas différents où un ASP.NET les applications MVC3 peuvent générer des 404s.
(généré automatiquement par ASP.NET Framework:)
(1) une URL ne trouve pas de correspondance dans la table des routes.
(généré automatiquement par ASP.NET MVC Framework:)
(2) une URL trouve une correspondance dans la table de route, mais spécifie un contrôleur inexistant.
(3) une URL trouve une correspondance dans la table de route, mais spécifie une action non-existante.
(généré manuellement:)
(4) une action renvoie un résultat Httpnotfound en utilisant la méthode HttpNotFound ().
(5) une action lance une HttpException avec le code d'état 404.
(6) une action modifie manuellement la réponse.StatusCode propriété à 404.
normalement, vous voulez accomplir 3 objectifs:
(1) afficher une page d'erreur 404 personnalisée à l'utilisateur.
(2) maintenir le code d'état 404 sur la réponse du client (particulièrement important pour le SEO).
(3) envoyer la réponse directement, sans impliquer une redirection 302.
il y a plusieurs façons d'y parvenir:
(1)
<system.web>
<customErrors mode="On">
<error statusCode="404" redirect="~/Errors/NotFound"/>
</customError>
</system.web>
problèmes avec cette solution:
- N'est pas conforme à l'objectif (1) dans les cas (1), (4), (6).
- Ne répond pas automatiquement à l'objectif 2. Il doit être programmé manuellement.
- N'est pas conforme à l'objectif (3).
(2)
<system.webServer>
<httpErrors errorMode="Custom">
<remove statusCode="404"/>
<error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
</httpErrors>
</system.webServer>
problèmes avec cette solution:
- fonctionne uniquement sur IIS 7+.
- N'est pas conforme à l'objectif (1) dans les cas (2), (3), (5).
- Ne répond pas automatiquement à l'objectif 2. Il doit être programmé manuellement.
(3)
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404"/>
<error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
</httpErrors>
</system.webServer>
problèmes avec cette solution:
- fonctionne uniquement sur IIS 7+.
- ne répond pas automatiquement à l'objectif 2. Il doit être programmé manuellement.
- il obscurcit le niveau d'application http exception. Par exemple: Je ne peux pas utiliser la section customErrors, système.Web.Mvc.HandleErrorAttribute, etc. Il ne peut pas seulement afficher des pages d'erreur génériques.
(4)
<system.web>
<customErrors mode="On">
<error statusCode="404" redirect="~/Errors/NotFound"/>
</customError>
</system.web>
et
<system.webServer>
<httpErrors errorMode="Custom">
<remove statusCode="404"/>
<error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
</httpErrors>
</system.webServer>
problèmes avec cette solution:
- fonctionne uniquement sur IIS 7+.
- ne répond pas automatiquement à l'objectif 2. Il doit être programmé manuellement.
- N'est pas conforme à l'objectif (3) dans les cas (2), (3), (5).
les gens qui ont eu des problèmes avec cela avant même essayé de créer leurs propres bibliothèques (voir http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html ). Mais la solution précédente semble couvrir tous les cas sans la complexité de l'utilisation d'une bibliothèque externe.
j'aime vraiment cottsaks solution et pense que son très clairement expliqué. mon seul ajout a été de modifier l'étape 2 comme suit:
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
}
essentiellement ceci empêche les urls contenant des actions invalides et les controllers de déclencher la routine d'exception deux fois. eg pour les urls telles que asdfsdf / dfgdfgd
la seule façon d'obtenir la méthode de travail de @cottsak pour les contrôleurs invalides était de modifier la demande de route existante dans CustomControllerFactory, comme suit:
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;
}
}
}
je dois mentionner que j'utilise MVC 2.0.
Voici une autre méthode utilisant les outils MVC que vous pouvez gérer les requêtes vers les noms de mauvais controller, les noms de mauvaise route, et tout autre critère que vous jugez approprié à l'intérieur d'une méthode D'Action. Personnellement, je préfère éviter autant de web.les paramètres de configuration comme possible, parce qu'ils font la redirection 302 / 200 et ne prennent pas en charge la réponse Write ( Server.Transfer
) en utilisant des vues de rasoir. Je préfère retourner un 404 avec une page d'erreur personnalisée pour des raisons de référencement.
une partie de ceci est une nouvelle prise en charge la technique de cottsak ci-dessus.
cette solution utilise également le web minimal.paramètres de configuration favorisant les filtres D'erreur MVC 3 à la place.
Utilisation
il suffit de lancer une HttpException à partir d'une action ou d'une action personnalisée ActionFilterAttribute.
Throw New HttpException(HttpStatusCode.NotFound, "[Custom Exception Message Here]")
Étape 1
ajouter le paramètre suivant à votre site web.config. Ceci est nécessaire pour utiliser HandleErrorAttribute de MVC.
<customErrors mode="On" redirectMode="ResponseRedirect" />
l'Étape 2
Ajouter un HandleHttpErrorAttribute personnalisé similaire à HandleErrorAttribute du cadre MVC, sauf pour les erreurs 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
l'Étape 3
ajouter des filtres à la collection Globalfilter ( GlobalFilters.Filters
) dans Global.asax
. Cet exemple acheminera toutes les erreurs Internalserverror (500) vers la vue partagée des erreurs ( Views/Shared/Error.vbhtml
). Les erreurs NotFound (404) seront envoyées à ErrorHttp404.vbhtml dans le partagé leur point de vue. J'ai ajouté une erreur 401 ici pour vous montrer comment cela peut être étendu pour des codes D'erreur HTTP supplémentaires. Notez que ces vues doivent être partagées, et qu'elles utilisent toutes l'objet System.Web.Mvc.HandleErrorInfo
comme modèle.
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"})
Étape 4
créez une classe de contrôleur de base et héritez-en dans vos contrôleurs. Cette étape nous permet de gérer les noms d'actions inconnus et d'augmenter L'erreur HTTP 404 à notre HandleHttpErrorAttribute.
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
Étape 5
créez un contrôleur d'accès au répertoire, et le surchargez dans votre Global.fichier asax dans Application_Start. Cette étape nous permet d'augmenter L'exception HTTP 404 lorsqu'un nom de contrôleur invalide a été spécifié.
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)
Étape 6
incluez un itinéraire spécial dans votre RoutTable.Les Routes pour L'action inconnue de BaseController. Cela nous aidera à augmenter un 404 dans le cas où un utilisateur accède à une commande inconnue, ou inconnu.
'BaseController
routes.MapRoute( _
"Unknown", "BaseController/{action}/{id}", _
New With {.controller = "BaseController", .action = "Unknown", .id = UrlParameter.Optional} _
)
résumé
cet exemple a démontré comment on peut utiliser le framework MVC pour retourner les Codes D'erreur Http 404 au navigateur sans redirection en utilisant les attributs de filtre et les vues d'erreur partagées. Il affiche également la même page d'erreur personnalisée lorsque des noms de controller et des noms d'action invalides sont spécifiés.
je vais ajouter une capture d'écran d'un invalide nom du contrôleur, nom de l'action, et un 404 personnalisé soulevé de L'action Home/TriggerNotFound si j'obtiens assez de votes pour poster un =). Fiddler renvoie un message 404 lorsque j'accède aux URLs suivantes en utilisant cette solution:
/InvalidController
/Home/InvalidRoute
/InvalidController/InvalidRoute
/Home/TriggerNotFound
cottsak du post ci-dessus et ces articles sont de bonnes références.
ma solution raccourcie qui fonctionne avec les zones non encombrées, les contrôleurs et les actions:
-
créez une vue 404.cshtml.
-
créez une classe de base pour vos contrôleurs:
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"); } }
-
créer une usine de contrôleur personnalisé retournant votre contrôleur de base comme un repli:
public class ControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType != null) return base.GetControllerInstance(requestContext, controllerType); return new Controller(); } }
-
ajouter à
Application_Start()
la ligne suivante:ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
dans MVC4 WebAPI 404 peut être manipulé de la manière suivante,
COURS 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;
}
HOME CONTROLLER
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>
GLOBAL
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 }
);
}
résultats
ma solution, au cas où quelqu'un la trouve utile.
Dans Web.config:
<system.web>
<customErrors mode="On" defaultRedirect="Error" >
<error statusCode="404" redirect="~/Error/PageNotFound"/>
</customErrors>
...
</system.web>
Dans 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();
}
}
ajoute un PageNotFound.cshtml
dans le dossier Shared
, et c'est tout.
il me semble que la norme CustomErrors
configuration devrait seulement fonctionner cependant, en raison de la dépendance sur Server.Transfer
il semble que la mise en œuvre interne de ResponseRewrite
n'est pas compatible avec MVC.
cela ressemble à un trou de fonctionnalité flagrant pour moi, donc j'ai décidé de ré-implémenter cette fonctionnalité en utilisant un module HTTP. La solution ci-dessous vous permet de gérer n'importe quel code D'état HTTP (y compris 404) en redirigeant vers n'importe quel code valide MVC route comme vous le feriez normalement.
<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite">
<error statusCode="404" redirect="404.aspx" />
<error statusCode="500" redirect="~/MVCErrorPage" />
</customErrors>
ceci a été testé sur les plates-formes suivantes;
- MVC4 en mode Pipeline intégré (IIS Express 8)
- MVC4 en Mode Classique (VS Development Server, Cassini)
- MVC4 en Mode Classique (IIS6)
prestations
- Générique solution qui peut être intégrée à n'importe quel projet MVC
- permet de prendre en charge la configuration traditionnelle des erreurs
- fonctionne à la fois en mode Pipeline intégré et en mode Classique
La Solution
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() {
}
}
}
Utilisation
incluez ceci comme dernier module HTTP dans votre web.config
<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>
pour ceux d'entre vous qui font attention, vous remarquerez qu'en mode Pipeline intégré cela répondra toujours avec HTTP 200 en raison de la façon dont Server.TransferRequest
fonctionne. Pour retourner le code d'erreur approprié, j'utilise le contrôleur d'erreur suivant.
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());
}
}
concernant les erreurs dans ASP.NET MVC est juste un emmerdeur. J'ai essayé beaucoup de suggestions sur cette page et sur d'autres questions et sites et rien ne fonctionne bien. Une suggestion a été de traiter les erreurs sur web.config à l'intérieur du système.webserver mais que juste renvoie des pages vierges .
mon but lorsque j'ai trouvé cette solution était de;
- NOT REDIRECT
- Return PROPER STATUS CODES not 200 / Ok like the default error handling
voici ma solution.
1 .Ajouter ce qui suit au système .web de la section
<system.web>
<customErrors mode="On" redirectMode="ResponseRewrite">
<error statusCode="404" redirect="~/Error/404.aspx" />
<error statusCode="500" redirect="~/Error/500.aspx" />
</customErrors>
<system.web>
ce qui précède gère toutes les URL qui ne sont pas gérées par les routes .config et unhandled exceptions, en particulier celles rencontrées sur les vues. Avis I utilisé aspx pas html . C'est pour que je puisse ajouter un code de réponse sur le code derrière.
2 . Créez un dossier appelé erreur (ou ce que vous préférez) à la racine de votre projet et ajoutez les deux formulaires Web. Ci-dessous est ma page 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>
et sur le code derrière J'ai mis le code de réponse
protected void Page_Load(object sender, EventArgs e)
{
Response.StatusCode = 404;
}
Faire de même pour les 500 page
3 .Pour gérer les erreurs au sein des contrôleurs. Il y a beaucoup de façons de le faire. C'est ce qui a fonctionné pour moi. Tous mes contrôleurs héritent d'un contrôleur de base. Dans le contrôleur de base, j'ai les méthodes suivantes
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 .Ajouter le CustomError.cshtml à votre partagé dossier vues. Ci-dessous est la mienne;
<h1>@ViewBag.Title</h1>
<br />
<p>@ViewBag.Message</p>
maintenant dans votre contrôleur d'application vous pouvez faire quelque chose comme ça;
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();
}
}
}
Maintenant, pour la mise en garde . Il ne gère pas les erreurs de fichiers statiques. Donc si vous avez une route telle que example.com/widgets et l'utilisateur le modifie en example.com/widgets.html , ils obtiendront la page d'erreur par défaut IIS donc vous devez gérer le niveau IIS les erreurs d'une autre façon.
affichant une réponse depuis mon commentaire était trop long...
C'est à la fois un commentaire et des questions à la licorne post / réponse:
https://stackoverflow.com/a/7499406/687549
je préfère cette réponse plutôt que les autres pour sa simplicité et le fait qu'apparemment certains gens de Microsoft ont été consultés. J'ai trois questions cependant et si elles peuvent être répondues alors je vais appeler cette réponse le saint graal de tous les 404/500 erreur réponses sur les interwebs pour un ASP.NET MVC (x) app.
@Pure.Krome
-
pouvez - vous mettre à jour votre réponse avec les éléments de SEO à partir des commentaires signalés par GWB (il n'y a jamais eu aucune mention de cela dans votre réponse) -
<customErrors mode="On" redirectMode="ResponseRewrite">
et<httpErrors errorMode="Custom" existingResponse="Replace">
? -
pouvez-vous demander à votre ASP.NET l'équipe des amis si c'est d'accord de le faire comme ça - serait agréable de avoir une certaine confirmation - peut-être que c'est un grand non-non pour changer
redirectMode
etexistingResponse
de cette façon pour être en mesure de jouer bien avec SEO?! -
pouvez-vous ajouter un peu de clarification autour de tous ces trucs (
customErrors redirectMode="ResponseRewrite"
,customErrors redirectMode="ResponseRedirect"
,httpErrors errorMode="Custom" existingResponse="Replace"
, supprimercustomErrors
complètement comme quelqu'un l'a suggéré) après avoir parlé à vos amis chez Microsoft?
comme je le disais; ce serait la supernice si nous pourrait rendre votre réponse plus complète car cela semble être une question assez populaire avec 54 000+ points de vue.
mise à Jour : Licorne réponse est un 302 Found et un 200 OK et ne peut pas être modifié pour ne revenir 404 à l'aide d'un itinéraire. Il doit s'agir d'un fichier physique qui n'est pas très MVC:ish. Alors passons à une autre solution. Dommage car cela semblait être l'ultime MVC:ish réponse à ce jour.
ajoutant ma solution, qui est presque identique à Herman Kan, avec une petite ride pour lui permettre de travailler pour mon projet.
créer un contrôleur d'erreur personnalisé:
public class Error404Controller : BaseController
{
[HttpGet]
public ActionResult PageNotFound()
{
Response.StatusCode = 404;
return View("404");
}
}
puis créer une usine de contrôleur personnalisé:
public class CustomControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
return controllerType == null ? new Error404Controller() : base.GetControllerInstance(requestContext, controllerType);
}
}
enfin, ajouter un contrôleur d'erreur personnalisé:
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));
}
Et c'est tout. Pas besoin pour le Web.changements de configuration.
1) Créer la classe contrôleur abstrait.
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 )Faites l'héritage de cette classe abstraite dans vos tous les contrôleurs
public class HomeController : MyController
{}
3) et ajouter une vue nommée "NotFound" dans votre dossier View-Shared.
j'ai parcouru la plupart des solutions postées sur ce fil. Bien que cette question soit peut-être ancienne, elle est encore très applicable aux nouveaux projets, même maintenant.j'ai donc passé beaucoup de temps à lire les réponses présentées ici et ailleurs.
comme @Marco a souligné les différents cas dans lesquels un 404 peut se produire, j'ai vérifié la solution que j'ai compilé ensemble par rapport à cette liste. En plus de sa liste d'exigences, j'en ai ajouté une autre.
- la solution devrait être capable de traiter les appels MVC ainsi que les appels AJAX/WebAPI de la manière la plus appropriée. (i.e. si 404 se produit dans MVC, il devrait montrer la page non trouvée et si 404 se produit dans WebAPI, il ne devrait pas détourner la réponse XML/JSON de sorte que la consommation Javascript peut le parser facilement).
cette solution est 2 fois:
Première partie provient de la @Guillaume à https://stackoverflow.com/a/27354140/2310818 . Leur solution prend en charge tout 404 causé par une route non valide, un contrôleur non valide et une action non valide.
l'idée est de créer un formulaire Web et de le faire appeler l'action NotFound de votre contrôleur D'erreurs MVC. Il fait tout cela sans aucune redirection de sorte que vous ne verrez pas un seul 302 Dans Fiddler. L'URL d'origine est également préservée, ce qui rend cette solution fantastique!
la deuxième partie vient de @Germán à https://stackoverflow.com/a/5536676/2310818 . Leur solution prend en charge tout 404 retourné par vos actions sous la forme de HttpNotFoundResult() ou de throw new HttpException()!
l'idée est de faire examiner par un filtre la réponse ainsi que l'exception lancée par vos contrôleurs MVC et d'appeler l'action appropriée dans votre contrôleur des erreurs. Encore cette solution fonctionne sans aucune redirection et l'url d'origine est préservé!
comme vous pouvez le voir, ces deux solutions combinées offrent un mécanisme de traitement des erreurs très robuste et ils répondent à toutes les exigences énumérées par @Marco ainsi que mes exigences. Si vous souhaitez voir un exemple de travail ou une démonstration de cette solution, merci de laisser dans les commentaires et je serais heureux de les mettre ensemble.
j'ai parcouru tous les articles mais rien ne fonctionne pour moi: Mon type d'utilisateur d'exigence n'importe quoi dans votre page d'url personnalisé 404 devrait montrer.J'ai pensé qu'il est très simple.Mais vous devez comprendre la manipulation de 404 correctement:
<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>
j'ai trouvé cet article très utile.doit être lu à la fois. Custome page d'erreur-Ben Foster