使用动态前缀更改MVC路由,同时保持向后URL兼容性

时间:2011-11-30 16:01:20

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

因为它不是,我有一个网站,你必须在一个网址上进入,并设置一个cookie来跟踪你所隶属的客户。我想改变它,以便某些控制器只使用这样的URL:

/ {的friendlyName} / {控制器} / {索引} / {ID}

友好名称是唯一的,让我在不使用cookie kludge的情况下选择正确的客户。

我有控制器:HomeRedirect我不希望友好名称部分(可能更多)。

我还有一些适合这个类别的人,我想进入他们自己的领域。我怎能不将这些区域包括为有效的友好名称?例如,我有一个控制器,用于为名为Framed的iframe中的内容提供服务。目前,这个网址看起来像/ Framed / action / id。我可以将它放在一个名为Framed的区域中,控制器的名称与操作相同,我仍然可以保持相同的URL。

对于控制器Error,我希望友好名称是可选的

我还有其他需要友好名称的控制器:SignInSignOutAccount

一旦我有路由,问题是改变代码,以便我的重定向保持友好。关于如何做到的任何想法?

我的问题只是提出了一个关于如何更改网站路由的良好计划。我必须保持一些网址的向后兼容性 - 即我不想要友好网址的任何部分,包括我讨论的控制器切入他们自己的区域。我正在寻找关于如何解决这个问题以及改变这些变化的任何好的建议。

2 个答案:

答案 0 :(得分:3)

要实现目标,您需要结合路线RouteConstraints。此外,您需要强制执行friendlyName唯一的规则,并且不同于任何控制器或区域的名称。

Global.asax.cs中的RegisterRoutes()中的以下路由应该足够了:

routes.MapRoute(
    "WithFriendlyName",
    "{friendlyName}/{controller}/{index}/{id}",
    new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    new { friendlyName = new MustBeFriendlyName() }
);

routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}", // URL with parameters
    new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    new { controller = new MustNotRequireFriendlyName() }
);

RouteConstraints应该是这样的:

using System;
using System.Web;
using System.Web.Routing;

namespace Examples.Extensions
{
    public class MustBeFriendlyName : IRouteConstraint
    {
        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            // return true if this is a valid friendlyName
            // MUST BE CERTAIN friendlyName DOES NOT MATCH ANY
            // CONTROLLER NAMES OR AREA NAMES
            var _db = new DbContext();
            return _db.FriendlyNames.FirstOrDefault(x => x.FriendlyName.ToLowerInvariant() ==
                values[parameterName].ToString().ToLowerInvariant()) != null;
        }
    }

    public class MustNotRequireFriendlyName : IRouteConstraint
    {
        private const string controllersRequiringFriendlyNames = 
            "SignIn~SignOut~Account";

        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            // return true if this controller does NOT require a friendlyName
            return controllersRequiringFriendlyNames.ToLowerInvariant()
                .Contains(values[parameterName].ToString().ToLowerInvariant());
        }
    }
} 

这应该让你开始。

对于重定向生成的URL,如果路由设置正确,则应生成URL生成,以便您可能需要的唯一更改是确保传递{friendlyName}。

当您进一步了解更改时,可能需要添加一些额外的路由和约束。

答案 1 :(得分:1)

只是想补充一点,因为可选前缀在过去的几天里一直在咬我。虽然我想使用@counsellorben提供的解决方案,但我还需要能够使用相同的名称来寻址路由,这在使用2条路由时是不可能的。

我花了一些头脑,但最后解决方案实际上看起来非常简单。我只需要创建一个中间聚合路径:

public class AggregateRoute : RouteBase
{
  private readonly RouteBase[] _routes;

  public AggregateRoute(params RouteBase[] routes)
  {
    _routes = routes;
  }

  public override RouteData GetRouteData(HttpContextBase httpContext)
  {
    RouteData routeData = null;
    foreach (var route in _routes)
    {
      routeData = route.GetRouteData(httpContext);
      if (routeData != null) break;
    }

    return routeData;
  }

  public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
  {
    VirtualPathData virtualPath = null;
    foreach (var route in _routes)
    {
      virtualPath = route.GetVirtualPath(requestContext, values);
      if (virtualPath != null) break;
    }

    return virtualPath;
  }
}

这允许我这样做:

routes.Add(
    "RouteName",
    new AggregateRoute(
      new Route("{path}", new MvcRouteHandler()),
      new Route("{prefix}/{path}", new MvcRouteHandler())
    )
);

这使得能够以相同的名称解析任一路线,这在单独添加两条路线时是不可能的:

Url.RouteLink("RouteName", new RouteValueDictionary{
  new{path="some-path"}});

Url.RouteLink("RouteName", new RouteValueDictionary{
  new{path="some-prefix/some-path"}});