I am trying to migrate my webapp from .net core 2.1 to 3.1 and in the process changed the routing to app.UseRouting() and app.UseEndpoints(default endpoint) method and removed app.UseMvc() as mentioned in the breaking changes document.
https://learn.microsoft.com/en-us/dotnet/core/compatibility/2.2-3.1#shared-framework-removed-microsoftaspnetcoreall
Post that, facing this issue.
Details
I have 3 controllers mentioned below in the code which are correctly versioned using the attributes
Example of V2 Controller
[Microsoft.AspNetCore.Mvc.ApiVersion("2.0")]
[Route("v{version:apiVersion}")]
Controllers have similar actions methods and when I try to hit any action (example: http://localhost:xxxx/v1/GetData). I get the below exception.
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: 'The request matched multiple endpoints. Matches:
Stateless1.Controllers.V3.SAPClientV3Controller.GetSap (SampleWebApp)
Stateless1.Controllers.V2.SAPClientV2Controller.GetSap (SampleWebApp)
Stateless1.Controllers.V1.SAPClientController.GetSap
PS: I have tested by removing this action method in rest of the two controllers and the call got through to the other controller irrespective of v1 or v2 or v3 in the http://localhost:xxxx/v1/GetData URL.
The code which supports multiple api versioning is also present in the start up.
services.AddApiVersioning((o) =>
{
o.ReportApiVersions = true;
o.DefaultApiVersion = new AspNetCore.Mvc.ApiVersion(1, 0);
o.AssumeDefaultVersionWhenUnspecified = true;
});
I had the exact same issue, and I found this ticket in microsoft's github issues https://github.com/microsoft/aspnet-api-versioning/issues/574
We need to specify the [ApiController] parameter so that the API versioning kicks in. This only started to happen when I migrated from dotnet core 2.0 to 3.1
For clarity here is what works:
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class MyController : Controller
{
[MapToApiVersion("1.0")]
public async Task<IActionResult> MethodA([FromBody] int? id)
{
return Ok();
}
}
}
[ApiController]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class MyControllerV2 : Controller
{
[MapToApiVersion("2.0")]
public async Task<IActionResult> MethodAV2([FromBody] int? id)
{
return Ok();
}
}
}
I gave V2 names for the sake of the example, but I believe the best way to version is to use it with namespaces like V2.Home.MyController
Related
I'm trying to create API with ASP.NET Core and their version. I installed Microsoft.AspNetCore.Mvc.Versioning. What I like to have is all the API with the version in the URL, so it is easy to understand what version of the API I use. For example /api/v1/TableAzureCategory.
For thata, in my Startup.cs I added the following lines of code:
services.AddApiVersioning(config =>
{
config.DefaultApiVersion = new ApiVersion(1, 0);
config.AssumeDefaultVersionWhenUnspecified = true;
config.ReportApiVersions = true;
config.ApiVersionReader = new UrlSegmentApiVersionReader();
});
Then, in my API controller I added some decorations
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class TableAzureCategoryController : ControllerBase
{
}
I run the application, open Swagger and this is what I see (basically the {version:apiVersion} is not replaced with the API version)
I looked around but I found only implementation like mine above. Any ideas?
Another way of achieving this would be to create a query-based versioning solution.
Let's say we have two controllers: ExampleV1Controller and ExampleV2Controller
using Microsoft.AspNetCore.Mvc;
namespace MyAPI.Controllers
{
[ApiController]
[ApiVersion("1.0")]
[Route("api/example")]
public class ExampleV1Controller : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return new OkObjectResult("Example API v1");
}
}
[ApiController]
[ApiVersion("2.0")]
[Route("api/example")]
public class ExampleV2Controller : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return new OkObjectResult("Example API v2");
}
}
}
As by your Startup.cs configuration, it will default to API version 1.0. To make a request to the V2 version, use https://localhost:5001/api/example?api-version=2.0.
I did not test this myself, but it should work.
The {version} route parameter behaves just like any other route parameter. If the route was values/{id}/subvalues you would expect a parameter named id that must be filled in.
The API Explorer extensions for API Versioning know the version associated with an API. This value is used as the default value, but OpenAPI/Swagger generators (ex: Swashbuckle) may not use the default value without a little help (refer to the end-to-end Swagger Example). If, and only if, you are versioning by URL segment, you can have the API Explorer extensions expand the route template with the default value and remove the API version parameter using the configuration:
services.AddVersionedApiExplorer(options => options.SubstituteApiVersionInUrl = true);
In the example provided, the version route parameter will be removed and the route template will be updated to "api/v1.0/TableAzureCategory", which I presume is what you want.
I am working on an API.
I have an "AbstractController.cs" and I am having difficulties calling a GET with two parameters.
[Route("api/[controller]")]
[ApiController]
public class AbstractController : ControllerBase
{
// GET: api/Abstract
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "missing implementation" };
}
Going to https://localhost:44363/api/abstract/ generates this.
["missing implementation"]
Awesome!
Now I need to make the Get method that passes in a show year, and code to a SQL query. Easy enough?
// GET: api/Abstract?ShowYear=2019&ShowCode=248621
[Route("api/{controller}/{ShowYear}/{ShowCode}")]
[HttpGet]
public Abstract Get(int ShowYear, int ShowCode) // GetAbstractByYearAndSHCode
{
string x = "";
return new Abstract();
}
No matter what I do I can't get this method to breakpoint/enter execution!
I'm guessing it's a routing issue, but I've tried most tenable ways of calling the endpoint.
Now I check the MS Documentation like any self-respecting-learning-programmer would.
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-3.1
Endpoint routing in ASP.NET Core 3.0 and later:
Doesn't have a concept of routes. Doesn't provide ordering guarantees
for the execution of extensibility, all endpoints are processed at
once.
GULP Sounds scary.
I haven't touched my middleware at this point.
All examples I've looked at don't have my "MapControllers();" method in their Startup.cs.
This is because it's new to .NET Core 3.0/3.1
The method: "Adds endpoints for controller actions to the IEndpointRouteBuilder without specifying any routes."
OK, so do I have to manually specify the route here still?
I did this, and it sorta works.
Well, it breakpoints in the startup.cs now when I go to https://localhost:44363/api/abstract/2019/287
Wait, it's not doing ANYTHING with my controller code! Why?
The following (also above) code ends up declaring as a null in the startup.cs
var controller = context.Request.RouteValues["Abstract"];
Hoping to learn what I'm doing wrong.
MapGet("/api/abstract/{showYear}/{showCode}", ...);
Isn't this code responsible for mapping that route with the controller named AbstractController.cs in my Controllers folder? Not hitting any breakpoints.
Edit:
Reading through this which compares the differences in the Startup.cs from .Net Core 2, 2.2, MVC projects vs 3.0
I have a good feeling reading all of this, I will find the issue.
https://andrewlock.net/comparing-startup-between-the-asp-net-core-3-templates/
Edit.. nevermind didn't find a resolution.
Edit 2:
Completely commenting out this troublesome code in startup.cs:
endpoints.MapGet("/api/abstract/{showYear}/{showCode}", async context =>
{
var controller = context.Request.RouteValues["Abstract"];
var showYear = context.Request.RouteValues["showYear"];
var showCode = context.Request.RouteValues["showCode"]; // just an example.
//await context.Response.WriteAsync($"Hello {showYear} is your year, {showCode} for the code!");
//GetAbstractByYearAndSHCode();
});
endpoints.MapGet("/api/abstract/{showYear}", async context =>
{
var name = context.Request.RouteValues["name"];
await context.Response.WriteAsync($"Hello {name}!");
});
Resolves my issue of not being able to reach the controller.
https://localhost:44363/api/abstract/2019 hits, and the id value is 2019. Great.
https://i.imgur.com/rmHyDPg.png the output looks great.
I am still not able to use > 1 parameter. How do I simply use the Year, and ShowCode paramaters? What's the syntax?
https://localhost:44363/api/abstract/2019
Just add the parameters to your attribute
[HttpGet("{ShowYear}/{ShowCode}")]
The "api/abstract" route is already used by the first method.
You cannot use it again for other actions. Create a new one as shown inline.
[HttpGet("{GetAbstractByYearAndSHCode}",Name = "GetAbstractByYearAndSHCode")]
public Abstract Get(int ShowYear, int ShowCode) // GetAbstractByYearAndSHCode
{
string x = "";
return new Abstract();
}
And call the url as shown:
https://localhost/api/abstract/GetAbstractByYearAndSHCode?ShowYear=1&ShowCode=5
I recently upgraded my project to .net-core 3.1 I noticed some bizarre behavior, all of my authenticated controllers 404.
I've left a default anonymous endpoint which I generally just use to verify that my api is working.
This controller is working just fine.
[AllowAnonymous]
[Route("api/[controller]")]
public class ValuesController : Controller
{
//...
}
This controller seems to work fine, despite not being decorated with the [ApiController]
I've found a related issue which state that this is related to the ApiVersioning
However I don't see a way to set that in the startup in .Net-Core 3.1
I've added [ApiController] to all of the authenticated controllers, the only affect I saw from this is that all of my Public Methods are now eligible to validations of the number of parameters allow to bind to the body. Everything still 404's
[ApiController]
[Route("api/[controller]")]
public class AccountController : Controller
{
//...
HttpPost("ExternalLogin")]
[AllowAnonymous]
public IActionResult ExternalLogin(string provider, string entryCode = null, string returnUrl = null)
{
//...
}
}
How can I get my controllers to receive the requests?
If you migrated from .NET Core 2.2 to 3.1, you will have to adjust Startup class:
In ConfigureServices:
Replace services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
with
services.AddControllers();
And in the Configure method:
Instead of
app.UseMvc();
Use:
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
[Authorize(AuthenticationSchemes = AuthenticationSchemes.CookieAuthenticationScheme)]
public class MyController : ControllerBase
{
[HttpPost("ui")]
[ProducesResponseType(
(int) HttpStatusCode.Created)]
public async Task Action1()
{
}
[HttpPost]
[ProducesResponseType(
(int)HttpStatusCode.Created)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.JwtAuthenticationScheme)]
public async Task Action2()
{
}
}
I have this controller where Action2 has Authorize attribute with different authentication scheme than that in Controller. But when I invoke Action2 with valid cookie authentication but invalid auth token then also Action2 is authorized - I was expecting to get 401/Unauthorized response.
Is this intended behavior?
Using Asp.net core 2.2
Before ASP.NET Core 2.1, all policies would be evaluated individually and they would all need to be satisfied.
This changed in ASP.NET Core 2.1, stating that this behaviour was unintended. In that release, policies would be combined, so that if at least one is satisfied, the authorization requirement of the request is satisfied as well.
The team exposed a new property called AllowCombiningAuthorizeFilters on MvcOptions in case people were relying on that behaviour.
See:
The official docs related to that property: https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.mvcoptions.allowcombiningauthorizefilters?view=aspnetcore-2.2
A blog post from Brock Allen exposing a scenario where this comes into play: https://brockallen.com/2018/07/15/beware-the-combined-authorize-filter-mechanics-in-asp-net-core-2-1/
If you want to revert to the old behaviour, you can use the following in your Startup class:
app.AddMvc(options =>
{
options.AllowCombiningAuthorizeFilters = false;
});
I'm new with attribute routing with aspnet web api.
public class UsersController : ApiController
{
[GET("users/me/brands/{id}")]
public IEnumerable<Brand> GetBrands(long id)
{
return _repository.FindByUser(id);
}
}
but I could not reach this action. I've tried so many ways:
http://example.com/api/users/brands/4
http://example.com/api/users/brands?id=4
http://example.com/users/brands?id=4
http://example.com/users/brands/4
PS: I also mapped as [GET("api/users/me/brands/{id}")]
What I'm missing?
There is a bug in the web api routing with MVC 4 and 4.5
A full explanation and work around can be found
MVC 4.5 Web API Routing not working?