是否可以在ASP.NET MVC中本地化URL /路由?

时间:2022-11-01 04:12:39

I'm working with a client that wants the URLs in our web application to be in French. I'm an English developer and we also have English clients. This is an interesting problem but I don't think its something the ASP.NET MVC Framework would support.

我正在与希望我们的Web应用程序中的URL使用法语的客户端合作。我是英语开发人员,我们也有英语客户。这是一个有趣的问题,但我不认为ASP.NET MVC框架会支持它。

Here's the scenario. The route...

这是场景。路线...

Specific EXAMPLE
English URL
www.*.com/questions/ask

具体示例英文网址www.*.com/questions/ask

would also support

也会支持

French URL
www.*.com/problème/poser

法语网址www.*.com/problème/poser

Generic EXAMPLE
English URL
http://clientA.product.com/AreaNameEnglish/ControllerNameEnglish/ActionNameEnglish/params

通用示例英文URL http://clientA.product.com/AreaNameEnglish/ControllerNameEnglish/ActionNameEnglish/params

also needs to support

还需要支持

French URL
http://clientB.product.com/AreaNameFrench/ControllerNameFrench/ActionNameFrench/params

法语网址http://clientB.product.com/AreaNameFrench/ControllerNameFrench/ActionNameFrench/params

So in MVC my Area, Controller and Actions all need to have both English and French translations.

所以在MVC我的区域,控制器和动作都需要有英文和法文翻译。

Obviously maintainability would be a HUGE issue if I were to go and hardcode all my Controllers, Views and Action names to French. Is there anyway to localize the route that is presented in the browser without doing this? Keeping in mind there are lots of different routes in the application. A couple Areas each with a handful of Controller each with many Actions?

如果我将所有控制器,视图和动作名称硬编码为法语,显然可维护性将是一个巨大的问题。无论如何本地化浏览器中显示的路线而不这样做?请记住,应用程序中有许多不同的路径。有几个区域,每个区域都有少量的控制器,每个区域都有许多动作?

Thanks,
Justin

谢谢,贾斯汀

EDIT
Thanks to @womp here is what I've come up with so far... Although in the end I took the approach which I posted as an answer.

编辑感谢@womp这是我到目前为止所提出的...虽然最后我采取了我发布的方法作为答案。

public class LocalizedControllerFactory : DefaultControllerFactory
{
    public override IController CreateController(RequestContext requestContext, string controllerName)
    {
        if (string.IsNullOrEmpty(controllerName))
            throw new ArgumentNullException("controllerName");

        if (CultureInfo.CurrentCulture.TwoLetterISOLanguageName == "fr")
        {
            controllerName = this.ReplaceControllerName(requestContext, controllerName);
            this.ReplaceActionName(requestContext);
            this.ReplaceAreaName(requestContext);
        }

        return base.CreateController(requestContext, controllerName);
    }

    private string ReplaceControllerName(RequestContext requestContext, string controllerName)
    {
        // would use the language above to pick the propery controllerMapper.  For now just have french
        Dictionary<string, string> controllerMapper = new Dictionary<string, string>()
        {
            {"frenchControllerA", "englishControllerA"},
            {"frenchControllerB", "englishControllerB"}
        };

        return this.ReplaceRouteValue(requestContext, "controller", controllerMapper);
    }

    private void ReplaceAreaName(RequestContext requestContext)
    {
        // would use the language above to pick the propery areaMapper.  For now just have french
        Dictionary<string, string> areaMapper = new Dictionary<string, string>()
        {
            {"frenchAreaX", "englishAreaX"},
            {"frenchAreaY", "englishAreaY"}
        };

        this.ReplaceRouteValue(requestContext, "area", areaMapper);
    }

    private void ReplaceActionName(RequestContext requestContext)
    {
        // would use the language above to pick the propery actionMapper.  For now just have french
        Dictionary<string, string> actionMapper = new Dictionary<string, string>()
        {
            {"frenchAction1", "englishAction1"},
            {"frenchAction2", "englishAction2"}
        };

        this.ReplaceRouteValue(requestContext, "action", actionMapper);
    }

    private string ReplaceRouteValue(RequestContext requestContext, string paramName, Dictionary<string, string> translationLookup)
    {
        if (requestContext.RouteData.Values[paramName] == null)
        {
            return null;
        }

        string srcRouteValue = requestContext.RouteData.Values[paramName] as string;
        if (srcRouteValue != null && translationLookup.ContainsKey(srcRouteValue))
        {
            requestContext.RouteData.Values[paramName] = translationLookup[srcRouteValue];
        }

        return requestContext.RouteData.Values[paramName] as string;
    }
}

