Routage vers les actions du contrôleur dans ASP.NET Core

Par Ryan Nowak, Kirk Larkinet Rick Anderson

Notes

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.

Important

Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.

Pour la version actuelle, consultez la version .NET 8 de cet article.

Les contrôleurs ASP.NET Core utilise l’intergiciel de routage pour mettre en correspondance les URL des requêtes entrantes et les mapper à des actions. Modèles de route :

  • Sont définis au démarrage dans Program.cs ou dans les attributs.
  • Décrire comment les chemins d’accès d’URL sont mis en correspondance avec les actions.
  • Sont utilisés pour générer des URL pour les liens. Les liens générés sont généralement retournés dans les réponses.

Les actions sont routées de façon conventionnelle ou routées par attribut. Le fait de placer une route sur le contrôleur ou sur l’action les rend « routés par attribut ». Pour plus d’informations, consultez Routage mixte.

Ce document :

  • Explique les interactions entre MVC et le routage :
    • Comment les applications MVC classiques utilisent les fonctionnalités de routage?
    • Couvre les deux :
    • Pour plus d’informations sur le routage avancé, consultez Routage.
  • Fait référence au système de routage par défaut appelé routage de point de terminaison. Il est possible d’utiliser des contrôleurs avec la version précédente du routage à des fins de compatibilité. Pour obtenir des instructions, consultez le guide de migration 2.2-3.0.

Configurer la route conventionnelle

Le modèle MVC ASP.NET Core génère un code de routage conventionnel semblable à ce qui suit :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

MapControllerRoute est utilisé pour créer une route unique. La route unique est nommé default route. La plupart des applications avec des contrôleurs et des vues utilisent un modèle de route default similaire à la route. REST Les API doivent utiliser le routage d’attributs.

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Le modèle de route "{controller=Home}/{action=Index}/{id?}" :

  • Correspond à un chemin d’accès d’URL comme /Products/Details/5

  • Extrait les valeurs { controller = Products, action = Details, id = 5 } d’itinéraire en jetons le chemin d’accès. L’extraction des valeurs de route entraîne une correspondance si l’application a un contrôleur nommé ProductsController et une Details action :

    public class ProductsController : Controller
    {
        public IActionResult Details(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }
    }
    

    MyDisplayRouteInfo est fourni par le package NuGet Rick.Docs.Samples.RouteInfo et affiche les informations de routage.

  • /Products/Details/5 model lie la valeur de id = 5 pour définir le id paramètre sur 5. Pour plus d’informations, consultez Liaison de modèle.

  • {controller=Home} définit Home comme controller par défaut.

  • {action=Index} définit Index comme action par défaut.

  • Le caractère ? dans {id?} définit id comme facultatif.

    • Les paramètres de route par défaut et facultatifs n’ont pas besoin d’être présents dans le chemin d’URL pour qu’une correspondance soit établie. Pour une description détaillée de la syntaxe du modèle de route, consultez Informations de référence sur le modèle de route.
  • Correspond au chemin d’accès d’URL /.

  • Produit les valeurs { controller = Home, action = Index }de route.

Valeurs pour controller et action utiliser les valeurs par défaut. id ne produit pas de valeur, car il n’existe pas de segment correspondant dans le chemin d’accès d’URL. / Correspond uniquement s’il existe une HomeController action et Index :

public class HomeController : Controller
{
    public IActionResult Index() { ... }
}

En utilisant la définition du contrôleur et le modèle de route précédents, l’action HomeController.Index est exécutée pour les chemins d’accès d’URL suivants :

  • /Home/Index/17
  • /Home/Index
  • /Home
  • /

Le chemin d’accès d’URL / utilise l’action et Home les contrôleurs par défaut Index du modèle de route. Le chemin d’accès d’URL /Home utilise l’action par défaut Index du modèle de route.

La méthode pratique MapDefaultControllerRoute :

app.MapDefaultControllerRoute();

Remplace :

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Important

Le routage est configuré à l’aide de l’intergiciel UseRouting et .UseEndpoints Pour utiliser des contrôleurs :

Les applications n’ont généralement pas besoin d’appeler UseRouting ou UseEndpoints. WebApplicationBuilder configure un pipeline d’intergiciels qui encapsule l’intergiciel ajouté dans Program.cs avec UseRouting et UseEndpoints. Pour plus d’informations, consultez Routage dans ASP.NET Core.

Routage conventionnel

Routage conventionnel est utilisé avec les contrôleurs et les vues. La route default :

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Le précédent est un exemple de route conventionnelle. Nous appelons ce style routage conventionnel, car il établit une convention pour les chemins d’URL :

  • Le premier segment de tracé, {controller=Home}, correspond au nom du contrôleur.
  • Le deuxième segment, {action=Index}, correspond au nom de l’action.
  • Le troisième segment, {id?} est utilisé pour un facultatif id. Le ? dans {id?} le rend facultatif. id est utilisé pour mapper à une entité de modèle.

À l’aide de cette route default, le chemin d’accès d’URL :

  • /Products/List mappe à l’action ProductsController.List .
  • /Blog/Article/17 mappe à BlogController.Article et le modèle lie généralement le id paramètre à 17.

Ce mappage :

  • Est basé uniquement sur les noms du contrôleur et des actions.
  • N’est pas basé sur des espaces de noms, des emplacements de fichiers sources ou des paramètres de méthode.

L’utilisation du routage conventionnel avec la route par défaut permet de créer l’application sans avoir à inventer un nouveau modèle d’URL pour chaque action. Pour une application avec des actions de style CRUD , ayant une cohérence pour les URL entre les contrôleurs :

  • Permet de simplifier le code.
  • Rend l’interface utilisateur plus prévisible.

Avertissement

Le id dans le code précédent est défini comme facultatif par le modèle de route. Les actions peuvent s’exécuter sans l’ID facultatif fourni dans le cadre de l’URL. En règle générale, quand id est omis de l’URL :

  • id est défini sur 0 par liaison de données.
  • Aucune entité n’est trouvée dans la base de données correspondant à id == 0.

Le routage par attribut vous donne un contrôle précis pour rendre le code obligatoire pour certaines actions et pas pour d’autres. Par convention, la documentation inclut des paramètres facultatifs comme id quand ils sont susceptibles d’apparaître dans une utilisation correcte.

La plupart des applications doivent choisir un schéma de routage de base et descriptif pour que les URL soient lisibles et explicites. La route conventionnelle par défaut {controller=Home}/{action=Index}/{id?} :

  • Prend en charge un schéma de routage de base et descriptif.
  • Est un point de départ pratique pour les applications basées sur une interface utilisateur.
  • Est le seul modèle d’itinéraire nécessaire pour de nombreuses applications d’interface utilisateur web. Pour les applications d’interface utilisateur web plus volumineuses, une autre route utilisant Zones est souvent tout ce qui est nécessaire.

MapControllerRoute et MapAreaRoute :

  • Attribuent automatiquement une valeur de commande à leurs points de terminaison en fonction de l’ordre qu’ils appellent.

Routage des points de terminaison dans ASP.NET Core :

  • N’a pas de concept de routes.
  • Ne fournit pas de garanties de classement pour l’exécution de l’extensibilité, tous les points de terminaison sont traités en même temps.

Activez la journalisation pour voir comment les implémentations de routage intégrées, comme Route, établissent des correspondances avec les requêtes.

Le routage des attributs est expliqué plus loin dans ce document.

Plusieurs routes conventionnelles

Plusieurs routes conventionnelles peuvent être configurées en ajoutant d’autres appels à MapControllerRoute et MapAreaControllerRoute. Ceci permet de définir plusieurs conventions ou d’ajouter des routes conventionnelles qui sont dédiées à une action spécifique, comme :

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

La routeblog dans le code précédent est une route conventionnelle dédiée. Il s’agit d’une route conventionnelle dédiée pour les raisons suivantes :

Étant donné que controller et action n’apparaissent pas dans le modèle de route "blog/{*article}" en tant que paramètres :

  • Ils peuvent uniquement avoir les valeurs { controller = "Blog", action = "Article" }par défaut .
  • Cette route est toujours mappée à l’action BlogController.Article.

/Blog, /Blog/Articleet /Blog/{any-string} sont les seuls chemins d’URL qui correspondent à la route du blog.

L’exemple précédent :

  • blog la route a une priorité plus élevée pour les correspondances que la routedefault , car elle est ajoutée en premier.
  • Il s’agit d’un exemple de routage de style Slug où il est courant d’avoir un nom d’article dans l’URL.

Avertissement

Dans ASP.NET Core, le routage ne :

  • Définit pas un concept appelé route. UseRouting ajoute la correspondance de routage au pipeline d’intergiciels. L’UseRoutingintergiciel examine l’ensemble des points de terminaison définis dans l’application et sélectionne la meilleure correspondance des points de terminaison en fonction de la requête.
  • Fournissez des garanties sur l’ordre d’exécution de l’extensibilité, comme IRouteConstraint ou IActionConstraint.

Consultez Routage pour obtenir des informations de référence sur le routage.

Ordre de routage conventionnel

Le routage conventionnel correspond uniquement à une combinaison d’action et de contrôleur qui sont définis par l’application. Ceci est conçu pour simplifier les cas où des routes conventionnelles se chevauchent. Ajouter des routes en utilisant MapControllerRoute, MapDefaultControllerRoute et MapAreaControllerRoute attribuent automatiquement une valeur de l’ordre à leurs points de terminaison en fonction de l’ordre qu’ils appellent. Les correspondances d’une route qui apparaît précédemment ont une priorité plus élevée. Le routage conventionnel est dépendant de l’ordre. En général, les routes avec des zones doivent être placées plus haut, car elles sont plus spécifiques que les routes sans zone. Les routes classiques dédiées avec des paramètres de route fourre-tout comme {*article} peuvent rendre un itinéraire trop gourmand, ce qui signifie qu’il correspond aux URL que vous avez l’intention de mettre en correspondance avec d’autres routes. Placez les routes globales plus loin dans la table de routage pour éviter les correspondances globales.

Avertissement

Un paramètre catch-all peut faire correspondre les mauvais routages en raison d’un bogue dans le routage. Les applications affectées par ce bogue présentent les caractéristiques suivantes :

  • Un routage catch-all, par exemple, {**slug}"
  • Le routage catch-all ne fait pas correspondre les demandes qu’il doit faire correspondre.
  • La suppression d’autres routes fait que la route catch-all commence à fonctionner.

Consultez les bogues GitHub 18677 et 16579, par exemple les cas qui ont rencontré ce bogue.

Un correctif d’opt-in pour ce bogue est contenu dans le Kit de développement logiciel (SDK) .NET Core 3.1.301 et versions ultérieures. Le code suivant définit un commutateur interne qui corrige ce bogue :

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

Résoudre les actions ambiguës

Lorsque deux points de terminaison correspondent via le routage, le routage doit effectuer l’une des opérations suivantes :

  • Choisissez le meilleur candidat.
  • Levée d'une exception.

Par exemple :

public class Products33Controller : Controller
{
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpPost]
    public IActionResult Edit(int id, Product product)
    {
        return ControllerContext.MyDisplayRouteInfo(id, product.name);
    }
}

Le contrôleur précédent définit deux actions qui correspondent :

  • Le chemin d’accès d’URL /Products33/Edit/17
  • Données de route { controller = Products33, action = Edit, id = 17 }.

Il s’agit d’un modèle classique pour les contrôleurs MVC :

  • Edit(int) affiche un formulaire pour modifier un produit.
  • Edit(int, Product) traite le formulaire publié.

Pour résoudre la route correcte :

  • Edit(int, Product) est sélectionné lorsque la requête est un http POST.
  • Edit(int) est sélectionné lorsque le verbe HTTP est autre chose. Edit(int) est généralement appelé par le biais de GET.

Le HttpPostAttribute, [HttpPost], est fourni pour le routage afin qu’il puisse choisir en fonction de la méthode HTTP de la requête. Le HttpPostAttribute fait Edit(int, Product) une meilleure correspondance que Edit(int).

Il est important de comprendre le rôle des attributs tels que HttpPostAttribute. Des attributs similaires sont définis pour d’autres verbes HTTP. Dans le routage conventionnel, il est courant que les actions utilisent le même nom d’action lorsqu’elles font partie d’un flux de travail de type montrer le formulaire, envoyer le formulaire. Par exemple, consultez Examiner les deux méthodes d’action Modifier.

Si le routage ne peut pas choisir un meilleur candidat, un AmbiguousMatchException est levée, répertoriant les plusieurs points de terminaison correspondants.

Noms de routes conventionnelles

Les chaînes "blog" et "default" dans les exemples suivants sont des noms de routes conventionnelles :

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

Les noms de routes donnent à la route un nom logique. La route nommée peut être utilisée pour la génération d’URL. Utiliser une route nommée simplifie considérablement la création d’URL quand l’ordonnancement des routes peut rendre compliquée la génération des URL. Les noms de routes doivent être unique à l’échelle de l’application.

