ASP.NET Core Web API - AmbiguousMatchException: The request matched multiple endpoints - asp.net-core

I've been looking at this, but still cannot find a solution, here goes:
To return all players, I will pass in something similar to:
http://localhost:7777/api/teams/34fe3b6f-ba23-4657-820a-6c59dd49173a/players
To return a specific player on a specific team, I will pass in somethign similar to:
http://localhost:7777/api/teams/34fe3b6f-ba23-4657-820a-6c59dd49173a/players/f7de7974-9cbb-4c2c-884e-29036d6c2d76
I keep getting the following error:
System.ArgumentException: 'The route parameter name 'id' appears more than one time in the route template. '
Could someone please advise how to fix this?
[Route("api/Teams/{Id}/Players}")]
[ApiController]
public class PlayersController : ControllerBase
{
[HttpGet]
public IActionResult GetAllTeamPlayers(Guid id)
{
return Ok();
}
[HttpGet]
public IActionResult GetTeamPlayer(Guid id, Guid id2)
{
return Ok();
}
}

You should define the route parameters like this:
[Route("api/Teams/{teamId}/}")]
[ApiController]
public class PlayersController : ControllerBase
{
[HttpGet("players")]
public IActionResult GetAllTeamPlayers([FromRoute] Guid teamId)
{
return Ok();
}
[HttpGet("players/{playerId}")]
public IActionResult GetTeamPlayer([FromRoute] Guid teamId, [FromRoute] Guid playerId)
{
return Ok();
}
}

Related

Asp.net core 2.2 api routing

In asp.net core 2.2 i have test api controller class and i have 2 get methods :
[Route("api/[controller]")]
[ApiController]
public class testController : Controller
{
// GET: api/test
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/test/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
....
}
How to use this route api/test?id=1 for get method by id ?
How to use this route api/test?id=1 for get method by id ?
Use Route Attribute.
Route templates applied to an action that begin with / or ~/ don't get combined with route templates applied to the controller.
[FromQuery] - Gets values from the query string.
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
//test url: /api/test?id=7
[HttpGet("/api/test")] // will ignore "api/[controller]" with "/"
public int Test([FromQuery]int id)
{
return id;
}
.....
}
Test of result in .Net Core 2.2 API

Asp.Net Core Api Default Routing

I have a very basic Asp.Net Core Api; my controller looks like this:
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
[HttpGet("{id}")]
public IEnumerable<Resource> Test(string id)
{
// Breakpoint here
}
I would expect the following URL to invoke the method, and fire the breakpoint:
https://localhost:5012/test/test/1
However, it doesn't. In fact, the following URL does:
https://localhost:5012/test/1
I was under the impression that the format for the URL was as follows (from startup):
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
So, unless that action is Index:
https://localhost:5012/controller/action/id
But it appears that the accepted format is:
https://localhost:5012/controller/id
My question is, why is this?
In addition to pwrigshihanomoronimo answer,
you can just change this
[HttpGet("{id}")]
public IEnumerable<Resource> Test(string id)
to
[HttpGet("[action]/{id}")]
public IEnumerable<Resource> Test(string id)
Actually it is ApiController attribute, who breaks your routing. Looks like app.UseEndpoints configures routes just for MVC. So the solution is to remove all attributes to have the following code
public class TestController : ControllerBase
{
public string Test(string id)
{
return "OK";
}
}
Or, if you want to keep ApiController attribute, you would need to adjust Route value as well. You can remove app.UseEndpoints, if you don't use MVC in your project
[ApiController]
[Route("[controller]/[action]")]
public class TestController : ControllerBase
{
[HttpGet("{id}")]
public string Test(string id)
{
return "OK";
}
}

How to use polymorphism one method on controller actions

