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", "" }, },
});
Pekne riesenie, mozno sa nim inspirujem ak budem riesit lokalizaciu v ASP.NET MVC
To se mi líbíTo se mi líbí
Kdyby Tě zajímalo moje řešení posunuté o kousek dál k finálnímu použití, mrkni sem: http://blog.maartenballiauw.be/post/2010/01/26/Translating-routes-(ASPNET-MVC-and-Webforms).aspx
To se mi líbíTo se mi líbí