一、介绍

在ASP.NET Core中,路由是将传入的URL请求映射到正确的控制器和操作的方法。Attribute路由是一种基于属性,用于定义路由规则的方式,通过在控制器类和操作方法上应用特定的属性,来定义URL模板。

  1. 基本概念:
  • **路由:**在ASP.NET Core中,路由是将URL请求映射到正确的控制器和操作的过程。路由中间件会按照定义的路由规则,将传入的HTTP请求匹配到正确的路由路径,进而执行对应的控制器和操作方法。
  • **控制器:**控制器是处理HTTP请求的类,其中包含处理请求的操作方法。在ASP.NET Core中,控制器类必须继承自Controller或ControllerBase类。
  • **操作方法:**操作方法是控制器中用于处理HTTP请求的具体实现。通过在控制器类或操作方法上应用特定的属性,可以定义URL模板,从而实现路由匹配。
  1. 重要性:
  • 可读性更强:使用属性路由,可以定义更加清晰和易读的路由路径,使得URL更加友好和易于理解。
  • **灵活性更高:**属性路由可以更加灵活地定义路由规则,支持多种路由匹配方式,如默认路由、自定义路由、参数路由等。
  • **可维护性更高:**属性路由的路由规则定义更加集中和清晰,易于维护和管理。同时,由于路由规则定义在控制器类和操作方法上,可以更好地与代码分离,提高代码的可读性和可维护性。
  • **性能更优:**属性路由在路由匹配时,可以利用编译时静态分析,提前解析路由模板,从而提高路由匹配的性能。

二、传统路由和属性路由的比较和选择

传统路由和属性路由(Attribute Routing)是ASP.NET Core中的两种主要路由(Routing)方式。下面是它们的比较和选择:

  1. 传统路由(Convention-based Routing):
    传统路由是一种基于约定的路由方式。在传统路由中,我们定义路由规则时,需要指定路由的URL模板以及相应的控制器和操作方法。传统路由是一种静态路由方式,它的路由规则是在应用程序启动时静态定义的。
  • 优点:
  • 简单易用:传统路由的路由规则定义简单明了,易于理解和使用。
  • 性能较高:传统路由的路由规则定义是在应用程序启动时静态定义的,因此在路由匹配时具有较高的性能。
  • 缺点:
  • 不够灵活:传统路由的路由规则定义是基于约定的,不够灵活,无法满足一些复杂的路由需求。
  • 可读性较差:传统路由的路由规则定义在代码中分散开来,可读性较差。
  1. 属性路由(Attribute Routing):
    属性路由是一种基于属性的路由方式。在属性路由中,我们可以在控制器类和操作方法上应用特定的属性来定义路由规则。属性路由是一种动态路由方式,它的路由规则是在运行时动态定义的。
  • 优点:
  • 更加灵活:属性路由的路由规则定义更加灵活,可以满足一些复杂的路由需求。
  • 可读性更好:属性路由的路由规则定义在代码中集中起来,可读性更好。
  • 可维护性更高:属性路由的路由规则定义更加集中和清晰,易于维护和管理。
  • 缺点:
  • 相对复杂:属性路由的路由规则定义相对复杂,需要一定的学习成本。
  • 性能较低:属性路由的路由规则定义是在运行时动态定义的,因此在路由匹配时性能相对较低。
  1. 选择:
    在选择传统路由和属性路由时,需要根据具体的应用场景和需求来选择。如果应用程序的路由规则比较简单,且性能要求较高,可以选择传统路由;如果应用程序的路由规则比较复杂,且需要更高的可读性和可维护性,可以选择属性路由。同时,在实际开发中,也可以结合使用传统路由和属性路由,以满足不同的路由需求。

三、Attribute路由的基本使用

3.1 在Controller上使用Attribute路由

在ASP.NET Core中,我们可以在控制器类上使用[Route]属性来定义控制器级别的路由规则。以下是一个示例:

[Route("api/[controller]")]
public class UsersController : Controller
{
    [HttpGet]
    public IActionResult Get()
    {
        return Ok();
    }

    [HttpGet("{id}")]
    public IActionResult Get(int id)
    {
        return Ok();
    }

    // ...
}

在上面的示例中,我们在UsersController类上使用了[Route("api/[controller]")]属性,这表示所有该控制器的操作方法都可以通过“api/users”路径访问。

Tip:这里的[controller]是一个占位符,它会被实际的控制器名称替换。例如,如果你访问api/users路径,[controller]将被替换为Users

此外,我们还分别在GetGet(int id)方法上使用了[HttpGet][HttpGet("{id}")]属性来定义它们的路由。其中,[HttpGet]表示该方法可以通过HTTP GET请求访问,而[HttpGet("{id}")]表示该方法可以通过具有id参数的HTTP GET请求访问。

