What is "name" property in the constructor for HttpGetAttribute? - c#

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.

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)
{
}
So, this can contribute in the route selection.
EDIT: 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.

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"

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

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:
You can have route template with parameters, and even provide template within Http verb template [Http...], but...
If needed to generate links to be returned to the client, to avoid some problems, it is easier to use the route name as explained here: https://csharp-video-tutorials.blogspot.com/2017/02/generating-links-using-route-names-in.html
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.

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;
}

Related

MVC Attribute Routing with query parameter not working

I am trying to combine my UI project and WebAPI project into one to make it more maintainable, however I am getting an error with the routing as below:
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:64182/api/v1/business?id=101'.",
"MessageDetail": "No type was found that matches the controller named 'api'."
}
I have added attribute routing on the method to get it to work, but it only works with the following url:
MVC ACtion:
[HttpGet, Route("api/v1/business/{id:int}")]
public IHttpActionResult Get([FromUri]int? id)
{
....
}
http://localhost:64182/api/v1/business/101
The intended url signature cannot change and it should still use the query parameter:
http://localhost:64182/api/v1/business?id=101
In the Route attribute, I cannot add a question mark because it is not allowed.
The system is already being used by many users and I cannot change the signature unfortunately otherwise this would break their systems.
How can I get this to work or what Route template can I use to include the query parameter?
I think the attribute [FromUri] is deprecated. Try using [FromRoute]. Also, I would structure my routing from the controller class.
The following is for http://localhost:64182/api/v1/business/101
[Route("api/v1/[controller]")]
[ApiController]
public class Business : ControllerBase
{
[HttpGet("/{id:int}")]
public async Task<ActionResult<YourBusinessDto>> Get([FromRoute] int id)
{
//Your code to get your business dto here.
}
}
The following is for http://localhost:64182/api/v1/business?id=101
[Route("api/v1/[controller]")]
[ApiController]
public class Business : ControllerBase
{
[HttpGet]
public async Task<ActionResult<YourBusinessDto>> Get([FromQuery] int id)
{
//Your code to get your business dto here.
}
}
In our order collection, each order has a unique identifier. We can go to the collection and request it by "id". Typical RESTful best practice, this can be retrieved by its route, for example "api/orders/1"
//api/orders/1
[HttpGet("api/orders/{id}")]
public string test1([FromRoute]int id)
{
return "test1";
}
This attribute will instruct the ASP.NET Core framework to treat this operation as a handler for the HTTP GET verb and handle routing. We provide an endpoint template as an attribute parameter. This template is used as the route that the framework will use to match incoming requests. In this template, the {id}​​ value corresponds to the route part as the "id" parameter. The FromRoute attribute tells the framework to look up the "id" value in the route (URL) and provide it as the id parameter.
In addition, we can easily write it to use the FromQuery property. This then instructs the framework to predict a query string with an "identifier" name and corresponding integer value. Then pass the value as the id parameter to the operation. Everything else is the same.
However, the most common method is the aforementioned FromRoute usage-where the identifier is part of the URI
//api/orders?id=1
[HttpGet("api/v1")]
public string test2([FromQuery]int id)
{
return "test2";
}
In addition, you can refer to this detailed article for more attribute usage, which may be helpful to you:
https://www.dotnetcurry.com/aspnet/1390/aspnet-core-web-api-attributes

Route with custom type doesn't show up in Swagger

I have an endpoint that takes a custom type (in this particular case a NodaTime.LocalDate). The type has a custom model binder and is mapped in Swagger using MapType<>.
When the type is used in the route as a path parameter, the endpoint doesn't show up in Swagger/Swashbuckle. However, if I remove it (so that it's included as a URL parameter instead), it shows just fine.
A simple version of the endpoint:
public class MyController : System.Web.Http.ApiController
{
[Route( "my/route/{date}" )] // Doesn't show up
[Route( "my/route" )] // Does show up
[HttpPut]
public Task<IHttpActionResult> MyEndpoint( LocalDate date, InputModel inputModel ) {
// Do stuff...
}
}
Note that both endpoints actually work, one just doesn't show up. I can also get the both endpoints to show up, if I change the type to DateTime- but I don't want that, since that would change the valid input set.
I solved this problem by adding custom binding and custom converter from string.
These actions are needed so that ApiExplorer resolve the route. After that Swagger resolves the route too.
See my example here: https://stackoverflow.com/a/47123965/908936

