使用ASP.NET MVC的多语言URL

时间:2011-02-02 11:44:26

标签: asp.net-mvc asp.net-mvc-3 asp.net-mvc-routing

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

这可以通过为每种语言添加到Global.asax.cs的多个静态路由来处理,但是我们希望使其非常动态,并且希望系统的用户能够通过以下方式更改URL的转换。内容管理系统。因此,我们需要从URL到控制器和操作的某种动态映射。

通过查看MVC3的源代码,我发现 MvcHandler ProcessRequestInit 方法负责确定要创建的控制器。它只是查看 RouteData 以获取控制器的名称。覆盖默认MVC路由的一种方法是创建使用自定义 RouteHandler 的简单默认路由。此 RouteHandler 强制MVC使用我自己的自定义子类 MvcHandler 来覆盖 ProcessRequestInit 方法。这个重写的方法在回调原始的 ProcessRequestInit 之前,将我自己动态找到的控制器和动作插入 RouteData

我试过这个:

的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的源代码才能使它工作。问题是 MvcHandler ProcessRequestInit 方法是私有的,因此无法覆盖。我修改了源代码并将其更改为protected virtual,这允许我覆盖它。

这一切都很棒但可能不是最好的解决方案。我总是需要分发我自己的System.Web.Mvc.dll版本,这很麻烦。使用RTM版本会好得多。

我是否遗漏了任何其他可能挂钩进入ASP.NET MVC的问题,这可以让我动态确定控制器和要启动的操作,具体取决于URL?我想到的另一种方法是在* Application_Start *上动态构建 RouteCollection ,但我认为这将使得在运行中更改它变得更加困难。

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

3 个答案:

答案 0 :(得分:5)

现在这已经很老了,以防万一其他人正在寻找类似的东西......

除非我完全误解你想做什么,否则它真的很简单。

步骤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实际调用控制器。

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

答案 1 :(得分:0)

如果您允许通过CMS修改网址,则必须保留网址的所有旧版本,以便301可以重定向到新网址。

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

查询,然后创建你的路线。

创建一个处理301s的路线

答案 2 :(得分:0)

我认为最优雅的解决方案是使用一些动作过滤器与自定义ActionInvoker相结合。这样,您可以调用已应用特定过滤器的操作。类似于ActionName属性,只能接受多个值(名称)。

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