I tried to convert ASP.NET WEB API to ASP.NET CORE WEB API and have errors
My code in ASP.NET WebAPI
public class TestController : ApiController
{
// GET /test
public object Get()
{
return "get";
}
// GET /test?id={id}
public object Get(string id)
{
return id;
}
// GET /test?id={id}&anyParam={anyParam}
public object Get(string id, string anyParam)
{
return id + anyParam;
}
}
config.Routes.MapHttpRoute("Controller", "{controller}");
Try to convert it to ASP.NET Core 2.1 / 3.0
[ApiController]
[Route("{controller}")]
public class TestController : ControllerBase
{
// GET /test
public object Get()
{
return "get";
}
// GET /test?id={id}
public object Get(string id)
{
return id;
}
// GET /test?id={id}&anyParam={anyParam}
public object Get(string id, string anyParam)
{
return id + anyParam;
}
}
services.AddControllers();
app.UseRouting();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
And i have in ASP.NET Core
AmbiguousMatchException: The request matched multiple endpoints
The sensible solution is just have one method that takes three parameters.
But, sensible solutions don't make for the most interesting stackoverflow answers, so here is how you can do this with two custom attributes, one which states the parameters that are required, and another which states which parameters are excluded:
public class RequireRequestParameterAttribute : ActionMethodSelectorAttribute
{
private readonly string[] _requiredNames;
public RequireRequestParameterAttribute(params string[] names)
{
this._requiredNames = names;
}
public override bool IsValidForRequest(
RouteContext routeContext,
ActionDescriptor action
) =>
this._requiredNames
.All(
routeContext
.HttpContext
.Request
.Query
.ContainsKey
);
}
public class DisallowRequestParameterAttribute : ActionMethodSelectorAttribute
{
private readonly string[] _forbiddenNames;
public DisallowRequestParameterAttribute(params string[] names)
{
this._forbiddenNames = names;
}
public override bool IsValidForRequest(
RouteContext routeContext,
ActionDescriptor action
) =>
!(this._forbiddenNames
.Any(
routeContext
.HttpContext
.Request
.Query
.ContainsKey
)
);
}
Now you can apply the attributes as follows:
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
// GET test
public object Get()
{
return "Get";
}
// GET test?id={id}
[RequireRequestParameter("id")]
[DisallowRequestParameter("anyParam")]
public object Get(string id)
{
return id;
}
// GET test?id={id}&anyParam={anyParam}
[RequireRequestParameter("id", "anyParam")]
public object Get(string id, string anyParam)
{
return $"{id}: {anyParam}";
}
}
This means if you add another method with a third parameter, you have the maintenance burden of adding or modifying the DisallowRequestParameter attribute on the other methods.
I look your generated urls on actions and they are both /test which cause AmbiguousMatchException because your parameters are GET and are optional.
I think you can have same names on actions but you need define different ROUTE attribute (diff urls) on actions. Eg. you can not use default route with polymorphism on controller actions.
[Route("Home/About")]
MVC controllers Mapping of controllers now takes place inside
UseEndpoints.
Add MapControllers if the app uses attribute routing.
Source
https://learn.microsoft.com/cs-cz/aspnet/core/mvc/controllers/routing?view=aspnetcore-3.0#attribute-routing
Thanks to daremachine with his answer I was able to find information on Google
First step in ASP.NET Core we need class which inherit ActionMethodSelectorAttribute
public class RequireRequestValueAttribute : ActionMethodSelectorAttribute
{
public RequireRequestValueAttribute(string name, string value = null)
{
Name = name;
Value = value;
}
public string Name { get; }
public string Value { get; }
public StringComparison ComparisonType { get; } = StringComparison.OrdinalIgnoreCase;
private bool ValueIsValid(object value)
{
return ValueIsValid(value?.ToString());
}
private bool ValueIsValid(string value)
{
if (Value == null)
{
return true;
}
return string.Equals(value, Value, ComparisonType);
}
public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
{
var value = default(object);
if (routeContext.RouteData.Values.TryGetValue(Name, out value) && ValueIsValid(value))
return true;
if (routeContext.RouteData.DataTokens.TryGetValue(Name, out value) && ValueIsValid(value))
return true;
if (routeContext.HttpContext.Request.Query.ContainsKey(Name))
{
var values = routeContext.HttpContext.Request.Query[Name];
if (values.Count <= 0)
{
if (ValueIsValid(null))
return true;
}
else if (values.Any(v => ValueIsValid(v)))
return true;
}
return false;
}
}
Then we can add to question methods [RequireRequestValue("")], the controller will look like this
[ApiController]
[Route("{controller}")]
public class TestController : ControllerBase
{
// GET /test
public object Get()
{
return "get";
}
// GET /test?id={id}
[RequireRequestValue("id")]
public object Get(string id)
{
return id;
}
}
But it can't polymorphism two similar fields, type id in my question
For asp net core 2. If you try to implement the same logic as was in web api controllers then use Microsoft.AspNetCore.Mvc.WebApiCompatShim. This nuget package provides compatibility in ASP.NET Core MVC with ASP.NET Web API 2 to simplify migration of existing Web API implementations. Please check this answer. Starting with ASP.NET Core 3.0, the Microsoft.AspNetCore.Mvc.WebApiCompatShim package is no longer available.

