.NET Core ApiController methods not called - c#

I have used scaffolding to create an API controller.
This is the test method I have added in there:
namespace MyApp.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase
{
[HttpPost]
public JsonResult VerifyIsLoggedIn()
{
Dictionary<string, bool> result = new Dictionary<string, bool> { { "Authenticated", true} };
return new JsonResult(result);
}
}
}
My Program.cs looks like so:
namespace MyApp
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
I run the app, get the login screen, manage to log in successfully, but then when I go to the URL below, I get an error stating "No webpage was found for the web address:"
https://localhost:12345/api/Authentication/VerifyIsLoggedIn
Seems like I have to make some changes to Program.cs, but everything I have tried hasn't been of any success. How do I resolve this?

Shown URL
https://localhost:12345/api/Authentication/VerifyIsLoggedIn
does not match the attribute route template for the shown controller action (see comments in code)
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase {
//POST api/Authentication
[HttpPost]
public IActionResult VerifyIsLoggedIn() {
var result = new { Authenticated = true };
return Ok(result);
}
}
Also if you try to view the URL in a browser it will default to HTTP GET while the controller can only serve HTTP POST requests.
You would need to update the HTTP Verb used and the route template
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase {
//GET api/Authentication/VerifyIsLoggedIn
[HttpGet("[action]")]
public IActionResult VerifyIsLoggedIn() {
var result = new { Authenticated = true };
return Ok(result);
}
}
Reference Routing to controller actions in ASP.NET Core
Reference Routing in ASP.NET Core

Am facing the same issue in my case when creating new API project uncheck the configure for HTTPS box.
Issue resolved in my case

Related

Microsoft.AspNetCore.OData 8: controller is not found by convention

As described in the docs, Microsoft.AspNetCore.OData 8 uses a convention to tie a controller to an entity set. They also provide a sample that demonstrates this behavior.
However, I've spent several hours to set up a minimal OData API with a simple controller, but the controller is neither shown by the OData routing debug page ($odata) nor can it be accessed through the browser.
This is the model:
namespace TestOData8
{
public class Dummy
{
public int Id { get; set; }
}
}
This is the controller:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Routing.Controllers;
namespace TestOData8.Controllers
{
public class DummiesController : ODataController
{
[HttpGet]
[EnableQuery]
public IActionResult Query(CancellationToken ct)
{
return Ok(Enumerable.Range(1, 10).Select(x => new Dummy() { Id = x }).AsQueryable());
}
}
}
And this code sets up the application (this is the .NET 6 way of setting this up; I've also tried .NET 5 without success):
using Microsoft.AspNetCore.OData;
using Microsoft.OData.Edm;
using Microsoft.OData.ModelBuilder;
using TestOData8;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers()
.AddOData(opt => opt.Count().Filter().Expand().Select().OrderBy().SetMaxTop(5)
.AddRouteComponents(GetEdmModel())
);
IEdmModel GetEdmModel()
{
var bldr = new ODataConventionModelBuilder();
bldr.EntitySet<Dummy>("Dummies");
return bldr.GetEdmModel();
}
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
//app.UseAuthorization();
app.UseODataRouteDebug();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
I've asserted that
the EDM model is registered (this $metadata endpoint shows the Dummy entity)
the name of the controller matches the entity set (Dummies -> DummiesController)
routing works for controllers that don't support OData
setting the EnableAttributeRouting option to false doesn't change the behavior
What am I missing/misunderstanding?
The fix was very simple: the name of the method in the controller must be "Get"; after changing the action name, the controller worked:
public class DummiesController : ODataController
{
[HttpGet]
[EnableQuery]
public IActionResult Get(CancellationToken ct)
{
return Ok(Enumerable.Range(1, 10).Select(x => new Dummy() { Id = x }).AsQueryable());
}
}
Thanks to #JamesLove for a small comment under another answer.

netcore - api versioning returns 404

I'm implementing Api Versioning in my NetCore 3 project using package Microsoft.AspNetCore.Mvc.Versioning but it's throwing me an error if I try to add version into my router - it works fine if I don't pass the route.
Here is what I have done:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Aus.GGG.Company
{
[Route("v{version:apiVersion}/[controller]")]
[ApiController]
[ApiVersion("1.0")]
public class BaseController : ControllerBase
{
[HttpGet("/test.json")]
public async Task<IActionResult> Get() => Ok("test");
}
}
That is how I have added my service:
public static IServiceCollection RegisterExtraStuff(this IServiceCollection services)
{
services.AddApiVersioning(config =>
{
config.DefaultApiVersion = new ApiVersion(1, 0);
config.AssumeDefaultVersionWhenUnspecified = true;
config.ReportApiVersions = true;
});
return services;
}
}
And here is the curl that doesn't works:
curl --location --request GET 'http://localhost:3000/v1.0/test.json
And the curl that works:
curl --location --request GET 'http://localhost:3000/test.json
In the last curl, I can see the extra header api-supported-versions on response so my configuration looks right, I guess.
Any idea on what is missing?
You add a slash at the begining in the action route, it will ignore the route attribute on the controller.
If you want the format like this:
http://localhost:3000/v1.0/test.json
You can change it like below:
[Route("v{version:apiVersion}")]
[ApiController]
[ApiVersion("1.0")]
public class BaseController : ControllerBase
{
[HttpGet("test.json")]
public async Task<IActionResult> Get() => Ok("test");
}
[ApiController]
[ApiVersion("1")]
[Route("api/v{version:apiVersion}/shared/[action]")]
public class DemoController : ControllerBase
{
public IActionResult TestMethod()
{
return ...
}
}