通过这种方式,我们可以方便地定义控制器级别的路由规则,从而更好地组织我们的代码和URL。

3.2 在Action上使用Attribute路由

在ASP.NET Core中,我们可以在操作方法上使用[Route]属性来定义操作方法级别的路由规则。以下是一个示例:

public class UsersController : Controller
{
    [HttpGet("{id:int}")]
    public IActionResult GetUser(int id)
    {
        // ...
    }

    [HttpPost]
    [Route("users/create")]
    public IActionResult CreateUser(UserViewModel model)
    {
        // ...
    }

    // ...
}

在上面的示例中,我们在GetUser方法上使用了[HttpGet("{id:int}")]属性,这表示该方法可以通过具有id参数的HTTP GET请求访问,并且id必须是整数类型。而在CreateUser方法上,我们使用了[HttpPost][Route("users/create")]属性。这表示该方法可以通过HTTP POST请求访问,并且可以通过“users/create”路径访问。
通过这种方式,我们可以更加精细地定义操作方法级别的路由规则,从而更好地满足我们的需求。注意,操作方法级别的路由规则会覆盖控制器级别的路由规则。如果一个操作方法上定义了路由规则,它将优先于控制器级别的路由规则。

3.3 使用自定义路由

在ASP.NET Core中,我们可以使用自定义路由来实现更加灵活的路由规则。以下是一个示例:

public class CustomRouteAttribute : Attribute, IConfigureRoute
{
    public string RouteName { get; set; }
    public string[] RouteNames { get; set; }
    public string Template { get; set; }
    public object[] Constraints { get; set; }
    public string[] Order { get; set; }

    public void Configure(RouteCollection routes, string routeName)
    {
        var route = new Route(Template, new CustomRouteHandler());

        if (Constraints != null)
        {
            foreach (var constraint in Constraints)
            {
                route.Constraints.Add(constraint.GetType().Name, constraint);
            }
        }

        if (Order != null)
        {
            var routeList = new List<Route>();
            foreach (var name in RouteNames)
            {
                var r = routes.GetRouteData(new MvcContext(HttpContext.Current));
                while (r != null)
                {
                    if (r.Route.RouteName.Equals(name, StringComparison.OrdinalIgnoreCase))
                    {
                        routeList.Add(r.Route);
                        break;
                    }
                    r = r.Route.Parent;
                }
            }

            if (routeList.Count > 0)
            {
                var index = routeList.IndexOf(route);
                if (index >= 0)
                {
                    routeList.RemoveAt(index);
                    routeList.Insert(index, route);
                }
            }
            else
            {
                routes.Add(route);
            }
        }
        else
        {
            routes.Add(route);
        }
    }
}

[CustomRoute(RouteName = "custom", Template = "custom/{id}")]
public class CustomController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

在上面的示例中,我们自定义了一个CustomRouteAttribute属性,并在CustomController上使用它来定义路由规则。该属性的Template属性定义了路由模板,RouteName属性定义了路由名称,还可以定义其他的路由约束和路由顺序等。在Configure方法中,我们通过RouteCollectionRouteName来添加路由规则,并且可以根据需要对路由规则进行排序。
通过这种方式,我们可以更加灵活地定义路由规则,从而更好地满足我们的需求。注意,在使用自定义路由时,需要将UseMvc替换为UseMvcWithDefaultRoute,并且需要在Startup.cs文件的ConfigureServices方法中注册自定义路由。

四、Attribute路由的高级使用

4.1 路由参数

在 ASP.NET Core 中,我们可以使用 Attribute 路由来定义路由参数。这可以让我们更精确地控制路由的生成。
下面是一个基本的例子:

[Route("api/[controller]")]
public class UsersController : Controller
{
    [HttpGet("{id:int}")]
    public IActionResult GetUser(int id)
    {
        // ...
    }
}

在这个例子中,[Route("api/[controller]")]是控制器级别的路由,表示所有 UsersController 的操作都会被路由到 “api/users” 路径。[HttpGet("{id:int}")]是操作级别的路由,表示 GetUser 方法可以被通过 GET 请求访问,并且需要一个整数参数 “id”。
你可以使用不同的 HTTP 方法来定义路由,例如:

[HttpGet("{id:int}")]
public IActionResult GetUser(int id)
{
    // ...
}

[HttpPost]
public IActionResult CreateUser(UserModel model)
{
    // ...
}