Resolve routes by named parameters

I have ASP Core 2.2 app. I defined controller:
using Microsoft.AspNetCore.Mvc;
namespace Web.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok();
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
return Ok();
}
}
}
When I request with url /api/users/3 everything works fine, method GetById is called. But if I try to request /api/users?id=3 method Get is called and I don't know how to fix that. Moreover I would like to create two similar method different only by parameter name. For example public IActionResult GetById(int id) and public IActionResult GetByAge(int age) so I need strict routing by named parameters if possible. I don't want to implement custom middleware to resolve routes myself I wanna try to find ASP feature for that.
The url /api/users/3 : "3" is used as part of the route value .
The url /api/users?id=3: "3" is used as a query string in the url .
Attribute routing with Http[Verb] attributes is the value of which is part of the route value
You could change the Route attribute above the controller to specify action name like below :
[Route("api/[controller]/[action]")]
[ApiController]
public class UsersController : ControllerBase
{
// Get api/users/get
[HttpGet]
public IActionResult Get()
{
return Ok();
}
//Get api/users/GetById/3
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
return Ok();
}
}
Reference :https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-2.2

ASP.Net Core 2.1 OData Parameter issue

I am trying to get OData to work in ASP.Net 2.1. The main Get is ok as I get the results from the DB.
When I try to call the second Get with a Parameter it returns with a 404. I put a breakpoint on the second get and it never hits. The http "get" statement http://localhost:5000/odata/userrole('Admin')
Application started. Press Ctrl+C to shut down.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5000/odata/userrole('Admin')
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 423.7117ms 404
What am I not doing right here? Thank you.
Microsoft.AspNetCore.Odata = v7.1.0
namespace MyApp.Controllers
{
public class UserRoleController : ODataController
{
private IMyDB _db;
public UserRoleController(IMyDB db)
{
_db = db;
}
[EnableQuery(PageSize = 20)]
public IActionResult Get()
{
return Ok(_db.UserRole().AsQueryable());
}
[EnableQuery]
public IActionResult Get([FromODataUri] string roletype)
{
return Ok(_db.UserRole().Find(roletype));
}
}
}
I have found out what is the problem. In the future if someone is working with OData this might help in countless hours of debugging.
The UserRole Class I have looks like below. Notice the data annotation [Key], that is what is causing the OData to ignore the path. I need to change the data Type to match the UserRole class "key" public IActionResult Get([FromODataUri] Guid roleid)
That fixes the problem. Hope this helps people in the future.
public class UserRole
{
[Key]
public Guid RoleId { get; set; }
public Role RoleType { get; set; }
public string RoleName { get; set; }
}