5

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 */
    }
 }
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Kevin Bowersox
  • 93,289
  • 19
  • 159
  • 189
  • 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. – AchoVasilev Jan 08 '22 at 12:23
  • 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. – Kevin Bowersox Jan 08 '22 at 12:26
  • Try looking into this one -> https://stackoverflow.com/Questions/9499794/single-controller-with-multiple-get-methods-in-asp-net-web-api. Maybe it could help. – AchoVasilev Jan 08 '22 at 12:37
  • 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 ui – Golden Lion Jan 08 '22 at 12:50
  • @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. – Kevin Bowersox Jan 08 '22 at 15:55
  • Why not use a post route with a frombody parameter and not use query string – Golden Lion Jan 08 '22 at 18:14

4 Answers4

13

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)

Paul Kertscher
  • 9,416
  • 5
  • 32
  • 57
  • 1
    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. – Paul Kertscher Feb 22 '22 at 06:22
  • 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. – OwnageIsMagic Mar 15 '23 at 14:31
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);
    }
}
rufiooo
  • 116
  • 8
  • 1
    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. – Kevin Bowersox Jan 08 '22 at 12:29
  • 1
    Do you mean this? [HttpGet("{parameter1}") [HttpGet("{parameter2}") [HttpGet("{parameter3}") – rufiooo Jan 08 '22 at 12:32
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

Ran Turner
  • 14,906
  • 5
  • 47
  • 53
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