ASP.NET Core Web API : route by query parameter
Asked Answered
I

4

6

I am coming from a heavy Java/Spring background and trying to transition some knowledge over to ASP.NET Core 6.

In Spring, on a RestController, I am able to route the request based on the presence of a query parameter.

So a HttpRequest with the uri: /students?firstName=Kevin can be routed to a different controller method than a HttpRequest with the uri: /students.

In ASP.NET Core 6, I am unable to determine if the equivalent is possible after working through some examples and reading the documentation for Web API.

Here is what I am trying to achieve, is this possible using two methods and routing configuration that will discern which controller method to invoke based on the query parameter?

 [ApiController]
 [Route("Students")]
 public class StudentHomeProfileController : ControllerBase
 {
    [HttpGet] //Route here when no parameters provided
    public async Task<ActionResult<IEnumerable<Student>>> GetStudentAsync()
    {
        /* Code omitted */
    }

    [HttpGet] //Route here when firstName query param provided
    public async Task<ActionResult<IEnumerable<Student>>> SearchStudentAsync([FromQuery] string firstName)
    {
        /* Code omitted */
    }
 }
Imbecile answered 8/1, 2022 at 12:17 Comment(6)
you can do something like this [HttpGet("SearchStudentAsync")] and it will route to students/searchstudentasync. Personally I have searched on a way to do it without a route parameter in the Http method attribute, but I never found one.Rhoades
Thanks @AchoVasilev. I figured I could just adjust the route, however I'm trying to stay as RESTFUL as possible. I have a prototype that adds [HttpGet("search")] to the second method. I believe I'll need to then handle the presence or lack of for each search query parameter using that approach.Imbecile
Try looking into this one -> stackoverflow.com/Questions/9499794/…. Maybe it could help.Rhoades
why not try a subcontroller routing approach? subcontrollers allow you to create routes like order\1\orderdetail\2 . dont use query parameters for routing. use controllers and subcontrollers. dynamic routing based on parameter pass will be confusing in the uiHalfandhalf
@GoldenLion I'm not trying to target a specific student by Id. I am trying to search for students with specific property values. For example, all students with the firstName Kevin or maybe within a certain grade.Imbecile
Why not use a post route with a frombody parameter and not use query stringHalfandhalf
N
3

You are trying to differentiate API calls using query params. this is not the way to do this. if you want to separate the calls you should probably use path params instead.

Read more about Routing in ASP.NET Core - https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-6.0

Noway answered 8/1, 2022 at 12:28 Comment(3)
I modified to the correct link. Didn't see the .net core in your question but my answer remains the same regardless the specific framework.Noway
Thanks, I agree. I'm trying to map a concept from Spring to ASP.NET that doesn't seem possible.Imbecile
Good luck my friend @KevinBowersoxNoway
B
15

While filtering by query parameters does not come with ASP.NET Core out of the box, it's not too hard to supply this functionality on your own.

When it comes to extensibility, ASP.NET has some superpowers, one of them is IActionConstraint, which

Supports conditional logic to determine whether or not an associated action is valid to be selected for the given request. (Source)

Creating an annotation to filter for query parameters is as easy as

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class QueryParameterConstraintAttribute : Attribute, IActionConstraint
{
    private readonly string _parameterName;

    public QueryParameterConstraintAttribute(string parameterName)
    {
        this._parameterName = parameterName;
    }

    public bool Accept(ActionConstraintContext context)
    {
        return context.RouteContext.HttpContext.Request.Query.Keys.Contains(this._parameterName);
    }

    public int Order { get; }
}

All that's left is annotating your controller method with that constraint

[HttpGet] //Route here when firstName query param provided
[QueryParameterConstraint("firstName")]
public async Task<ActionResult<IEnumerable<Student>>> SearchStudentAsync([FromQuery] string firstName)
{
    /* Code omitted */
}

In a quick test I was able to confirm that it seems to work as intended, even if you add multiple of those attributes for different query parameters (if all conditions match, the route is called).

(Please note, this was tested with .NET Core 2.1. Anyway, it shuold be pretty much the same with .NET 6)

Bordiuk answered 22/2, 2022 at 6:21 Comment(2)
I am aware that there is an accepted answer, but I still wanted to provide my solution for the issue, just in case that anybody else is looking for.Bordiuk
You can also add IActionModelConvention which will do something like action.Selectors[0].ActionConstraints.Add(new QueryParameterConstraintAttribute(action.Parameters[0].Name)) to get rid of attribute on every action.Livre
B
5

I think you are looking for something like this, you need to specify the parameter in the "HttpGet" attribute

https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-6.0#attribute-routing-with-http-verb-attributes

[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
    [HttpGet]   // GET /api/test2
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int/{id:int}")] // GET /api/test2/int/3
    public IActionResult GetIntProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
    public IActionResult GetInt2Product(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}
Brindled answered 8/1, 2022 at 12:25 Comment(2)
So this switches over to a Route Based Parameter. I was hoping to stick with the query parameters because there will be up to 3 of them.Imbecile
Do you mean this? [HttpGet("{parameter1}") [HttpGet("{parameter2}") [HttpGet("{parameter3}")Brindled
N
3

You are trying to differentiate API calls using query params. this is not the way to do this. if you want to separate the calls you should probably use path params instead.

Read more about Routing in ASP.NET Core - https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-6.0

Noway answered 8/1, 2022 at 12:28 Comment(3)
I modified to the correct link. Didn't see the .net core in your question but my answer remains the same regardless the specific framework.Noway
Thanks, I agree. I'm trying to map a concept from Spring to ASP.NET that doesn't seem possible.Imbecile
Good luck my friend @KevinBowersoxNoway
J
0

ASP.Net Framework Web API 2 was able select an action based on query params. See action selection algorithm 3.b.

For an action to be selected by query params alone, an empty string route attribute was necessary [Route("")].

Silly .Net Core 😆

John answered 26/7, 2023 at 2:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.