Controller action HttpGet executes wrong action - c#

I have created a new Web API project from the Visual Studio Templates and then I have followed the following tutorial for adding OData to this project.
https://devblogs.microsoft.com/odata/supercharging-asp-net-core-api-with-odata/
Calling
https://localhost:xxx/api/Assets
and
https://localhost:xxx/api/Assets/1
return all Assets, while the latter should return only 1 asset (where id = 1)
My code:
public class AssetsController : ControllerBase
{
private IAssetService _service;
private IMapper _mapper;
public AssetsController (IAssetService _service, IMapper mapper)
{
this._service = _service;
this._mapper = mapper;
}
[HttpGet]
[EnableQuery()]
public ActionResult<IEnumerable<Asset>> Get()
{
return this._service.GetAllAssets().ToList();
}
[HttpGet("{id}")]
[EnableQuery()]
public Asset Get(int id)
{
return _service.GetById(id);
}
}
I have debugged to verify that the Get(int id) function is never called.
I have tried defining my route explicitly like this:
[HttpGet]
[Route("GetById/{id}")]
[EnableQuery()]
public Asset Get(int id)
{
return _service.GetById(id);
}
EDIT
Routing defined in startup:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
/* snip */
app.UseMvc(routeBuilder =>
{
routeBuilder.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
routeBuilder.Select().Filter().OrderBy().Expand().Count().MaxTop(10);
routeBuilder.MapODataServiceRoute("api", "api", GetEdmModel());
});
}
This makes no difference.
Any ideas?

There're two approaches to solve this question.
Approach 1 : rename the id parameter to key
According to the OData v4 Web API docs :
Here are some rules for the method signatures:
If the path contains a key, the action should have a parameter named key.
If the path contains a key into a navigation property, the action should have a > parameter named relatedKey.
POST and PUT requests take a parameter of the entity type.
PATCH requests take a parameter of type Delta, where T is the entity type.
We should have a parameter named key:
[HttpGet("{id}")] // actually, this line doesn't matter if you're using OData, but it's better to rename it to `key` too
[EnableQuery()]
public IActionResult Get(int key)
{
....
}
Approach 2: rename the Get method to GetAsset
According to OData v4 Web API docs:
When Web API gets an OData request, it maps the request to a controller name and an action name. The mapping is based on the HTTP method and the URI. For example, GET /odata/Products(1) maps to ProductsController.GetProduct.
We could also rename the action method to GetAsset as below:
[HttpGet("{id}")]
[EnableQuery()]
public IActionResult GetAsset(int id)
{
...
}

this worked me...
[HttpGet]
public ActionResult<IEnumerable<Asset>> Get()
{
return this._service.GetAllAssets().ToList();
}
[HttpGet("{id}")]
public Asset Get(int id)
{
return _service.GetById(id);
}

Related

Routing in dot net core with more than one parameter

Please let me know the exact method to ensure that correct parameters are sent in URL in order to navigate correctly in dot net core.
[HttpGet("{id},{id2}",Name ="Edit")]
[AllowAnonymous]
public ActionResult Edit(int id, int id2)
{
return Ok(3);
}
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
When i try navigating to following url:
/api/test/Edit?id=1&id2=4
it gets navigated to the other method Get and returns value as string.
Startup.cs file has the following contents
app.UseMvcWithDefaultRoute();
Ensure that the correct route templates are applied to the actions
[Route("api/[controller]")]
public class TestController: Controller {
//GET api/test/edit?id=1&id2=4
[HttpGet("Edit")]
[AllowAnonymous]
public ActionResult Edit(int id, int id2) {
//...
return Ok(3);
}
//GET api/test/5
[HttpGet("{id}")]
public string Get(int id) {
return "value";
}
}
Reference Routing to controller actions in ASP.NET Core
.Net Core 3.1.3
In the Startup.Configure Method:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
Controller Annotation: [Route("[controller]/[action]")]
Action Method Annotation: [HttpGet("{param1:int}/{param2:int}")]
From the postman: https://localhost:5001/controller/action/param1/param2
Hope this helps!

cannot find C# netcore controller

I have added a netcore controller in my existing IdentityServer4 project. Here is my code
namespace IdentityServer4.Quickstart.UI
{
public class VersionController : Controller
{
IVersionService _repository;
public VersionController(IVersionService repository)
{
_repository = repository;
}
[HttpGet(nameof(GetBackgroundId))]
public IActionResult GetBackgroundId()
{
return new OkObjectResult(_repository.GetBackgroundId());
}
[HttpPut(nameof(SetBackgroundId))]
public IActionResult SetBackgroundId([FromQuery]int id)
{
_repository.SetBackgroundId(id);
return new NoContentResult();
}
}
}
I also have the following line of code in startup.cs
app.UseMvcWithDefaultRoute();
I can access the account controller by the following url
http://localhost:5001/account/login
However, I cannot access the version controller by the following url:
http://localhost:5001/version/GetBackgroundId
The error code is 404.
What is wrong?
You are missing a route prefix for controller. You are using attribute routing so you need to include the entire desired route.
The current GetBackgroundId controller action would map to
http://localhost:5001/GetBackgroundId
Add a route to the controller
[Route("[controller]")]
public class VersionController : Controller {
IVersionService _repository;
public VersionController(IVersionService repository) {
_repository = repository;
}
//Match GET version/GetBackgroundId
[HttpGet("[action]")]
public IActionResult GetBackgroundId() {
return Ok(_repository.GetBackgroundId());
}
//Match PUT version/SetBackgroundId?id=5
[HttpPut("[action]")]
public IActionResult SetBackgroundId([FromQuery]int id) {
_repository.SetBackgroundId(id);
return NoContent();
}
}
Also note the use of route tokens and that instead of newing up the responses, Controller already has helper methods that provide those results.
Reference Routing to controller actions in ASP.NET Core

