使用 ASP.NET MVC 的多语言 URL
我正在制定一个新项目的概念,我需要支持多语言 URL。理想情况下,所有 URL 都需要使用用户的母语。因此,我们不想使用 domain.com/en/contact 和 domain.com/es/contact,但我们喜欢 domain.com/contact strong> 和 domain.com/contactar(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 方法是私有的,因此无法重写。我修改了源代码并将其更改为受保护的虚拟,这允许我覆盖它。
这一切都很好,但可能不是最好的解决方案。我总是需要分发我自己的 System.Web.Mvc.dll 版本,这很麻烦。如果能配合 RTM 版本使用就更好了。
我是否错过了挂钩 ASP.NET MVC 的任何其他可能性,这些可能性允许我根据 URL 动态确定要启动的控制器和操作?我想到的另一种方法是在 *Application_Start* 上动态构建 RouteCollection 但我认为这将使动态更改它变得更加困难。
我将不胜感激任何我还没有找到的钩子技巧。
I’m working out the concepts for a new project where I need to support for multilingual URL’s. Ideally all URL’s need to be in the native language of the user. So we don’t want to use domain.com/en/contact and domain.com/es/contact but we like domain.com/contact and domain.com/contactar (contactar is Spanish for contact). Internally both should be routed to the same ContactController class.
This could be handled by adding multiple static routes to Global.asax.cs for each language but we’d like to make this very dynamic and would like the user of the system to be able to change the translation of the URL’s through the content management system. So we need some kind of dynamic mapping from URL’s to controllers and actions.
By looking at the source code of MVC3 I figured out that the ProcessRequestInit method of MvcHandler is responsible for determining which controller to create. It simply looks in the RouteData to get the name of the controller. One way to override the default MVC routing would be to create a simple default route that uses a custom RouteHandler. This RouteHandler forces MVC to use my own custom subclassed version of MvcHandler that overrides the ProcessRequestInit method. This overridden method insert my own dynamically found controller and action into the RouteData before calling back to the original ProcessRequestInit.
I’ve tried this:
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);
}
}
In this handler I hardcoded the controller and action for testing purposes to some fixed values but it’s not difficult to make this dynamic. It works but the only problem is that I had to modify the source code of ASP.NET MVC3 to get it working. The problem is that the ProcessRequestInit method of MvcHandler is private and thus cannot be overridden. I’ve modified the source code and changed it to protected virtual which allows me to override it.
This is all great but possibly not the best solution. It’s cumbersome that I would always need to distribute my own version of System.Web.Mvc.dll. It would be much better that it would work with the RTM version.
Am I missing any other possibilities of hooking into ASP.NET MVC that would allow me to dynamically determine the controller and action to launch, depending on the URL? One other way I thought of is to build the RouteCollection dynamically on *Application_Start* but I think that will make it more difficult to change it on the fly.
I would appreciate any tips of hooks that I’ve not yet found.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这现在已经相当老了,以防万一其他人正在寻找类似的东西......
除非我完全误解了你想要做什么,否则它真的很简单。
步骤 1:向 global.ascx.cs 添加一条新路由,其中包含对您的个人路由引擎的引用
确保它位于路由列表中的正确位置,以便其他路由引擎可以在需要时捕获它之前的内容,或继续如果您的引擎不处理特定路线,则进行路线搜索。我把它放在忽略之后,但在 MVC 默认路由之前。
步骤 2:创建内容路由引擎,确保它继承自 System.Web.Routing.RouteBase 抽象类,并根据需要重写 GetRouteData 和 GetVirtualPath 方法,例如
,这应该可以做到。您可以在引擎中根据需要动态更改路由,而无需更改 MVC 源。让标准 MVC RouteHandler 实际调用控制器。
后记:显然上面的代码不是生产标准 - 编写它是为了让发生的事情尽可能明显。
This is fairly old now, nut just in case anyone else is looking for something similar...
Unless I'm completely misunderstanding what you want to do, it's pretty simple really.
Step 1: Add a new route to global.ascx.cs containing a reference to your personal routing engine
Make sure that it is in the right place in the list of routes so that other routing engines can catch stuff before it if required, or continue the route search if your engine doesn't handle a particular route. I put it after the ignores, but before the MVC default routes.
Step 2: Create the Content Routing Engine, making sure that it inherites from System.Web.Routing.RouteBase abstract class, and overrides the GetRouteData and GetVirtualPath methods as required e.g.
and that should do it. You can change routes as dynamically as you wish within your engine, and no changes to MVC source required. Let the standard MVC RouteHandler actually invoke the controller.
Postscript: Obviously the code above is not production standard - it's written to make it as obvious as possible what's going on.
如果您允许通过 CMS 修改 url,则您必须保留所有旧版本的 url,以便可以 301 重定向到新版本。
最好的选择是将 url 标记(例如“contactar”)与其相应的控制器一起放入数据库中。
查询它,并据此创建您的路线。
创建一条处理 301 的路由
If you are allowing modification of urls through your CMS, then you will have to keep all old versions of the urls so that you can 301 redirect to the new ones.
The best bet for this will be to put the url tokens eg "contactar" in the db along with its corresponding controller.
query that, and create your routes out of that.
create a route that will handle the 301s
我认为最优雅的解决方案是使用一些动作过滤器与自定义 ActionInvoker 相结合。这样,您就可以调用应用了特定过滤器的操作。像 ActionName 属性一样,只能接受多个值(名称)。
编辑:看看
ActionMethodSelectorAttribute
,也许您毕竟不需要自定义 ActionInvoker。I think that most elegant solution would be using some action filter combined with custom ActionInvoker. That way, you could invoke an action that has specific filters applied. Something like ActionName attribute, only capable to accept multiple values (names).
Edit: Take a look at
ActionMethodSelectorAttribute
, meybe you don't need a custom ActionInvoker after all.