Mvc: Is it possible to have multiple GETs that vary only by parameters in ASP.NET Core?

Created on 29 Sep 2017  路  9Comments  路  Source: aspnet/Mvc

I asked this question also on Stack Overflow.

I want to build truly RESTful web service so don't want to leverage RPC-style, so have currently this:

public async Task<IActionResult> GetByParticipant(string participantId, string participantType, string programName)
{
}

public async Task<IActionResult> GetByProgram(string programName)
{
}

I believe that would work in ASP.NET Web API. But I'm getting an exception in Core:

AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:

TermsController.GetByParticipant (ParticipantTerms.Api)

TermsController.GetByProgram (ParticipantTerms.Api)

Neither of the attributes actually help:

  • [HttpGet]
  • [ActionName]
  • [FromQuery]

Most helpful comment

@davidfowl How do would you achieve an API like this?

class Foo {
  Guid? ParentId;
}

GET /api/foos Return all foos
GET /api/foos?parentId={Guid?} all foos where foo.ParentId=={parentId}

[HttpGet]
Get();

[HttpGet]
Get(Guid? parentId)

Won't work because these are seen as the same route. Really needs some distinction between "unbound" and "null"

@mguinness, good find!

I think that the assumption that a non-present value in the URL should bind to a value as default(value) is, erm, not great!

All 9 comments

Just disambiguate by putting a route on them.

hi @davidfowl, that's the caveat of my question, if I specify different routes, e.g. /api/terms and /api/terms/all, then I feel it won't be truly/fully/properly RESTful. So wondering how can I have multiple GETs against same resource, i.e. /api/terms.

If that isn't supported out-of-the-box then how can I customize this behavior?

If that isn't supported out-of-the-box then how can I customize this behavior?

Take a look here https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiOverloadingApplicationModelConvention.cs

@abatishchev - I suggest either writing a single action that considers participantId and participantType optional

or

writing an IActionConstraint that selects GetByParticipant when both of those values are present

I installed the shim package:

Install-Package Microsoft.AspNetCore.Mvc.WebApiCompatShim -Version 2.0.0

Then added the convention:

 services.AddMvcCore(options =>
    {
    options.Conventions.Add(container.GetInstance<WebApiOverloadingApplicationModelConvention>());
    })

Inherited my controller from ApiController:

public sealed class TermsController : ApiController
{
}

But still getting same exception:

AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
Microsoft.UPP.Calc.ParticipantTerms.Api.Controllers.v1.TermsController.GetByParticipant (Microsoft.UPP.Calc.ParticipantTerms.Api)
Microsoft.UPP.Calc.ParticipantTerms.Api.Controllers.v1.TermsController.GetByProgram (Microsoft.UPP.Calc.ParticipantTerms.Api)

Even if two my GETs have completely different parameters.

Please suggest what I might doing wrong. Thanks!

I never meant for you to use the shim, I meant for you to look at the code because it's an example or writing an IActionConstraint. I should have made that more clear.

The short answer is that there's no built-in way using ASP.NET Core MVC to do this, without using add-ons (we don't really consider the Web API compat shim to be a core component).

I'm also not sure that this has anything to do with being RESTful or not. It sounds like you have a "search API" so I would recommend doing something similar to what @rynowak suggested by making the various search parameters optional. I suggest encapsulating them in a class like so:

```c#
public async Task SearchForStuff(ParticipantSearchOptions searchOptions)
{
}

public class ParticipantSearchOptions
{
public string ParticipantId { get; set; }
public string ParticipantType { get; set; }
public string ProgramName { get; set; }
}
```

Closing because there are no plans to implement this.

Possible workaround using action constraint.

@davidfowl How do would you achieve an API like this?

class Foo {
  Guid? ParentId;
}

GET /api/foos Return all foos
GET /api/foos?parentId={Guid?} all foos where foo.ParentId=={parentId}

[HttpGet]
Get();

[HttpGet]
Get(Guid? parentId)

Won't work because these are seen as the same route. Really needs some distinction between "unbound" and "null"

@mguinness, good find!

I think that the assumption that a non-present value in the URL should bind to a value as default(value) is, erm, not great!

Was this page helpful?
0 / 5 - 0 ratings