ASP.NET Core API Controller ambiguous method issue - c#

I came across this issue, and couldn't find a quick guide to is, so here I am.
I've created and ASP.NET Core API.
On one controller, i've defined 2 get methods. Each accepts different parameter, but both are Strings. This creates following issue.
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches:
Airbnb.Controllers.ListingsAndReviewsController.GetById (Airbnb)
Airbnb.Controllers.ListingsAndReviewsController.GetByName (Airbnb)
My methods were looking like this.
[HttpGet("{id}")]
public IEnumerable<ListingsAndReviews> GetById(String id)
{
}
[HttpGet("{name}")]
public IEnumerable<ListingsAndReviews> GetByName(String name)
{
}

As you can see, the name of these methods is different, but they have same parameters, which causes issue.
To solve this issue, you have to set new Property "Order" to the HttpGet method attribute.
[HttpGet("id/{id}", Order = 0)]
public IEnumerable<ListingsAndReviews> GetById(String id)
{
}
[HttpGet("name/{name}", Order = 1)]
public IEnumerable<ListingsAndReviews> GetByName(String name)
{
}
The Order property differentiates between the 2 methods.
The best practice might be adding the order property to every method.
Hope this can help someone, if not, feel free to suggest better solution.
[Edit]
I ended up with following structure for the template
"id/{id}"
"name/{name}"
this forces the route to first set the filtered parameter, then the value.
Following structure is now established
localhost/ListingsAndReviews
localhost/ListingsAndReviews/id/1
localhost/ListingsAndReviews/name/SomeName

I have to ask : Are you sure the "ID" will not be an integer or a guid or something like that ?
If it's the case you can directly use the expected type into the action:
public IEnumerable<ListingsAndReviews> GetById(Guid id)
It's a really a string (as the name) you can use route parameter constraint :
That way you can define what the "ID" will looks like and what the name will looks like.
By example if you know that ID will only have 5 characters max you could do:
[HttpGet("{id:maxlength(5)}")]
I just gave you the mechanism but the specific answer to you issue will depend on : What are the IDs looks like ? (I guess the name can be anything...).
If you cannot differentiate ID and name you shouldn't have 2 endpoints (but maybe in one endpoint you can search once for the ID and once for the name as a fallback).

Related

WebApi: What is the difference between these 2 types of parameters

This is the first time that i'm seeing this ways of doing things in a web-api.
So let me put you in context:
I have a web api where we have two routes:
Method one:
[HttpGet]
[Route("message-activity")]
public PagedResult<AuditedMessage> GetMessageActivity(int page)
{
}
Method 2:
[HttpGet]
[Route("message-activity/{messageID}")]
public AuditedMessage GetMessage(string messageID)
{
}
My question is:
Talking about good practices in webApi's.
-Is the first method acceptable?
-Is it right?
-Is that anything wrong with the second method?
Thanks!
Using first method, parameter will be specified using query string.
i.e : message-activity?page=paramValue
While Using second method, you can simply pass parameter in the API call URL
i.e : message-activity/paramValue
Both of the method will work equally
In light of your edited question:
There might be something wrong, or it might not be.
To elaborate: The second version just returns a single result, the first one returns a paged list of results.
If I have an existing messageID and I am only interested in this message, the second one is the best.
If I need to to show a list of messages, the first one is vastly better.
On a normal CRUD Controller, I would expect both methods.
to be honest I don't like any of them.
why?
let's look at the first one:
GetMessageActivity(int page)
What message?
what does page mean?
what's the page size?
if you want it to make sense and paginate the results then perhaps it could look like this:
[HttpGet]
[Route("message-activity")]
public PagedResult<MessageActivity> GetMessageActivity(string messageID, int pageSize, int pageNumber)
{
}
of course you'd update the route to match that.
That would make more sense to me. you return this MessageActivity data, whatever that is, not a message. If you want to return a message then return a message like in the second method.
Second one is strange as well:
[HttpGet]
[Route("message-activity/{messageID}")]
public AuditedMessage GetMessage(string messageID)
{
}
Well, if you want to load a message than the route should be message, not message-activity.
Something like:
[HttpGet]
[Route("message/{messageID}")]
public AuditedMessage GetMessage(string messageID)
{
}
I would change both so they make sense. An API is written for clients and those clients need to understand very quickly what's going on.
You can't compare the two as they refer to different things. You want to work with a message then your route, params and outputs need to reflect that. If you want to work with this MessageActivity then let your endpoint reflect that

C# Webapi attribute routing is not working

