Specify proper route to call a WebApi 2 Controller method - c#

I created an PublicController.cs that will returns list of agents. I then build and run the project and open Postman change the mode to get and call http://localhost:50159/api/public/Agents the message I get back is
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:50159/api/public/Agents'.",
"MessageDetail": "No action was found on the controller 'Public' that matches the request."
}
Is there something I am missing?
[HttpGet]
public IHttpActionResult Agents(string key, string diseaseId)
{
GuardKey(key);
var result = AgentsDataService.GetAgents();
return Json(result);
}

By default, WebApi 2 controllers' names are registered by convention i.e. PublicController gives us /api/Public during runtime.
you try to access api/public/Agents, however your controller may lie at api/Public.
You need to specify a Route in RouteConfig.cs or apply a Route as an attribute for a method.
There is a good Microsoft article that describes routes more precisely.

Related

Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints

I have an ASP.Net Core WebAPI, I got below requirement as
I have 2 methods to handle HTTP GET requests, the first one for GetCustomer by id(string) and the other one to GetCustomer by email(string).
//GET : api/customers/2913a1ad-d990-412a-8e30-dbe464c2a85e
[HttpGet("{id}")]
public async Task<ActionResult<Customer>> GetCustomer([FromRoute]string id)
{
}
// GET: api/customers/myemail#gmail.com
[HttpGet("{name}")]
public async Task<ActionResult<Customer>> GetCustomerByEmail([FromRoute]string email)
{
}
when I try to this Endpoint, I get exception as:
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.
Which is quite Obivious & understood.
It could be easily addressed by appending/prepending some string in the route like
/api/customer/{id}
/api/customer/emai/{id}
However not so convinced with this apporach however on googling I get the below SO link
How to handle multiple endpoints in ASP.Net Core 3 Web API properly
But this guy has one parameter as int while the other as string so route constraint rescued.
however, I see some people posting/suggesting on the same post to add [Route("")] but I didn't get how this attribute is useful ?
afaik, Route("") and HTTPGet("") //any HttpVerb serves the same purpose?
Anyways how could I handle my requirement elegantly?
You can add route constraints to disambiguate between the two routes:
[HttpGet("{id:guid}")]
[HttpGet("{name}")]
You could also create your own email constraint or use regex(.) constraint for the email parameter.

Failed to load resource: the server responded with a status of 404 ()

I am writing a REST API in .net core. I am trying to test the API using Postman and I am getting an error saying
Failed to load resource: the server responded with a status of 404 ()
I know this error occurs when the route does not match. Not sure, what am I doing wrong with the route. Below is my code with the Route at the top:
namespace RecLoad.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class RecLoadPrimeController : ControllerBase
{
[Route("RecLoadPrime/insertRecLoadData/{RecStartDate}/{RecEndDate}")]
[HttpPost]
public void insertRecLoadData(string RecStartDate, string RecEndDate)
{
RecLoadDataProvider dataProvider = new RecLoadDataProvider();
dataProvider.InsertCardsData(RecStartDate, RecEndDate);
}
}
}
The URL that I am trying to test in Postman is below:
https://localhost:44360/api/RecLoadPrime/insertRecLoadData/?RecStartDate=01/01/2020&RecEndDate=01/02/2020
I am very new to API, this is the first API that I am writing. Below is the image for application structure. Its extremely simple:
Any help will be greatly appreciated.
A 404 error means not found. This means Postman cant find the end point you are trying to hit.
Your [Route] attribute needs to be updated. The root of this endpoint (controller) it's RecLoadPrime. So get rid of that part. If you are just trying to test, update it to [Route("insert")].
Using ? in your URL means you are passing query parameters. Which are usually used on GET requests not on POST requests.
Web API expects you to use Model Binding for passing in parameters. Meaning map the post parameters to a strongly typed .NET object, not to single parameters. Alternatively, you can also accept a FormDataCollection parameter on your API method to get a name value collection of all POSTed values.
For example: Create a small class called Card, with the properties startDate, and endDate. Make them DateTime. Now use that in the method signature public void insertRecLoadData([FromBody]Card card)
In Postman, you are now going to use the Body option and create a JSON representation of this new class we created.
For example: { "startDate": "2020-03-23", "endDate": "2020-03-27" }
In the route, you are going to use: POST | https://localhost:44360/api/insertRecLoadData/insert
Make sure you set breakpoints in your controller. Not sure how you have setup your project but I'd suggest reading up more on how to setup a Web API using ASP.NET Core. Look into RESTful design to also get an idea on how to best setup these end points.
Good luck!
The current route configuration on your controller and on your action will result in duplicated section in your route. Specifically, the route the action will be associated with will be "api/RecLoadPrime/RecLoadPrime/insertRecLoadData/{RecStartDate}/{RecEndDate}".
Consider removing the RecLoadPrim/ prefix from your action route attribute as follows:
[Route("insertRecLoadData/{RecStartDate}/{RecEndDate}")]

OData v4 Custom Action for File Upload

I have an OData controller with standard verbs for CRUD. Everything is working fine. Now I need to add a custom action to perform file upload. I try to add a method to my existing controller like this:
[HttpPost]
[Route("UploadFile")]
public async Task<HttpResponseMessage> UploadFile()
{
//handle uploaded content logic here...
}
But when I try to invoke it by doing a POST:
http://localhost/UploadFile
I get this error:
System.InvalidOperationException: No non-OData HTTP route registered.
What should I do for this custom action to allow file upload?
You need to declare the Action as part of the EdmModel, in the following example I am assuming that your Entity Type is Attachment, and your controller class name is AttachmentsController. By convention, your EntitySet name must then be Attachments
var attachments = builder.EntitySet<Attachment>("Attachments");
attachments.Action(nameof(AttachmentsController.UploadFile))
.Returns<System.Net.Http.HttpResponseMessage>();
The important part of the above statement is the return type, if you do not declare the return type correctly in your EdmModel then you will find your endpoints returning 406 errors - Unacceptable, even though your method executes correctly, which is really confusing the first time you run into it. This is because OData will still try to parse your response to match the Accept header from the request before completing the response.
Try to use 'nameof' when mapping functions and actions instead of 'magic strings' or constants so that the compiler can pickup basic issues like wrongly defined route.
With this approach you do not need the Route attribute on the method header and the action will be included in the metadata document and therefore swagger outputs.

