ASP.NET MVC 通过方法属性路由 [关闭]

Posted

技术标签:

【中文标题】ASP.NET MVC 通过方法属性路由 [关闭]【英文标题】:ASP.NET MVC Routing Via Method Attributes [closed] 【发布时间】:2010-10-28 01:03:01 【问题描述】:

在*** Podcast #54 中,Jeff 提到他们通过处理路由的方法上方的属性在 *** 代码库中注册他们的 URL 路由。听起来是个不错的概念(Phil Haack 就路线优先级提出了警告)。

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

此外,使用这种路由风格的任何“最佳实践”?

【问题讨论】:

【参考方案1】:

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

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

我实际上已经为我的 MVC 项目做了类似的事情(我不知道 Jeff 是如何使用 *** 做的)。我定义了一组自定义属性: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 的更改:

将 MapRoute 调用替换为对 RouteUtility.RegisterUrlRoutesFromAttributes 函数的一次调用:

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

        RouteUtility.RegisterUrlRoutesFromAttributes(routes);
    

RouteUtility.RegisterUrlRoutesFromAttributes的定义:

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

【讨论】:

我猜用属性这样做会阻止使用路由默认值和路由约束... 使用这种方法,我从不需要默认路由,因为您将每个路由绑定到特定方法。你对约束是正确的。我研究了能够将约束添加为属性属性,但遇到了一个障碍,因为 MVC 约束是使用匿名对象指定的,而属性属性只能是简单类型。我仍然可能认为将约束作为属性(使用更多编码),但我还没有打扰它,因为到目前为止我的 MVC 工作中还没有真正需要约束(我倾向于验证路由值在控制器中)。 非常好!我们的 RouteAttribute 与此非常相似,只是添加了一些额外的辅助功能。我必须添加一个详细说明差异的答案。 这太棒了。我很喜欢它。 这太棒了!我使用 MvcContrib 已经有一段时间了,不知道它在那里。您在原始帖子中提到您没有时间记录它。还是这样吗?似乎至少在 MvcContrib 文档中提及它会非常有帮助,以便开发人员至少知道它在那里。谢谢!【参考方案2】:

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

这是一个无耻的插件,因为我是项目作者。但是,如果我不是很高兴使用它的话。你可能也是。 github存储库wiki中有大量文档和示例代码。

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

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

我确定还有一些我忘记的东西。看看这个。通过nuget安装很轻松。

注意:截至 2012 年 4 月 16 日,AttributeRouting 还支持新的 Web API 基础架构。以防万一您正在寻找可以处理的东西。 Thanks subkamran!

【讨论】:

这个项目似乎比提到的其他选项更成熟(更好的文档、更多功能、完整的测试套件) 我同意,这似乎可以满足您的所有需求,并且带有很好的示例文档。 非常感谢。我很高兴使用这个解决方案,它解决了我所有的路由冲突、歧义和混乱。 嘿,你应该在你的 github 页面上写下上面的要点,因为我在寻找更多细节时发现了这个 SO 帖子 :) 只是为了扮演魔鬼的拥护者,在一个地方声明你的路线有什么好处吗?比如我们是否会失去任何东西,或者以任何方式受限于切换到这种方法?【参考方案3】:

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();

【讨论】:

非常好!我特别喜欢可选参数的 ?param 命名法。【参考方案4】:

这篇文章只是为了扩展 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 以便我可以将它们粘贴到参数。

【讨论】:

是的,我对路由命名行为有点摇摆不定。最好做你所做的,只需按原样使用属性中的内容或使其为空。将默认/约束放在参数本身上的好主意。我可能会在某个时候将其发布在 codeplex 上以更好地管理更改。【参考方案5】:

我已将这两种方法组合成一个科学怪人版本,供任何需要的人使用。 (我喜欢可选的参数符号,但也认为它们应该是与默认/约束分开的属性,而不是全部混合在一起)。

http://github.com/djMax/AlienForce/tree/master/Utilities/Web/

【讨论】:

【参考方案6】:

我需要使用 AsyncController 让 ITCloud 路由在 asp.net mvc 2 中工作——为此,只需在源代码中编辑 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 中,用熟悉的 UrlRouteUrlRouteParameterDefault 属性装饰 XXXXCompleted ActionResult:

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

    ...

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

【讨论】:

仅供参考,约定是在 ActionNameAsync 方法而不是 ActionNameCompleted 方法上具有 MVC 相关属性。 谢谢 -- 没有意识到这一点。

以上是关于ASP.NET MVC 通过方法属性路由 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

asp.net 5 MVC6 中标签助手和路由属性之间的奇怪行为

ASP.NET MVC AcceptVerbs 和注册路由

Asp.Net MVC的路由

asp.net core 系列 5 MVC框架路由(上)

ASP.NET MVC Core/6:多个提交按钮

ASP.NET MVC Core/6:多个提交按钮