ASP.NET MVC a routing

ASP.NET MVC není dodáváno jako jediná assembly System.Web.Mvc, ale spolu s ní dostáváme také System.Web.Abstractions a System.Web.Routing. Právě poslední z nich je zodpovědná za routing, což je důležitá součást ASP.NET MVC. Přitom ale tato assembly nereferencuje System.Web.Mvc, takže routing může být použit i pro klasické WebForms. O tom ale až někdy příště, teď bych se rád podíval na zoubek samotnému routingu jako takovému, což v zásadě není nic složitého.

Když si vytvoříme nový ASP.NET MVC projekt, máme v souboru global.asax.cs takovýto kód:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "Default",                                              // Route name
        "{controller}/{action}/{id}",                           // URL with parameters
        new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
    );
} 

Kód je myslím docela samopopisující – první parametr udává název routovacího pravidla (musí být unikátní). Druhý parametr popisuje samotné routování, které se musí shodovat s URL aktuálního požadavku, aby bylo dané routovací pravidlo použito. Zde můžeme používat ve složených závorkách
parametry, se kterými můžeme dále pracovat.
Třetí parametr udává defaultní hodnoty jednotlivých parametrů. Zajímavý, ale často opomíjený, je čtvrtý (zde nepoužitý) parametr. Má stejnou strukturu jako třetí, tedy jedná se o anonymní objekt, jehož properties se shodují s názvy routovacích parametrů, pouze místo defaultních hodnot zde můžeme specifikovat omezení, jakou hodnotu musí parametr mít, aby se dané routovací pravidlo použilo. Můžeme zadat buď string, který se následně zkusí matchnout jako regulární výraz. Jinou možností je místo stringu zadat instanci třídy implementující rozhraní System.Web.Routing.IRouteConstraint, které nám dává takřka neomezené možnosti.

Extension metoda MapRoute ve svém důsledku přidává další routovací položku (reprezentovanou třídou Route) do kolekce System.Web.Routing.RouteTable.Routes. A co taková routovací položka musí umět? Inu to samé, co jí předepisuje implementovat abstraktní třída RouteBase:

public abstract class RouteBase
{
    public abstract RouteData GetRouteData(HttpContextBase httpContext);
    public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
} 

První metoda se používá tehdy, když nám přijde nějaký požadavek na stránku. De facto zajišťuje překlad z parametrů požadavku (především URL) na kolekci informací pro routování.
Toto se děje díky HTTP modulu System.Web.Routing.UrlRoutingModule, jehož aktivace je automaticky obsažena ve web.config. Tento modul volá na property System.Web.Routing.RouteTable.Routes metodu GetRouteData, která pouze proiteruje všechny routovací položky a zavolá na nich metodu s týmž jménem. Iterace se zastaví, když se narazí na první položku, která vrátí nenullovou hodnotu. Pořadí položek v routovací tabulce je tedy velmi důležité!

Za povšimnutí stojí, že metoda GetRouteData vrací třídu RouteData, jenž má položku RouteHandler typu IRouteHandler, což je rozhraní s jedinou metodou metodou GetHttpHandler. Často používaná extension metoda MapRoute inicializuje položku RouteHandler na novou instanci třídy System.Web.Mvc.MvcRouteHandler, která ve své jediné metodě vrací instanci třídy System.Web.Mvc.MvcHandler.
Právě toto je jedno z důležitých místo, kde dochází k propojení ASP.NET MVC a routingu.

Druhá metoda routovacího záznamu, GetVirtualPath, se používá při opačném procesu – tedy když chceme z routovacích dat získat URL. To se typicky děje při generování URL z routovacích dat, což jsou v případě ASP.NET MVC především údaje o kontroleru a akci. Když se podíváme např. na to, jak funguje obligátní metoda Html.ActionLink, tak zjistíme, že volá metodu System.Web.Mvc.UrlHelper.GenerateUrl, která dále volá na property System.Web.Routing.RouteTable.Routes metodu GetVirtualPath, která opět pouze proiteruje všechny routovací záznamy a vrátí první nenullový výsledek.

Lidé často chtějí umístit část webu, která se týká administrace, do části webu s prefixem „Admin/„. Upraví tedy routing následujícím způsobem:

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
    "Admin",                                                // Route name
    "Admin/{controller}/{action}/{id}",                     // URL with parameters
    new { controller = "AdminHome", action = "Index", id = "" }  // Parameter defaults
);

routes.MapRoute(
    "Default",                                              // Route name
    "{controller}/{action}/{id}",                           // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
); 

Když nám přijde tedy požadavek na stránku začínající na „Admin/„, tak se použije pravidlo Admin a všechno je v pohodě.
Problém ale nastává tehdy, když chceme generovat odkazy. Když totiž dochází k iteraci routovacích položek, tak zde není žádný důvod, proč nepoužít první položku. A tak se použije. A místo např. „Home/Detail/1“ dostaneme „Admin/Home/Detail/1„, což se nám moc nelíbí.

První řešení je rychlé, ale není moc pěkné. Jednoduše přidáme do routovacího pravidla Admin takové omezení, které jednoduše vyjmenuje všechny kontrolery, které se v admin části webu používají:

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
    "Admin",                                                // Route name
    "Admin/{controller}/{action}/{id}",                     // URL with parameters
    new { controller = "AdminHome", action = "Index", id = "" },// Parameter defaults
    new { controller = "AdminHome|AdminUpdate" }            // Parameter constraints
);

routes.MapRoute(
    "Default",                                              // Route name
    "{controller}/{action}/{id}",                           // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
); 

Jak jsem psal, metoda Html.ActionLink volá metodu System.Web.Mvc.UrlHelper.GenerateUrl, která se dále stará o vygenerování URL. Metoda GenerateUrl má jeden parametr, který je nyní pro nás důležitý – routeName. Pomocí něho můžeme specifikovat, jaké konkrétní routovací pravidlo se má použít. Všechny varianty metody Html.ActionLink předávají tento parametr jako null, takže si můžeme napsat vlastní extension metody v následujícím stylu:

public static string BetterActionLink(this HtmlHelper htmlHelper, string routeName, string linkText, string actionName)
{
    if (string.IsNullOrEmpty(linkText))
    {
        throw new ArgumentNullException("linkText");
    }
    if (string.IsNullOrEmpty(actionName))
    {
        throw new ArgumentNullException("actionName");
    }
    string requiredString = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");
    return htmlHelper.GenerateLink(linkText, routeName, actionName, requiredString, new RouteValueDictionary(), new RouteValueDictionary());
} 

Díky takovéto extension metodě můžeme generovat linky s použitím námi zadaného routovacího pravidla. Místo vlastních extension metod ale doporučuji použít již existující Html.RouteLink, které umožňují specifikaci routovacího pravidla, které se má použít 😉

Zanechat odpověď

Vyplňte detaily níže nebo klikněte na ikonu pro přihlášení:

Logo WordPress.com

Komentujete pomocí vašeho WordPress.com účtu. Odhlásit /  Změnit )

Facebook photo

Komentujete pomocí vašeho Facebook účtu. Odhlásit /  Změnit )

Připojování k %s

Tento web používá Akismet na redukci spamu. Zjistěte více o tom, jak jsou data z komentářů zpracovávána.