Question Comment puis-je gérer correctement 404 dans ASP.NET MVC?


J'utilise RC2

Utiliser le routage d'URL: 

routes.MapRoute(
    "Error",
     "{*url}",
     new { controller = "Errors", action = "NotFound" }  // 404s
);

Ce qui précède semble prendre en charge les requêtes comme celle-ci (en supposant que les tables de routage par défaut ont été configurées par le projet MVC initial): "/ blah / blah / blah / blah"

Remplacement de HandleUnknownAction () 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 gèrent pas une requête vers un contrôleur Bad / Unknown. Par exemple, je n'ai pas de "/ IDoNotExist", si je demande ceci, je reçois la page 404 générique du serveur Web et non la page 404 si j'utilise le routage + la substitution.

Donc finalement, ma question est: Y at-il un moyen d'attraper ce type de demande en utilisant une route ou quelque chose d'autre dans le cadre MVC lui-même? 

OU dois-je simplement utiliser par défaut Web.Config customErrors comme mon gestionnaire 404 et oublier tout cela? Je suppose que si je vais avec customErrors je vais devoir stocker la page générique 404 en dehors de / Views en raison des restrictions Web.Config sur l'accès direct.


411
2018-03-06 18:21


origine


Réponses:


Le code provient de http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx et fonctionne dans ASP.net MVC 1.0 ainsi

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

248
2018-03-06 21:49



Exigences pour 404

Ce qui suit sont mes exigences pour une solution 404 et ci-dessous je montre comment je l'implémente:

  • Je veux gérer les routes correspondantes avec de mauvaises actions
  • Je veux gérer les routes correspondantes avec de mauvais contrôleurs
  • Je veux gérer des routes non appariées (urls arbitraires que mon application ne peut pas comprendre) - je ne veux pas que ces dernières remontent au Global.asax ou aux 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, personnalisé 404s - comme quand un ID est soumis pour un objet qui n'existe pas (peut-être supprimé)
  • Je veux que tous mes 404 retournent une vue MVC (pas une page statique) à laquelle je peux pomper plus de données plus tard si nécessaire (bons 404 conceptions) et ils doit renvoie le code d'état HTTP 404

Solution

Je pense que tu devrais sauver Application_Error dans le Global.asax pour les choses plus élevées, comme les exceptions non gérées et la journalisation (comme La réponse de Shay Jacoby montre) mais pas la manipulation 404. C'est pourquoi ma suggestion garde les choses 404 hors du fichier Global.asax.

Étape 1: Avoir une place commune pour la logique d'erreur 404

C'est une bonne idée pour la maintenabilité. Utilisez un ErrorController afin que les améliorations futures de votre bien conçu 404 page peut facilement s'adapter. 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 pour pouvoir facilement appeler votre action personnalisée 404 et vous connecter HandleUnknownAction

404s dans ASP.NET MVC doivent être capturés à un certain nombre d'endroits. Le premier est HandleUnknownAction.

le InvokeHttp404 méthode crée un lieu commun pour le réacheminement vers le ErrorController et notre nouveau Http404 action. Pense 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 Dependency Injection dans votre Controller Factory et branchez 404 HttpExceptions

Comme si (il ne doit pas être StructureMap):

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

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

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 à la Application_Error gestionnaire.

C'est la deuxième place pour attraper les 404.

Étape 4: Ajoutez une route NotFound à Global.asax pour les URL qui ne sont pas analysées dans votre application

Cette route devrait indiquer notre Http404 action. Notez le url param sera une URL relative car 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" });

Ceci est le troisième et dernier endroit pour attraper 404 dans une application MVC que vous ne vous invoquez pas. Si vous n'attrapez pas les routes inégalées ici, MVC transmettra le problème à ASP.NET (Global.asax) et vous ne le voulez pas vraiment dans cette situation.

Étape 5: Enfin, invoquer 404s lorsque votre application ne trouve pas quelque chose

Comme quand une mauvaise identification 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 connecté dans moins d'endroits avec moins de code mais je pense que cette solution est plus facile à maintenir, plus testable et assez pragmatique.

Merci pour les commentaires jusqu'à présent. J'adorerais avoir plus.

NOTE: Ceci a été modifié de manière significative à partir de ma réponse originale mais le but / les exigences sont les mêmes - c'est pourquoi je n'ai pas ajouté une nouvelle réponse


251
2018-03-31 22:27



ASP.NET MVC ne supporte pas très bien les pages 404 personnalisées. Usine de contrôleur personnalisée, itinéraire attrape-tout, classe de contrôleur de base avec HandleUnknownAction - Argh!