A decent start. If I localize just the ControllerName and ActionName in the Url it will find and render the proper View. However I have the following problems.

一个不错的开始。如果我只在Url中本地化ControllerName和ActionName,它将找到并呈现正确的视图。但是我有以下问题。

Area Name can't be translated
Localizing the Area means the Controller.View() method fails to find Views. Even though I've replaced the Area name in the request context the ViewEngineCollection.Find() method doesn't seem to pick it up. Anywhere in my Controller class that does "return View()" fails to find the default view for its action. If I don't localize the Area then the other steps work.

区域名称无法转换本地化区域意味着Controller.View()方法无法查找视图。尽管我已经在请求上下文中替换了Area名称,但ViewEngineCollection.Find()方法似乎并未将其取出。我的Controller类中“返回View()”的任何位置都无法找到其操作的默认视图。如果我没有本地化区域,那么其他步骤工作。

RedirectToAction or Html.ActionLink
Anytime the application calls RedirectToAction or if I use an Html.ActionLink helper or something similiar the Urls generate are the English ones. It looks like I'm going to have to add logic somewhere possibly in multiple spots to convert an English Url to the French (or other language) one.

RedirectToAction或Html.ActionLink只要应用程序调用RedirectToAction,或者如果我使用Html.ActionLink帮助程序,或者Urls生成的类似内容都是英文版。看起来我将不得不在某个地方添加逻辑可能在多个位置将英语URL转换为法语(或其他语言)。

2 个解决方案

#1


14  

The following blog contains a complete solution this exact problem. Its actually a very elegant solution which I highly recommend.

以下博客包含完整解决方案这个确切的问题。它实际上是一个非常优雅的解决方案,我强烈推荐

https://blog.maartenballiauw.be/post/2010/01/26/translating-routes-(aspnet-mvc-and-webforms).html

https://blog.maartenballiauw.be/post/2010/01/26/translating-routes-(aspnet-mvc-and-webforms).html

Note to get it working for AREAs I had to add the following extension method to his "TranslatedRouteCollectionExtensions.cs" class:

注意让它适用于AREA我必须将以下扩展方法添加到他的“TranslatedRouteCollectionExtensions.cs”类:

    public static Route MapTranslatedRoute(this AreaRegistrationContext areaContext, string name, string url, object defaults, object routeValueTranslationProviders, bool setDetectedCulture)
    {
        TranslatedRoute route = new TranslatedRoute(
            url,
            new RouteValueDictionary(defaults),
            new RouteValueDictionary(routeValueTranslationProviders),
            setDetectedCulture,
            new MvcRouteHandler());

        route.DataTokens["area"] = areaContext.AreaName;

        // disabling the namespace lookup fallback mechanism keeps this areas from accidentally picking up
        // controllers belonging to other areas
        bool useNamespaceFallback = (areaContext.Namespaces == null || areaContext.Namespaces.Count == 0);
        route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback;

        areaContext.Routes.Add(route);

        return route;
    }

However, even with this a translated route with an AREA can be read and interpreted the routes generated always seem to include an English AREA name but localized everything else.

但是,即使这样,可以读取和解释带有AREA的已转换路线,生成的路线似乎总是包含英语AREA名称,但本地化其他所有内容。

I was directed to a blog via the same question asked on the ASP.NET MVC Forums

通过ASP.NET MVC论坛上提出的相同问题,我被引导到博客

#2


3  

The MVC framework supports pretty much any routing scenario you can think of, but not necessarily with the default routing classes.

MVC框架几乎支持您可以想到的任何路由方案,但不一定支持默认路由类。

Most localization solutions I've run across involve using the same Controller and Action method names, but specifying a culture parameter in the route which dictates which translated version of the View is presented. For example,

我遇到的大多数本地化解决方案都涉及使用相同的Controller和Action方法名称,但在路由中指定了一个culture参数,该参数决定了View的哪个翻译版本。例如,

http://clientA.product.com/AreaName/Controller/Action    //en-US
http://clientB.product.com/es-MX/AreaName/Controller/Action   // spanish

If you really must have translated URL's though, I don't see much other choice then to maintain a mapping table somewhere. If I understand your question correctly, you need to be able to map all the different language translations of "questions" (controller) and "ask" (action) to the same controller/action method combination.

如果你真的必须翻译过URL,那么在某处维护映射表时我没有其他选择。如果我正确理解您的问题,您需要能够将“问题”(控制器)和“问”(动作)的所有不同语言翻译映射到相同的控制器/操作方法组合。

However, once you've built this table somewhere (resource files?), you can easily override the DefaultControllerFactory that the framework is using, and implement your own logic for determining the controller to instantiate. So instead of just matching the {controller} token from the URL as a simple string comparison, you can implement logic to check it against your mapping table to pick the right controller.