ASP.NET Core controller usefull errormessage return

My application is an ASP.NET Core 1.0 Web API.
[HttpGet("{someData:MinLength(5):MaxLength(5)}")]
[Produces("application/json")]
public async Task<IActionResult> GetSomeData(string someData)
{
return this.Ok(JsonConvert.SerializeObject("Data is: " + someData));
}
So when i pass a parameter with the length 6, the controller returns the Response Code 404 and the Response body no content because the parameter doesnt have the length 5.
This information is pretty useless for me, is there a way to return a more usefull error message?
I know i could just hardcode the error message in every controller like that:
[HttpGet("{someData}")]
[Produces("application/json")]
public async Task<IActionResult> GetSomeData(string someData)
{
if (someData.Length != 5)
{
return this.StatusCode(404, JsonConvert.SerializeObject("The data has to be 5 digits long."));
}
return this.Ok(JsonConvert.SerializeObject("Data is: " + someData));
}
The problem about this is, I have many controllers in my application and I dont want to validate the parameters everytime.
Is there a way the controller returns a more usefull Responsebody by itself? Or do I really have to add a validation method in each controller and forget about the [HttpGet] parameters and its error return?
Thank you very much
Route constraints are used to restrict which requested paths make it to a given action / route destination. They're not meant to perform model validation - that's done later and through a separate mechanism. If the request doesn't conform to your route constraints, it simply won't match that route. If it doesn't match any route, you'll get a 404.
As you noted in your own answer, this is covered in the routing docs: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing#route-template-reference
Alright I found an answer:
It's not possible to get better error messages.
Warning
Avoid using constraints for input validation, because doing so means
that invalid input will result in a 404 (Not Found) instead of a 400
with an appropriate error message. Route constraints should be used to
disambiguate between similar routes, not to validate the inputs for a
particular route.
found here

Attribute based webapi2 routing returns 404 for some methods

I'm presently working on a project that has been upgraded to Webapi2 from Webapi. Part of the conversion includes the switch to using attribute based routing.
I've appropriately setup my routes in the Global.asax (as follows)
GlobalConfiguration.Configure(config => config.MapHttpAttributeRoutes());
and removed the previous routing configuration.
I have decorated all of my API controllers with the appropriate System.Web.Http.RouteAttribute and System.Web.Http.RoutePrefixAttribute attributes.
If I inspect System.Web.Http.GlobalConfiguration.Configuration.Routes with the debugger I can see that all my expected routes are registered in the collection. Likewise the appropriate routes are available within the included generated Webapi Help Page documentation as expected.
Even though all appears to be setup properly a good number of my REST calls result in a 404 not found response from the server.
I've found some notable similarities specific to GET methods (this is all I've tested so far)
If a method accepts 0 parameters it will fail
If a route overrides the prefix it will fail
If a method takes a string parameter it is likely to succeed
return type seems to have no affect
Naming a route seems to have no affect
Ordering a route seems to have no affect
Renaming the underlying method seems to have no affect
Worth noting is that my API controllers appear in a separate area, but given that some routes do work I don't expect this to be the issue at hand.
Example of non-functional method call
[RoutePrefix("api/postman")]
public class PostmanApiController : ApiController
{
...
[HttpGet]
[Route("all", Name = "GetPostmanCollection")]
[ResponseType(typeof (PostmanCollectionGet))]
public IHttpActionResult GetPostmanCollection()
{
return Ok(...);
}
...
}
I expect this to be available via http://[application-root]/api/postman/all
Interestingly enough a call to
Url.Link("GetPostmanCollection", null)
will return the above expected url
A very similar example of method calls within the same controller where some work and some do not.
[RoutePrefix("api/machine")]
public class MachineApiController : ApiController
{
...
[HttpGet]
[Route("byowner/{owner}", Name = "GetPostmanCollection")]
public IEnumerable<string> GetByOwner([FromUri] string owner)
{
...
}
...
[HttpGet]
[Route("~/api/oses/{osType}")]
public IEnumerable<OsAndVersionGet> GetOSes([FromUri] string osType)
{
...
}
...
}
Where a call to http://[application-root]/api/machineby/ownername succeeds and http://[application-root]/api/oses/osType does not.
I've been poking at this far too long, any idea as to what the issue may be?
Check that you configure your HttpConfiguration via the MapHttpAttributeRoutes method before any ASP.NET MVC routing registration.
In accordance to Microsoft's CodePlex entry on Attribute Routing in MVC and Web API the Design section states:
In most cases, MapHttpAttributeRoutes or MapMvcAttributeRoutes will be
called first so that attribute routes are registered before the global
routes (and therefore get a chance to supersede global routes).
Requests to attribute routed controllers would also be filtered to
only those that originated from an attribute route.
Therefore, within the Global.asax (or where registering routes) it is appropriate to call:
GlobalConfiguration.Configure(c => c.MapHttpAttributeRoutes()); // http routes
RouteTable.Routes.MapRoute(...); // mvc routes
In my case it was a stupid mistake, I am posting this so people behind me making the same mistake may read this before they check everything else at quantum level.
My mistake was, my controller's name did not end with the word Controller.
Happy new year

Categories

Resources