C# Webapi attribute routing is not working - c#

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.

Related

C# Core MVC How to apply custom URL to controller action

Question:
Edit: It seems my question is actually not a routing issue but an anchoring issue.
If I have assigned a route:
[Route("~/Envelope/List/AcademicYear/{year}")]
public IActionResult AcademicYear(string year)
{
}
How would I correctly use an asp-action to call this route?
using
<a asp-action="List/AcademicYear/" asp-route-id="#Model.AcademicYear">#Model.AcademicYear</a>
returns a url with a %2f (Envelopes/List%2fAcademicYear/2122) instead of / (Envelopes/List/AcademicYear/2122) and thus results in a 404 error
How do I use Custom URL with asp-action to call a specific Action in my Controller?
or
How do I change the routing so I can call an action from a controller with a non default route mapping?
Context:
I've read https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-5.0
and yet i'm still confused on the whole concept of routing and how to interacts with controllers and actions.
In my application I have a controller called Envelope - it controls everything to do with my Envelopes.
I have a class in the Envelopes controller called
public class EnvelopeController : Controller {
public IActionResult List() {... return View()}
and it returns the List.cshtml view. The current url as set by default route mapping: /Envelope/List
In the List.cshtml I have a link that is intended to filter the List on a year parameter
<a asp-action="AcademicYear" asp-route-academicYear="#Model.AcademicYear"> #Model.AcademicYear</a>
My intention is to pass this into a method in the Envelopes controller called "AcademicYear" that gathers the Envelope data stored in temp data, deseralises it and then returns a filtered version based on the parameter:
public IActionResult AcademicYear(string academicYear) { return View("List", newViewModel)}
The return url after this point is correctly: /Envelope/AcademicYear?academicYear=21%2F22
However I would Like to know how to change this so even though I call the Action
<a asp-action="AcademicYear" asp-route-academicYear="#Model.AcademicYear"/>
the URL returned would look like this /Envelope/List/AcademicYear/2122/
Is there a way of doing this? Am I looking at the problem the wrong way? I have thought about simply passing a parameter in the List action and running some form of control to do either set of operations depending on the parameters existence but realistically the List method and the filtering AcademicYear method aren't really doing the same thing and I'd like to seperate out the code into different methods if possible.
Even if its not the appropriate solution I would still like to know if it is possible to change the URL routing for an action after it has been called.
Edit :
I have tried using HttpGet(List/AcademicYear/{academicYear:[a-zA-Z]} however when I do this I can't actually call List/AcademicYear as an asp-action due to the "/" and how that encodes to %2f
Answer:
With the help of the below solutions I realised I was looking at the problem wrong and was actually having issues creating correct anchors.
Reading: https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-5.0#url-generation-and-ambient-values-1
I realised the answer was staring me in the face and I used this alongside the Routes provided for in the answers
<a id="#academicYear.Name" href="#Url.Action("AcademicYear", "Envelope",new {year= academicYear.Name})">
Maybe you just need to decorate your action like that:
[HttpGet("List/AcademicYear/{year:int}")] // or HttpPost it depends on you
public IActionResult AcademicYear(string year) { }
You can add several attribute routes to the action.
[Route("~/Envelope/List/AcademicYear/{year}", Name="ListRoute")]
[Route("~/Envelope/AcademicYear/{year}")]
public IActionResult AcademicYear(string year) { }
and if you need this url
http://localhost:xxxx/Envelope/List/AcademicYear/2021
you can use this html helper, that will create a right anchor tag for you using an attribute route name.
#Html.RouteLink(#Model.AcademicYear, "ListRoute", new { year = #Model.AcademicYear })
I added Name="ListRoute" to the attribute routing (see above)

Using "api" in RoutePrefix Attribute in an MVC Controller causes errors, works with ApiController

This might be a weird question, but I struggled for hours with this issue and at the end I have no idea what is happening and feel like I fundamentally misunderstood something about all of this.
Following example:
[RoutePrefix("api/customers")]
public class ExampleController : Controller
{
[HttpGet]
[Route("{customerId}")]
public ActionResult Read(string customerId)
{
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
[HttpPost]
[Route("")]
public ActionResult Create(CreateCustomerModel model)
{
// do stuff
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
}
When I design my controller like this, using an MVC controller, I get an error when I try to make a request to the first action (GET localhost:1234/api/customers/blabla)
I did a lot of trial and error to find out why this is happening. I get the following error message btw (which is in german unfortunately):
Translated:
Message: No HTTP-Resource was found, that matches the Request-URI \"http://localhost:53741/api/customers/blabla\"
MessageDetail: No Type was found, that matches a controller with the name \"customers\"
Now, the thing is, if I change the Routeprefix, so it doesn't contain "api", but something else instead, for example "mapi" (or whatever), then it works! So I was wondering if "api" is some kind of keyword.
I then changed the Controller to ApiController (and adjusting all the types and stuff), and then it still worked!
This makes it seem like when the route starts with the word "api", the programm expects an ApiController?
Is that true? Why?
You might say, why don't you just use an ApiController, it's made exactly for the purpose of creating an API, and you are probably right in some way, but I only had bad experiences with the ApiController compared to the MVC controller. It is much more restricted and less advanced (for example you can only bind a single parameter from the body, I forgot about all the other restrictions). I researched many times why you should use ApiController and when you should use Controller, but never found a compelling argument for ApiController, because I can do everything with Controller too (my only guess is that it has some overhead or something). Anyways, that is not the point of the question, the point is why can't I use "api" in Controller.
This is ASP.NET Framework 4.7.2 btw.

Using regex on MVC routes for controller action to allow only calls that don't contain a given word

I have the following actions defined for two seperate controllers -
Controller A -
[HttpPost]
[Route("~/{configurationId:int}/device/")]
public ActionResult AddDevice()
Controller B -
[HttpPut]
[Route("~/{configurationId:int}/{fieldname:string}/")]
public ActionResult EditConfiguration()
I need to leave the routes defined as is due to design considerations, etc. I am trying to add a regular expression to Controller B action to exclude the word 'device' for fieldname so that the routes don't 'collide'. This is what I have and it seems to work in regex testing tool.
[HttpPut]
[Route("~/{configurationId:int}/{fieldName:regex(^\b([a-z0-9-]+)\b(?<!device)$)}/")]
public ActionResult EditConfiguration()
If I call it with this route it says it cannot find the resource. Any ideas/suggestions on how to alter my regex so this will work? -
configuration/3/lock-timeout/
I ended up figuring this out. Turns out my regex was too complicated. Here is the working expression for anyone looking to put a route constraint to exclude a given word (in my case 'device':
[Route("~/{configurationId:int}/{fieldName:regex(^(?!.*device).*$)}/")]

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.

Request matched multiple actions resulting in ambiguity for actions with different parameters in ASP.NET 5 / MVC 6

I have a simple route in my project:
routes.MapRoute(
name: "api",
template: "api/{controller}/{action}");
In my controller I have two actions:
[HttpGet]
public string Get(string value)
{
return value;
}
[HttpGet]
public string Get(int id)
{
return id.ToString();
}
Now when I try to do a url like api/controller/get?id=1 it does not work because the framework cannot distinguish between two actions. As far as I remember it did work pretty well in ordinary web api because it's obvious that this url matches only one of the actions based on it's parameter. Did I do something wrong or it's not supported in the new MVC6?
Did I do something wrong or it's not supported in the new MVC6?
MVC Action Selector dosen't regard Action's parameters during select action. Therefore you can't have two actions correspond one route template. Except e.g actions have different Action Constraints (HttpPost, HttpGet).
Choose action logic in code.
In theory choosen logic between some actions based on parameters have to be into SelectBestActions method, but it do nothing

Categories