但是,一旦你在某个地方构建了这个表(资源文件?),你就可以轻松地覆盖框架正在使用的DefaultControllerFactory,并实现自己的逻辑来确定要实例化的控制器。因此,您可以实现逻辑以根据映射表检查它以选择正确的控制器,而不是仅仅将URL中的{controller}令牌与URL进行匹配。

For a walkthrough of creating a custom controller factory, check this great blog post. It's actually a localization example as well, but it's based on the user's culture settings, rather than the language of the URL.

有关创建自定义控制器工厂的演练,请查看此博文。它实际上也是一个本地化示例,但它基于用户的文化设置,而不是URL的语言。

#1


14  

The following blog contains a complete solution this exact problem. Its actually a very elegant solution which I highly recommend.

以下博客包含完整解决方案这个确切的问题。它实际上是一个非常优雅的解决方案,我强烈推荐

https://blog.maartenballiauw.be/post/2010/01/26/translating-routes-(aspnet-mvc-and-webforms).html

https://blog.maartenballiauw.be/post/2010/01/26/translating-routes-(aspnet-mvc-and-webforms).html

Note to get it working for AREAs I had to add the following extension method to his "TranslatedRouteCollectionExtensions.cs" class:

注意让它适用于AREA我必须将以下扩展方法添加到他的“TranslatedRouteCollectionExtensions.cs”类:

    public static Route MapTranslatedRoute(this AreaRegistrationContext areaContext, string name, string url, object defaults, object routeValueTranslationProviders, bool setDetectedCulture)
    {
        TranslatedRoute route = new TranslatedRoute(
            url,
            new RouteValueDictionary(defaults),
            new RouteValueDictionary(routeValueTranslationProviders),
            setDetectedCulture,
            new MvcRouteHandler());

        route.DataTokens["area"] = areaContext.AreaName;

        // disabling the namespace lookup fallback mechanism keeps this areas from accidentally picking up
        // controllers belonging to other areas
        bool useNamespaceFallback = (areaContext.Namespaces == null || areaContext.Namespaces.Count == 0);
        route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback;

        areaContext.Routes.Add(route);

        return route;
    }

However, even with this a translated route with an AREA can be read and interpreted the routes generated always seem to include an English AREA name but localized everything else.

但是,即使这样,可以读取和解释带有AREA的已转换路线,生成的路线似乎总是包含英语AREA名称,但本地化其他所有内容。

I was directed to a blog via the same question asked on the ASP.NET MVC Forums

通过ASP.NET MVC论坛上提出的相同问题,我被引导到博客

#2


3  

The MVC framework supports pretty much any routing scenario you can think of, but not necessarily with the default routing classes.

MVC框架几乎支持您可以想到的任何路由方案,但不一定支持默认路由类。

Most localization solutions I've run across involve using the same Controller and Action method names, but specifying a culture parameter in the route which dictates which translated version of the View is presented. For example,

我遇到的大多数本地化解决方案都涉及使用相同的Controller和Action方法名称,但在路由中指定了一个culture参数,该参数决定了View的哪个翻译版本。例如,

http://clientA.product.com/AreaName/Controller/Action    //en-US
http://clientB.product.com/es-MX/AreaName/Controller/Action   // spanish

If you really must have translated URL's though, I don't see much other choice then to maintain a mapping table somewhere. If I understand your question correctly, you need to be able to map all the different language translations of "questions" (controller) and "ask" (action) to the same controller/action method combination.

如果你真的必须翻译过URL,那么在某处维护映射表时我没有其他选择。如果我正确理解您的问题,您需要能够将“问题”(控制器)和“问”(动作)的所有不同语言翻译映射到相同的控制器/操作方法组合。

However, once you've built this table somewhere (resource files?), you can easily override the DefaultControllerFactory that the framework is using, and implement your own logic for determining the controller to instantiate. So instead of just matching the {controller} token from the URL as a simple string comparison, you can implement logic to check it against your mapping table to pick the right controller.

但是,一旦你在某个地方构建了这个表(资源文件?),你就可以轻松地覆盖框架正在使用的DefaultControllerFactory,并实现自己的逻辑来确定要实例化的控制器。因此,您可以实现逻辑以根据映射表检查它以选择正确的控制器,而不是仅仅将URL中的{controller}令牌与URL进行匹配。

For a walkthrough of creating a custom controller factory, check this great blog post. It's actually a localization example as well, but it's based on the user's culture settings, rather than the language of the URL.

有关创建自定义控制器工厂的演练,请查看此博文。它实际上也是一个本地化示例,但它基于用户的文化设置,而不是URL的语言。