有没有办法让RoutePrefix以可选参数开头?

时间:2015-06-25 14:49:07

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

我想通过以下网址访问自行车控制器:

/bikes     // (default path for US)
/ca/bikes  // (path for Canada)

实现这一目标的一种方法是每个操作使用多个路由属性:

[Route("bikes")]
[Route("{country}/bikes")]
public ActionResult Index()

为了保持干爽,我更喜欢使用RoutePrefix,但不允许使用多个路线前缀:

[RoutePrefix("bikes")]
[RoutePrefix("{country}/bikes")] // <-- Error: Duplicate 'RoutePrefix' attribute    
public class BikesController : BaseController

    [Route("")]
    public ActionResult Index()

我尝试过使用此路线前缀:

[RoutePrefix("{country}/bikes")]
public class BikesController : BaseController

结果:/ ca /​​ bikes工作,/自行车404s。

我试过让国家选择:

[RoutePrefix("{country?}/bikes")]
public class BikesController : BaseController

同样的结果:/ ca /​​ bikes有效,/自行车404s。

我尝试给国家/地区一个默认值:

[RoutePrefix("{country=us}/bikes")]
public class BikesController : BaseController

同样的结果:/ ca /​​ bikes有效,/自行车404s。

是否有其他方法可以使用属性路由实现我的目标? (是的,我知道我可以通过在RouteConfig.cs中注册路由来完成这些工作,但这不是我在这里寻找的东西)。

我使用的是Microsoft.AspNet.Mvc 5.2.2。

仅供参考:这些是简化示例 - 实际代码具有{country}值的IRouteConstraint,例如:

[Route("{country:countrycode}/bikes")]

4 个答案:

答案 0 :(得分:0)

你是不对的,你不能有多个路由前缀,这意味着解决这个特定的用例不会是直截了当的。关于通过对项目进行最少量修改来实现所需目标的最佳方法是将控制器子类化。例如:

[RoutePrefix("bikes")]
public class BikeController : Controller
{
    ...
}

[RoutePrefix("{country}/bikes")]
public class CountryBikeController : BikeController
{
}

您的子类控制器将继承BikeController的所有操作,因此您无需重新定义任何内容。但是,在生成URL并让它们到正确的位置时,您需要明确控制器名称:

@Url.Action("Index", "CountryBike", new { country = "us" }

或者,如果您使用的是命名路由,则必须覆盖子类控制器中的操作,以便应用新的路由名称:

[Route("", Name = "CountryBikeIndex")]
public override ActionResult Index()
{
    base.Index();
}

另外,请记住,在路由前缀中使用参数时,该控制器中的所有操作都应采用参数:

public ActionResult Index(string country = "us")
{
    ...

答案 1 :(得分:0)

您可以使用具有两个有序选项的属性路由。

  routes.MapRoute(
     name: "Region",
     url: "{countryCode}/{controller}/{action}/{id}",
     defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
     constraints: new { countryCode = @"\w{2}" }
  );

如果您计划为所有网页设置区域特定路线,则可以在默认路线上方添加路线配置路线。这仅适用于没有属性路由的视图/控制器。

{{1}}

答案 2 :(得分:0)

NightOwl888详细介绍了我遇到的最佳解决方案,以回答以下问题:ASP.NET MVC 5 culture in route and url。下面的代码是我的文章的修剪版本。它在MVC5中为我工作。

使用单个RoutePrefix装饰每个控制器,没有文化段。应用程序启动时,自定义MapLocalizedMvc​​AttributeRoutes方法为每个控制器操作添加本地化路由条目。

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        // Omitted for brevity

        MapLocalizedMvcAttributeRoutes(routes, "{culture}/", new { culture = "[a-z]{2}-[A-Z]{2}" });
    }

    static void MapLocalizedMvcAttributeRoutes(RouteCollection routes, string urlPrefix, object constraints)
    {
        var routeCollectionRouteType = Type.GetType("System.Web.Mvc.Routing.RouteCollectionRoute, System.Web.Mvc");
        var subRouteCollectionType = Type.GetType("System.Web.Mvc.Routing.SubRouteCollection, System.Web.Mvc");
        var linkGenerationRouteType = Type.GetType("System.Web.Mvc.Routing.LinkGenerationRoute, System.Web.Mvc");
        FieldInfo subRoutesInfo = routeCollectionRouteType.GetField("_subRoutes", BindingFlags.NonPublic | BindingFlags.Instance);
        PropertyInfo entriesInfo = subRouteCollectionType.GetProperty("Entries");
        MethodInfo addMethodInfo = subRouteCollectionType.GetMethod("Add");

        var localizedRouteTable = new RouteCollection();
        var subRoutes = Activator.CreateInstance(subRouteCollectionType);
        Func<Route, RouteBase> createLinkGenerationRoute = (Route route) => (RouteBase)Activator.CreateInstance(linkGenerationRouteType, route);

        localizedRouteTable.MapMvcAttributeRoutes();

        foreach (var routeCollectionRoute in localizedRouteTable.Where(rb => rb.GetType().Equals(routeCollectionRouteType)))
        {
            // routeCollectionRoute._subRoutes.Entries
            foreach (RouteEntry routeEntry in (IEnumerable)entriesInfo.GetValue(subRoutesInfo.GetValue(routeCollectionRoute)))
            {
                var localizedRoute = CreateLocalizedRoute(routeEntry.Route, urlPrefix, constraints);
                var localizedRouteEntry = new RouteEntry(string.IsNullOrEmpty(routeEntry.Name) ? null : $"{routeEntry.Name}_Localized", localizedRoute);
                // Add localized and default routes and subroute entries
                addMethodInfo.Invoke(subRoutes, new[] { localizedRouteEntry });
                addMethodInfo.Invoke(subRoutes, new[] { routeEntry });
                routes.Add(createLinkGenerationRoute(localizedRoute));
                routes.Add(createLinkGenerationRoute(routeEntry.Route));
            }
        }
        var routeEntries = Activator.CreateInstance(routeCollectionRouteType, subRoutes);
        routes.Add((RouteBase)routeEntries);
    }

    static Route CreateLocalizedRoute(Route route, string urlPrefix, object constraints)
    {
        var routeUrl = urlPrefix + route.Url;
        var routeConstraints = new RouteValueDictionary(constraints);
        // combine with any existing constraints
        foreach (var constraint in route.Constraints)
        {
            routeConstraints.Add(constraint.Key, constraint.Value);
        }
        return new Route(routeUrl, route.Defaults, routeConstraints, route.DataTokens, route.RouteHandler);
    }
}

答案 3 :(得分:-1)

我不确定这是否是最佳方式,但我会为加拿大创建一个区域。 并且可以在加拿大创建一个BikeController。

enter image description here

接下来我将从加拿大采取行动指向默认控制器

make sure you take care of namespaces or you'll see error multiple controllers match the route.


    // GET: Canada/Bike
    //[UseUsControllerAction]
    public ActionResult Index()
    {
        return new Web.Controllers.BikeController().Index();
    }

您必须按照此方法重复某些代码。 为了坚持DRY原则,我将创建一个知道要指向哪个控制器或动作的属性。