Non-attribute routes in ASP.Net Core WebApi

I need to build project, that implement REST API predefined by vendor application(which will consume it) - there is about thousand of REST-resources with some actions defined by different HTTP-Verb's(POST, GET, PUT, DELETE, etc..).
So, ideally, for each resource i should have single class like this:
public class SomethingController
{
public Something Post(string name, DateTime time)
{
// ...
}
public int PostStrange(string text)
{
// ...
}
public Something Put([FromBody]Something item)
{
// ...
}
public void Delete(int id)
{
// ...
}
}
In previous versions i can just call MapHttpRoute while registering routes, inherit classes like this from ApiController - and ASP.NET Web Api will do as i need... But in .NET Core i can't find anything like MapHttpRoute/ApiController.. Now there is routing and http-verb attributes, and i need to define everything explicitly for each class/method:
[Route("api/[controller]")]
public class SomethingController : Controller
{
[HttpPost]
public Something Post(string name, DateTime time)
{
// ...
}
[HttpPost("api/[controller]/strange")]
public int PostStrange(string text)
{
// ...
}
[HttpPut]
public Something Put([FromBody]Something item)
{
// ...
}
[HttpDelete]
public void Delete(int id)
{
// ...
}
}
Writing this attributes for each of thousands REST-resources is very boring and error prone...
Do i miss something here? Why in pretty new and modern ASP.NET Core that very common and important thing as building REST-Api made so over-complicated, compared to old ASP.NET?
There is nuget package Microsoft.AspNetCore.Mvc.WebApiCompatShim which main goal is to make migration from web api to core easier. It also provides a way to perform convention-based routing to actions you need. So, first install that package, then in startup:
public void ConfigureServices(IServiceCollection services) {
// add conventions here
services.AddMvc().AddWebApiConventions();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
app.UseMvc(routes => {
// map one global route
routes.MapWebApiRoute("WebApi", "api/{controller}");
});
}
After this small configuration you can inherit your controllers either from ApiController, which is added in package above for convenience of migration from web api, or native asp.net core Controller. Example of ApiController:
public class SomeController : ApiController {
// maps to GET /api/Some
// note - no routing attributes anywhere
public HttpResponseMessage Get() {
return new HttpResponseMessage(HttpStatusCode.OK);
}
// maps to POST /api/Some
public HttpResponseMessage Post() {
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
Native asp.net core controller:
// mark with these attributes for it to work
[UseWebApiRoutes]
[UseWebApiActionConventions]
public class TestController : Controller {
// maps to GET /api/Test
// no routing attributes, but two "conventions" attributes
public IActionResult Get(string p) {
return new ObjectResult(new { Test = p });
}
}
You can also mark your base controller with these attributes:
[UseWebApiRoutes]
[UseWebApiActionConventions]
public class BaseController : Controller {
}
public class TestController : BaseController {
// maps to GET /api/Test
// no attributes
public IActionResult Get(string p) {
return new ObjectResult(new { Test = p });
}
}
If you are not migrating from web api - I'd suggest to use native Controller. ApiController has different structure (similar to asp.net web api ApiController), so there is not much reason to use it for anything other than its intended goal (migration from web api).
MapRoute is still there https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing
Attribute routing compliments MapRoute, not replaces it.
Apparently there are quite a few examples which drop the piece about Routing in order to simplify example. So just dig dipper.

No non-OData HTTP route registered

I followed this tutorial to create a WebAPI REST service.
After that, I could load the list of all contacts by pointing at http://baseaddress/api/Contacts.
Then I added the following code in the Register method in WebApiConfig.cs in order to enable an OData endpoint:
config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Contact>("Contacts");
config.MapODataServiceRoute(
routeName: "OData",
routePrefix: "odata",
model: builder.GetEdmModel());
And also added the [EnableQuery] parameter on the Contact.GetContacts() method. That way, I am able to query for particular contacts like this:
http://baseaddress/odata/Contacts?$filter=startswith(Name,'A')
and it works like charm.
Unfortunately, when I put the [EnableQuery], the WebAPI endpoint stops working, showing instead the following error:
No non-OData HTTP route registered.
in System.Web.OData.Extensions.HttpConfigurationExtensions.GetNonODataRootContainer(HttpConfiguration configuration)
in System.Web.OData.Extensions.HttpRequestMessageExtensions.GetRootContainer(HttpRequestMessage request, String routeName)
in System.Web.OData.Extensions.HttpRequestMessageExtensions.CreateRequestScope(HttpRequestMessage request, String routeName)
in System.Web.OData.Extensions.HttpRequestMessageExtensions.CreateRequestContainer(HttpRequestMessage request, String routeName)
...
What should I do to fix this?
I encountered this problem, and since I'm working with dependency injections I managed to solve this by adding GlobalConfiguration.Configuration.EnableDependencyInjection() to my startup.cs
ex.
using System.Web.OData.Extensions;
public class Startup
{
public void Configuration(IAppBuilder app)
{
GlobalConfiguration.Configuration.EnableDependencyInjection();
}
}
Adding config.EnableDependencyInjection() in Startup.cs worked for me.
var config = new HttpConfiguration();
config.EnableDependencyInjection();
The key with this issue is to use .EnableDependencyInjection() on Configure method in Startup.cs
If you are using ASP.net Core endpoint routing (recommended if you
have at least .net core 3.0 and Microsoft.AspNetCore.OData v7.4.0)
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.Select().Filter().OrderBy().Count().MaxTop(10);
endpoints.EnableDependencyInjection();//This guy solves the problem
endpoints.MapODataRoute("odata", "odata", GetEdmModel());
});
Otherwise if you are using MVC routing (only way available prior .net
core 3.0 and Microsoft.AspNetCore.OData v7.4.0)
app.UseMvc(routeBuilder =>
{
routeBuilder.Select().Filter().OrderBy().Count().MaxTop(10);
routeBuilder.EnableDependencyInjection();//This guy solves the problem
routeBuilder.MapODataServiceRoute("odata", "odata", GetEdmModel());
});
Further reading: https://devblogs.microsoft.com/odata/enabling-endpoint-routing-in-odata/
I encountered this problem before and by the adding the line below it worked for me
protected void Application_Start()
{
GlobalConfiguration.Configuration.EnableDependencyInjection();
.... }
I got this error after updating my WebApi project dependencies (NuGet) to:
Microsoft.AspNet.OData, version="7.0.1"
Microsoft.OData.Core, version="7.5.0"
Microsoft.OData.Edm, version="7.5.0"
Microsoft.Spatial, version="7.5.0"
After downgrading to the versions I used before, the error was gone again:
Microsoft.AspNet.OData, version="5.8.0"
Microsoft.OData.Core, version="6.19.0"
Microsoft.OData.Edm, version="6.19.0"
Microsoft.Spatial, version="6.19.0"
that problem I solved this way:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
.... existing code...
//added:
//(OData need this)
config.EnableDependencyInjection();
config.Expand().Select().OrderBy().Filter();
}
}
In my case, I got the above error even though I had a separate project, which had no Odata code at all. So this was a very strange message to get.
My solution was to delete all the packages from the packages folder. After that it worked again. Some Odata packages were still in the this folder.
In my case, I had a web site with ODATA routes (and controllers), and other API routes (and controllers). What happened was my other routes were simply conflicting with ODATA ones, even with different C# namespaces and classes, etc.
Initially my controller was like this for example:
public class UserController : ApiController
{
[HttpPost]
public void Create([FromBody] string email)
{
}
}
I also had a "User" route in ODATA somewhere (different namespace, different urls, etc.). So I had to add explicitely the Route attribute, like this:
public class UserController : ApiController
{
[HttpPost]
[Route("api/user/create")]
public void Create([FromBody] string email)
{
}
}
I also got this error, and in my case it was an issue with case-sensitivity. I had called
https://www.example.com/odata/MyEntities instead of
https://www.example.com/odata/myentities, as it was registered.
Be sure to check your route configuration and calling url.
the controller name has to match the name you put in builder.EntitySet<SomeType>()
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
config.Count().Filter().OrderBy().Expand().Select();
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Order>("Order");// now the controller must be named OrderController
config.MapODataServiceRoute(routeName: "odata", routePrefix: null, model: builder.GetEdmModel());
appBuilder.UseWebApi(config);
}
}
public class OrderController : ODataController
{
// test data source
public List<Order> OrdersList { get; set; } = new List<Order>()
{
// put some test data
};
[HttpGet]
[EnableQuery]
public IQueryable<Order> Get() //Get: http://localhost:port/Order
{
return Orders.AsQueryable();
}
[HttpGet]
[EnableQuery]
public Order Get(int key) //Get: http://localhost:port/Order(key)
{
return Orders.FirstOrDefault(x => x.ID == key);
}
[HttpPost]
public bool Post(Order entity) //Post: http://localhost:port/Order
{
Orders.Add(entity);
return true;
}
[HttpPut]
public bool Put(int key, Order entity) //Put: http://localhost:port/Order(key)
{
var idx = Orders.FindIndex(x => x.ID == key);
Orders[idx] = entity;
return true;
}
[HttpPatch]
public bool Patch(int key, Order entity) //Patch: http://localhost:port/Order(key)
{
var idx = Orders.FindIndex(x => x.ID == key);
Orders[idx] = entity;
return true;
}
[HttpDelete]
public bool Delete(int key) //Delete: http://localhost:port/Order(key)
{
var idx = Orders.FindIndex(x => x.ID == key);
Orders.RemoveAt(idx);
return true;
}
}
PS: am using Microsoft.AspNet.OData 7.5.5
I just migrate from core 2.1 to .net core 3.1, and have the same issue. Here is how I fix:
app.UseMvc(routeBuilder =>
{
routeBuilder.EnableDependencyInjection(); //..another config
});