Noms des routes :

  • N’ont aucun impact sur la correspondance d’URL ou la gestion des requêtes.
  • Sont utilisés uniquement pour la génération d’URL.

Le concept de nom de route est représenté dans le routage sous la forme IEndpointNameMetadata. Les termes nom de la route et nom du point de terminaison :

  • Sont interchangeables.
  • Celui qui est utilisé dans la documentation et le code dépend de l’API décrite.

Routage des attributs pour REST les API

Les API REST doivent utiliser le routage d’attributs pour modéliser les fonctionnalités de l’application sous la forme d’un ensemble de ressources dans lequel les opérations sont représentées par des verbes HTTP.

Le routage par attributs utilise un ensemble d’attributs pour mapper les actions directement aux modèles de routes. Le code suivant est classique pour une REST API et est utilisé dans l’exemple suivant :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Dans le code précédent, MapControllers est appelé pour mapper les contrôleurs routés d’attribut.

Dans l’exemple suivant :

  • HomeController correspond à un ensemble d’URL similaires à {controller=Home}/{action=Index}/{id?} routes conventionnelles par défaut.
public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

L’action HomeController.Index sera exécutée pour tous les chemins d’accès d’URL /, /Home, /Home/Index ou /Home/Index/3.

Cet exemple met en évidence une différence importante en termes de programmation entre le routage par attributs et le routage conventionnel. Le routage d’attributs nécessite plus d’entrée pour spécifier une route. La route par défaut conventionnel gère les routes de manière plus succincte. Cependant, le routage par attributs permet (et nécessite) un contrôle précis des modèles de routes qui s’appliquent à chaque action.

Avec le routage d’attributs, les noms du contrôleur et des actions ne jouent aucun rôle dans lequel l’action est mise en correspondance, sauf si le remplacement de jeton est utilisé. L’exemple suivant correspond aux mêmes URL que l’exemple précédent :

public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Le code suivant utilise le remplacement de jeton pour action et controller :

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("[controller]/[action]")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("[controller]/[action]")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Le code suivant applique le contrôleur [Route("[controller]/[action]")] :

