As I can see the biggest advantage of the Name
property of the HttpMethodAttribute
(which is the base class of HttpGetAttribute
) is that you can distinguish method overloads:
[HttpGet(Name="ById"]
public IActionResult GetBy(int id)
{
}
[HttpGet(Name="ByExternalId"]
public IActionResult GetBy(Guid id)
{
}
UPDATE #1: I've revised answer
The above sample code would result in an AmbiguousMatchException
, where it is stating the same Template
has been registered for different action.
I had put together another sample and used the following RouteDebugger to get insight. In the Configure
method I've called the app.UseRouteDebugger()
method to be able to see the registered routes in json format under the /route-debugger url.
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet()]
public IActionResult GetA(string a)
{
return Ok(nameof(GetA));
}
[HttpGet(template: "GetB")]
public IActionResult GetB(string b)
{
return Ok(nameof(GetB));
}
[HttpGet(template: "GetC", Name= "GetD")]
public IActionResult GetD(string d, string e)
{
return CreatedAtRoute(routeName:"GetC", routeValues: new { c = "v"}, value: null);
}
[HttpGet(template: "GetC/{c}", Name = "GetC")]
public IActionResult GetC(string c)
{
return Ok(nameof(GetC));
}
}
The route table would look like this:
[
{
"Action":"GetA",
"Controller":"Test",
"Name":null,
"Template":"api/Test",
"Contraint":[{}]
},
{
"Action":"GetB",
"Controller":"Test",
"Name":null,
"Template":"api/Test/GetB",
"Contraint":[{}]
},
{
"Action":"GetD",
"Controller":"Test",
"Name":"GetD",
"Template":"api/Test/GetC",
"Contraint":[{}]
},
{
"Action":"GetC",
"Controller":"Test",
"Name":"GetC",
"Template":"api/Test/GetC/{c}",
"Contraint":[{}]
}
]
As you seen the following happened:
GetA method
- It is exposed under the controller route because not
Template
has been specified.
- The route itself does not have a
Name
so you can't refer to this route via its name inside ActionLink
or CreatedAtRoute
, etc.
GetB method
- It is exposed under the
api/test/getb
, because the controller's and the action's Template
properties are combined.
- The route itself does not have a
Name
so you can't refer to this route via its name inside ActionLink
or CreatedAtRoute
, etc.
GetC method
- It is exposed under the
api/test/getc/{c}
, because the controller's and the action's Template
properties are combined. The c
parameter can accept any value. If it is not provided then GetD
will be called, because that was registered first.
- The route has a
Name
(GetC
) so you can refer to this route via its name inside ActionLink
or CreatedAtRoute
, etc. Like as we did it inside GetD
GetD method
- It is exposed under the
api/test/getc
, because the controller's and the action's Template
properties are combined. Because it was registered prior GetC
method's route that's why it will called if no further path is provided.
- The route has a
Name
(GetD
). In the CreatedAtRoute
we are referring to GetC
via its name not through its route. If we are replacing the Name to GetC
then it would throw the following exception at runtime:
InvalidOperationException: The following errors occurred with
attribute routing information:
Error 1: Attribute routes with the same name 'GetC' must have the same
template: Action: 'Controllers.TestController.GetD ()' - Template:
'api/Test/GetC' Action: 'Controllers.TestController.GetC ()' -
Template: 'api/Test/GetC/{c}
Summary
- If you register two routes with the same
Template
it will throw an AmbiguousMatchException
at runtime when you make a call against that route. (So, other routes will work perfectly fine)
- If you register two routes with the same
Name
it will throw an InvalidOperationException
at runtime when you make any call. (So, other routes will not work)
- Route's
Name
provides the ability to refer them easily without knowing the exact Template
. This separation allows us to change the Template
without affecting referring links.