Multiple controllers with same URL routes but different HTTP methods

I've got a following two controllers:
[RoutePrefix("/some-resources")
class CreationController : ApiController
{
[HttpPost, Route]
public ... CreateResource(CreateData input)
{
// ...
}
}
[RoutePrefix("/some-resources")
class DisplayController : ApiController
{
[HttpGet, Route]
public ... ListAllResources()
{
// ...
}
[HttpGet, Route("{publicKey:guid}"]
public ... ShowSingleResource(Guid publicKey)
{
// ...
}
}
All three actions got in fact three different routes:
GET /some-resources
POST /some-resources
GET /some-resources/aaaaa-bbb-ccc-dddd
If I put them into single controller everything works just fine, however if I separate them (as shown above) WebApi throws following exception:
Multiple controller types were found that match the URL. This can
happen if attribute routes on multiple controllers match the requested
URL.
This message is quite obvious. It seems WebApi does not take HTTP method into account when looking for a right candidate for controller/action.
How could I achieve the expected behavior?
UPDATE: I've digged a little into Web API internals and I understand that's the way it works by default. My goal is to separate the code and logic - in real world case those controllers have different dependencies and are a bit more complex. For the sake of maintenance, testability, project organization etc. they should be different objects (SOLID and stuff).
I thought I could override some WebAPI services (IControllerSelector etc) however this seems to be a little bit risky and non-standard approach for this simple and - as I assumed - common case.
UPDATE
Based on your comments, updated question and the answer provided here
Multiple Controller Types with same Route prefix ASP.NET Web Api
Desired result can be achieved via custom route constraints for the HTTP method applied to controller actions.
On inspection of the default Http{Verb} attributes ie [HttpGet], [HttpPost] and the RouteAttribute, which by the way are sealed, I realized that their functionality can be combine into one class similar to how they are implemented in Asp.Net-Core.
The following is for GET and POST, but it shouldn't be difficult to create constraints for the other HTTP methods PUT, DELETE...etc to be applied to the controllers.
class HttpGetAttribute : MethodConstraintedRouteAttribute {
public HttpGetAttribute(string template) : base(template, HttpMethod.Get) { }
}
class HttpPostAttribute : MethodConstraintedRouteAttribute {
public HttpPostAttribute(string template) : base(template, HttpMethod.Post) { }
}
The important class is the route factory and the constraint itself. The framework already has base classes that take care of most of the route factory work and also a HttpMethodConstraint so it is just a matter of applying the desired routing functionality.
class MethodConstraintedRouteAttribute
: RouteFactoryAttribute, IActionHttpMethodProvider, IHttpRouteInfoProvider {
public MethodConstraintedRouteAttribute(string template, HttpMethod method)
: base(template) {
HttpMethods = new Collection<HttpMethod>(){
method
};
}
public Collection<HttpMethod> HttpMethods { get; private set; }
public override IDictionary<string, object> Constraints {
get {
var constraints = new HttpRouteValueDictionary();
constraints.Add("method", new HttpMethodConstraint(HttpMethods.ToArray()));
return constraints;
}
}
}
So given the following controller with the custom route constraints applied...
[RoutePrefix("api/some-resources")]
public class CreationController : ApiController {
[HttpPost("")]
public IHttpActionResult CreateResource(CreateData input) {
return Ok();
}
}
[RoutePrefix("api/some-resources")]
public class DisplayController : ApiController {
[HttpGet("")]
public IHttpActionResult ListAllResources() {
return Ok();
}
[HttpGet("{publicKey:guid}")]
public IHttpActionResult ShowSingleResource(Guid publicKey) {
return Ok();
}
}
Did an in-memory unit test to confirm functionality and it worked.
[TestClass]
public class WebApiRouteTests {
[TestMethod]
public async Task Multiple_controllers_with_same_URL_routes_but_different_HTTP_methods() {
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
var errorHandler = config.Services.GetExceptionHandler();
var handlerMock = new Mock<IExceptionHandler>();
handlerMock
.Setup(m => m.HandleAsync(It.IsAny<ExceptionHandlerContext>(), It.IsAny<System.Threading.CancellationToken>()))
.Callback<ExceptionHandlerContext, CancellationToken>((context, token) => {
var innerException = context.ExceptionContext.Exception;
Assert.Fail(innerException.Message);
});
config.Services.Replace(typeof(IExceptionHandler), handlerMock.Object);
using (var server = new HttpTestServer(config)) {
string url = "http://localhost/api/some-resources/";
var client = server.CreateClient();
client.BaseAddress = new Uri(url);
using (var response = await client.GetAsync("")) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
using (var response = await client.GetAsync("3D6BDC0A-B539-4EBF-83AD-2FF5E958AFC3")) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
using (var response = await client.PostAsJsonAsync("", new CreateData())) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
}
public class CreateData { }
}
ORIGINAL ANSWER
Referencing : Routing and Action Selection in ASP.NET Web API
That's because it uses the routes in the route table to find the controller first and then checks for Http{Verb} to select an action. which is why it works when they are all in the same controller. if it finds the same route to two different controllers it doesn't know when one to select, hence the error.
If the goal is simple code organization then take advantage of partial classes
ResourcesController.cs
[RoutePrefix("/some-resources")]
partial class ResourcesController : ApiController { }
ResourcesController_Creation.cs
partial class ResourcesController {
[HttpPost, Route]
public ... CreateResource(CreateData input) {
// ...
}
}
ResourcesController_Display.cs
partial class ResourcesController {
[HttpGet, Route]
public ... ListAllResources() {
// ...
}
[HttpGet, Route("{publicKey:guid}"]
public ... ShowSingleResource(Guid publicKey) {
// ...
}
}

Categories

Resources