通过方法属性的ASP.NET MVC路由


80

StackOverflow Podcast#54中,Jeff提到他们通过处理路由的方法上方的属性将其URL路由注册到StackOverflow代码库中。听起来像是个好主意(Phil Haack提出了关于路线优先级的警告)。

有人可以提供一些样本来实现这一目标吗?

另外,是否有使用这种样式的路由的“最佳实践”?

Answers:


62

更新:这已经发布在codeplex上。完整的源代码以及预编译的程序集都可以下载。我还没有时间在站点上发布文档,因此,现在这样的SO帖子就足够了。

更新:我添加了一些新属性来处理1)路由顺序,2)路由参数约束和3)路由参数默认值。下面的文本反映了此更新。

我实际上已经为我的MVC项目做过类似的事情(我不知道Jeff是如何用stackoverflow做到这一点的)。我定义了一组自定义属性:UrlRoute,UrlRouteParameterConstraint,UrlRouteParameterDefault。它们可以附加到MVC控制器操作方法,以使路由,约束和默认值自动绑定到它们。

用法示例:

(请注意,此示例有些人为设计,但演示了此功能)

public class UsersController : Controller
{
    // Simple path.
    // Note you can have multiple UrlRoute attributes affixed to same method.
    [UrlRoute(Path = "users")]
    public ActionResult Index()
    {
        return View();
    }

    // Path with parameter plus constraint on parameter.
    // You can have multiple constraints.
    [UrlRoute(Path = "users/{userId}")]
    [UrlRouteParameterConstraint(Name = "userId", Regex = @"\d+")]
    public ActionResult UserProfile(int userId)
    {
        // ...code omitted

        return View();
    }

    // Path with Order specified, to ensure it is added before the previous
    // route.  Without this, the "users/admin" URL may match the previous
    // route before this route is even evaluated.
    [UrlRoute(Path = "users/admin", Order = -10)]
    public ActionResult AdminProfile()
    {
        // ...code omitted

        return View();
    }

    // Path with multiple parameters and default value for the last
    // parameter if its not specified.
    [UrlRoute(Path = "users/{userId}/posts/{dateRange}")]
    [UrlRouteParameterConstraint(Name = "userId", Regex = @"\d+")]
    [UrlRouteParameterDefault(Name = "dateRange", Value = "all")]
    public ActionResult UserPostsByTag(int userId, string dateRange)
    {
        // ...code omitted

        return View();
    }

UrlRouteAttribute的定义:

/// <summary>
/// Assigns a URL route to an MVC Controller class method.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class UrlRouteAttribute : Attribute
{
    /// <summary>
    /// Optional name of the route.  If not specified, the route name will
    /// be set to [controller name].[action name].
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Path of the URL route.  This is relative to the root of the web site.
    /// Do not append a "/" prefix.  Specify empty string for the root page.
    /// </summary>
    public string Path { get; set; }

    /// <summary>
    /// Optional order in which to add the route (default is 0).  Routes
    /// with lower order values will be added before those with higher.
    /// Routes that have the same order value will be added in undefined
    /// order with respect to each other.
    /// </summary>
    public int Order { get; set; }
}

UrlRouteParameterConstraintAttribute的定义:

/// <summary>
/// Assigns a constraint to a route parameter in a UrlRouteAttribute.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class UrlRouteParameterConstraintAttribute : Attribute
{
    /// <summary>
    /// Name of the route parameter on which to apply the constraint.
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Regular expression constraint to test on the route parameter value
    /// in the URL.
    /// </summary>
    public string Regex { get; set; }
}

UrlRouteParameterDefaultAttribute的定义:

/// <summary>
/// Assigns a default value to a route parameter in a UrlRouteAttribute
/// if not specified in the URL.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class UrlRouteParameterDefaultAttribute : Attribute
{
    /// <summary>
    /// Name of the route parameter for which to supply the default value.
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Default value to set on the route parameter if not specified in the URL.
    /// </summary>
    public object Value { get; set; }
}

对Global.asax.cs的更改:

用对RouteUtility.RegisterUrlRoutesFromAttributes函数的单个调用替换对MapRoute的调用:

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        RouteUtility.RegisterUrlRoutesFromAttributes(routes);
    }

