What is "name" property in the constructor for HttpGetAttribute?
Asked Answered
F

6

20

When I use HttpGet(...), intellisense tells me that besides the first argument, i.e. pattern, I also have name and order. While the latter is obvious to me, I got a bit uncertain on what the parameter name had as its purpose.

Heading over to the docs, I see that the constructor of HttpGet only declares a single parameter. That confused me and I suspect that I'm missing something or using the Framework's version instead of Core, or something.

Flouncing answered 22/6, 2020 at 7:19 Comment(2)
Can you advise what more you're looking for in this question?Gosser
@John Check the comment to your answer.Flouncing
Z
4

It says they can be used to generate a URL based on a specific route. I don't think I understand what it means (despite understanding the words as such). Please elaborate

Here are my two cents:

When designing a proper API, the level 3 of maturity model talks about HATEOAS constraint (Hypermedia As Transfer Engine Of Application State). More information about it can be found at : Maturity models of Rest API

In order to traverse through a resource and to know the actions that are available with that resource we generate links for the resource.

The generation of links is done with the help of URLHelper library, which takes in a name parameter to define the link. The name we associate with in the URLHelper library, is the name defined for HTTP GET/POST/PUT etc action using the attribute Name parameter.

In simple terms, it is an identifier for your Route.

P.S

A complete working example of a web api with HATEOAS can be found on github:

Web Api with HATEOAS example

Zymometer answered 3/7, 2020 at 16:32 Comment(2)
I don't understand what difference in how the application works is if I use [HttpGet("{id}", Name = "FindPetById")] compared to [HttpGet("{id}"]? Where does the difference becomes effective?Flouncing
The controller can have multiple HttpGet, HttpPost actions. If we do not name the actions, it is not possible for us to reference them while generating links with the help of urlhelper.Zymometer
F
10

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

  1. 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)
  2. 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)
  3. 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.
Ferrous answered 3/7, 2020 at 6:11 Comment(5)
I don't understand how that differs from using a path, i.e. Template. What do we gain by applying the Name then?Flouncing
@KonradViltersten I've revised my answer please check it.Ferrous
As stated the route names must be globally unique, so a convention like [HttpGet("{id)", Name="[controller]ById")] is useful - this avoids ById/ByName clashing on different controllers for exampleClangor
When you say As I can see the biggest advantage of the Name property of the HttpMethodAttribute is that you can distinguish method overloads - you mean you can't do the same by using Template to define different endpoint names ? I do not see why. I mean, I have been doing it using Template for a long time... perhaps I can infer from @PaulHatcher 's remark that using Name there will be some enforcement in that the route is unique - and by using Template not ?Adrienadriena
@Adrienadriena Thanks for the question. The first version of my answer is definitely contains an invalid statement. I've corrected that under the update #1 section. I'll strike through that statement to avoid further confusion.Ferrous
G
6

Name is the route name, as distinct from the template. See the docs page.

The docs state:

Route names can be used to generate a URL based on a specific route. Route names:

  • Have no impact on the URL matching behavior of routing.
  • Are only used for URL generation.

Route names must be unique application-wide.

You can use the name to generate URLs for named routes using IUrlHelper. For example, if you named a route "Konrad", you could generate a link like so:

string url = urlHelper.Link("Konrad", new { id = 5, query = "test" });

Where id and query are the route parameters.

By the way, the issue you had with the documentation is that HttpGet is an Attribute. The attribute syntax allows you to specify values for the attributes' properties by name after any positional constructor values.

Considering the following attribute, you can see that the constructor accepts int a, but there's also a string propery: B.

public class TestAttribute : System.Attribute
{
    public TestAttribute(int a)
    {
    }

    public string B {get;set;}
}

To use such an attribute, you could apply it in the following ways:

[TestAttribute(5)] // B is null
[TestAttribute(5, B = "hello")] // B is "hello"

or simply as Test:

[Test(5)] // B is null
[Test(5, B = "hello")] // B is "hello"
Gosser answered 22/6, 2020 at 7:23 Comment(4)
I find the section of the docs rather scarce on information. Perhaps I should ask the question from a different direction. How is the name used? What is it good for? It says they can be used to generate a URL based on a specific route. I don't think I understand what it means (despite understanding the words as such). Please elaborate.Flouncing
Am I to understand that using urlHelper.Link("Konrad", ...) is equivalent to urlHelper.Link("beep/blopp", ...) given that the controller named beep has a method called blopp (or, possibly, a different name but the path template, i.e. [HttpGet("blopp", "Konrad")])?Flouncing
@Konrad In response to your question on other answers: we're using route names in our API in order to differentiate method overloads for automatic link generation (I created code to convert from an Expression<Func<...>> to a link. We've automated name generation though, rather than manually adding a name to each action.Gosser
I still fail to understand the gain of having that facility. If I have two methods - one accepting an integer and the other accepting an integer and a string, why don't I use [HttpGet("{id}")] and [HttpGet("{id}/{property}")]? I sense there's a more specialized case that I'm simply unable to come up with that requires Name too but the issue you've mentioned seems to be solvable without it. Please elaborate. I sense that you've actually got why I'm confused as to the purpose of the property Name.Flouncing
Z
4