Les pages d'erreur personnalisées IIS constituent une meilleure alternative à ce jour:

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


223
2017-09-21 12:02



Réponse rapide / TL; DR

enter image description here

Pour les fainéants là-bas:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

Ensuite, supprimez cette ligne de global.asax

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

Et ce n'est que pour IIS7 + et IIS Express. 

Si vous utilisez Cassini .. bien .. euh .. er .. maladroit ... awkward


Longue réponse expliquée

Je sais que cela a été répondu. Mais la réponse est vraiment simple (bravo à David fowler et Damian Edwards pour vraiment répondre à cela).

Il y a pas besoin de faire quelque chose de personnalisé.

Pour ASP.NET MVC3, tous les morceaux sont là.

Étape 1 -> Mettez à jour votre site web.config à 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, prenez note des 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, inclure les erreurs qui se produisent dans mon code. il s'agit d'une erreur de serveur interne 500

Voyez comment la première section de <system.web> a seulement un entrée personnalisée? le statusCode="404" entrée? J'ai seulement énuméré un code de statut parce que toutes les autres erreurs, y compris le 500 Server Error (c'est-à-dire l'erreur fatale qui se produit lorsque votre code a un bug et bloque la demande de l'utilisateur) .. toutes les autres erreurs sont traitées par le paramètre defaultRedirect="/ServerError" .. qui dit, si vous n'êtes pas une page 404 introuvable, alors s'il vous plaît aller à l'itinéraire /ServerError.

D'accord. c'est à l'écart .. maintenant à mes routes énumérées dans global.asax

Étape 2 - Création des routes dans Global.asax

Voici ma section de route complète ..

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

Cela liste deux routes ignorées -> axd's et favicons (ooo! bonus ignore route, pour vous!) Ensuite (et l'ordre est IMPERATIF ICI), j'ai mes deux routes de gestion d'erreur explicites .. suivies de toutes les autres routes. Dans ce cas, celui par défaut. Bien sûr, j'en ai plus, mais c'est spécial pour mon site web. Assurez-vous simplement que les itinéraires d'erreur sont en haut de la liste. L'ordre est impératif.

Enfin, alors que nous sommes à l'intérieur de notre global.asax fichier, nous n’enregistrons PAS globalement l’attribut HandleError. Non, non, non monsieur. Nadda. Nan. 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, vérifions cela. Tout d'abord, il y a NON  [HandleError] attribuer ici. Pourquoi? Parce que le construit ASP.NET framework 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 dur là-bas. Si vous souhaitez afficher des informations sur les exceptions, vous pouvez utiliser Server.GetLastError() pour obtenir cette information.

Bonus WTF: Oui, j'ai effectué une troisième méthode d'action pour tester la gestion des erreurs.

Étape 4 - Créer les vues

Et enfin, créez deux vues. Mettez em dans le point de vue normal, pour ce contrôleur.

enter image description here

Commentaires bonus

  • Vous n'avez pas besoin d'un Application_Error(object sender, EventArgs e)
  • Les étapes ci-dessus fonctionnent parfaitement à 100% avec Elmah. Elmah effraction wroxs!

Et ça, mes amis, ça devrait être ça.

Maintenant, félicitations pour avoir lu autant et avoir une licorne comme prix!

enter image description here


152
2018-01-26 23:20



J'ai enquêté BEAUCOUP sur la façon de gérer correctement les 404 dans MVC (spécifiquement MVC3), et ceci, IMHO est la meilleure solution que je propose:

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

(Optionnel)

Explication:

AFAIK, il existe 6 cas différents qu'une application ASP.NET MVC3 peut générer 404s.

(Généré automatiquement par ASP.NET Framework :)

(1) Une URL ne trouve pas de correspondance dans la table de routage.

(Généré automatiquement par ASP.NET MVC Framework :)

(2) Une URL trouve une correspondance dans la table de routage, mais spécifie un contrôleur inexistant.

(3) Une URL trouve une correspondance dans la table de routage, mais spécifie une action inexistante.

(Généré manuellement :)

(4) Une action renvoie un HttpNotFoundResult en utilisant la méthode HttpNotFound ().

(5) Une action déclenche une exception HttpException avec le code d'état 404.

(6) Une action modifie manuellement la propriété Response.StatusCode à 404.

Normalement, vous voulez accomplir 3 objectifs:

(1) Afficher une page d'erreur 404 personnalisée pour l'utilisateur.

(2) Maintenir le code d'état 404 sur la réponse du client (particulièrement important pour le référencement).

(3) Envoyez la réponse directement, sans impliquer une redirection 302.

