2011-02-02 117 views
3

我正在制定一个新项目的概念,我需要支持多语言URL。理想情况下,所有URL都需要使用用户的本地语言。所以,我们不希望使用domain.com/en/contactdomain.com/es/contact但我们喜欢domain.com/contactdomain.com/contactar(contactar是西班牙语联系)。内部两者应该路由到相同的ContactController类。使用ASP.NET MVC的多语言网址

这可以通过为每种语言向Global.asax.cs添加多条静态路由来处理,但我们希望使其变得非常动态,并希望系统的用户能够更改URL的翻译通过内容管理系统。所以我们需要一些从URL到控制器和动作的动态映射。

通过查看MVC3的源代码我想通了MvcHandlerProcessRequestInit方法是负责确定创建哪个控制器。它仅查找RouteData以获取控制器的名称。覆盖默认MVC路由的一种方法是创建一个简单的默认路由,使用自定义RouteHandler。 This RouteHandler强制MVC使用我自己的定制子版本MvcHandler,它覆盖了ProcessRequestInit方法。此重写方法将我自己的动态找到的控制器和操作插入RouteData,然后再调用回原始ProcessRequestInit

我已经试过这样:

的Global.asax.cs

routes.Add(
    new Route("{*url}", new MultilingualRouteHandler()) 
    { 
     Defaults = new RouteValueDictionary(new { controller = "Default", action = "Default" }) 
    } 
); 

MultilingualRouteHandler.cs

public class MultilingualRouteHandler : IRouteHandler 
{ 

    public IHttpHandler GetHttpHandler(RequestContext requestContext) 
    { 
     return new MultilingualMVCHandler(requestContext); 
    } 

} 

MultilingualMvcHandler.cs

public class MultilingualMVCHandler : MvcHandler 
{ 

    public MultilingualMVCHandler(RequestContext context) : base(context) 
    { 
    } 

    protected override void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) 
    { 

     if (RequestContext.RouteData.Values.ContainsKey("controller")) 
     { 
      RequestContext.RouteData.Values.Remove("controller"); 
     } 

     if (RequestContext.RouteData.Values.ContainsKey("action")) 
     { 
      RequestContext.RouteData.Values.Remove("action"); 
     } 

     RequestContext.RouteData.Values.Add("controller", "Product"); 
     RequestContext.RouteData.Values.Add("action", "Index"); 

     base.ProcessRequestInit(httpContext, out controller, out factory); 

    } 

} 

在这个处理程序中,我将控制器和操作硬编码为测试目的的固定值,但要实现这种动态并不困难。它的工作原理,但唯一的问题是,我不得不修改ASP.NET MVC3的源代码以使其工作。问题是ProcessRequestInit方法MvcHandler是私有的,因此不能被覆盖。我修改了源代码并将其更改为受保护的虚拟,这使我可以覆盖它。

这是伟大的,但可能不是最好的解决方案。我总是需要分发我自己版本的System.Web.Mvc.dll,这很麻烦。它会好得多,它会与RTM版本一起工作。

我是否缺少任何其他可能性挂钩到ASP.NET MVC,这将允许我动态地确定控制器和要启动的操作,具体取决于URL?我想到的另一种方式是在* Application_Start *上动态创建RouteCollection,但我认为这会让它在运行中更改变得更加困难。

我会很感激我还没有找到的钩子的任何提示。

回答

4

现在这是相当老,螺母以防万一别人正在寻找类似的东西...

除非我完全误解你想要做什么,这是很简单实在。

第1步:添加一个新的路线global.ascx.cs包含您的个人路由引擎参考

routes.Add(new MyProject.Routing.ContentRoutingEngine()); 

确保它在正确的地方的路线的列表,以便其他路由如果需要,引擎可以在它之前捕获东西,或者如果引擎不处理特定路由,则继续路径搜索。我把它放在忽略之后,但在MVC默认路由之前。

第2步:创建内容路由引擎,确保它从System.Web.Routing.RouteBase抽象类继承,并根据需要覆盖GetRouteData和GetVirtualPath方法,例如,

public class ContentRoutingEngine : RouteBase 
{ 
    public override RouteData GetRouteData(HttpContextBase httpContext) 
    { 
     var routeHandler = new MvcRouteHandler(); 
     var currentRoute = new Route("{controller}/{action}", routeHandler); 
     var routeData = new RouteData(currentRoute, routeHandler); 

     // set your values dynamically here 
     routeData.Values["controller"] = "Home" ; 
     // or 
     routeData.Values.Add("action", "Index"); 

     // return the route, or null to have it passed to the next routing engine in the list 
     return routeData; 
    } 

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) 
    { 
     //implement this to return url's for routes, or null to just pass it on 
     return null; 
    } 
} 

而且应该这样做。您可以根据需要在引擎中动态更改路由,并且不需要更改MVC源。让标准的MVC RouteHandler实际调用控制器。

后记:很明显,上面的代码不是生产标准 - 它的写法是尽可能明显地发生了什么。

+0

看起来像一个很好的解决方案,可能工作一样好或比实际的IRouteHandler或Conroller实现更好......我现在要玩它:) – bbqchickenrobot 2011-06-24 21:55:16

0

如果您允许通过您的CMS修改网址,那么您将不得不保留所有旧版本的网址,以便您可以301重定向到新的网址。

最好的选择是将url标记(例如“contactar”)与相应的控制器一起放入db中。

查询,并创建你的路线。

创建将处理301重定向

0

我认为最完美的解决方案将使用一些行为过滤器定制ActionInvoker相结合的路线。这样,您可以调用应用了特定过滤器的操作。像ActionName属性一样,只能接受多个值(名称)。

编辑:看看ActionMethodSelectorAttribute,meybe毕竟你不需要自定义的ActionInvoker。