It says they can be used to generate a URL based on a specific route. I don't think I understand what it means (despite understanding the words as such). Please elaborate

Here are my two cents:

When designing a proper API, the level 3 of maturity model talks about HATEOAS constraint (Hypermedia As Transfer Engine Of Application State). More information about it can be found at : Maturity models of Rest API

In order to traverse through a resource and to know the actions that are available with that resource we generate links for the resource.

The generation of links is done with the help of URLHelper library, which takes in a name parameter to define the link. The name we associate with in the URLHelper library, is the name defined for HTTP GET/POST/PUT etc action using the attribute Name parameter.

In simple terms, it is an identifier for your Route.

P.S

A complete working example of a web api with HATEOAS can be found on github:

Web Api with HATEOAS example

Zymometer answered 3/7, 2020 at 16:32 Comment(2)
I don't understand what difference in how the application works is if I use [HttpGet("{id}", Name = "FindPetById")] compared to [HttpGet("{id}"]? Where does the difference becomes effective?Flouncing
The controller can have multiple HttpGet, HttpPost actions. If we do not name the actions, it is not possible for us to reference them while generating links with the help of urlhelper.Zymometer
P
1

I think this and this could help.

Route name

The following code defines a route name of Products_List:

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Route names can be used to generate a URL based on a specific route. Route names:

Have no impact on the URL matching behavior of routing.
Are only used for URL generation.
Route names must be unique application-wide.

Contrast the preceding code with the conventional default route, which defines the id parameter as optional ({id?}). The ability to precisely specify APIs has advantages, such as allowing /products and /products/5 to be dispatched to different actions.

In asp.net core source code I've found only this usage

[HttpGet("{id}", Name = "FindPetById")]
[ProducesResponseType(typeof(Pet),  StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Pet>> FindById(int id)
{
     var pet = await DbContext.Pets
                .Include(p => p.Category)
                .Include(p => p.Images)
                .Include(p => p.Tags)
                .FirstOrDefaultAsync(p => p.Id == id);
     if (pet == null)
     {
         return new NotFoundResult();
     }

     return pet;
}
Pilgrim answered 3/7, 2020 at 11:23 Comment(1)
Well, can you explain what the Name parameter contributes with in [HttpGet("{id}", Name = "FindPetById")]? What is the gain compared to [HttpGet("{id}"]? Where does the difference becomes effective? In the browser? In the code? I simply can't see how those two attributes (with and without Name) cause a difference in the application.Flouncing
L
1

After some searching, managed to find at least somewhat clear simple example with explanation, see section 5.5.2 here: https://livebook.manning.com/book/asp-net-core-in-action/chapter-5/254

The linked section contains two pieces of code, from which it is obvious, besides others already have mentioned:

About "URL generation", instead of hard-coded routes for certain API resources on the client side (e.g., details for the user with certain id value), Controller can send URL for each user, so the client doesn't need to have any logic about creating those URLs. Also, what about multimedia resources (images, videos, etc.), or any resources that have dynamically created URLs for protection purposes? Client side shouldn't know anything about how to construct those URLs, just obtain them from the Controller. In that way, it is not needed to have all URLs (or routes) predefined on both client and server side.

Lakenyalaker answered 19/2, 2023 at 2:37 Comment(0)
O
0

That is left over from previous versions. The default weather forecast adds it. I checked the source for HttpGetAttribute and it only has template.

Basically name doesn't do anything. Instead just use no named argument or use template (which will default to template).


using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Routing;

namespace Microsoft.AspNetCore.Mvc
{
    /// <summary>
    /// Identifies an action that supports the HTTP GET method.
    /// </summary>
    public class HttpGetAttribute : HttpMethodAttribute
    {
        private static readonly IEnumerable<string> _supportedMethods = new [] { "GET" };

        /// <summary>
        /// Creates a new <see cref="HttpGetAttribute"/>.
        /// </summary>
        public HttpGetAttribute()
            : base(_supportedMethods)
        {
        }

        /// <summary>
        /// Creates a new <see cref="HttpGetAttribute"/> with the given route template.
        /// </summary>
        /// <param name="template">The route template. May not be null.</param>
        public HttpGetAttribute(string template)
            : base(_supportedMethods, template)
        {
            if (template == null)
            {
                throw new ArgumentNullException(nameof(template));
            }
        }
    }
}
Orientation answered 22/8, 2023 at 16:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.