Il y a plusieurs façons d'essayer d'accomplir ceci:

(1)

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

Problèmes avec cette solution:

  1. Ne respecte pas l'objectif (1) dans les cas (1), (4), (6).
  2. Ne se conforme pas automatiquement à l'objectif (2). Il doit être programmé manuellement.
  3. Ne respecte pas 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:

  1. Ne fonctionne que sur IIS 7+.
  2. Ne respecte pas l'objectif (1) dans les cas (2), (3), (5).
  3. Ne se conforme 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:

  1. Ne fonctionne que sur IIS 7+.
  2. Ne se conforme pas automatiquement à l'objectif (2). Il doit être programmé manuellement.
  3. Il masque les exceptions http au niveau de l'application. Par exemple. ne peut pas utiliser la section customErrors, System.Web.Mvc.HandleErrorAttribute, etc. Il ne peut pas seulement afficher les 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:

  1. Ne fonctionne que sur IIS 7+.
  2. Ne se conforme pas automatiquement à l'objectif (2). Il doit être programmé manuellement.
  3. Ne respecte pas 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é d'utiliser une bibliothèque externe.


81
2018-05-29 20:50



J'aime vraiment la solution de cottsaks et pense que c'est très clairement expliqué. mon seul ajout était 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
}

Fondamentalement, cela arrête les URL contenant des actions invalides et les contrôleurs de déclencher la routine d'exception deux fois. par exemple pour les URLs telles que asdfsdf / dfgdfgd


13
2017-07-29 23:43



La seule façon que je pouvais obtenir @ méthode de cottsak travailler pour les contrôleurs invalides a été de modifier la demande d'itinéraire existant dans le 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 devrais mentionner que j'utilise MVC 2.0.


6
2017-11-01 14:11



Voici une autre méthode utilisant les outils MVC que vous pouvez gérer avec des requêtes adressées à des noms de contrôleurs incorrects, des noms d'itinéraires incorrects et tout autre critère qui vous semble approprié dans une méthode Action. Personnellement, je préfère éviter autant de paramètres web.config que possible, car ils effectuent la redirection 302/200 et ne prennent pas en charge ResponseRewrite (Server.Transfer) en utilisant les 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 nouvelle prise sur la technique de cottsak ci-dessus.

Cette solution utilise également des paramètres web.config minimaux favorisant les filtres d'erreur MVC 3.

Usage

Il suffit de lancer une exception HttpException à partir d'une action ou d'un ActionFilterAttribute personnalisé.

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

Étape 1

Ajoutez le paramètre suivant à votre web.config. Ceci est requis pour utiliser HandleErrorAttribute de MVC.

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

Étape 2

Ajoutez un HandleHttpErrorAttribute personnalisé similaire au HandleErrorAttribute du framework MVC, à l'exception des 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

Étape 3

Ajouter des filtres à GlobalFilterCollection (GlobalFilters.Filters) dans Global.asax. Cet exemple acheminera toutes les erreurs InternalServerError (500) vers la vue partagée Error (Views/Shared/Error.vbhtml). Les erreurs NotFound (404) seront également envoyées à ErrorHttp404.vbhtml dans les vues partagées. 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 System.Web.Mvc.HandleErrorInfo objet 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 en héritez dans vos contrôleurs. Cette étape nous permet de gérer les noms d'action inconnus et de générer l'erreur HTTP 404 dans 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 remplacement ControllerFactory et remplacez-le dans votre fichier Global.asax dans Application_Start. Cette étape nous permet de générer l'exception HTTP 404 lorsqu'un nom de contrôleur non valide 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 une route spéciale dans votre RoutTable.Routes pour l'action Inconnu BaseController. Cela nous aidera à élever un 404 dans le cas où un utilisateur accède à un contrôleur inconnu, ou une action inconnue.

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

Résumé

Cet exemple montre comment utiliser le framework MVC pour renvoyer les codes d'erreur HTTP au navigateur sans redirection à l'aide des attributs de filtre et des vues d'erreur partagées. Il montre également l'affichage de la même page d'erreur personnalisée lorsque des noms de contrôleur et des noms d'action non valides sont spécifiés.

J'ajouterai une capture d'écran d'un nom de contrôleur non valide, d'un nom d'action et d'un 404 personnalisé généré par l'action Home / TriggerNotFound si j'obtiens suffisamment de votes pour en publier un =). Fiddler renvoie un message 404 lorsque j'accède aux URL suivantes à l'aide de cette solution:

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

Le post de cottsak ci-dessus et ces articles étaient de bonnes références.


4
2018-05-23 13:09