Multiple controller types were found error in c# web api 2.0

Why this doesn't work? I get error: System.InvalidOperationException: Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
public class ConfigUpdateController: ApiController
{
[HttpPut]
[Route("api/device/{serial}/config")]
public IHttpActionResult Update(
[FromUri] string serial,
[FromBody] Configuration configuration)
{
}
}
public class ConfigQueryController: ApiController
{
[HttpGet]
[Route("api/device/{serial}/config")]
public IHttpActionResult Get(
[FromUri] string serial)
{
}
}
The reason why i want to have methods for same resource in separate controllers is decoupling queries from commands.
EDIT
To be honest, it's ample code to illustrate my problem, so please don't bother commenting controllers naming ect. It's not important in context of my question.
EDIT 2
I've found here web-api overview thet routing has 3 phases:
Routing has three main phases:
Matching the URI to a route template.
Selecting a controller.
Selecting an action.
So it seems this does not work because controller can't be resolved and method verb (PUT, GET) are not even checked? O_o
Read the error carefully, then look at your attribute routing. You have identical URLs for two different actions.
Program would have no idea which action to execute.
use [FromRoute] instead of [FromUri] annotations

Multiple controller types were found that match the URL. ASP.Net MVC

This is regarding defining routes using route attribute. I have two controllers
1st Controller
[AllowAnonymous]
[Route("Member/Login")]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
2nd Controller
[Route("{CategoryURL}/{Keywords}")]
public ActionResult BrowseProducts(string CategoryURL, string Keywords)
{
}
I am getting below error If try to access URL xyz.net/Member/Login
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
The request has found the following matching controller types:
XYZ.Controllers.AccountController
XYZ.Controllers.CoursesController
I am aware that I have Optional Parameters for second controller, hence when I try to access xyz.net/Member/Login, it finds two action methods to go for and getting the Multiple controller error.
My question is, how could I fix this issue without changing my current Routings. I tried Order, Precedence but nothing worked out.
As I commented above, based on your elected tags you are using MVC 4 yet attribute routing is supported in MVC 5. If you really are in MVC 5, then the following reference would be very helpful for you:
https://blogs.msdn.microsoft.com/webdev/2013/10/17/attribute-routing-in-asp-net-mvc-5/
Most importantly, it's crucial that when you register your routes you active attribute routing -->
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapMvcAttributeRoutes();
}
And also ensure you don't have any other routes being defined that could confict with the attributes you've elected to use.
Otherwise your selected attribute should work fine. Pretty simple application.
The web api constraints may also aply for what you are trying to accomplish
http://www.asp.net/web-api/overview/web-api-routing-and-actions

What is the advantage of adding the [HttpGet] annotation?

In the event that a Controller specifies a route:
[Route("api/platypus/getPlatypi")]
public List<Platypus> GetAllPlatypi()
{
return _platypusRepository.GetPlatypi();
}
...is there any advantage to annotating it with a "[HttpGet]" like so:
[HttpGet]
[Route("api/platypus/getPlatypi")]
public List<Platypus> GetAllPlatypi()
{
return _platypusRepository.GetPlatypi();
}
?
For the example you have given there is not any advantage to adding the HTTP method attribute. By convention Web API will try to match a controller method that starts with the HTTP request method (GET, POST, PUT etc.).
In your example the method GetAllPlatypi will be considered for a match for all GET requests to that controller.
If however your method was named FindAllPlatypi you would need to add the [HttpGet] attribute to make it clear that this method is meant for GET requests.

Categories