RouteUtility.RegisterUrlRoutesFromAttributes的定义:

完整的源代码在codeplex上。如果您有任何反馈或错误报告,请访问该站点。


我想这样做是因为使用属性会阻止使用路由默认值和路由约束...
Nicolas Cadilhac 09年

使用这种方法,我不需要默认路由,因为您可以将每个路由绑定到特定方法。您对约束的看法是正确的。我研究了能够将约束作为属性属性添加,但是遇到了一个麻烦,因为MVC约束是使用匿名对象指定的,并且属性属性只能是简单类型。我仍然认为可以将约束作为属性来进行编码(使用更多代码),但是我还没有对此感到烦恼,因为到目前为止,我在MVC工作中实际上并不需要约束(我倾向于验证路由值在控制器中)。
DSO,2009年

3
非常好!我们的RouteAttribute与此非常相似,只添加了一些附加的辅助功能。我必须添加一个详细说明差异的答案。
Jarrod Dixon

1
这是太棒了。我喜欢这个。
BowserKingKoopa

1
这很棒!我使用MvcContrib已有一段时间了,不知道它在那里。您在原始帖子中提到没有时间进行记录。还是这样吗?似乎至少在MvcContrib文档中提到它会很有帮助,以便开发人员至少知道它的存在。谢谢!
托德·梅尼尔

44

您也可以尝试AttributeRouting,可从github或通过nuget获得

这是一个无耻的插件,因为我是项目的作者。但是当我使用它不是很开心时,请当一下。你可能也是。github存储库Wiki中有大量文档和示例代码。

有了这个库,您可以做很多事情:

  • 使用GET,POST,PUT和DELETE属性装饰您的操作。
  • 将多个路由映射到一个动作,并使用Order属性对其进行排序。
  • 使用属性指定路由默认值和约束。
  • 使用简单的?指定可选参数。参数名称前的令牌。
  • 指定用于支持命名路由的路由名称。
  • 在控制器或基本控制器上定义MVC区域。
  • 使用应用于控制器或基本控制器的路由前缀将路由分组或嵌套在一起。
  • 支持旧版网址。
  • 在控制器内以及在控制器和基本控制器之间,为操作定义的路由之间设置路由的优先级。
  • 自动生成小写出站URL。
  • 定义自己的自定义路由约定,并将其应用到控制器上以为没有模板属性的控制器中的操作生成路由(请考虑使用RESTful样式)。
  • 使用提供的HttpHandler调试路由。

我确定我还有其他一些遗忘的东西。一探究竟。通过nuget安装很容易。

注意:从2012年4月16日开始,AttributeRouting也支持新的Web API基础结构。以防万一您正在寻找可以解决问题的东西。谢谢subkamran


10
与提到的其他选项相比,该项目似乎更成熟(更好的文档,更多的功能,完整的测试套件)
David Laing

3
我同意,这似乎可以完成您可能想要的所有事情,并且提供了很好的示例文档。
Mike Chamberlain'7

3
非常感谢你。我很高兴使用此解决方案,它解决了我所有的路由冲突,歧义和混乱。
Valamas

3
嗨,您应该在github页面上写上面的要点,因为我发现这个SO帖子是因为我正在寻找更多详细信息:)
GONeale

2
只是为了扮演魔鬼拥护者,将您的路线全部集中在一处是否有好处?就像我们会丢失任何东西,还是以任何方式受限于此方法?
GONeale

9

1.下载 RiaLibrary.Web.dll并在ASP.NET MVC网站项目中引用它

2.使用[Url]属性对控制器方法进行分类:

public SiteController : Controller
{
    [Url("")]
    public ActionResult Home()
    {
        return View();
    }

    [Url("about")]
    public ActionResult AboutUs()
    {
        return View();
    }

    [Url("store/{?category}")]
    public ActionResult Products(string category = null)
    {
        return View();
    }
}

