Create rule S6934: A Route attribute should be added to the controller when a route template is specified at the action level (#3676)
This commit is contained in:
parent
6798817826
commit
71960b568a
@ -1,5 +1,7 @@
|
||||
// C#
|
||||
* ASP.NET
|
||||
* ASP.NET Core
|
||||
* ASP.NET MVC 4.x
|
||||
* Razor
|
||||
* .NET
|
||||
* Entity Framework Core
|
||||
|
3
rules/S6934/csharp/metadata.json
Normal file
3
rules/S6934/csharp/metadata.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
|
||||
}
|
125
rules/S6934/csharp/rule.adoc
Normal file
125
rules/S6934/csharp/rule.adoc
Normal file
@ -0,0 +1,125 @@
|
||||
When a route template is defined through an attribute on an action method, conventional routing for that action is disabled. To maintain good practice, it's recommended not to combine conventional and attribute-based routing within a single controller to avoid unpredicted behavior. As such, the controller should exclude itself from conventional routing by applying a `[Route]` attribute.
|
||||
|
||||
== Why is this an issue?
|
||||
|
||||
In https://learn.microsoft.com/en-us/aspnet/core/mvc/overview[ASP.NET Core MVC], the https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing[routing] middleware utilizes a series of rules and conventions to identify the appropriate controller and action method to handle a specific HTTP request. This process, known as _conventional routing_, is generally established using the https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.controllerendpointroutebuilderextensions.mapcontrollerroute[`MapControllerRoute`] method. This method is typically configured in one central location for all controllers during the application setup.
|
||||
|
||||
Conversely, _attribute routing_ allows routes to be defined at the controller or action method level. It is possible to https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing#mixed-routing-attribute-routing-vs-conventional-routing[mix both mechanisms]. Although it's permissible to employ diverse routing strategies across multiple controllers, combining both mechanisms within one controller can result in confusion and increased complexity, as illustrated below.
|
||||
|
||||
[source,csharp]
|
||||
----
|
||||
// Conventional mapping definition
|
||||
app.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||
|
||||
public class PersonController
|
||||
{
|
||||
// Conventional routing:
|
||||
// Matches e.g. /Person/Index/123
|
||||
public IActionResult Index(int? id) => View();
|
||||
|
||||
// Attribute routing:
|
||||
// Matches e.g. /Age/Ascending (and model binds "Age" to sortBy and "Ascending" to direction)
|
||||
// but does not match /Person/List/Age/Ascending
|
||||
[HttpGet(template: "{sortBy}/{direction}")]
|
||||
public IActionResult List(string sortBy, SortOrder direction) => View();
|
||||
}
|
||||
----
|
||||
|
||||
== How to fix it in ASP.NET Core
|
||||
|
||||
When any of the controller actions are annotated with a https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.routing.httpmethodattribute[`HttpMethodAttribute`] with a route template defined, you should specify a route template on the controller with the https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.routeattribute[`RouteAttribute`] as well.
|
||||
|
||||
=== Code examples
|
||||
|
||||
==== Noncompliant code example
|
||||
|
||||
[source,csharp,diff-id=1,diff-type=noncompliant]
|
||||
----
|
||||
public class PersonController : Controller
|
||||
{
|
||||
// Matches /Person/Index/123
|
||||
public IActionResult Index(int? id) => View();
|
||||
|
||||
// Matches /Age/Ascending
|
||||
[HttpGet(template: "{sortBy}/{direction}")] // Noncompliant: The "Index" and the "List" actions are
|
||||
// reachable via different routing mechanisms and routes
|
||||
public IActionResult List(string sortBy, SortOrder direction) => View();
|
||||
}
|
||||
----
|
||||
|
||||
==== Compliant solution
|
||||
|
||||
[source,csharp,diff-id=1,diff-type=compliant]
|
||||
----
|
||||
[Route("[controller]/{action=Index}")]
|
||||
public class PersonController : Controller
|
||||
{
|
||||
// Matches /Person/Index/123
|
||||
[Route("{id?}")]
|
||||
public IActionResult Index(int? id) => View();
|
||||
|
||||
// Matches Person/List/Age/Ascending
|
||||
[HttpGet("{sortBy}/{direction}")] // Compliant: The route is relative to the controller
|
||||
public IActionResult List(string sortBy, SortOrder direction) => View();
|
||||
}
|
||||
----
|
||||
|
||||
There are also alternative options to prevent the mixing of conventional and attribute-based routing:
|
||||
|
||||
[source,csharp]
|
||||
----
|
||||
// Option 1. Replace the attribute-based routing with a conventional route
|
||||
app.MapControllerRoute(
|
||||
name: "Lists",
|
||||
pattern: "{controller}/List/{sortBy}/{direction}",
|
||||
defaults: new { action = "List" } ); // Matches Person/List/Age/Ascending
|
||||
|
||||
// Option 2. Use a binding, that does not depend on route templates
|
||||
public class PersonController : Controller
|
||||
{
|
||||
// Matches Person/List?sortBy=Age&direction=Ascending
|
||||
[HttpGet] // Compliant: Parameters are bound from the query string
|
||||
public IActionResult List(string sortBy, SortOrder direction) => View();
|
||||
}
|
||||
|
||||
// Option 3. Use an absolute route
|
||||
public class PersonController : Controller
|
||||
{
|
||||
// Matches Person/List/Age/Ascending
|
||||
[HttpGet("/[controller]/[action]/{sortBy}/{direction}")] // Illustrate the expected route by starting with "/"
|
||||
public IActionResult List(string sortBy, SortOrder direction) => View();
|
||||
}
|
||||
----
|
||||
|
||||
== Resources
|
||||
|
||||
=== Documentation
|
||||
|
||||
* Microsoft Learn - https://learn.microsoft.com/en-us/aspnet/core/mvc/overview[Overview of ASP.NET Core MVC]
|
||||
* Microsoft Learn - https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing[Routing to controller actions in ASP.NET Core]
|
||||
* Microsoft Learn - https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing#mixed-routing-attribute-routing-vs-conventional-routing[Mixed routing: Attribute routing vs conventional routing]
|
||||
* Microsoft Learn - https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.routing.httpmethodattribute[HttpMethodAttribute Class]
|
||||
* Microsoft Learn - https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.routeattribute[RouteAttribute Class]
|
||||
|
||||
=== Articles & blog posts
|
||||
|
||||
* Medium - https://medium.com/quick-code/routing-in-asp-net-core-c433bff3f1a4[Routing in ASP.NET Core]
|
||||
|
||||
ifdef::env-github,rspecator-view[]
|
||||
|
||||
'''
|
||||
== Implementation Specification
|
||||
(visible only on this page)
|
||||
|
||||
=== Message
|
||||
|
||||
Specify the RouteAttribute when an HttpMethodAttribute or RouteAttribute is specified at an action level.
|
||||
|
||||
=== Highlighting
|
||||
|
||||
* Primary location: Controller class declaration identifier
|
||||
* Secondary location: The identifier of the controller action method declaration
|
||||
|
||||
endif::env-github,rspecator-view[]
|
25
rules/S6934/metadata.json
Normal file
25
rules/S6934/metadata.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"title": "A Route attribute should be added to the controller when a route template is specified at the action level",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant\/Issue",
|
||||
"constantCost": "5min"
|
||||
},
|
||||
"tags": [
|
||||
"asp.net"
|
||||
],
|
||||
"defaultSeverity": "Major",
|
||||
"ruleSpecification": "RSPEC-6934",
|
||||
"sqKey": "S6934",
|
||||
"scope": "Main",
|
||||
"defaultQualityProfiles": ["Sonar way"],
|
||||
"quickfix": "partial",
|
||||
"code": {
|
||||
"impacts": {
|
||||
"MAINTAINABILITY": "HIGH"
|
||||
},
|
||||
"attribute": "CLEAR"
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user