I have two different webapi controllers with actions as shown below. For some reason i keep getting "Multiple controller types were found that match the URL...." exception. Can anyone help me to figure out what i am doing wrong here?
ControllerA
[HttpGet]
[Route("clips/{Id}", Name = "GetById")]
public async Task<HttpResponseMessage> Get(string Id)
{
}
*Id is alphanumeric and it always starts with a number
ControllerB
[HttpGet]
[Route("clips/product", Name="GetXProducts")]
public async Task<HttpResponseMessage> GetXProducts([FromUri]SearchCriteria searchCriteria)
{
}
Thanks
You can apply a regular expression to both routes to ensure that it is chosen when the appropriate parameter is passed:
[Route(#"clips/{Id:regex([0-9]*[A-Z0-9]*)}", Name = "GetById")]
You will need to apply a similar expression to the other route to exclude these hits.
But I agree with #ryancdotnet that you should probably reevaluate your routes.
It looks like the issue is because your {id} on Controller A is of type string, and the Route for Controller B is essentially the same thing, as product is also a string, therefore Routing does not know how to differentiate between the two operations.

How to retrieve a suffix from url as an action parameter when performing attribute routing in ASP.NET

Given Attribute Routing in ASP.Net Core (but I guess MVC and WebAPI work the same way), I want to be able to do something like this:
[Route("api/[controller]")]
public class SampleController : Controller {
// GET api/sample/123e4567-e89b-12d3-a456-426655440000/folder/subfolder/file.css
[HttpGet("{id}")] // this is wrong, how should it be written correctly?
public string Get(Guid id, string urlSuffix) {
return null; // return stuff based on the id and the full url
}
}
in the URL taken as example in the comment (api/sample/123e4567-e89b-12d3-a456-426655440000/folder/subfolder/file.css) the SampleController.Get method should be called with the following parameters:
id: 123e4567-e89b-12d3-a456-426655440000
urlSuffix: folder/subfolder/file.css or /folder/subfolder/file.css (I don't really care for the leading /)
If there are additional query parameters, these should be included in the suffix as well.
I thought about using the raw request URL, but I'd still need a way to specify an action which is executed and what ever I thought of was too late, ASP.Net already figured out that there isn't any URL for the given action.
I would like to use controllers for this, instead of adding some "raw" code into the ASP.Net Core execution pipeline.
Update:
This exact example doesn't work for me with asp.net core dotnet core and kestrel service:
[Route("api/[controller]")]
public class SampleController : Controller
{
// GET api/values/5
[HttpGet("{id}/{urlSuffix}")]
public object Get(string id, string urlSuffix)
{
return new {id, urlSuffix};
}
}
When I call http://localhost:5000/api/sample/some-id/folder I get the correct result, but when I call http://localhost:5000/api/sample/some-id/folder/subfolder/file.extension I get a 404 error.
Referencing: Handling a Variable Number of Segments in a URL Pattern
Sometimes you have to handle URL requests that contain a variable
number of URL segments. When you define a route, you can specify that
if a URL has more segments than there are in the pattern, the extra
segments are considered to be part of the last segment. To handle
additional segments in this manner you mark the last parameter with an
asterisk (*). This is referred to as a catch-all parameter. A route
with a catch-all parameter will also match URLs that do not contain
any values for the last parameter.
Your template and placeholders will change to ...
[HttpGet("{id:guid}/{*urlSuffix}")]
Given the following URL ...
"api/sample/123e4567-e89b-12d3-a456-426655440000/folder/subfolder/file.css"
then
id = 123e4567-e89b-12d3-a456-426655440000
urlSuffix = "folder/subfolder/file.css"
Because the / is already part of the template, it will be excluded from the urlSuffix parameter.
the *urlSuffix acts as a catch all for everything after the {id}/ in the URL. If there are additional query parameters, these will also be included in the urlSuffix as well.
You were getting the not found error because your example URL could not find a matching route of api/sample/{id}.
I included the :guid route constraint based on your original example expecting a Guid for id parameter.
If the id is not going to be a Guid always you can remove the constraint and it will work for your updated example.

C# MVC - Routing Issue / Confusion

I am developing one application in the ASP.NET MVC C# on the .NET 4 framework.
I confused in routing and I do the research and developed the one demo version It works as I want but I want to know is which method is best practice for developing application.
First I register the route like this:
routes.MapRoute(
name: "RoutesTesting",
url: "{controller}/{action}/{a}/{b}/{c}/{d}/{e}",
defaults: new { controller = "Home", action = "Test", e = UrlParameter.Optional }
);
I have one class that have the properties and it's name same as the route's parameter.
class MyClass{
public string a{get;set;}
public string b{get;set;}
public string c{get;set;}
public string d{get;set;}
public string e{get;set;}
}
Now I created the tow methods that works find and get the data from the URL successfully.
Method 1:
public ActionResult Test(MyClass objMyClass){
}
Method 2:
public ActionResult Test(string a,string b,string c,string d,string e=String.Empty){
}
My question is:
Is routing doing that conversation in my action method? Like it convert the parameter values in the `MyClass' object's properties?
Which method is best practice to use?
Is method 1 will throw any error or exception when the conversation is not possible ?
Thanks in advance...
The behavior you are seeing is a part of ASP.NET's Model Binding. It's the magic that lets you send across a JSON object of {"firstName":"Jonathon","lastName":"Chase"} and have to automagically be mapped to a model Person that looks like so:
public class Person {
public string FirstName {get;set;}
public string LastName {get;set;}
}
The fact that you can create a route like that is merely a consequence of this. Model Binding is a complex subject, but I can touch on some aspects of how you're forming your route, especially if the action you're creating is going to have a side-effect, such as writing to a database.
Typically if you're going to have a method that will effect state, you should use an Http verb other than Get, and send the model across in the body of the request, rather than in the query/url string. The Model Binding will take care of the mapping for you either way.
You should prefer to use a strong model rather than multiple primitives as parameters, especially in cases where the information will be sent in the body of a request over the query string.
These points are debatable, however, and shouldn't be considered hard or fast rules for the most part.
As to your last point, if the parameters are incorrect enough that the Route can't identifier the action or controller, you should get a 404. However, if you have a valuetype that isn't nullable as an expected routed property that isn't properly sent across, you should expect a 500 with an InvalidOperationException.
Take a look at How model binding works
Is routing doing that conversation in my action method? Like it
convert the parameter values in the `MyClass' object's properties?
The framework model binder is doing the conversion based on the actions parameter.
Which method is best practice to use?
That is an opinionated question. Depends on which one suits your needs. The framework handles both.
Is method 1 will throw any error or exception when the conversation is
not possible ?
Model binder will pass null to the action parameter for the properties that don't match.

Can I have multiple POST methods in Web API with different complex parameter types?

I'm new to Web API...
Here's my basic route:
config.Routes.MapHttpRoute(
name: "Workitems",
routeTemplate: "api/{controller}/{workitemID}",
defaults: new { controller = "workitems", workitemID = RouteParameter.Optional }
);
Here's what I'd like to have:
public HttpResponseMessage Post( [FromBody] FolderModel theModel )
public HttpResponseMessage Post( [FromBody] DocumentModel theModel )
But, Web API doesn't find my second Post method. I've done lots of searching here and in Google but haven't found anything that works for me (well). I know I could add a 2nd unused parameter to the 2nd method - but that's too much of a hack. If this were normal C# code, the compiler would have no problem knowing which to choose b/c the methods have different signatures. But Web API is not smart enough.
I looked at custom constraints but that didn't seem appropriate. I also cannot use different {actions} as that violates RESTful constraints (no RPC, just resources) for my API. I also cannot put the 2nd Post on a different controller.
The only way I've gotten this to work is to wrap both FolderModel and DocumentModel in a parent object like this:
public class WorkitemCreateModel
{
public DocumentModel Document { get; set; }
public FolderModel Folder { get; set; }
}
public HttpResponseMessage Post( [FromBody] WorkitemCreateModel theModel )
Then have a single Post method that takes WorkitemCreateModel. But then it's the responsibility of the developer using my API that they must pass in WorkitemCreateModel but they must only pass in a DocumentModel object OR a FolderModel object. It's annoying too b/c my GET API can return either a DocumentModel object or a FolderModel object. So, it would be nice to just pass the object you get from the GET into the POST. But that doesn't work and they must wrap it in a WorkitemCreateModel object first.
Any other suggestions?
BTW: this website is the best! I've found SO many answers here!
This might be an old post but I am adding this info just in case for people like me who came here looking for answers.
The short answer here is NO, its not possible.
The problem is with the way the routing works, especially the part about choosing what action method to use. Here is an extract from ASP .NET article (https://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection),
The goal of the selection algorithm is to select an action from the static description, before invoking any bindings. Therefore, complex types are excluded from the matching algorithm.
So while matching action methods to the path, Web API disregards all Complex types in the parameter list for that method and when you do that both of your methods have 0 parameters and that's why you are facing this problem.
Hope this helps...
Have you considered changing the method names and trying using the [HttpPost] attribute?
Source : http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
This might answer your question:
Multiple HttpPost method in Web API controller

Categories