我正在研究一个新项目的概念,我需要支持多语言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 ,但我认为这将使得在运行中更改它变得更加困难。
我很感激我还没有找到任何挂钩的提示。
答案 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。