顺便说一句,“?” 登录“ {?category}”参数表示该参数是可选的。您无需在路由默认值中明确指定此值,它等于此值:

routes.MapRoute("Store", "store/{category}",
new { controller = "Store", action = "Home", category = UrlParameter.Optional });

3.更新Global.asax.cs文件

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoutes(); // This does the trick
    }

    protected void Application_Start()
    {
        RegisterRoutes(RouteTable.Routes);
    }
}

如何设置默认值和约束?例:

public SiteController : Controller
{
    [Url("admin/articles/edit/{id}", Constraints = @"id=\d+")]
    public ActionResult ArticlesEdit(int id)
    {
        return View();
    }

    [Url("articles/{category}/{date}_{title}", Constraints =
         "date=(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])")]
    public ActionResult Article(string category, DateTime date, string title)
    {
        return View();
    }
}

如何设置订购?例:

[Url("forums/{?category}", Order = 2)]
public ActionResult Threads(string category)
{
    return View();
}

[Url("forums/new", Order = 1)]
public ActionResult NewThread()
{
    return View();
}

1
非常好!我特别喜欢{?param}可选参数的命名法。
Jarrod Dixon

3

这篇文章只是为了扩展DSO的答案。

在将路线转换为属性时,我需要处理ActionName属性。因此在GetRouteParamsFromAttribute中:

ActionNameAttribute anAttr = methodInfo.GetCustomAttributes(typeof(ActionNameAttribute), false)
    .Cast<ActionNameAttribute>()
    .SingleOrDefault();

// Add to list of routes.
routeParams.Add(new MapRouteParams()
{
    RouteName = routeAttrib.Name,
    Path = routeAttrib.Path,
    ControllerName = controllerName,
    ActionName = (anAttr != null ? anAttr.Name : methodInfo.Name),
    Order = routeAttrib.Order,
    Constraints = GetConstraints(methodInfo),
    Defaults = GetDefaults(methodInfo),
});

我也发现路线的名称不合适。该名称是使用controllerName.RouteName动态生成的。但是我的路由名称是控制器类中的const字符串,我使用这些const也调用Url.RouteUrl。这就是为什么我真的需要属性中的路线名称为路线的实际名称。

我要做的另一件事是将默认属性和约束属性转换为AttributeTargets.Parameter,以便可以将其固定在params上。


是的,我对路线命名行为有些疑惑。最好执行您所做的事情,只需按原样使用whats属性或将其设置为null。将默认值/约束放在参数本身上的好主意。我可能会将此代码发布在Codeplex上,以更好地管理更改。
DSO


0

我需要使用AsyncController在asp.net mvc 2中运行ITCloud路由-为此,只需在源代码中编辑RouteUtility.cs类并重新编译即可。您必须从第98行的动作名称中删除“已完成”

// Add to list of routes.
routeParams.Add(new MapRouteParams()
{
    RouteName = String.IsNullOrEmpty(routeAttrib.Name) ? null : routeAttrib.Name,
    Path = routeAttrib.Path,
    ControllerName = controllerName,
    ActionName = methodInfo.Name.Replace("Completed", ""),
    Order = routeAttrib.Order,
    Constraints = GetConstraints(methodInfo),
    Defaults = GetDefaults(methodInfo),
    ControllerNamespace = controllerClass.Namespace,
});

然后,在AsyncController,装饰XXXXCompleted的ActionResult与熟悉UrlRouteUrlRouteParameterDefault属性:

[UrlRoute(Path = "ActionName/{title}")]
[UrlRouteParameterDefault(Name = "title", Value = "latest-post")]
public ActionResult ActionNameCompleted(string title)
{
    ...
}

希望对遇到同样问题的人有所帮助。


仅供参考,约定是在ActionNameAsync方法而不是ActionNameCompleted方法上具有与MVC相关的属性。
Erv Walter 2010年

谢谢-没有意识到这一点。
TimDog 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.