2
votes

Good afternoon,

I'm having some trouble with endpoint routing in my web API using attribute routing and the ASP.NET core routing middleware.

I have an API controller that looks roughly like the following:

public class UsersController : ControllerBase
{
    [HttpGet]
    [Route("v1/users/{id}", Name = nameof(GetUser), Order = 1)]
    public async Task<ActionResult> GetUser([FromQuery(Name = "id")] string userGuid)
    {
       // Implementation omitted.
    }

    [HttpGet]
    [Route("v1/users/me", Name = nameof(GetCurrentUser), Order = 0)]
    public async Task<ActionResult> GetCurrentUser()
    {
        // Implementation omitted.
    }
}

I am trying to configure the endpoint routing so that requests for 'v1/users/me' are routed to the 'GetCurrentUser()' method, while requests matching the template 'v1/users/{id}' (where {id} != me) are routed to the 'GetUser()' method. I was hoping that this could be solved by placing the 'v1/users/me' endpoint before the other endpoint in the endpoint order, but it seems the order parameter isn't respected by the routing middleware. I've also tried explicitly mapping the 'v1/users/me' endpoint before mapping the remaining endpoints, but this doesn't seem to work either.

Here is the current startup configuration:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
   if (env.IsDevelopment())
   {
       app.UseDeveloperExceptionPage();
   }

   app.UseHttpsRedirection();
   app.UseStaticFiles();
   app.UseResponseCompression();
   app.UseRouting();
   app.UseAuthentication();
   app.UseAuthorization();

   app.UseEndpoints(endpoints =>
   {
       // Explicit mapping of the GetCurrentUser endpoint - doesn't seem to do anything.
       // endpoints.MapControllerRoute("v1/users/me", "Users/GetCurrentUser");

       endpoints.MapControllers();
   }
}

Is this possible to achieve with attribute routing, and if so, what am I missing?

Thanks!

2
is id an int or it can be any string?xavier
A string-encoded GUID is expected, but it can be any string.Henrik

2 Answers

2
votes

This should already work fine if you just leave the defaults like this:

[HttpGet("v1/users/{id}")]
public async Task<ActionResult> GetUser(string id)
{
    return Ok(new { id = id });
}

[HttpGet("v1/users/me")]
public async Task<ActionResult> GetCurrentUser()
{
    return Ok(new { id = "current" });
}

With attribute routing, routes that contain constant parts are favored over routes that contain a route variable at the same location. So v1/users/me is ranked higher than v1/users/{id} with id = "me", so you should see the GetCurrentUser run when you access that route. This is regardless of method ordering within your controller.

0
votes

The issue was with the annotation of the API endpoint methods.

I mistakenly marked the parameter in the GetUser(string id) endpoint with the [FromQuery] attribute rather than the [FromRoute].

The following works as expected:

public class UsersController : ControllerBase
{
    [HttpGet]
    [Route("v1/users/{id}", Name = nameof(GetUser))]
    public async Task<ActionResult> GetUser([FromRoute(Name = "id")] string userGuid)
    {
       // Changed from [FromQuery(Name = "id")] to [FromRoute(Name = "id")]

       // Implementation omitted.
    }

    [HttpGet]
    [Route("v1/users/me", Name = nameof(GetCurrentUser))]
    public async Task<ActionResult> GetCurrentUser()
    {
        // Implementation omitted.
    }
}