控制器名称上的MVC路由约束

时间:2012-05-02 03:35:29

标签: c# asp.net asp.net-mvc routes asp.net-mvc-4

我的路线设置如下:

        context.MapRoute(
        name: "Area",
        url: "Area/{controller}/{action}",
        defaults: new
        {
            controller = "Home",
            action = "Dashboard"
        }
        );

        context.MapRoute(
        name: "AccountArea",
        url: "Area/{accountFriendlyId}/{controller}/{action}",
        defaults: new
        {
            controller = "Home",
            action = "Dashboard",
            accountFriendlyId = RouteParameter.Optional
        }
        );

        context.MapRoute(
        name: "AccountCampaignArea",
        url: "Area/{accountFriendlyId}/{campaignFriendlyId}/{controller}/{action}",
        defaults: new
                {
                    controller = "Home", 
                    action = "Dashboard",
                    accountFriendlyId = RouteParameter.Optional,
                    campaignFriendlyId = RouteParameter.Optional
                }
        );

我迫切希望Area/friendlyAccountName/Home带我去Dashboard(),但这不起作用(404)。我认为原因是我们去寻找一个friendlyAccountName控制器。

我知道如果我应该选择在我的一个控制器之后命名一个帐户,一切都崩溃了,那么在字符串无法找到相应控制器的情况下,有没有办法进入下一个路径?有没有办法在每次修改控制器列表时使用反射并避免维护约束?

修改

你知道一种不使用反射的方法,或者至少包含派生类型搜索到这个区域的方法吗?当第二个路径参数与控制器名称匹配时,我不喜欢产生两次开销的想法(传递约束然后在构造控制器时再次搜索)。我希望有一种方法可以在构建控制器然后备份并进入下一个路径时捕获异常。

3 个答案:

答案 0 :(得分:7)

为什么你需要第一条路线?如果{accountFriendlyId}是可选的,您应该能够省略它并获得与您的第一个注册路由相同的路由默认值。

这样它首先匹配AccountArea命名的路由,这就是你想要的,然后如果没有指定{accountFriendlyId},它会将该区域后的第一个令牌视为控制器

事实上,我觉得你应该能够完全删除前两条路线并坚持使用最后一条路线,因为前两条路线参数是可选的,默认值是相同的。

<强>更新

由于{accountFriendlyId}可能是有效的控制器操作名称,因此您可以执行其他一些操作:

  1. {accountFriendlyId}移至路线的末尾,而不是在开头。这遵循更自然的URL样式,即最广泛的资源,以及资源中的特定细节。
  2. 使用route constraints。理论上,您可以使用反射生成正则表达式以匹配custom constraint中的控制器名称,或者您可以手动将它们写出来。这样的事情:
  3. context.MapRoute(

        name: "Area",
        url: "Area/{controller}/{action}",
        defaults: new
        {
            controller = "Home",
            action = "Dashboard",
            new { controller = @"(Account|Profile|Maintenance)" }
        }
    

    );

答案 1 :(得分:6)

最终,为了方便我想要的东西(这取决于让应用程序动态区分任意字符串和控制器名称),我设置了这样的路线:

public override void RegisterArea(AreaRegistrationContext context)
    {
        context.MapRoute(
        name: "AccountCampaignArea",
        url: "Area/{accountFriendlyId}/{campaignFriendlyId}/{controller}/{action}",
        defaults: new
            {
                controller = "Home",
                action = "Dashboard",
                accountFriendlyId = RouteParameter.Optional,
                campaignFriendlyId = RouteParameter.Optional,
                id = UrlParameter.Optional
            },
        constraints: new { accountFriendlyId = new ControllerNameConstraint(), campaignFriendlyId = new ControllerNameConstraint() }
        );

        context.MapRoute(
            name: "AccountArea",
            url: "Area/{accountFriendlyId}/{controller}/{action}",
            defaults: new
                {
                    controller = "Home",
                    action = "Dashboard",
                    accountFriendlyId = RouteParameter.Optional,
                    id = UrlParameter.Optional
                },
            constraints: new { accountFriendlyId = new ControllerNameConstraint() }
            );

        context.MapRoute(
        name: "Area",
        url: "Area/{controller}/{action}",
        defaults: new
            {
                controller = "Home",
                action = "Dashboard"
            }
        );
    }

并设置这样的约束(约束也可以称为NotControllerNameContraint):

public class ControllerNameConstraint : IRouteConstraint
{
    private static List<Type> GetSubClasses<T>()
    {
        return Assembly.GetCallingAssembly().GetTypes().Where(
            type => type.IsSubclassOf(typeof(T))).ToList();
    }

    public List<string> GetControllerNames()
    {
        List<string> controllerNames = new List<string>();
        GetSubClasses<Controller>().ForEach(
            type => controllerNames.Add(type.Name));
        return controllerNames;
    }
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (values.ContainsKey(parameterName))
        {
            string stringValue = values[parameterName] as string;
            return !GetControllerNames().Contains(stringValue + "Controller");
        }

        return true;
    }
}

致谢:https://stackoverflow.com/a/1152735/938472

答案 2 :(得分:2)

这是一个网址空间问题。你如何区分accountFriendlyId,campaignFriendlyId和控制器?简单的方法是将它们放在URL的不同部分,但是使用您的路由,控制器可以是第二,第三或第四段。您必须使用约束来消除歧义,并按如下顺序排序:

context.MapRoute(null, "Area/{controller}/{action}",
   new { controller = "Home", action = "Dashboard" },
   new { controller = "Foo|Bar" });

context.MapRoute(null, "Area/{accountFriendlyId}/{controller}/{action}",
   new { controller = "Home", action = "Dashboard" },
   new { controller = "Foo|Bar" });

context.MapRoute(null, "Area/{accountFriendlyId}/{campaignFriendlyId}/{controller}/{action}",
   new { controller = "Home", action = "Dashboard" });

你建议的想法,如果找不到控制器然后尝试下一个匹配的路线,它不会那样工作,一旦路线匹配它,你将不得不修改UrlRoutingModule试图让它发挥作用。