[Route("[controller]/[action]")]
public class HomeController : Controller
{
    [Route("~/")]
    [Route("/Home")]
    [Route("~/Home/Index")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Dans le code précédent, les modèles de Index méthode doivent être ajoutés / ou ~/ aux modèles d’itinéraire. Les modèles de routes appliqués à une action qui commencent par / ou ~/ ne sont pas combinés avec les modèles de routes appliqués au contrôleur.

Pour plus d’informations sur la sélection du modèle de route, consultez Priorité du modèle de route.

Noms de routage réservés

Les mots clés suivants sont des noms de paramètres de route réservés lors de l’utilisation de contrôleurs ou Razor de pages :

  • action
  • area
  • controller
  • handler
  • page

L’utilisation page comme paramètre d’itinéraire avec le routage d’attributs est une erreur courante. Cela entraîne un comportement incohérent et confus avec la génération d’URL.

public class MyDemo2Controller : Controller
{
    [Route("/articles/{page}")]
    public IActionResult ListArticles(int page)
    {
        return ControllerContext.MyDisplayRouteInfo(page);
    }
}

Les noms de paramètres spéciaux sont utilisés par la génération d’URL pour déterminer si une opération de génération d’URL fait référence à une Razor page ou à un contrôleur.

Les mots clés suivants sont réservés dans le contexte d’une Razor vue ou d’une Razor page :

  • page
  • using
  • namespace
  • inject
  • section
  • inherits
  • model
  • addTagHelper
  • removeTagHelper

Ces mots clés ne doivent pas être utilisés pour les générations de liens, les paramètres liés au modèle ou les propriétés de niveau supérieur.

Modèles de verbes HTTP

ASP.NET Core a les modèles de verbes HTTP suivants :

Modèles de route

ASP.NET Core a les modèles d’itinéraire suivants :

Routage par attributs avec des attributs du verbe Http

Examinons le contrôleur ci-dessous :

[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
    [HttpGet]   // GET /api/test2
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int/{id:int}")] // GET /api/test2/int/3
    public IActionResult GetIntProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
    public IActionResult GetInt2Product(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Dans le code précédent :

  • Chaque action contient l’attribut [HttpGet], qui limite la correspondance aux requêtes HTTP GET uniquement.
  • L’action GetProduct inclut le "{id}" modèle et est donc id ajoutée au "api/[controller]" modèle sur le contrôleur. Le modèle de méthodes est "api/[controller]/{id}". Par conséquent, cette action ne correspond qu’aux demandes GET pour le formulaire /api/test2/xyz,/api/test2/123,/api/test2/{any string}, etc.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • L’action GetIntProduct contient le modèle "int/{id:int}". La :int partie du modèle limite les valeurs de routage id aux chaînes qui peuvent être converties en entier. Une requête d’obtention pour /api/test2/int/abc :
    • Ne correspond pas à cette action.
    • Retourne une erreur 404 Introuvable.
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • L’action GetInt2Product contient {id} dans le modèle, mais ne limite id pas les valeurs qui peuvent être converties en entier. Une requête d’obtention pour /api/test2/int2/abc :
    • Correspond à cette route.
    • La liaison de données ne parvient pas à convertir abc en entier. Le paramètre id de la méthode est entier.
    • Retourne une requête incorrecte 400, car la liaison de données n’a pas pu convertir abc en entier.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

Le routage par attributs peut utiliser des attributs HttpMethodAttribute tels que HttpPostAttribute, HttpPutAttribute, et HttpDeleteAttribute. Tous les attributs de verbe HTTP acceptent un modèle de route. L’exemple suivant montre deux actions qui correspondent au même modèle de route :

[ApiController]
public class MyProductsController : ControllerBase
{
    [HttpGet("/products3")]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpPost("/products3")]
    public IActionResult CreateProduct(MyProduct myProduct)
    {
        return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
    }
}

À l’aide du chemin d’accès d’URL /products3 :

  • L’action MyProductsController.ListProducts s’exécute lorsque le verbe HTTP est GET.
  • L’action MyProductsController.CreateProduct s’exécute lorsque le verbe HTTP est POST.

Lors de la génération d’une REST API, il est rare que vous deviez utiliser [Route(...)] sur une méthode d’action, car l’action accepte toutes les méthodes HTTP. Il est préférable d’utiliser les attributs des verbes HTTP plus spécifiques pour plus de précision quant à ce qui est pris en charge par votre API. Les clients des REST API doivent normalement connaître les chemins et les verbes HTTP qui correspondent à des opérations logiques spécifiques.

REST API doivent utiliser le routage d’attributs pour modéliser les fonctionnalités de l’application sous la forme d’un ensemble de ressources dans lequel les opérations sont représentées par des verbes HTTP. Cela signifie que plusieurs opérations (comme GET et POST) sur la même ressource logique utilisent la même URL. Le routage d’attributs fournit le niveau de contrôle nécessaire pour concevoir avec soin la disposition des points de terminaison publics d’une API.

Dans la mesure où une route d’attribut s’applique à une action spécifique, il est facile de placer les paramètres nécessaires dans la définition du modèle de route. Dans l’exemple suivant, id est obligatoire dans le chemin d’accès d’URL :

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

L’action Products2ApiController.GetProduct(int) :

  • Est exécuté avec le chemin d’accès d’URL comme /products2/3
  • N’est pas exécuté avec le chemin d’accès d’URL /products2.

L’attribut [Consumes] permet à une action de limiter les types de contenu de la demande pris en charge. Pour plus d’informations, consultez Définir les types de contenu de la requête pris en charge avec l’attribut [Consumes].

Consultez Routage pour obtenir une description complète des modèles de routes et des options associées.

Pour plus d’informations sur [ApiController], consultez Attribut ApiController.

Nom de l’itinéraire

Le code suivant définit un nom de route Products_List :

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Les noms de routes peuvent être utilisés pour générer une URL basée sur une route spécifique. Noms des routes :

  • N’ont aucun impact sur le comportement de correspondance d’URL du routage.
  • Sont utilisés uniquement pour la génération d’URL.

Les noms de routes doivent être unique à l’échelle de l’application.

Comparez le code précédent avec la route par défaut conventionnelle, qui définit le paramètre id comme étant facultatif ({id?}). La possibilité de spécifier les API avec précision présente des avantages, par exemple de permettre de diriger /products et /products/5 vers des actions différentes.

Combiner des routes d’attributs

Pour rendre le routage par attributs moins répétitif, les attributs de route sont combinés avec des attributs de route sur les actions individuelles. Les modèles de routes définis sur le contrôleur sont ajoutés à des modèles de routes sur les actions. Placer un attribut de route sur le contrôleur a pour effet que toutes les actions du contrôleur utilisent le routage par attributs.

[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
    [HttpGet]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Dans l’exemple précédent :

  • Le chemin d’accès de /products l’URL peut correspondre ProductsApi.ListProducts
  • Le chemin d’accès de /products/5 l’URL peut correspondre ProductsApi.GetProduct(int).

Ces deux actions correspondent seulement à HTTP GET, car elles sont marquées avec l’attribut [HttpGet].

Les modèles de routes appliqués à une action qui commencent par / ou ~/ ne sont pas combinés avec les modèles de routes appliqués au contrôleur. L’exemple suivant met en correspondance avec un ensemble de chemins d’accès d’URL similaires à la route par défaut.

[Route("Home")]
public class HomeController : Controller
{
    [Route("")]
    [Route("Index")]
    [Route("/")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("About")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Le tableau suivant explique les [Route] attributs du code précédent :

Attribut Combine avec [Route("Home")] Définit le modèle de route
[Route("")] Oui "Home"
[Route("Index")] Oui "Home/Index"
[Route("/")] Non ""
[Route("About")] Oui "Home/About"

Ordre de route des attributs

Le routage génère une arborescence et correspond à tous les points de terminaison simultanément :

  • Les entrées de route se comportent comme si elles sont placées dans un ordre idéal.
  • Les routes les plus spécifiques ont une chance d’être exécutées avant les routes plus générales.

Par exemple, une route d’attribut comme blog/search/{topic} est plus spécifique qu’une route d’attribut comme blog/{*article}. La route blog/search/{topic} a une priorité plus élevée, par défaut, car elle est plus spécifique. Avec le routage conventionnel, le développeur est responsable du placement des routes dans l’ordre souhaité.

Les routes d’attributs peuvent configurer un ordre à l’aide de la propriété Order. Tous les attributs de route fournis par l’infrastructure incluent Order . Les routes sont traitées selon un ordre croissant de la propriété Order. L’ordre par défaut est 0. La définition d’une route avec Order = -1 fait que cette route s’exécute avant les routes qui ne définissent pas d’ordre. La définition d’une route avec Order = 1 fait que cette route s’exécute après l’ordre des routes par défaut.

Évitez de dépendre de Order. Si l’espace d’URL d’une application nécessite des valeurs d’ordre explicites pour router correctement, il est probable qu’il prête également à confusion pour les clients. D’une façon générale, le routage par attributs sélectionne la route correcte avec la mise en correspondance d’URL. Si l’ordre par défaut utilisé pour la génération d’URL ne fonctionne pas, l’utilisation à titre de remplacement d’un nom de route est généralement plus simple que d’appliquer la propriété Order.

Considérez les deux contrôleurs suivants qui définissent tous deux la correspondance /homede route :

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}
public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

La requête /home avec le code précédent lève une exception similaire à ce qui suit :

AmbiguousMatchException: The request matched multiple endpoints. Matches:

 WebMvcRouting.Controllers.HomeController.Index
 WebMvcRouting.Controllers.MyDemoController.MyIndex

L’ajout Order à l’un des attributs de route résout l’ambiguïté :

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
    return ControllerContext.MyDisplayRouteInfo();
}

Avec le code précédent, /home exécute le point de HomeController.Index terminaison. Pour accéder à MyDemoController.MyIndex, demandez /home/MyIndex. Remarque :

  • Le code précédent est un exemple ou une conception de routage médiocre. Il a été utilisé pour illustrer la propriété Order.
  • La propriété Order résout uniquement l’ambiguïté. Ce modèle ne peut pas être mis en correspondance. Il serait préférable de supprimer le modèle [Route("Home")].

Pour plus d’informations sur l’ordre d’itinéraire avec Razor Pages, consultez Conventions de route et d’application :Razor Ordre de routage.

Dans certains cas, une erreur HTTP 500 est retournée avec des routes ambiguës. Utilisez la journalisation pour voir quels points de terminaison sont à l’origine de AmbiguousMatchException.

Remplacement de jetons dans les modèles de routes [contrôleur], [action], [zone]

Pour plus de commodité, les routes d’attribut prennent en charge le remplacement de jetons, qui se fait via la mise entre crochets d’un jeton ([, ]). Les jetons [action], [area] et [controller] sont remplacés par les valeurs du nom d’action, du nom de la zone et du nom du contrôleur de l’action où la route est définie :

[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
    [HttpGet]
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }


    [HttpGet("{id}")]
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Dans le code précédent :

[HttpGet]
public IActionResult List()
{
    return ControllerContext.MyDisplayRouteInfo();
}
  • Correspondances /Products0/List
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • Correspondances /Products0/Edit/{id}

Le remplacement des jetons se produit à la dernière étape de la création des routes d’attribut. L’exemple précédent se comporte de la même manière que le code suivant :

public class Products20Controller : Controller
{
    [HttpGet("[controller]/[action]")]  // Matches '/Products20/List'
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("[controller]/[action]/{id}")]   // Matches '/Products20/Edit/{id}'
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Si vous lisez ceci dans une autre langue que l’anglais, faites-le nous savoir dans ce problème de discussion GitHub si vous souhaitez voir les commentaires de code dans votre langue maternelle.

Les routes d’attribut peuvent aussi être combinées avec l’héritage. Combiné avec le remplacement de jetons, c’est puissant. Le remplacement des jetons s’applique aussi aux noms de routes définis par des routes d’attribut. [Route("[controller]/[action]", Name="[controller]_[action]")] génère un nom de route unique pour chaque action :

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}

public class Products11Controller : MyBase2Controller
{
    [HttpGet]                      // /api/products11/list
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]             //    /api/products11/edit/3
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Pour faire correspondre le délimiteur littéral de remplacement de jetons [ ou ], placez-le en échappement en répétant le caractère ([[ ou ]]).

Utiliser un transformateur de paramètre pour personnaliser le remplacement des jetons

Le remplacement des jetons peut être personnalisé à l’aide d’un transformateur de paramètre. Un transformateur de paramètre implémente IOutboundParameterTransformer et transforme la valeur des paramètres. Par exemple, un transformateur de paramètre SlugifyParameterTransformer personnalisé transforme la valeur de la route SubscriptionManagement en subscription-management :

using System.Text.RegularExpressions;

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string? TransformOutbound(object? value)
    {
        if (value == null) { return null; }

        return Regex.Replace(value.ToString()!,
                             "([a-z])([A-Z])",
                             "$1-$2",
                             RegexOptions.CultureInvariant,
                             TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
    }
}

RouteTokenTransformerConvention est une convention de modèle d’application qui :

  • Applique un transformateur de paramètre à toutes les routes d’attribut dans une application.
  • Personnalise les valeurs de jeton de route d’attribut quand elles sont remplacées.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

La méthode ListAllprécédente correspond à /subscription-management/list-all.

Le RouteTokenTransformerConvention est inscrit en tant qu’option :

using Microsoft.AspNetCore.Mvc.ApplicationModels;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
    options.Conventions.Add(new RouteTokenTransformerConvention(
                                 new SlugifyParameterTransformer()));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Consultez la documentation web MDN sur Slug pour la définition de Slug.

Avertissement

Lorsque vous utilisez System.Text.RegularExpressions pour traiter une entrée non approuvée, passez un délai d’expiration. Un utilisateur malveillant peut fournir une entrée à RegularExpressions, provoquant une attaque par déni de service. Les API d’infrastructure ASP.NET Core qui utilisent RegularExpressions passent un délai d’expiration.

Routes d’attribut multiples

Le routage par attributs prend en charge la définition de plusieurs routes pour atteindre la même action. L’utilisation la plus courante de ceci est d’imiter le comportement de la route conventionnelle par défaut, comme le montre l’exemple suivant :

[Route("[controller]")]
public class Products13Controller : Controller
{
    [Route("")]     // Matches 'Products13'
    [Route("Index")] // Matches 'Products13/Index'
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

Le fait de placer plusieurs attributs de route sur le contrôleur signifie que chacun d’eux se combine avec chacun des attributs de route sur les méthodes d’action :

[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
    [HttpPost("Buy")]       // Matches 'Products6/Buy' and 'Store/Buy'
    [HttpPost("Checkout")]  // Matches 'Products6/Checkout' and 'Store/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Toutes les contraintes de routes de verbe HTTP implémentent IActionConstraint.

Lorsque plusieurs attributs de route qui implémentent IActionConstraint sont placés sur une action :

  • Chaque contrainte d’action se combine avec le modèle de route appliqué au contrôleur.
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
    [HttpPut("Buy")]        // Matches PUT 'api/Products7/Buy'
    [HttpPost("Checkout")]  // Matches POST 'api/Products7/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

L’utilisation de plusieurs routes sur des actions peut sembler utile et puissante. Il est préférable de conserver l’espace URL de votre application de base et bien défini. Utilisez plusieurs routes sur les actions seulement là où c’est nécessaire, par exemple pour prendre en charge des clients existants.

Spécification facultative de paramètres, de valeurs par défaut et de contraintes pour les routes d’attribut

Les routes d’attribut prennent en charge la même syntaxe inline que les routes conventionnelles pour spécifier des paramètres, des valeurs par défaut et des contraintes facultatifs.

public class Products14Controller : Controller
{
    [HttpPost("product14/{id:int}")]
    public IActionResult ShowProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Dans le code précédent, [HttpPost("product14/{id:int}")] applique une contrainte de route. L’action Products14Controller.ShowProduct est mise en correspondance uniquement par des chemins d’accès d’URL tels que /product14/3. La partie {id:int} modèle de routage limite ce segment à des entiers uniquement.

Pour une description détaillée de la syntaxe du modèle de route, consultez Informations de référence sur le modèle de route.

Attributs de route personnalisés à l’aide de IRouteTemplateProvider

Tous les attributs de route implémentent IRouteTemplateProvider. Le Runtime ASP.NET Core :

  • Recherche des attributs sur les classes de contrôleur et les méthodes d’action au démarrage de l’application.
  • Utilise les attributs qui implémentent IRouteTemplateProvider pour générer l’ensemble initial de routes.

Implémentez IRouteTemplateProvider pour définir des attributs de route personnalisés. Chaque IRouteTemplateProvider vous permet de définir une route avec un modèle, un nom et un ordre de route personnalisés :

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
    public string Template => "api/[controller]";
    public int? Order => 2;
    public string Name { get; set; } = string.Empty;
}

[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
    // GET /api/MyTestApi
    [HttpGet]
    public IActionResult Get()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

La méthode précédente Get retourne Order = 2, Template = api/MyTestApi.

Utiliser le modèle d’application pour personnaliser les routes des attributs

L’ancien modèle d’application :

  • Modèle objet créé au démarrage dans Program.cs.
  • Contient toutes les métadonnées utilisées par ASP.NET Core pour router et exécuter les actions dans une application.

Le modèle d’application inclut toutes les données collectées à partir des attributs de route. Les données des attributs de routage sont fournies par l’implémentation IRouteTemplateProvider. Conventions :

  • Peut être écrit pour modifier le modèle d’application afin de personnaliser le comportement du routage.
  • Sont lus au démarrage de l’application.

Cette section montre un exemple de base de personnalisation du routage avec le modèle d’application. Le code suivant aligne approximativement les routes avec la structure de dossiers du projet.

public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
    private readonly string _baseNamespace;

    public NamespaceRoutingConvention(string baseNamespace)
    {
        _baseNamespace = baseNamespace;
    }

    public void Apply(ControllerModel controller)
    {
        var hasRouteAttributes = controller.Selectors.Any(selector =>
                                                selector.AttributeRouteModel != null);
        if (hasRouteAttributes)
        {
            return;
        }

        var namespc = controller.ControllerType.Namespace;
        if (namespc == null)
            return;
        var template = new StringBuilder();
        template.Append(namespc, _baseNamespace.Length + 1,
                        namespc.Length - _baseNamespace.Length - 1);
        template.Replace('.', '/');
        template.Append("/[controller]/[action]/{id?}");

        foreach (var selector in controller.Selectors)
        {
            selector.AttributeRouteModel = new AttributeRouteModel()
            {
                Template = template.ToString()
            };
        }
    }
}

Le code suivant empêche l’application de la namespace convention aux contrôleurs qui sont routés par l’attribut :

public void Apply(ControllerModel controller)
{
    var hasRouteAttributes = controller.Selectors.Any(selector =>
                                            selector.AttributeRouteModel != null);
    if (hasRouteAttributes)
    {
        return;
    }

Par exemple, le contrôleur suivant n’utilise pas NamespaceRoutingConvention :

[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
    // /managers/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        return Content($"Index- template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

La méthode NamespaceRoutingConvention.Apply :

  • Ne fait rien si le contrôleur est routé par l’attribut.
  • Définit le modèle de contrôleurs en fonction de namespace, avec la base namespace supprimée.

Le NamespaceRoutingConvention peut être appliqué dans Program.cs :

using My.Application.Controllers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
    options.Conventions.Add(
     new NamespaceRoutingConvention(typeof(HomeController).Namespace!));
});

var app = builder.Build();

Par exemple, prenons le contrôleur suivant :

using Microsoft.AspNetCore.Mvc;

namespace My.Application.Admin.Controllers
{
    public class UsersController : Controller
    {
        // GET /admin/controllers/users/index
        public IActionResult Index()
        {
            var fullname = typeof(UsersController).FullName;
            var template = 
                ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
            var path = Request.Path.Value;

            return Content($"Path: {path} fullname: {fullname}  template:{template}");
        }

        public IActionResult List(int? id)
        {
            var path = Request.Path.Value;
            return Content($"Path: {path} ID:{id}");
        }
    }
}

Dans le code précédent :

  • La base namespace est My.Application.
  • Le nom complet du contrôleur précédent est My.Application.Admin.Controllers.UsersController.
  • Le NamespaceRoutingConvention définit le modèle de contrôleurs sur Admin/Controllers/Users/[action]/{id?.

Le NamespaceRoutingConvention peut également être appliqué en tant qu’attribut sur un contrôleur :

[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
    // /admin/controllers/test/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        var actionname = ControllerContext.ActionDescriptor.ActionName;
        return Content($"Action- {actionname} template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

Routage mixte : routage conventionnel et routage par attributs

Les applications ASP.NET Core peuvent combiner l’utilisation du routage conventionnel et du routage par attributs. Il est courant d’utiliser des routes conventionnelles pour les contrôleurs délivrant des pages HTML pour les navigateurs, et le routage par attributs pour les contrôleurs délivrant des API REST.

Les actions sont routées de façon conventionnelle ou routées par attribut. Le fait de placer une route sur le contrôleur ou sur l’action les rend « routés par attribut ». Les actions qui définissent des routes d’attribut ne sont pas accessibles via les routes conventionnelles et vice versa. Tout attribut de route sur le contrôleur a pour effet que toutes les actions du contrôleur sont routées par attributs.

Le routage d’attributs et le routage conventionnel utilisent le même moteur de routage.

Routage avec des caractères spéciaux

Le routage avec des caractères spéciaux peut entraîner des résultats inattendus. Par exemple, considérez un contrôleur avec la méthode d’action suivante :

[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null || todoItem.Name == null)
    {
        return NotFound();
    }

    return todoItem.Name;
}

Lorsque string id contient les valeurs encodées suivantes, des résultats inattendus peuvent se produire :

ASCII Encoded
/ %2F
+

Les paramètres de routage ne sont pas toujours décodés par URL. Ce problème peut être résolu à l’avenir. Pour plus d’informations, consultez ce problème GitHub ;

Génération d’URL et valeurs ambiantes

Les applications peuvent utiliser les fonctionnalités de génération d’URL de routage pour générer des liens URL vers des actions. La génération d’URL élimine le codage en dur des URL, ce qui rend un code plus robuste et plus facile à maintenir. Cette section se concentre sur les fonctionnalités de génération d’URL fournies par MVC et couvre seulement les principes de base du fonctionnement de la génération d’URL. Pour une description détaillée de la génération d’URL, consultez Routage.

L’interface IUrlHelper est l’élément d’infrastructure sous-jacent entre MVC et le routage pour la génération d’URL. Une instance de IUrlHelper est disponible par le biais de la propriété Url dans les contrôleurs, les vues et les composants de vue.

Dans l’exemple suivant, l’interface IUrlHelper est utilisée par le biais de la propriété Controller.Url pour générer une URL vers une autre action.

public class UrlGenerationController : Controller
{
    public IActionResult Source()
    {
        // Generates /UrlGeneration/Destination
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Si l’application utilise la route conventionnelle par défaut, la valeur de la variable url est la chaîne de chemin d’URL /UrlGeneration/Destination. Ce chemin d’accès d’URL est créé par routage en combinant :

  • Valeurs de routage de la requête actuelle, appelées valeurs ambiantes.
  • Valeurs transmises à Url.Action et en remplaçant ces valeurs dans le modèle de route :
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

La valeur de chaque paramètre de route du modèle de route est remplacée en établissant une correspondance avec les valeurs et les valeurs ambiantes. Un paramètre de route qui n’a pas de valeur peut :

  • Utilisez une valeur par défaut s’il en a une.
  • Être ignoré s’il est facultatif. Par exemple, le id à partir du modèle de route{controller}/{action}/{id?}.

La génération d’URL échoue si un paramètre de route obligatoire n’a pas de valeur correspondante. Si la génération d’URL échoue pour une route, la route suivante est essayée, ceci jusqu’à ce que toutes les routes aient été essayées ou qu’une correspondance soit trouvée.

L’exemple précédent de Url.Action suppose le routage conventionnel. La génération d’URL fonctionne de façon similaire avec le routage par attributs, même si les concepts sont différents. Avec un routage conventionnel :

  • Les valeurs de route sont utilisées pour développer un modèle.
  • Les valeurs de routage pour controller et action s’affichent généralement dans ce modèle. Cela fonctionne, car les URL mises en correspondance par le routage respectent une convention.

L’exemple suivant utilise un attribut de routage :

public class UrlGenerationAttrController : Controller
{
    [HttpGet("custom")]
    public IActionResult Source()
    {
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination")]
    public IActionResult Destination()
    {
       return ControllerContext.MyDisplayRouteInfo();
    }
}

L’action Source dans le code précédent génère custom/url/to/destination.

LinkGeneratora été ajouté dans ASP.NET Core 3.0 comme alternative à IUrlHelper. LinkGenerator offre des fonctionnalités similaires, mais plus flexibles. Chaque méthode sur IUrlHelper a également une famille de méthodes correspondante sur LinkGenerator.

Génération des URL par nom d’action

Url.Action, LinkGenerator.GetPathByAction et toutes les surcharges associées sont tous conçus pour générer le point de terminaison cible en spécifiant un nom de contrôleur et un nom d’action.

Lors de l’utilisation de Url.Action, les valeurs de routage actuelles pour controller et action sont fournies par le runtime :

  • La valeur de controller et action fait partie des valeurs ambiantes et des valeurs. La méthode Url.Action utilise toujours les valeurs actuelles de action et de controller, et génère un chemin d’accès d’URL qui route vers l’action actuelle.

Le routage essaye d’utiliser les valeurs dans les valeurs ambiantes pour renseigner les informations qui n’ont pas été fournies lors de la génération d’une URL. Considérez une route comme {a}/{b}/{c}/{d} avec des valeurs ambiantes { a = Alice, b = Bob, c = Carol, d = David } :

  • Le routage contient suffisamment d’informations pour générer une URL sans valeurs supplémentaires.
  • Le routage contient suffisamment d’informations, car tous les paramètres de route ont une valeur.

Si la valeur { d = Donovan } est ajoutée :

  • La valeur { d = David } est ignorée.
  • Le chemin d’accès d’URL généré est Alice/Bob/Carol/Donovan.

Avertissement : Les chemins d’accès d’URL sont hiérarchiques. Dans l’exemple précédent, si la valeur { c = Cheryl } est ajoutée :

  • Les deux valeurs { c = Carol, d = David } sont ignorées.
  • Il n’y a plus de valeur pour d et la génération d’URL échoue.
  • Les valeurs souhaitées de c et d doivent être spécifiées pour générer une URL.

Vous pouvez vous attendre à rencontrer ce problème avec l’itinéraire {controller}/{action}/{id?}par défaut. Ce problème est rare dans la pratique, car Url.Action spécifie toujours explicitement une controller valeur et action.

Plusieurs surcharges de Url.Action prennent un objet de valeurs d’itinéraire pour fournir des valeurs pour les paramètres de route autres que controller et action. L’objet valeurs de route est fréquemment utilisé avec id. Par exemple : Url.Action("Buy", "Products", new { id = 17 }). Objet de valeurs de route :

  • Par convention est généralement un objet de type anonyme.
  • Il peut s’agir d’un IDictionary<> ou d’un POCO).

Toutes les valeurs de route supplémentaires qui ne correspondent pas aux paramètres de route sont placées dans la chaîne de requête.

public IActionResult Index()
{
    var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
    return Content(url!);
}

Le code précédent génère /Products/Buy/17?color=red.

Le code suivant génère une URL absolue :

public IActionResult Index2()
{
    var url = Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme);
    // Returns https://localhost:5001/Products/Buy/17
    return Content(url!);
}

Pour créer une URL absolue, utilisez l’une des options suivantes :

  • Une surcharge qui accepte un protocol. Par exemple, le code précédent.
  • LinkGenerator.GetUriByAction, qui génère des URI absolus par défaut.

Générer des URL par route

Le code précédent a montré la génération d’une URL en passant le nom du contrôleur et le nom de l’action. IUrlHelper fournit également la famille de méthodes Url.RouteUrl. Ces méthodes sont similaires à Url.Action, mais elle ne copient pas les valeurs actuelles de action et de controller vers les valeurs de route. Utilisation la plus courante pour Url.RouteUrl :

  • Spécifie un nom d’itinéraire pour générer l’URL.
  • En règle générale, ne spécifie pas de contrôleur ou de nom d’action.
public class UrlGeneration2Controller : Controller
{
    [HttpGet("")]
    public IActionResult Source()
    {
        var url = Url.RouteUrl("Destination_Route");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

Le fichier suivant Razor génère un lien HTML versDestination_Route :

<h1>Test Links</h1>

<ul>
    <li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>

Générer des URL en HTML et Razor

IHtmlHelper fournit les HtmlHelperméthodes Html.BeginForm et Html.ActionLink pour générer <form> et <a> éléments respectivement. Ces méthodes utilisent la méthode Url.Action pour générer une URL et ils acceptent les arguments similaires. Les pendants de Url.RouteUrl pour HtmlHelper sont Html.BeginRouteForm et Html.RouteLink, qui ont des fonctionnalités similaires.

Les TagHelpers génèrent des URL via le TagHelper form et le TagHelper <a>. Ils utilisent tous les deux IUrlHelper pour leur implémentation. Pour plus d’informations, consultez Tag Helpers dans les formulaires .

Dans les vues, IUrlHelper est disponible via la propriété Url pour toute génération d’URL ad hoc non couverte par ce qui figure ci-dessus.

Génération d’URL dans résultats de l’action

Les exemples précédents ont montré l’utilisation IUrlHelper dans un contrôleur. L’utilisation la plus courante dans un contrôleur consiste à générer une URL dans le cadre du résultat d’une action.

Les classes de base ControllerBase et Controller fournissent des méthodes pratiques pour les résultats d’action qui référencent une autre action. Une utilisation typique est de rediriger après acceptation de l’entrée utilisateur :

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
    if (ModelState.IsValid)
    {
        // Update DB with new details.
        ViewData["Message"] = $"Successful edit of customer {id}";
        return RedirectToAction("Index");
    }
    return View(customer);
}

Les méthodes de fabrique de résultats d’action comme RedirectToAction et CreatedAtAction suivent un modèle similaire aux méthodes sur IUrlHelper.

Cas spécial pour les routes conventionnelles dédiées

Le routage conventionnel peut utiliser un type spécial de définition de route appelé route conventionnelle dédiée. Dans l’exemple suivant, la route nommée blog est une route conventionnelle dédiée :

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

À l’aide des définitions de route précédentes, Url.Action("Index", "Home") génère le chemin d’accès / d’URL à l’aide de l’itinéraire default , mais pourquoi? Vous pouvez deviner que les valeurs de route { controller = Home, action = Index } seraient suffisantes pour générer une URL avec blog, et que le résultat serait /blog?action=Index&controller=Home.

Les routes conventionnelles dédiées s’appuient sur un comportement spécial des valeurs par défaut qui n’ont pas de paramètre de route correspondant qui empêche la route d’être trop globale avec la génération d’URL. Dans ce cas, les valeurs par défaut sont { controller = Blog, action = Article }, et ni controller ni action n’apparaissent comme paramètre de route. Quand le routage effectue une génération d’URL, les valeurs fournies doivent correspondre aux valeurs par défaut. La génération d’URL avec blog échoue, car les valeurs { controller = Home, action = Index } ne correspondent pas à { controller = Blog, action = Article }. Le routage essaye alors d’utiliser default, ce qui réussit.

Zones (Areas)

Les zones sont une fonctionnalité MVC utilisée pour organiser des fonctionnalités connexes dans un groupe distinct :

  • Espace de noms de routage pour les actions du contrôleur.
  • Structure des dossiers pour les vues.

L’utilisation de zones permet à une application d’avoir plusieurs contrôleurs portant le même nom, pour autant qu’ils soient dans des zones différentes. L’utilisation de zones crée une hiérarchie qui permet le routage par ajout d’un autre paramètre de route, area, à controller et à action. Cette section explique comment le routage interagit avec les zones. Voir Zones pour plus de détails sur l’utilisation des zones dans les vues.

L’exemple suivant configure MVC pour utiliser la route conventionnelle par défaut et une arearoutearea nommée Blog :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{    
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapAreaControllerRoute("blog_route", "Blog",
        "Manage/{controller}/{action}/{id?}");
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");

app.Run();

Dans le code précédent, MapAreaControllerRoute est appelé pour créer le "blog_route". Le deuxième paramètre, "Blog", est le nom de la zone.

Lors de la correspondance d’un chemin d’URL comme /Manage/Users/AddUser, la route "blog_route" génère les valeurs de route { area = Blog, controller = Users, action = AddUser }. La valeur de route area est générée par une valeur par défaut pour area. La route créée par MapAreaControllerRoute est équivalente à la suivante :

app.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
        defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");

MapAreaControllerRoute crée une route avec à la fois une valeur par défaut et une contrainte pour area en utilisant le nom de la zone fournie, dans ce cas Blog. La valeur par défaut garantit que la route produit toujours { area = Blog, ... }, et la contrainte nécessite la valeur { area = Blog, ... } pour la génération d’URL.

Le routage conventionnel est dépendant de l’ordre. En général, les routes avec des zones doivent être placées plus haut, car elles sont plus spécifiques que les routes sans zone.

Avec l’exemple précédent, les valeurs de route { area = Blog, controller = Users, action = AddUser } sont mises en correspondance avec l’action suivante :

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}

L’attribut [Zone] indique qu’un contrôleur fait partie d’une zone. Ce contrôleur se trouve dans la zone Blog. Les contrôleurs sans attribut [Area] ne sont membres d’aucune zone et ne sont pas trouvés en correspondance quand la valeur de route area est fournie par le routage. Dans l’exemple suivant, seul le premier contrôleur répertorié peut correspondre aux valeurs de route { area = Blog, controller = Users, action = AddUser }.

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace2
{
    // Matches { area = Zebra, controller = Users, action = AddUser }
    [Area("Zebra")]
    public class UsersController : Controller
    {
        // GET /zebra/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace3
{
    // Matches { area = string.Empty, controller = Users, action = AddUser }
    // Matches { area = null, controller = Users, action = AddUser }
    // Matches { controller = Users, action = AddUser }
    public class UsersController : Controller
    {
        // GET /users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }
    }
}

L’espace de noms de chaque contrôleur est affiché ici pour l’exhaustivité. Si les contrôleurs précédents utilisaient le même espace de noms, une erreur du compilateur serait générée. Les espaces de noms de classe n’ont pas d’effet sur le routage de MVC.

Les deux premiers contrôleurs sont membres de zones, et ils sont trouvés en correspondance seulement quand le nom de leur zone respective est fourni par la valeur de route area. Le troisième contrôleur n’est membre d’aucune zone et peut être trouvé en correspondance seulement quand aucune valeur pour area n’est fournie par le routage.

En termes de mise en correspondance avec aucune valeur, l’absence de la valeur area est identique à une valeur null ou de chaîne vide pour area.

Lors de l’exécution d’une action à l’intérieur d’une zone, la valeur de route pour area est disponible en tant que valeur ambiante, que le routage peut utiliser pour la génération d’URL. Cela signifie que par défaut, les zones agissent par attraction pour la génération d’URL, comme le montre l’exemple suivant.

app.MapAreaControllerRoute(name: "duck_route",
                                     areaName: "Duck",
                                     pattern: "Manage/{controller}/{action}/{id?}");
app.MapControllerRoute(name: "default",
                             pattern: "Manage/{controller=Home}/{action=Index}/{id?}");
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace4
{
    [Area("Duck")]
    public class UsersController : Controller
    {
        // GET /Manage/users/GenerateURLInArea
        public IActionResult GenerateURLInArea()
        {
            // Uses the 'ambient' value of area.
            var url = Url.Action("Index", "Home");
            // Returns /Manage/Home/Index
            return Content(url);
        }

        // GET /Manage/users/GenerateURLOutsideOfArea
        public IActionResult GenerateURLOutsideOfArea()
        {
            // Uses the empty value for area.
            var url = Url.Action("Index", "Home", new { area = "" });
            // Returns /Manage
            return Content(url);
        }
    }
}

Le code suivant génère une URL vers /Zebra/Users/AddUser :

public class HomeController : Controller
{
    public IActionResult About()
    {
        var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
        return Content($"URL: {url}");
    }

Définition d’action

Les méthodes publiques sur un contrôleur, sauf celles qui sont avec l’attribut NonAction, sont des actions.

Exemple de code

Déboguer les diagnostics

Pour obtenir une sortie de diagnostic de routage détaillée, définissez Logging:LogLevel:Microsoft sur Debug. Dans l’environnement de développement, définissez le niveau de journal dans appsettings.Development.json :

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

Les contrôleurs ASP.NET Core utilise l’intergiciel de routage pour mettre en correspondance les URL des requêtes entrantes et les mapper à des actions. Modèles de route :

  • Sont définies dans le code de démarrage ou dans des attributs.
  • Décrire comment les chemins d’accès d’URL sont mis en correspondance avec les actions.
  • Sont utilisés pour générer des URL pour les liens. Les liens générés sont généralement retournés dans les réponses.

Les actions sont routées de façon conventionnelle ou routées par attribut. Le fait de placer une route sur le contrôleur ou sur l’action les rend « routés par attribut ». Pour plus d’informations, consultez Routage mixte.

Ce document :

  • Explique les interactions entre MVC et le routage :
    • Comment les applications MVC classiques utilisent les fonctionnalités de routage?
    • Couvre les deux :
    • Pour plus d’informations sur le routage avancé, consultez Routage.
  • Fait référence au système de routage par défaut ajouté dans ASP.NET Core 3.0, appelé routage de point de terminaison. Il est possible d’utiliser des contrôleurs avec la version précédente du routage à des fins de compatibilité. Pour obtenir des instructions, consultez le guide de migration 2.2-3.0. Reportez-vous à la version 2.2 de ce document pour obtenir des informations de référence sur le système de routage hérité.

Configurer la route conventionnelle

Startup.Configure a généralement un code similaire à ce qui suit lors de l’utilisation d’un routage conventionnel :

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

À l’intérieur de l’appel à UseEndpoints, MapControllerRoute est utilisé pour créer une route unique. La route unique est nommé default route. La plupart des applications avec des contrôleurs et des vues utilisent un modèle de route default similaire à la route. REST Les API doivent utiliser le routage d’attributs.

Le modèle de route "{controller=Home}/{action=Index}/{id?}" :

  • Correspond à un chemin d’accès d’URL comme /Products/Details/5

  • Extrait les valeurs { controller = Products, action = Details, id = 5 } d’itinéraire en jetons le chemin d’accès. L’extraction des valeurs de route entraîne une correspondance si l’application a un contrôleur nommé ProductsController et une Details action :

    public class ProductsController : Controller
    {
        public IActionResult Details(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }
    }
    

    MyDisplayRouteInfo est fourni par le package NuGet Rick.Docs.Samples.RouteInfo et affiche les informations de routage.

  • /Products/Details/5 model lie la valeur de id = 5 pour définir le id paramètre sur 5. Pour plus d’informations, consultez Liaison de modèle.

  • {controller=Home} définit Home comme controller par défaut.

  • {action=Index} définit Index comme action par défaut.

  • Le caractère ? dans {id?} définit id comme facultatif.

  • Les paramètres de route par défaut et facultatifs n’ont pas besoin d’être présents dans le chemin d’URL pour qu’une correspondance soit établie. Pour une description détaillée de la syntaxe du modèle de route, consultez Informations de référence sur le modèle de route.

  • Correspond au chemin d’accès d’URL /.

  • Produit les valeurs { controller = Home, action = Index }de route.

Valeurs pour controller et action utiliser les valeurs par défaut. id ne produit pas de valeur, car il n’existe pas de segment correspondant dans le chemin d’accès d’URL. / Correspond uniquement s’il existe une HomeController action et Index :

public class HomeController : Controller
{
  public IActionResult Index() { ... }
}

En utilisant la définition du contrôleur et le modèle de route précédents, l’action HomeController.Index est exécutée pour les chemins d’accès d’URL suivants :

  • /Home/Index/17
  • /Home/Index
  • /Home
  • /

Le chemin d’accès d’URL / utilise l’action et Home les contrôleurs par défaut Index du modèle de route. Le chemin d’accès d’URL /Home utilise l’action par défaut Index du modèle de route.

La méthode pratique MapDefaultControllerRoute :

endpoints.MapDefaultControllerRoute();

Remplace :

endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");

Important

Le routage est configuré à l’aide de l’intergiciel UseRouting,.MapControllerRoute et MapAreaControllerRoute. Pour utiliser des contrôleurs :

Routage conventionnel

Routage conventionnel est utilisé avec les contrôleurs et les vues. La route default :

endpoints.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Le précédent est un exemple de route conventionnelle. Nous appelons ce style routage conventionnel, car il établit une convention pour les chemins d’URL :

  • Le premier segment de tracé, {controller=Home}, correspond au nom du contrôleur.
  • Le deuxième segment, {action=Index}, correspond au nom de l’action.
  • Le troisième segment, {id?} est utilisé pour un facultatif id. Le ? dans {id?} le rend facultatif. id est utilisé pour mapper à une entité de modèle.

À l’aide de cette route default, le chemin d’accès d’URL :

  • /Products/List mappe à l’action ProductsController.List .
  • /Blog/Article/17 mappe à BlogController.Article et le modèle lie généralement le id paramètre à 17.

Ce mappage :

  • Est basé uniquement sur les noms du contrôleur et des actions.
  • N’est pas basé sur des espaces de noms, des emplacements de fichiers sources ou des paramètres de méthode.

L’utilisation du routage conventionnel avec la route par défaut permet de créer l’application sans avoir à inventer un nouveau modèle d’URL pour chaque action. Pour une application avec des actions de style CRUD , ayant une cohérence pour les URL entre les contrôleurs :

  • Permet de simplifier le code.
  • Rend l’interface utilisateur plus prévisible.

Avertissement

Le id dans le code précédent est défini comme facultatif par le modèle de route. Les actions peuvent s’exécuter sans l’ID facultatif fourni dans le cadre de l’URL. En règle générale, quand id est omis de l’URL :

  • id est défini sur 0 par liaison de données.
  • Aucune entité n’est trouvée dans la base de données correspondant à id == 0.

Le routage par attribut vous donne un contrôle précis pour rendre le code obligatoire pour certaines actions et pas pour d’autres. Par convention, la documentation inclut des paramètres facultatifs comme id quand ils sont susceptibles d’apparaître dans une utilisation correcte.

La plupart des applications doivent choisir un schéma de routage de base et descriptif pour que les URL soient lisibles et explicites. La route conventionnelle par défaut {controller=Home}/{action=Index}/{id?} :

  • Prend en charge un schéma de routage de base et descriptif.
  • Est un point de départ pratique pour les applications basées sur une interface utilisateur.
  • Est le seul modèle d’itinéraire nécessaire pour de nombreuses applications d’interface utilisateur web. Pour les applications d’interface utilisateur web plus volumineuses, une autre route utilisant Zones est souvent tout ce qui est nécessaire.

MapControllerRoute et MapAreaRoute :

  • Attribuent automatiquement une valeur de commande à leurs points de terminaison en fonction de l’ordre qu’ils appellent.

Routage des points de terminaison dans ASP.NET Core 3.0 et versions ultérieures :

  • N’a pas de concept de routes.
  • Ne fournit pas de garanties de classement pour l’exécution de l’extensibilité, tous les points de terminaison sont traités en même temps.

Activez la journalisation pour voir comment les implémentations de routage intégrées, comme Route, établissent des correspondances avec les requêtes.

Le routage des attributs est expliqué plus loin dans ce document.

Plusieurs routes conventionnelles

Plusieurs routes conventionnelles peuvent être ajoutés à l’intérieur UseEndpoints en ajoutant des appels à MapControllerRoute et MapAreaControllerRoute. Ceci permet de définir plusieurs conventions ou d’ajouter des routes conventionnelles qui sont dédiées à une action spécifique, comme :

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.MapControllerRoute(name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
});

La routeblog dans le code précédent est une route conventionnelle dédiée. Il s’agit d’une route conventionnelle dédiée pour les raisons suivantes :

Étant donné que controller et action n’apparaissent pas dans le modèle de route "blog/{*article}" en tant que paramètres :

  • Ils peuvent uniquement avoir les valeurs { controller = "Blog", action = "Article" }par défaut .
  • Cette route est toujours mappée à l’action BlogController.Article.

/Blog, /Blog/Articleet /Blog/{any-string} sont les seuls chemins d’URL qui correspondent à la route du blog.

L’exemple précédent :

  • blog la route a une priorité plus élevée pour les correspondances que la routedefault , car elle est ajoutée en premier.
  • Il s’agit d’un exemple de routage de style Slug où il est courant d’avoir un nom d’article dans l’URL.

Avertissement

Dans ASP.NET Core 3.0 et versions ultérieures, le routage ne :

  • Définit pas un concept appelé route. UseRouting ajoute la correspondance de routage au pipeline d’intergiciels. L’UseRoutingintergiciel examine l’ensemble des points de terminaison définis dans l’application et sélectionne la meilleure correspondance des points de terminaison en fonction de la requête.
  • Fournissez des garanties sur l’ordre d’exécution de l’extensibilité, comme IRouteConstraint ou IActionConstraint.

Consultez Routage pour obtenir des informations de référence sur le routage.

Ordre de routage conventionnel

Le routage conventionnel correspond uniquement à une combinaison d’action et de contrôleur qui sont définis par l’application. Ceci est conçu pour simplifier les cas où des routes conventionnelles se chevauchent. Ajouter des routes en utilisant MapControllerRoute, MapDefaultControllerRoute et MapAreaControllerRoute attribuent automatiquement une valeur de l’ordre à leurs points de terminaison en fonction de l’ordre qu’ils appellent. Les correspondances d’une route qui apparaît précédemment ont une priorité plus élevée. Le routage conventionnel est dépendant de l’ordre. En général, les routes avec des zones doivent être placées plus haut, car elles sont plus spécifiques que les routes sans zone. Les routes classiques dédiées avec des paramètres de route fourre-tout comme {*article} peuvent rendre un itinéraire trop gourmand, ce qui signifie qu’il correspond aux URL que vous avez l’intention de mettre en correspondance avec d’autres routes. Placez les routes globales plus loin dans la table de routage pour éviter les correspondances globales.

Avertissement

Un paramètre catch-all peut faire correspondre les mauvais routages en raison d’un bogue dans le routage. Les applications affectées par ce bogue présentent les caractéristiques suivantes :

  • Un routage catch-all, par exemple, {**slug}"
  • Le routage catch-all ne fait pas correspondre les demandes qu’il doit faire correspondre.
  • La suppression d’autres routes fait que la route catch-all commence à fonctionner.

Consultez les bogues GitHub 18677 et 16579, par exemple les cas qui ont rencontré ce bogue.

Un correctif d’opt-in pour ce bogue est contenu dans le Kit de développement logiciel (SDK) .NET Core 3.1.301 et versions ultérieures. Le code suivant définit un commutateur interne qui corrige ce bogue :

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

Résoudre les actions ambiguës

Lorsque deux points de terminaison correspondent via le routage, le routage doit effectuer l’une des opérations suivantes :

  • Choisissez le meilleur candidat.
  • Levée d'une exception.

Par exemple :

    public class Products33Controller : Controller
    {
        public IActionResult Edit(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }

        [HttpPost]
        public IActionResult Edit(int id, Product product)
        {
            return ControllerContext.MyDisplayRouteInfo(id, product.name);
        }
    }
}

Le contrôleur précédent définit deux actions qui correspondent :

  • Le chemin d’accès d’URL /Products33/Edit/17
  • Données de route { controller = Products33, action = Edit, id = 17 }.

Il s’agit d’un modèle classique pour les contrôleurs MVC :

  • Edit(int) affiche un formulaire pour modifier un produit.
  • Edit(int, Product) traite le formulaire publié.

Pour résoudre la route correcte :

  • Edit(int, Product) est sélectionné lorsque la requête est un http POST.
  • Edit(int) est sélectionné lorsque le verbe HTTP est autre chose. Edit(int) est généralement appelé par le biais de GET.

Le HttpPostAttribute, [HttpPost], est fourni pour le routage afin qu’il puisse choisir en fonction de la méthode HTTP de la requête. Le HttpPostAttribute fait Edit(int, Product) une meilleure correspondance que Edit(int).

Il est important de comprendre le rôle des attributs tels que HttpPostAttribute. Des attributs similaires sont définis pour d’autres verbes HTTP. Dans le routage conventionnel, il est courant que les actions utilisent le même nom d’action lorsqu’elles font partie d’un flux de travail de type montrer le formulaire, envoyer le formulaire. Par exemple, consultez Examiner les deux méthodes d’action Modifier.

Si le routage ne peut pas choisir un meilleur candidat, un AmbiguousMatchException est levée, répertoriant les plusieurs points de terminaison correspondants.

Noms de routes conventionnelles

Les chaînes "blog" et "default" dans les exemples suivants sont des noms de routes conventionnelles :

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.MapControllerRoute(name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
});

Les noms de routes donnent à la route un nom logique. La route nommée peut être utilisée pour la génération d’URL. Utiliser une route nommée simplifie considérablement la création d’URL quand l’ordonnancement des routes peut rendre compliquée la génération des URL. Les noms de routes doivent être unique à l’échelle de l’application.

Noms des routes :

  • N’ont aucun impact sur la correspondance d’URL ou la gestion des requêtes.
  • Sont utilisés uniquement pour la génération d’URL.

Le concept de nom de route est représenté dans le routage sous la forme IEndpointNameMetadata. Les termes nom de la route et nom du point de terminaison :

  • Sont interchangeables.
  • Celui qui est utilisé dans la documentation et le code dépend de l’API décrite.

Routage des attributs pour REST les API

Les API REST doivent utiliser le routage d’attributs pour modéliser les fonctionnalités de l’application sous la forme d’un ensemble de ressources dans lequel les opérations sont représentées par des verbes HTTP.

Le routage par attributs utilise un ensemble d’attributs pour mapper les actions directement aux modèles de routes. Le StartUp.Configurecode suivant est classique pour une REST API et est utilisé dans l’exemple suivant :

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

Dans le code précédent, MapControllers est appelé à l’intérieur de UseEndpoints pour mapper les contrôleurs routés d’attribut.

Dans l’exemple suivant :

  • HomeController correspond à un ensemble d’URL similaires à {controller=Home}/{action=Index}/{id?} routes conventionnelles par défaut.
public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

L’action HomeController.Index sera exécutée pour tous les chemins d’accès d’URL /, /Home, /Home/Index ou /Home/Index/3.

Cet exemple met en évidence une différence importante en termes de programmation entre le routage par attributs et le routage conventionnel. Le routage d’attributs nécessite plus d’entrée pour spécifier une route. La route par défaut conventionnel gère les routes de manière plus succincte. Cependant, le routage par attributs permet (et nécessite) un contrôle précis des modèles de routes qui s’appliquent à chaque action.

Avec le routage d’attributs, les noms du contrôleur et des actions ne jouent aucun rôle dans lequel l’action est mise en correspondance, sauf si le remplacement de jeton est utilisé. L’exemple suivant correspond aux mêmes URL que l’exemple précédent :

public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Le code suivant utilise le remplacement de jeton pour action et controller :

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("[controller]/[action]")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("[controller]/[action]")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Le code suivant applique le contrôleur [Route("[controller]/[action]")] :

[Route("[controller]/[action]")]
public class HomeController : Controller
{
    [Route("~/")]
    [Route("/Home")]
    [Route("~/Home/Index")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Dans le code précédent, les modèles de Index méthode doivent être ajoutés / ou ~/ aux modèles d’itinéraire. Les modèles de routes appliqués à une action qui commencent par / ou ~/ ne sont pas combinés avec les modèles de routes appliqués au contrôleur.

Pour plus d’informations sur la sélection du modèle de route, consultez Priorité du modèle de route.

Noms de routage réservés

Les mots clés suivants sont des noms de paramètres de route réservés lors de l’utilisation de contrôleurs ou Razor de pages :

  • action
  • area
  • controller
  • handler
  • page

L’utilisation page comme paramètre d’itinéraire avec le routage d’attributs est une erreur courante. Cela entraîne un comportement incohérent et confus avec la génération d’URL.

public class MyDemo2Controller : Controller
{
    [Route("/articles/{page}")]
    public IActionResult ListArticles(int page)
    {
        return ControllerContext.MyDisplayRouteInfo(page);
    }
}

Les noms de paramètres spéciaux sont utilisés par la génération d’URL pour déterminer si une opération de génération d’URL fait référence à une Razor page ou à un contrôleur.

Les mots clés suivants sont réservés dans le contexte d’une Razor vue ou d’une Razor page :

  • page
  • using
  • namespace
  • inject
  • section
  • inherits
  • model
  • addTagHelper
  • removeTagHelper

Ces mots clés ne doivent pas être utilisés pour les générations de liens, les paramètres liés au modèle ou les propriétés de niveau supérieur.

Modèles de verbes HTTP

ASP.NET Core a les modèles de verbes HTTP suivants :

Modèles de route

ASP.NET Core a les modèles d’itinéraire suivants :

Routage par attributs avec des attributs du verbe Http

Examinons le contrôleur ci-dessous :

[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
    [HttpGet]   // GET /api/test2
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int/{id:int}")] // GET /api/test2/int/3
    public IActionResult GetIntProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
    public IActionResult GetInt2Product(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Dans le code précédent :

  • Chaque action contient l’attribut [HttpGet], qui limite la correspondance aux requêtes HTTP GET uniquement.
  • L’action GetProduct inclut le "{id}" modèle et est donc id ajoutée au "api/[controller]" modèle sur le contrôleur. Le modèle de méthodes est "api/[controller]/{id}". Par conséquent, cette action ne correspond qu’aux demandes GET pour le formulaire /api/test2/xyz,/api/test2/123,/api/test2/{any string}, etc.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • L’action GetIntProduct contient le modèle "int/{id:int}". La :int partie du modèle limite les valeurs de routage id aux chaînes qui peuvent être converties en entier. Une requête d’obtention pour /api/test2/int/abc :
    • Ne correspond pas à cette action.
    • Retourne une erreur 404 Introuvable.
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • L’action GetInt2Product contient {id} dans le modèle, mais ne limite id pas les valeurs qui peuvent être converties en entier. Une requête d’obtention pour /api/test2/int2/abc :
    • Correspond à cette route.
    • La liaison de données ne parvient pas à convertir abc en entier. Le paramètre id de la méthode est entier.
    • Retourne une requête incorrecte 400, car la liaison de données n’a pas pu convertir abc en entier.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

Le routage par attributs peut utiliser des attributs HttpMethodAttribute tels que HttpPostAttribute, HttpPutAttribute, et HttpDeleteAttribute. Tous les attributs de verbe HTTP acceptent un modèle de route. L’exemple suivant montre deux actions qui correspondent au même modèle de route :

[ApiController]
public class MyProductsController : ControllerBase
{
    [HttpGet("/products3")]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpPost("/products3")]
    public IActionResult CreateProduct(MyProduct myProduct)
    {
        return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
    }
}

À l’aide du chemin d’accès d’URL /products3 :

  • L’action MyProductsController.ListProducts s’exécute lorsque le verbe HTTP est GET.
  • L’action MyProductsController.CreateProduct s’exécute lorsque le verbe HTTP est POST.

Lors de la génération d’une REST API, il est rare que vous deviez utiliser [Route(...)] sur une méthode d’action, car l’action accepte toutes les méthodes HTTP. Il est préférable d’utiliser les attributs des verbes HTTP plus spécifiques pour plus de précision quant à ce qui est pris en charge par votre API. Les clients des REST API doivent normalement connaître les chemins et les verbes HTTP qui correspondent à des opérations logiques spécifiques.

REST API doivent utiliser le routage d’attributs pour modéliser les fonctionnalités de l’application sous la forme d’un ensemble de ressources dans lequel les opérations sont représentées par des verbes HTTP. Cela signifie que plusieurs opérations (comme GET et POST) sur la même ressource logique utilisent la même URL. Le routage d’attributs fournit le niveau de contrôle nécessaire pour concevoir avec soin la disposition des points de terminaison publics d’une API.

Dans la mesure où une route d’attribut s’applique à une action spécifique, il est facile de placer les paramètres nécessaires dans la définition du modèle de route. Dans l’exemple suivant, id est obligatoire dans le chemin d’accès d’URL :

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

L’action Products2ApiController.GetProduct(int) :

  • Est exécuté avec le chemin d’accès d’URL comme /products2/3
  • N’est pas exécuté avec le chemin d’accès d’URL /products2.

L’attribut [Consumes] permet à une action de limiter les types de contenu de la demande pris en charge. Pour plus d’informations, consultez Définir les types de contenu de la requête pris en charge avec l’attribut [Consumes].

Consultez Routage pour obtenir une description complète des modèles de routes et des options associées.

Pour plus d’informations sur [ApiController], consultez Attribut ApiController.

Nom de l’itinéraire

Le code suivant définit un nom de route Products_List :

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Les noms de routes peuvent être utilisés pour générer une URL basée sur une route spécifique. Noms des routes :

  • N’ont aucun impact sur le comportement de correspondance d’URL du routage.
  • Sont utilisés uniquement pour la génération d’URL.

Les noms de routes doivent être unique à l’échelle de l’application.

Comparez le code précédent avec la route par défaut conventionnelle, qui définit le paramètre id comme étant facultatif ({id?}). La possibilité de spécifier les API avec précision présente des avantages, par exemple de permettre de diriger /products et /products/5 vers des actions différentes.

Combiner des routes d’attributs

Pour rendre le routage par attributs moins répétitif, les attributs de route sont combinés avec des attributs de route sur les actions individuelles. Les modèles de routes définis sur le contrôleur sont ajoutés à des modèles de routes sur les actions. Placer un attribut de route sur le contrôleur a pour effet que toutes les actions du contrôleur utilisent le routage par attributs.

[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
    [HttpGet]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Dans l’exemple précédent :

  • Le chemin d’accès de /products l’URL peut correspondre ProductsApi.ListProducts
  • Le chemin d’accès de /products/5 l’URL peut correspondre ProductsApi.GetProduct(int).

Ces deux actions correspondent seulement à HTTP GET, car elles sont marquées avec l’attribut [HttpGet].

Les modèles de routes appliqués à une action qui commencent par / ou ~/ ne sont pas combinés avec les modèles de routes appliqués au contrôleur. L’exemple suivant met en correspondance avec un ensemble de chemins d’accès d’URL similaires à la route par défaut.

[Route("Home")]
public class HomeController : Controller
{
    [Route("")]
    [Route("Index")]
    [Route("/")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("About")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Le tableau suivant explique les [Route] attributs du code précédent :

Attribut Combine avec [Route("Home")] Définit le modèle de route
[Route("")] Oui "Home"
[Route("Index")] Oui "Home/Index"
[Route("/")] Non ""
[Route("About")] Oui "Home/About"

Ordre de route des attributs

Le routage génère une arborescence et correspond à tous les points de terminaison simultanément :

  • Les entrées de route se comportent comme si elles sont placées dans un ordre idéal.
  • Les routes les plus spécifiques ont une chance d’être exécutées avant les routes plus générales.

Par exemple, une route d’attribut comme blog/search/{topic} est plus spécifique qu’une route d’attribut comme blog/{*article}. La route blog/search/{topic} a une priorité plus élevée, par défaut, car elle est plus spécifique. Avec le routage conventionnel, le développeur est responsable du placement des routes dans l’ordre souhaité.

Les routes d’attributs peuvent configurer un ordre à l’aide de la propriété Order. Tous les attributs de route fournis par l’infrastructure incluent Order . Les routes sont traitées selon un ordre croissant de la propriété Order. L’ordre par défaut est 0. La définition d’une route avec Order = -1 fait que cette route s’exécute avant les routes qui ne définissent pas d’ordre. La définition d’une route avec Order = 1 fait que cette route s’exécute après l’ordre des routes par défaut.

Évitez de dépendre de Order. Si l’espace d’URL d’une application nécessite des valeurs d’ordre explicites pour router correctement, il est probable qu’il prête également à confusion pour les clients. D’une façon générale, le routage par attributs sélectionne la route correcte avec la mise en correspondance d’URL. Si l’ordre par défaut utilisé pour la génération d’URL ne fonctionne pas, l’utilisation à titre de remplacement d’un nom de route est généralement plus simple que d’appliquer la propriété Order.

Considérez les deux contrôleurs suivants qui définissent tous deux la correspondance /homede route :

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}
public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

La requête /home avec le code précédent lève une exception similaire à ce qui suit :

AmbiguousMatchException: The request matched multiple endpoints. Matches:

 WebMvcRouting.Controllers.HomeController.Index
 WebMvcRouting.Controllers.MyDemoController.MyIndex

L’ajout Order à l’un des attributs de route résout l’ambiguïté :

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
    return ControllerContext.MyDisplayRouteInfo();
}

Avec le code précédent, /home exécute le point de HomeController.Index terminaison. Pour accéder à MyDemoController.MyIndex, demandez /home/MyIndex. Remarque :

  • Le code précédent est un exemple ou une conception de routage médiocre. Il a été utilisé pour illustrer la propriété Order.
  • La propriété Order résout uniquement l’ambiguïté. Ce modèle ne peut pas être mis en correspondance. Il serait préférable de supprimer le modèle [Route("Home")].

Pour plus d’informations sur l’ordre d’itinéraire avec Razor Pages, consultez Conventions de route et d’application :Razor Ordre de routage.

Dans certains cas, une erreur HTTP 500 est retournée avec des routes ambiguës. Utilisez la journalisation pour voir quels points de terminaison sont à l’origine de AmbiguousMatchException.

Remplacement de jetons dans les modèles de routes [contrôleur], [action], [zone]

Pour plus de commodité, les routes d’attribut prennent en charge le remplacement de jetons, qui se fait via la mise entre crochets d’un jeton ([, ]). Les jetons [action], [area] et [controller] sont remplacés par les valeurs du nom d’action, du nom de la zone et du nom du contrôleur de l’action où la route est définie :

[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
    [HttpGet]
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }


    [HttpGet("{id}")]
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Dans le code précédent :

[HttpGet]
public IActionResult List()
{
    return ControllerContext.MyDisplayRouteInfo();
}
  • Correspondances /Products0/List
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • Correspondances /Products0/Edit/{id}

Le remplacement des jetons se produit à la dernière étape de la création des routes d’attribut. L’exemple précédent se comporte de la même manière que le code suivant :

public class Products20Controller : Controller
{
    [HttpGet("[controller]/[action]")]  // Matches '/Products20/List'
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("[controller]/[action]/{id}")]   // Matches '/Products20/Edit/{id}'
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Si vous lisez ceci dans une autre langue que l’anglais, faites-le nous savoir dans ce problème de discussion GitHub si vous souhaitez voir les commentaires de code dans votre langue maternelle.

Les routes d’attribut peuvent aussi être combinées avec l’héritage. Combiné avec le remplacement de jetons, c’est puissant. Le remplacement des jetons s’applique aussi aux noms de routes définis par des routes d’attribut. [Route("[controller]/[action]", Name="[controller]_[action]")] génère un nom de route unique pour chaque action :

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}

public class Products11Controller : MyBase2Controller
{
    [HttpGet]                      // /api/products11/list
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]             //    /api/products11/edit/3
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Pour faire correspondre le délimiteur littéral de remplacement de jetons [ ou ], placez-le en échappement en répétant le caractère ([[ ou ]]).

Utiliser un transformateur de paramètre pour personnaliser le remplacement des jetons

Le remplacement des jetons peut être personnalisé à l’aide d’un transformateur de paramètre. Un transformateur de paramètre implémente IOutboundParameterTransformer et transforme la valeur des paramètres. Par exemple, un transformateur de paramètre SlugifyParameterTransformer personnalisé transforme la valeur de la route SubscriptionManagement en subscription-management :

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string TransformOutbound(object value)
    {
        if (value == null) { return null; }

        return Regex.Replace(value.ToString(),
                             "([a-z])([A-Z])",
                             "$1-$2",
                             RegexOptions.CultureInvariant,
                             TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
    }
}

RouteTokenTransformerConvention est une convention de modèle d’application qui :

  • Applique un transformateur de paramètre à toutes les routes d’attribut dans une application.
  • Personnalise les valeurs de jeton de route d’attribut quand elles sont remplacées.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

La méthode ListAllprécédente correspond à /subscription-management/list-all.

RouteTokenTransformerConvention est inscrit en tant qu’option dans ConfigureServices.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
    {
        options.Conventions.Add(new RouteTokenTransformerConvention(
                                     new SlugifyParameterTransformer()));
    });
}

Consultez la documentation web MDN sur Slug pour la définition de Slug.

Avertissement

Lorsque vous utilisez System.Text.RegularExpressions pour traiter une entrée non approuvée, passez un délai d’expiration. Un utilisateur malveillant peut fournir une entrée à RegularExpressions, provoquant une attaque par déni de service. Les API d’infrastructure ASP.NET Core qui utilisent RegularExpressions passent un délai d’expiration.

Routes d’attribut multiples

Le routage par attributs prend en charge la définition de plusieurs routes pour atteindre la même action. L’utilisation la plus courante de ceci est d’imiter le comportement de la route conventionnelle par défaut, comme le montre l’exemple suivant :

[Route("[controller]")]
public class Products13Controller : Controller
{
    [Route("")]     // Matches 'Products13'
    [Route("Index")] // Matches 'Products13/Index'
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

Le fait de placer plusieurs attributs de route sur le contrôleur signifie que chacun d’eux se combine avec chacun des attributs de route sur les méthodes d’action :

[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
    [HttpPost("Buy")]       // Matches 'Products6/Buy' and 'Store/Buy'
    [HttpPost("Checkout")]  // Matches 'Products6/Checkout' and 'Store/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Toutes les contraintes de routes de verbe HTTP implémentent IActionConstraint.

Lorsque plusieurs attributs de route qui implémentent IActionConstraint sont placés sur une action :

  • Chaque contrainte d’action se combine avec le modèle de route appliqué au contrôleur.
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
    [HttpPut("Buy")]        // Matches PUT 'api/Products7/Buy'
    [HttpPost("Checkout")]  // Matches POST 'api/Products7/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

L’utilisation de plusieurs routes sur des actions peut sembler utile et puissante. Il est préférable de conserver l’espace URL de votre application de base et bien défini. Utilisez plusieurs routes sur les actions seulement là où c’est nécessaire, par exemple pour prendre en charge des clients existants.

Spécification facultative de paramètres, de valeurs par défaut et de contraintes pour les routes d’attribut

Les routes d’attribut prennent en charge la même syntaxe inline que les routes conventionnelles pour spécifier des paramètres, des valeurs par défaut et des contraintes facultatifs.

public class Products14Controller : Controller
{
    [HttpPost("product14/{id:int}")]
    public IActionResult ShowProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Dans le code précédent, [HttpPost("product14/{id:int}")] applique une contrainte de route. L’action Products14Controller.ShowProduct est mise en correspondance uniquement par des chemins d’accès d’URL tels que /product14/3. La partie {id:int} modèle de routage limite ce segment à des entiers uniquement.

Pour une description détaillée de la syntaxe du modèle de route, consultez Informations de référence sur le modèle de route.

Attributs de route personnalisés à l’aide de IRouteTemplateProvider

Tous les attributs de route implémentent IRouteTemplateProvider. Le Runtime ASP.NET Core :

  • Recherche des attributs sur les classes de contrôleur et les méthodes d’action au démarrage de l’application.
  • Utilise les attributs qui implémentent IRouteTemplateProvider pour générer l’ensemble initial de routes.

Implémentez IRouteTemplateProvider pour définir des attributs de route personnalisés. Chaque IRouteTemplateProvider vous permet de définir une route avec un modèle, un nom et un ordre de route personnalisés :

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
    public string Template => "api/[controller]";
    public int? Order => 2;
    public string Name { get; set; }
}

[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
    // GET /api/MyTestApi
    [HttpGet]
    public IActionResult Get()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

La méthode précédente Get retourne Order = 2, Template = api/MyTestApi.

Utiliser le modèle d’application pour personnaliser les routes des attributs

L’ancien modèle d’application :

  • est un modèle objet créé au démarrage.
  • Contient toutes les métadonnées utilisées par ASP.NET Core pour router et exécuter les actions dans une application.

Le modèle d’application inclut toutes les données collectées à partir des attributs de route. Les données des attributs de routage sont fournies par l’implémentation IRouteTemplateProvider. Conventions :

  • Peut être écrit pour modifier le modèle d’application afin de personnaliser le comportement du routage.
  • Sont lus au démarrage de l’application.

Cette section montre un exemple de base de personnalisation du routage avec le modèle d’application. Le code suivant aligne approximativement les routes avec la structure de dossiers du projet.

public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
    private readonly string _baseNamespace;

    public NamespaceRoutingConvention(string baseNamespace)
    {
        _baseNamespace = baseNamespace;
    }

    public void Apply(ControllerModel controller)
    {
        var hasRouteAttributes = controller.Selectors.Any(selector =>
                                                selector.AttributeRouteModel != null);
        if (hasRouteAttributes)
        {
            return;
        }

        var namespc = controller.ControllerType.Namespace;
        if (namespc == null)
            return;
        var template = new StringBuilder();
        template.Append(namespc, _baseNamespace.Length + 1,
                        namespc.Length - _baseNamespace.Length - 1);
        template.Replace('.', '/');
        template.Append("/[controller]/[action]/{id?}");

        foreach (var selector in controller.Selectors)
        {
            selector.AttributeRouteModel = new AttributeRouteModel()
            {
                Template = template.ToString()
            };
        }
    }
}

Le code suivant empêche l’application de la namespace convention aux contrôleurs qui sont routés par l’attribut :

public void Apply(ControllerModel controller)
{
    var hasRouteAttributes = controller.Selectors.Any(selector =>
                                            selector.AttributeRouteModel != null);
    if (hasRouteAttributes)
    {
        return;
    }

Par exemple, le contrôleur suivant n’utilise pas NamespaceRoutingConvention :

[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
    // /managers/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        return Content($"Index- template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

La méthode NamespaceRoutingConvention.Apply :

  • Ne fait rien si le contrôleur est routé par l’attribut.
  • Définit le modèle de contrôleurs en fonction de namespace, avec la base namespace supprimée.

Le NamespaceRoutingConvention peut être appliqué dans Startup.ConfigureServices :

namespace My.Application
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews(options =>
            {
                options.Conventions.Add(
                    new NamespaceRoutingConvention(typeof(Startup).Namespace));
            });
        }
        // Remaining code ommitted for brevity.

Par exemple, prenons le contrôleur suivant :

using Microsoft.AspNetCore.Mvc;

namespace My.Application.Admin.Controllers
{
    public class UsersController : Controller
    {
        // GET /admin/controllers/users/index
        public IActionResult Index()
        {
            var fullname = typeof(UsersController).FullName;
            var template = 
                ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
            var path = Request.Path.Value;

            return Content($"Path: {path} fullname: {fullname}  template:{template}");
        }

        public IActionResult List(int? id)
        {
            var path = Request.Path.Value;
            return Content($"Path: {path} ID:{id}");
        }
    }
}

Dans le code précédent :

  • La base namespace est My.Application.
  • Le nom complet du contrôleur précédent est My.Application.Admin.Controllers.UsersController.
  • Le NamespaceRoutingConvention définit le modèle de contrôleurs sur Admin/Controllers/Users/[action]/{id?.

Le NamespaceRoutingConvention peut également être appliqué en tant qu’attribut sur un contrôleur :

[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
    // /admin/controllers/test/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        var actionname = ControllerContext.ActionDescriptor.ActionName;
        return Content($"Action- {actionname} template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

Routage mixte : routage conventionnel et routage par attributs

Les applications ASP.NET Core peuvent combiner l’utilisation du routage conventionnel et du routage par attributs. Il est courant d’utiliser des routes conventionnelles pour les contrôleurs délivrant des pages HTML pour les navigateurs, et le routage par attributs pour les contrôleurs délivrant des API REST.

Les actions sont routées de façon conventionnelle ou routées par attribut. Le fait de placer une route sur le contrôleur ou sur l’action les rend « routés par attribut ». Les actions qui définissent des routes d’attribut ne sont pas accessibles via les routes conventionnelles et vice versa. Tout attribut de route sur le contrôleur a pour effet que toutes les actions du contrôleur sont routées par attributs.

Le routage d’attributs et le routage conventionnel utilisent le même moteur de routage.

Génération d’URL et valeurs ambiantes

Les applications peuvent utiliser les fonctionnalités de génération d’URL de routage pour générer des liens URL vers des actions. La génération d’URL élimine le codage en dur des URL, ce qui rend un code plus robuste et plus facile à maintenir. Cette section se concentre sur les fonctionnalités de génération d’URL fournies par MVC et couvre seulement les principes de base du fonctionnement de la génération d’URL. Pour une description détaillée de la génération d’URL, consultez Routage.

L’interface IUrlHelper est l’élément d’infrastructure sous-jacent entre MVC et le routage pour la génération d’URL. Une instance de IUrlHelper est disponible par le biais de la propriété Url dans les contrôleurs, les vues et les composants de vue.

Dans l’exemple suivant, l’interface IUrlHelper est utilisée par le biais de la propriété Controller.Url pour générer une URL vers une autre action.

public class UrlGenerationController : Controller
{
    public IActionResult Source()
    {
        // Generates /UrlGeneration/Destination
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Si l’application utilise la route conventionnelle par défaut, la valeur de la variable url est la chaîne de chemin d’URL /UrlGeneration/Destination. Ce chemin d’accès d’URL est créé par routage en combinant :

  • Valeurs de routage de la requête actuelle, appelées valeurs ambiantes.
  • Valeurs transmises à Url.Action et en remplaçant ces valeurs dans le modèle de route :
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

La valeur de chaque paramètre de route du modèle de route est remplacée en établissant une correspondance avec les valeurs et les valeurs ambiantes. Un paramètre de route qui n’a pas de valeur peut :

  • Utilisez une valeur par défaut s’il en a une.
  • Être ignoré s’il est facultatif. Par exemple, le id à partir du modèle de route{controller}/{action}/{id?}.

La génération d’URL échoue si un paramètre de route obligatoire n’a pas de valeur correspondante. Si la génération d’URL échoue pour une route, la route suivante est essayée, ceci jusqu’à ce que toutes les routes aient été essayées ou qu’une correspondance soit trouvée.

L’exemple précédent de Url.Action suppose le routage conventionnel. La génération d’URL fonctionne de façon similaire avec le routage par attributs, même si les concepts sont différents. Avec un routage conventionnel :

  • Les valeurs de route sont utilisées pour développer un modèle.
  • Les valeurs de routage pour controller et action s’affichent généralement dans ce modèle. Cela fonctionne, car les URL mises en correspondance par le routage respectent une convention.

L’exemple suivant utilise un attribut de routage :

public class UrlGenerationAttrController : Controller
{
    [HttpGet("custom")]
    public IActionResult Source()
    {
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination")]
    public IActionResult Destination()
    {
       return ControllerContext.MyDisplayRouteInfo();
    }
}

L’action Source dans le code précédent génère custom/url/to/destination.

LinkGeneratora été ajouté dans ASP.NET Core 3.0 comme alternative à IUrlHelper. LinkGenerator offre des fonctionnalités similaires, mais plus flexibles. Chaque méthode sur IUrlHelper a également une famille de méthodes correspondante sur LinkGenerator.

Génération des URL par nom d’action

Url.Action, LinkGenerator.GetPathByAction et toutes les surcharges associées sont tous conçus pour générer le point de terminaison cible en spécifiant un nom de contrôleur et un nom d’action.

Lors de l’utilisation de Url.Action, les valeurs de routage actuelles pour controller et action sont fournies par le runtime :

  • La valeur de controller et action fait partie des valeurs ambiantes et des valeurs. La méthode Url.Action utilise toujours les valeurs actuelles de action et de controller, et génère un chemin d’accès d’URL qui route vers l’action actuelle.

Le routage essaye d’utiliser les valeurs dans les valeurs ambiantes pour renseigner les informations qui n’ont pas été fournies lors de la génération d’une URL. Considérez une route comme {a}/{b}/{c}/{d} avec des valeurs ambiantes { a = Alice, b = Bob, c = Carol, d = David } :

  • Le routage contient suffisamment d’informations pour générer une URL sans valeurs supplémentaires.
  • Le routage contient suffisamment d’informations, car tous les paramètres de route ont une valeur.

Si la valeur { d = Donovan } est ajoutée :

  • La valeur { d = David } est ignorée.
  • Le chemin d’accès d’URL généré est Alice/Bob/Carol/Donovan.

Avertissement : Les chemins d’accès d’URL sont hiérarchiques. Dans l’exemple précédent, si la valeur { c = Cheryl } est ajoutée :

  • Les deux valeurs { c = Carol, d = David } sont ignorées.
  • Il n’y a plus de valeur pour d et la génération d’URL échoue.
  • Les valeurs souhaitées de c et d doivent être spécifiées pour générer une URL.

Vous pouvez vous attendre à rencontrer ce problème avec l’itinéraire {controller}/{action}/{id?}par défaut. Ce problème est rare dans la pratique, car Url.Action spécifie toujours explicitement une controller valeur et action.

Plusieurs surcharges de Url.Action prennent un objet de valeurs d’itinéraire pour fournir des valeurs pour les paramètres de route autres que controller et action. L’objet valeurs de route est fréquemment utilisé avec id. Par exemple : Url.Action("Buy", "Products", new { id = 17 }). Objet de valeurs de route :

  • Par convention est généralement un objet de type anonyme.
  • Il peut s’agir d’un IDictionary<> ou d’un POCO).

Toutes les valeurs de route supplémentaires qui ne correspondent pas aux paramètres de route sont placées dans la chaîne de requête.

public IActionResult Index()
{
    var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
    return Content(url);
}

Le code précédent génère /Products/Buy/17?color=red.

Le code suivant génère une URL absolue :

public IActionResult Index2()
{
    var url = Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme);
    // Returns https://localhost:5001/Products/Buy/17
    return Content(url);
}

Pour créer une URL absolue, utilisez l’une des options suivantes :

  • Une surcharge qui accepte un protocol. Par exemple, le code précédent.
  • LinkGenerator.GetUriByAction, qui génère des URI absolus par défaut.

Générer des URL par route

Le code précédent a montré la génération d’une URL en passant le nom du contrôleur et le nom de l’action. IUrlHelper fournit également la famille de méthodes Url.RouteUrl. Ces méthodes sont similaires à Url.Action, mais elle ne copient pas les valeurs actuelles de action et de controller vers les valeurs de route. Utilisation la plus courante pour Url.RouteUrl :

  • Spécifie un nom d’itinéraire pour générer l’URL.
  • En règle générale, ne spécifie pas de contrôleur ou de nom d’action.
public class UrlGeneration2Controller : Controller
{
    [HttpGet("")]
    public IActionResult Source()
    {
        var url = Url.RouteUrl("Destination_Route");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

Le fichier suivant Razor génère un lien HTML versDestination_Route :

<h1>Test Links</h1>

<ul>
    <li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>

Générer des URL en HTML et Razor

IHtmlHelper fournit les HtmlHelperméthodes Html.BeginForm et Html.ActionLink pour générer <form> et <a> éléments respectivement. Ces méthodes utilisent la méthode Url.Action pour générer une URL et ils acceptent les arguments similaires. Les pendants de Url.RouteUrl pour HtmlHelper sont Html.BeginRouteForm et Html.RouteLink, qui ont des fonctionnalités similaires.

Les TagHelpers génèrent des URL via le TagHelper form et le TagHelper <a>. Ils utilisent tous les deux IUrlHelper pour leur implémentation. Pour plus d’informations, consultez Tag Helpers dans les formulaires .

Dans les vues, IUrlHelper est disponible via la propriété Url pour toute génération d’URL ad hoc non couverte par ce qui figure ci-dessus.

Génération d’URL dans résultats de l’action

Les exemples précédents ont montré l’utilisation IUrlHelper dans un contrôleur. L’utilisation la plus courante dans un contrôleur consiste à générer une URL dans le cadre du résultat d’une action.

Les classes de base ControllerBase et Controller fournissent des méthodes pratiques pour les résultats d’action qui référencent une autre action. Une utilisation typique est de rediriger après acceptation de l’entrée utilisateur :

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
    if (ModelState.IsValid)
    {
        // Update DB with new details.
        ViewData["Message"] = $"Successful edit of customer {id}";
        return RedirectToAction("Index");
    }
    return View(customer);
}

Les méthodes de fabrique de résultats d’action comme RedirectToAction et CreatedAtAction suivent un modèle similaire aux méthodes sur IUrlHelper.

Cas spécial pour les routes conventionnelles dédiées

Le routage conventionnel peut utiliser un type spécial de définition de route appelé route conventionnelle dédiée. Dans l’exemple suivant, la route nommée blog est une route conventionnelle dédiée :

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.MapControllerRoute(name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
});

À l’aide des définitions de route précédentes, Url.Action("Index", "Home") génère le chemin d’accès / d’URL à l’aide de l’itinéraire default , mais pourquoi? Vous pouvez deviner que les valeurs de route { controller = Home, action = Index } seraient suffisantes pour générer une URL avec blog, et que le résultat serait /blog?action=Index&controller=Home.

Les routes conventionnelles dédiées s’appuient sur un comportement spécial des valeurs par défaut qui n’ont pas de paramètre de route correspondant qui empêche la route d’être trop globale avec la génération d’URL. Dans ce cas, les valeurs par défaut sont { controller = Blog, action = Article }, et ni controller ni action n’apparaissent comme paramètre de route. Quand le routage effectue une génération d’URL, les valeurs fournies doivent correspondre aux valeurs par défaut. La génération d’URL avec blog échoue, car les valeurs { controller = Home, action = Index } ne correspondent pas à { controller = Blog, action = Article }. Le routage essaye alors d’utiliser default, ce qui réussit.

Zones (Areas)

Les zones sont une fonctionnalité MVC utilisée pour organiser des fonctionnalités connexes dans un groupe distinct :

  • Espace de noms de routage pour les actions du contrôleur.
  • Structure des dossiers pour les vues.

L’utilisation de zones permet à une application d’avoir plusieurs contrôleurs portant le même nom, pour autant qu’ils soient dans des zones différentes. L’utilisation de zones crée une hiérarchie qui permet le routage par ajout d’un autre paramètre de route, area, à controller et à action. Cette section explique comment le routage interagit avec les zones. Voir Zones pour plus de détails sur l’utilisation des zones dans les vues.

L’exemple suivant configure MVC pour utiliser la route conventionnelle par défaut et une arearoutearea nommée Blog :

app.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute("blog_route", "Blog",
        "Manage/{controller}/{action}/{id?}");
    endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});

Dans le code précédent, MapAreaControllerRoute est appelé pour créer le "blog_route". Le deuxième paramètre, "Blog", est le nom de la zone.

Lors de la correspondance d’un chemin d’URL comme /Manage/Users/AddUser, la route "blog_route" génère les valeurs de route { area = Blog, controller = Users, action = AddUser }. La valeur de route area est générée par une valeur par défaut pour area. La route créée par MapAreaControllerRoute est équivalente à la suivante :

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
        defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
    endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});

MapAreaControllerRoute crée une route avec à la fois une valeur par défaut et une contrainte pour area en utilisant le nom de la zone fournie, dans ce cas Blog. La valeur par défaut garantit que la route produit toujours { area = Blog, ... }, et la contrainte nécessite la valeur { area = Blog, ... } pour la génération d’URL.

Le routage conventionnel est dépendant de l’ordre. En général, les routes avec des zones doivent être placées plus haut, car elles sont plus spécifiques que les routes sans zone.

Avec l’exemple précédent, les valeurs de route { area = Blog, controller = Users, action = AddUser } sont mises en correspondance avec l’action suivante :

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}

L’attribut [Zone] indique qu’un contrôleur fait partie d’une zone. Ce contrôleur se trouve dans la zone Blog. Les contrôleurs sans attribut [Area] ne sont membres d’aucune zone et ne sont pas trouvés en correspondance quand la valeur de route area est fournie par le routage. Dans l’exemple suivant, seul le premier contrôleur répertorié peut correspondre aux valeurs de route { area = Blog, controller = Users, action = AddUser }.

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace2
{
    // Matches { area = Zebra, controller = Users, action = AddUser }
    [Area("Zebra")]
    public class UsersController : Controller
    {
        // GET /zebra/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace3
{
    // Matches { area = string.Empty, controller = Users, action = AddUser }
    // Matches { area = null, controller = Users, action = AddUser }
    // Matches { controller = Users, action = AddUser }
    public class UsersController : Controller
    {
        // GET /users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }
    }
}

L’espace de noms de chaque contrôleur est affiché ici pour l’exhaustivité. Si les contrôleurs précédents utilisaient le même espace de noms, une erreur du compilateur serait générée. Les espaces de noms de classe n’ont pas d’effet sur le routage de MVC.

Les deux premiers contrôleurs sont membres de zones, et ils sont trouvés en correspondance seulement quand le nom de leur zone respective est fourni par la valeur de route area. Le troisième contrôleur n’est membre d’aucune zone et peut être trouvé en correspondance seulement quand aucune valeur pour area n’est fournie par le routage.

En termes de mise en correspondance avec aucune valeur, l’absence de la valeur area est identique à une valeur null ou de chaîne vide pour area.

Lors de l’exécution d’une action à l’intérieur d’une zone, la valeur de route pour area est disponible en tant que valeur ambiante, que le routage peut utiliser pour la génération d’URL. Cela signifie que par défaut, les zones agissent par attraction pour la génération d’URL, comme le montre l’exemple suivant.

app.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute(name: "duck_route", 
                                     areaName: "Duck",
                                     pattern: "Manage/{controller}/{action}/{id?}");
    endpoints.MapControllerRoute(name: "default",
                                 pattern: "Manage/{controller=Home}/{action=Index}/{id?}");
});
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace4
{
    [Area("Duck")]
    public class UsersController : Controller
    {
        // GET /Manage/users/GenerateURLInArea
        public IActionResult GenerateURLInArea()
        {
            // Uses the 'ambient' value of area.
            var url = Url.Action("Index", "Home");
            // Returns /Manage/Home/Index
            return Content(url);
        }

        // GET /Manage/users/GenerateURLOutsideOfArea
        public IActionResult GenerateURLOutsideOfArea()
        {
            // Uses the empty value for area.
            var url = Url.Action("Index", "Home", new { area = "" });
            // Returns /Manage
            return Content(url);
        }
    }
}

Le code suivant génère une URL vers /Zebra/Users/AddUser :

public class HomeController : Controller
{
    public IActionResult About()
    {
        var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
        return Content($"URL: {url}");
    }

Définition d’action

Les méthodes publiques sur un contrôleur, sauf celles qui sont avec l’attribut NonAction, sont des actions.

Exemple de code

Déboguer les diagnostics

Pour obtenir une sortie de diagnostic de routage détaillée, définissez Logging:LogLevel:Microsoft sur Debug. Dans l’environnement de développement, définissez le niveau de journal dans appsettings.Development.json :

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}