ASP.NET MVC – lokalizace url

Na dnešním internetu je důležité, aby url měly ten správný tvar, a díky tomu je do jisté míry můžeme považovat za součást prezentační vrstvy. Určitě ale nechceme, aby nějaké stringy z prezentační vrstvy ovlivňovaly to, jak se budou jmenovat naše třídy nebo metody v aplikaci. Ale právě přesně to se v ASP.NET MVC děje – adresa ve tvaru „/controllerName/actionName“ je typicky mapována na akční metodu s názvem „actionName“ ve třídě „controllerNameController“. No ale kdo by chtěl mít metodu s názvem „追加する“? A co na to lokalizace?

Ti zkušenější z vás jistě vědí, že v případě akčních metod se dá toto vyřešit pomocí atributu ActionName. Pokud nám jde o jednorázové přejmenování, tak je toto řešení v pohodě. Pokud je naším cílem ale nějaké obecnější schéma přejmenování, pak s ActionName nevystačíme. Pokud např. děláme web v češtině a názvy controllerů a akčních metod chceme mít anglicky, tak s ActionName vystačíme.

Předpokládejme teď ale situaci, kdy web má pro více jazyků stejnou strukturu, liší se jen obsah a url. Pak jistě nebudeme chtít mít controllery s názvy Product, Produkt, Producto atd. a podobně pro akční metody. Tedy vnitřek aplikace bude pro všechny jazyky stejný, lišit se budou pouze url (lokalizaci obsahu teď řešit nebudeme). A abychom nemuseli mít uvnitř aplikace code-path pro každý jazyk, bylo by pro nás nejvýhodnější převést problém na předchozí případ, tedy pracovat jen s jedněmi názvy. Naším cílem je tedy jakási normalizace url do standardního tvaru.

Ve webové aplikaci většinou pracujeme s url na dvou místech – hned na začátku zpracování požadavku (abychom věděli, co vůbec dělat, jaký controller a akční metodu vybrat) a při generování url. Obě tyto věci má pod palcem routing, takže si napíšeme vlastní routovací pravidlo, což je velmi jednoduché. Odvodíme třídu od standardní System.Routing.Route a přetížíme metody GetRouteData (používá se na začátku zpracování požadavku) a GetVirtualPath (používá se při generování url), ve kterých zajistíme nahrazení lokalizovaného stringu za normalizovaný, resp. opačně. Jde nám tedy o to, abychom po vstupu url do systému ji znormalizovali, a po výstupu ze systému ji zlokalizovali.
Zde uvedu příklad, který umožní odekorovat controller atributem ControllerName a tím změnit název controlleru pro routing (tedy obdoba již zmiňovaného ActionName).


[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]

public class ControllerNameAttribute : Attribute

{

    public ControllerNameAttribute(string nameForRouting)

    {

        NameForRouting = nameForRouting;

    }

 

    public string NameForRouting

    {

        get;

        set;

    }

}

 

public class LocalizableRoute : Route

{

 

    public LocalizableRoute(string url, IRouteHandler routeHandler)

        : base(url, routeHandler)

    {

    }

 

    public override RouteData GetRouteData(System.Web.HttpContextBase httpContext)

    {

        var res = base.GetRouteData(httpContext);

        if (res == null)

        {

            return null;

        }

        object o;

        string s;

        if (res.Values.TryGetValue("controller", out o) && (s = o as string) != null)

        {

            if (RouteToController.TryGetValue(s, out s))

            {

                res.Values["controller"] = s;

            }

        }

        return res;

    }

 

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)

    {

        object o;

        string s;

        if (values.TryGetValue("controller", out o) && (s = o as string) != null)

        {

            if (ControllerToRoute.TryGetValue(s, out s))

            {

                values["controller"] = s;

            }

        }

        return base.GetVirtualPath(requestContext, values);

    }

 

    static Dictionary<string, string> RouteToController = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

    static Dictionary<string, string> ControllerToRoute = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

 

    static LocalizableRoute()

    {

        var ct = typeof(System.Web.Mvc.IController);

        var at = typeof(ControllerNameAttribute);

        foreach (var a in AppDomain.CurrentDomain.GetAssemblies().Where(aa => !aa.GlobalAssemblyCache))

        {

            foreach (var t in a.GetTypes().Where(tt => tt.IsClass && !tt.IsAbstract && ct.IsAssignableFrom(tt)))

            {

                var controllerName = t.Name;

                if (controllerName.StartsWith("T4MVC_"))

                {

                    continue;

                }

                var att = t.GetCustomAttributes(at, true);

                if (att != null && att.Length > 0)

                {

                    var routingName = ((MvcUtils.Attributes.ControllerNameAttribute)att[0]).NameForRouting;

                    if (controllerName.EndsWith("Controller"))

                    {

                        controllerName = controllerName.Substring(0, controllerName.Length - "Controller".Length);

                    }

                    if (RouteToController.ContainsKey(routingName))

                    {

                        throw new Exception(string.Format("Controller's routing name '{0}' is ambigious.", routingName));

                    }

                    if (ControllerToRoute.ContainsKey(controllerName))

                    {

                        throw new Exception(string.Format("Controller's regular name '{0}' is ambigious.", routingName));

                    }

                    RouteToController.Add(routingName, controllerName);

                    ControllerToRoute.Add(controllerName, routingName);

                }

            }

        }

    }

}

Uvedená ukázka je jen takový návod „jak na to“. Např. pokud máme url ve tvaru „langCode/controller/action„, tak můžeme (de)lokalizaci provádět podle hodnoty langCode, čímž už získáváme ony avizované lokalizované url.

Nyní tedy máme napsanou vlastní třídu routovacích pravidel a už nám zbývá jen vytvořit konkrétní pravidla, typicky v metodě Application_Start v souboru Global.asax.cs. Nemůžeme použít extenzní metodu MapRoute, protože ta používá natvrdo třídu System.Routing.Route, ale i tak se nejedná o nic složitého:


routes.Add("Default", new LocalizableRoute("{controller}/{action}/{id}", new MvcRouteHandler())

{

    Defaults = new RouteValueDictionary { { "controller", "domu" }, { "action", "Index" }, { "id", "" }, },

});

2 thoughts on “ASP.NET MVC – lokalizace url

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.