Avoiding "Request matched multiple actions resulting in ambiguity" error in ASP.Net Core

I am trying to do something simple and trivial - or at least I thought.
I am trying to write a base class that can be inherited by each micro-service project I spin up. The point of this base class is to test connectivity from HTTP all the way through to SQL. It is NOT enabled in PROD.
This is one of the (simpler) base classes:
public class DevTestControllerBase: ApiControllerBase
{
public DevTestControllerBase(IHostingEnvironment env, IConfiguration configuration = null, IMediator mediator = null) : base(env, configuration, mediator)
{
}
[HttpGet]
public IActionResult Get()
{
var response = Mediator.Send(new QueryGet());
return Ok(response.Result);
}
[HttpGet("{id}", Name = "Get")]
public IActionResult Get(Guid id)
{
var response = Mediator.Send(new QueryGetById(id));
return Ok(response.Result);
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]DevTestModelBinding value)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var response = await Mediator.Send(new CommandPost(value));
return Created("Get", new { id = response });
}
[HttpPut("{id}")]
public IActionResult Put(Guid id, [FromBody]DevTestModelBinding value)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
Mediator.Send(new CommandPut(id, value));
return Ok();
}
[HttpDelete("{id}")]
public IActionResult Delete(Guid id)
{
Mediator.Send(new CommandDelete(id));
return Ok();
}
}
I was hoping to use it as:
[Produces("application/json")]
[Route("api/DevTest")]
public class DevTestController : DevTestControllerBase
{
public DevTestController(IHostingEnvironment env, IConfiguration configuration, IMediator mediator) : base(env, configuration, mediator) { }
}
Unfortunately, it produces this error instead:
AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
MyNamespace.Providers.WebApi.Features.DevTest.DevTestController.Get
(MyNamespace.Providers.WebApi)
MyNamespace.Infrastructure.Web.Controllers.DevTestControllerBase.Get
(MyNamespace.Infrastructure.Web)
And since I wanted to use Swagger, I am also getting this error when trying to hit the Swagger endpoint:
An unhandled exception has occurred while executing the request
System.NotSupportedException: Ambiguous HTTP method for action -
MyNamespace.Providers.WebApi.Features.DevTest.DevTestController.Get
(MyNamespace.Providers.WebApi). Actions require an explicit HttpMethod
binding for Swagger 2.0
Make your base controllers abstract. Otherwise, they participate in routing as possible controllers that can be routed to. Although I'm a little surprised, honestly, that the routing based on controller name convention still works with a class ending in ControllerBase instead of just Controller, it would appear that ASP.NET Core sees them both as named DevTest, and therefore ambiguous.
You could probably alternatively rename the base controller(s) to something like BaseDevTestController (i.e. with "Base" before "Controller"), which would then make the names DevTest and BaseDevTest, removing the abmiguity. However, it's still a better idea to just make it abstract, as it should be anyways. You wouldn't want someone to actually be able to navigate directly to your base controller(s).

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.

Adding WebApi to existing MVC project - No type found that maches the controller named "x"

I've recently asked a few questions about the best way to create a web api which utilises the same url as my main mvc site. I deduced the best way was to make the necessary changes to my MVC site to include web api and the necessary routing.
I have mainly followed How to add Web API to an existing ASP.NET MVC 4 Web Application project? but I have run into problems. The code compiles fine and it is clearly looking for the route but I get the error:
No HTTP resource was found that matches the request URI 'http://localhost:2242/api/value'.
No type was found that matches the controller named 'value'.
My WebApiConfig:
class WebApiConfig
{
public static void Register(HttpConfiguration configuration)
{
configuration.Routes.MapHttpRoute("API Default", "api/{controller}/{id}",
new { id = RouteParameter.Optional });
}
}
my global.asax:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Database.SetInitializer<ApplicationDbContext>(null);
}
}
my api controller:
public class ValuesController1 : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<controller>/5
public string Get(int id)
{
return "value";
}
// POST api/<controller>
public void Post([FromBody]string value)
{
}
// PUT api/<controller>/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/<controller>/5
public void Delete(int id)
{
}
}
Other posts have corroborated that this is a correct and working setup...I created a separate webapi project to compare and this is all correct routing wise apparently. It would be far preferable to build this into my MVC website, does anyone have any ideas? This poster No type was found that matches controller had the same problem and the solution he found was to copy everything into a new project....that really isn't something I want to do/see why I should need to do.
I think it is because of your Controller's name : ValuesController1
A controller has to be suffixed by "Controller", the 1 may be the cause of your issue.
The name of the controller ValuesController1 doesn't match convention - in order for the default route to match /api/value based on the default convention set in your call to configuration.Routes.MapHttpRoute(...), the controller should be called ValueController:
public class ValueController : ApiController
{
public IEnumerable<string> Get()
// ...
However, if you intend to deviate from the configured convention, you can apply RouteAttribute and RoutePrefixAttribute in conjunction with the Http* verb attributes to customise controller and method routes, e.g.
[RoutePrefix("api/Foo")]
public class ValuesController : ApiController
{
// get api/Foo/value
[HttpGet]
[Route("value")]
public IEnumerable<string> NameDoesntMatter()
{
return new string[] { "value1", "value2" };
}
// get api/Foo/value/123
[HttpGet]
[Route("value/{id}")]
public string AnotherRandomName(int id)
{
return "value";
}
Before using the RouteAttribute you will need to add the following to your WebApiConfig.Register(HttpConfiguration config):
config.MapHttpAttributeRoutes();
Even with the routing attributes, note however that the controller class name still needs to end with the suffix Controller, i.e. cannot end in the suffix 1. It is surprisingly difficult to alter this convention.

Categories

Resources