在上面的例子中,[HttpPost]表示 CreateUser 方法只能通过 POST 请求访问。
你还可以使用自定义的路由约束来限制路由参数的取值范围,例如:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class PositiveIntConstraint : Attribute, IHttpRouteConstraint
{
    public bool Match(HttpContext context, RouteData data, string parameterName, out RouteValueDictionary values)
    {
        var value = data.Values[parameterName];
        if (value != null && value.ToString().IsInt() && Convert.ToInt32(value) > 0)
        {
            values = new RouteValueDictionary(new { });
            return true;
        }
        return false;
    }
}

public class UsersController : Controller
{
    [HttpGet("{id}", Name = "GetUserById")]
    [PositiveIntConstraint]
    public IActionResult GetUser(int id)
    {
        // ...
    }
}

在上面的例子中,PositiveIntConstraint是一个自定义的路由约束,它限制了路由参数 “id” 必须是一个大于 0 的整数。如果不符合这个条件,路由请求将会失败。

4.2 其他高级功能

除了上述的路由参数,Attribute 路由还有其他一些高级功能,包括:

  1. 路由模板:你可以使用和传统路由一样的模板语法来定义 Attribute 路由。比如,[Route("{controller}/{action}/{id}")]
  2. 可选参数:你可以定义可选的路由参数。比如,[Route("products/{id:int?}")],这里 id 是可选的。
  3. 默认值:你可以给路由参数设置默认值。比如,[Route("{controller=Home}/{action=Index}/{id=0}")],这里 controller 的默认值是 Home,action 的默认值是 Index,id 的默认值是 0。
  4. 约束类型:除了整数,你还可以对其他类型的参数进行约束。比如,[HttpGet("{id:guid}")] 可以约束 id 必须是 GUID 类型。
  5. 自定义约束:你可以定义自己的约束。比如,你可以定义一个约束来检查一个字符串是否是一个有效的 email 地址。
  6. 嵌套路由:你可以在一个路由中嵌套另一个路由。比如,[Route("{category}/{product}")],这里 category 和 product 都是路由参数。
  7. 静态和动态路由:你可以结合使用静态和动态路由。比如,[Route("/about")][Route("{id}")] 可以同时存在。

五、示例

好的,以下是一个综合了前面所有内容的示例:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class PositiveIntConstraint : Attribute, IHttpRouteConstraint
{
    public bool Match(HttpContext context, RouteData data, string parameterName, out RouteValueDictionary values)
    {
        var value = data.Values[parameterName];
        if (value != null && value.ToString().IsInt() && Convert.ToInt32(value) > 0)
        {
            values = new RouteValueDictionary(new { });
            return true;
        }
        return false;
    }
}

public class UsersController : Controller
{
    [HttpGet("{id}", Name = "GetUserById")]
    [PositiveIntConstraint]
    public IActionResult GetUser(int id)
    {
        // ...
    }

    [HttpGet("users/edit/{id:guid}")]
    public IActionResult EditUser(Guid id)
    {
        // ...
    }

    [HttpPost]
    [Route("users/create")]
    public IActionResult CreateUser(UserModel model)
    {
        // ...
    }

    [Route("users/deleted/{id}")]
    public IActionResult DeleteUser(int id)
    {
        // ...
    }

    [Route("users/restore/{id}")]
    public IActionResult RestoreUser(int id)
    {
        // ...
    }

    [Route("{*url}", Name = "PageNotFound")]
    public IActionResult PageNotFound()
    {
        // ...
    }
}

在上面的例子中,我们定义了一个 PositiveIntConstraint 约束来限制路由参数必须是一个大于 0 的整数。我们定义了 5 个不同的路由:

  • GetUser 方法可以通过 /users/123 这样的 URL 访问,其中 123 是一个大于 0 的整数。
  • EditUser 方法可以通过 /users/edit/456 这样的 URL 访问,其中 456 是一个 GUID 类型的参数。
  • CreateUser 方法可以通过 /users/create 这样的 URL 访问,不需要任何参数。
  • DeleteUser 方法可以通过 /users/deleted/789 这样的 URL 访问,其中 789 是一个大于 0 的整数。
  • RestoreUser 方法可以通过 /users/restore/1000 这样的 URL 访问,其中 1000 是一个大于 0 的整数。
  • 如果请求的 URL 不符合上面的任何一个路由,那么就会返回 PageNotFound 方法的结果,这个方法会返回一个 404 页面。

六、总结

Attribute路由是一种强大的路由机制,允许我们在 ASP.NET Core 中灵活地定义路由。通过使用各种属性和约束,我们可以精确控制 URL 的生成和解析。Attribute 路由提供了路由参数、可选参数、默认值、约束类型、自定义约束、嵌套路由、静态和动态路由等高级功能。这使得我们能够构建复杂而强大的 Web 应用程序,同时也提高了代码的可读性和可维护性。通过合理地使用 Attribute 路由,我们可以提升 Web 应用程序的性能和用户体验。