Web API 2 controller does not accept QueryString parameters - c#

I have a GET() controller to retrieve a list of entities. I want to pass a parameter to the action to filter the list of objects returned as follows:
Mysite.com/Users?nameContains=john
This is my action definition:
public IEnumerable<object> Get(string nameContains)
{
// I want to use nameContains here
}
I get an error:
The requested resource does not support http method 'GET'.
If I revert the method to not get that parameter, it works.

Try this
public IEnumerable<object> Get([FromUri] string nameContains)
{
// I want to use nameContains here
}
Also since you are working in Web Api 2, you can make use of attribute routing
[Route("users")]
public IEnumerable<object> Get([FromUri] string nameContains)
{

Sorry, it was my mistake, I used 2 parameters and I didn't pass one of them (nor assigned it a default value) so it returned an error. Cheers.

You can add a new route to the WebApiConfig entries.
For instance, your method definition:
public IEnumerable<object> Get(string nameContains)
{
// I want to use nameContains here
}
add:
config.Routes.MapHttpRoute(
name: "GetSampleObject",
routeTemplate: "api/{controller}/{nameContains}"
);
Then add the parameters to the HTTP call:
GET //<service address>/Api/Data/test
or use HttpUtility.ParseQueryString in your method
// uri: /Api/Data/test
public IEnumerable<object> Get()
{
NameValueCollection nvc = HttpUtility.ParseQueryString(Request.RequestUri.Query);
var contains = nvc["nameContains"];
// BL with nameContains here
}

Related

Why am I getting "Multiple actions were found ..." when I have exactly one Post action?

My FBMessageController has nothing but the following methods:
public string Get() { ... }
[ChildActionOnly]
public SendResponse Send(ComplexType msg) { ... }
[ChildActionOnly]
public SendResponse SendImage(string x, string y) { ... }
[HttpPost]
public SendResponse Post([FromBody]AnotherComplexType yyy) { ... }
public void Put(..) { ... }
public void Delete(..) { ... }
However, when I tried to send a request using POST to .../api/fbMessage,
I get the following exception:
"Mutliple actions were found that match the request"
WebApiConfig.Register contains the default code:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
What is causing the error?
If HTTP method attribute is not defined specifically, default is POST. So Send() is also considered as post method and the exception occurs because more than one action were found.
You can try installing Debug Router tool to get visual understanding of how the controller and action were selected. Here's a link
Though you seem to have already isolated the issue, I did the following integration test to recreate your problem and help explain what is causing the error.
[TestClass]
public class FBMessageControllerTests {
[TestMethod]
public async Task HttpClient_Should_Get_OKStatus_From_Post_To_FBMessage() {
using (var server = new TestServer()) {
var config = server.Configuration;
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
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);
var client = server.CreateClient();
string url = "http://localhost/api/fbMessage";
var body = new { body = "Hello World" };
using (var response = await client.PostAsJsonAsync(url, body)) {
var message = await response.Content.ReadAsStringAsync();
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, message);
}
}
}
[Authorize]
public class FBMessageController : ApiController {
public string Get() {
return null;
}
[System.Web.Mvc.ChildActionOnly]
public SendResponse Send(ComplexType test) {
return null;
}
[System.Web.Mvc.ChildActionOnly]
public SendResponse SendImage(string x, string y) {
return null;
}
[HttpPost]
public SendResponse Post([FromBody]AnotherComplexType body) {
return null;
}
public void Put(string gbody) {
return;
}
public void Delete(string body) {
return;
}
}
public class SendResponse { }
public class ComplexType { }
public class AnotherComplexType { }
}
Which produced the following result message:
Multiple actions were found that match the request:
Send on type MiscUnitTests+FBMessageControllerTests+FBMessageController
Post on type MiscUnitTests+FBMessageControllerTests+FBMessageController
The above message tells you exactly what caused the problem. The framework usually tells you why you got certain errors. Sometimes you have to know where to look.
Referencing the following Routing and Action Selection in ASP.NET Web API, here is why you got the problem.
here is the action selection algorithm.
Create a list of all actions on the controller that match the HTTP request method.
If the route dictionary has an "action" entry, remove actions whose name does not match this value.
Try to match action parameters to the URI, as follows:
For each action, get a list of the parameters that are a simple type, where the binding gets the parameter from the URI.
Exclude optional parameters.
From this list, try to find a match for each parameter name, either in the route dictionary or in the URI query string. Matches are
case insensitive and do not depend on the parameter order.
Select an action where every parameter in the list has a match in the URI.
If more that one action meets these criteria, pick the one with the most parameter matches.
Ignore actions with the [NonAction] attribute.
Step #3 is probably the most confusing. The basic idea is that a
parameter can get its value either from the URI, from the request
body, or from a custom binding. For parameters that come from the URI,
we want to ensure that the URI actually contains a value for that
parameter, either in the path (via the route dictionary) or in the
query string.
For example, consider the following action:
public void Get(int id)
The id parameter binds to the URI. Therefore, this action can only
match a URI that contains a value for "id", either in the route
dictionary or in the query string.
Optional parameters are an exception, because they are optional. For
an optional parameter, it's OK if the binding can't get the value from
the URI.
Complex types are an exception for a different reason. A complex type
can only bind to the URI through a custom binding. But in that case,
the framework cannot know in advance whether the parameter would bind
to a particular URI. To find out, it would need to invoke the binding.
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.
After the action is selected, all parameter bindings are invoked.
Summary:
The action must match the HTTP method of the request.
The action name must match the "action" entry in the route dictionary, if present.
For every parameter of the action, if the parameter is taken from the URI, then the parameter name must be found either in the route
dictionary or in the URI query string. (Optional parameters and
parameters with complex types are excluded.)
Try to match the most number of parameters. The best match might be a method with no parameters.
Hope this helps you understand Why you got the “Multiple actions were found …” message.
Happy coding!!!
Can you try to decorate all your action with attribute like this to see if anything change
[HttpGet]
public string Get() { ... }
[ChildActionOnly]
public SendResponse Send(..) { ... }
[ChildActionOnly]
public SendResponse SendImage(..) { ... }
[HttpPost]
public SendResponse Post([FromBody]xxx yyy) { ... }
[HttpPut]
public void Put(..) { ... }
[HttpDelete]
public void Delete(..) { ... }
May be your Put and Post are conflicting somehow.

Why is my attribute being fired on all actions, including ones that don't have the attribute?

I have a controller in my web api. Let's call it TimeController.
I have a GET action and a PUT action. They look like this:
public class TimeController : ApiController
{
[HttpGet]
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, DateTime.UtcNow);
}
[HttpPut]
public HttpResponseMessage Put(int id)
{
_service.Update(id);
return Request.CreateResponse(HttpStatusCode.OK);
}
}
I also have a route config as follows:
routes.MapHttpRoute("DefaultApi", "{controller}/{id}", new { id = RouteParameter.Optional });
so I can access it in a restful manner.
Now I also want to version the GET action using a custom Route attribute. I'm using code very similar to what Richard Tasker talks about in this blog post.
(the difference being that I use a regular expression to get the version from the accept header. Everything else is pretty much the same)
So my controller now looks like this:
public class TimeController : ApiController
{
private IService _service;
public TimeController(IService service)
{
_service = service;
}
[HttpGet, RouteVersion("Time", 1)]
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.Ok, DateTime.UtcNow);
}
[HttpGet, RouteVersion("Time", 2)]
public HttpResponseMessage GetV2()
{
return Request.CreateResponse(HttpStatusCode.Ok, DateTime.UtcNow.AddDays(1));
}
[HttpPut]
public HttpResponseMessage Put(int id)
{
_service.Update(id);
return Request.CreateResponse(HttpStatusCode.OK);
}
}
However, now when I try to access the PUT endpoint I'm getting a 404 response from the server. If I step through the code in debug mode, I can see that the RouteVersion attribute is being fired, even though I haven't decorated the action with it.
If I add the attribute to the PUT action with a version of 1, or I add the built in Route attribute like this: Route("Time") then it works.
So my question is: why is the attribute firing even though I haven't decorated the action with it?
Edit: Here is the code for the attribute:
public class RouteVersion : RouteFactoryAttribute
{
private readonly int _allowedVersion;
public RouteVersion(string template, int allowedVersion) : base(template)
{
_allowedVersion = allowedVersion;
}
public override IDictionary<string, object> Constraints
{
get
{
return new HttpRouteValueDictionary
{
{"version", new VersionConstraint(_allowedVersion)}
};
}
}
}
public class VersionConstraint : IHttpRouteConstraint
{
private const int DefaultVersion = 1;
private readonly int _allowedVersion;
public VersionConstraint(int allowedVersion)
{
_allowedVersion = allowedVersion;
}
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
if (routeDirection != HttpRouteDirection.UriResolution)
{
return true;
}
int version = GetVersionFromHeader(request) ?? DefaultVersion;
return (version == _allowedVersion);
}
private int? GetVersionFromHeader(HttpRequestMessage request)
{
System.Net.Http.Headers.HttpHeaderValueCollection<System.Net.Http.Headers.MediaTypeWithQualityHeaderValue> acceptHeader = request.Headers.Accept;
var regularExpression = new Regex(#"application\/vnd\.\.v([0-9]+)",
RegexOptions.IgnoreCase);
foreach (var mime in acceptHeader)
{
Match match = regularExpression.Match(mime.MediaType);
if (match.Success)
{
return Convert.ToInt32(match.Groups[1].Value);
}
}
return null;
}
}
Edit2: I think there is some confusion so I've updated the Put action to match the route config
So my question is: why is the attribute firing even though I haven't decorated the action with it?
It is clear from both the way your question is phrased "when I try to access the PUT endpoint" and the fact that it matches the GET action (and then subsequently runs its constraint) that you have not issued a PUT request to the server. Most browsers are not capable of issuing a PUT request, you need a piece of code or script to do that.
Example
using (var client = new System.Net.WebClient())
{
// The byte array is the data you are posting to the server
client.UploadData(#"http://example.com/time/123", "PUT", new byte[0]);
}
Reference: How to make a HTTP PUT request?
I think its because of your action signature in combination with the default route
In your default route you specify the Id attribute as optional, however in your action you use the parameter days, in this case the framework can't resolve it. you either have to add it as a query string parameter eg:
?days={days}
Or change the signature to accept id as input.
Since it can't resove the action with days in the url it will return a 404
Personally i don't use the default routes and always use Attribute routing to prevent this kinda behavior
So my question is: why is the attribute firing even though I haven't decorated the action with it?
Any controller methods that do not have a route attribute use convention-based routing. That way, you can combine both types of routing in the same project.
Please see this link :
attribute-routing-in-web-api-2
Also as method is not decorated with route attribute, When the Web API framework receives an HTTP request, it tries to match the URI against one of the route templates in the routing table. If no route matches, the client receives a 404 error. That is why you are getting 404
Please see this one as well : Routing in ASP.NET Web API

call to web api with string parameter

I have a web api where I have 2 methods, one without parameter and two with different types of parameter (string and int). When calling the string method it doesnt work...what am I missing here?
public class MyControllerController : ApiController
{
public IHttpActionResult GetInt(int id)
{
return Ok(1);
}
public IHttpActionResult GetString(string test)
{
return Ok("it worked");
}
}
WebApiConfig.cs:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
My Call:
/api/MyController/MyString //Doesnt work
/api/MyController/1 //work
I get following error:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Http.IHttpActionResult GetInt(Int32)' in 'TestAngular.Controllers.MyControllerController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
What am I missing in my request?
Here is my solution:
without changing default route in webapiconfig.cs file
add just a route to your string function:
[Route("Api/MyController/GetString/{test}")]
public IHttpActionResult GetString(string test)
http://localhost:49609/api/MyController/GetString/stringtest
Also this uri's should work:
api/MyController/GetAll
api/MyController/GetString?param=string
api/MyController/GetInt?param=1
I think this is much clearer and should always work.
You use the routing behavior.
See here:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection
You have to change your uri's
/api/MyController
/api/MyController/string
/api/MyController/1
You don't have to specify the methods.
You could take look at this tutorial on asp.net for further clarification.
It's been a while since you posted this, but I think I have the answer.
First off, there are two issues. First, as Pinback noted, you can't use the same route for two different endpoints.
However if you just eliminate the int method, you'll still run into the problem.
Remember: the default route looks like this: api/{controller}/{id}
In order to bind the parameter, it has to be called "id", and not "test".
Change the signature to this:
public IHttpActionResult GetString(string id)
and it will work.
(you can also change {id} to {test} in the webapiconfig.cs file).
You can also take string parameter in body
string body;
using (var sr = new StreamReader(Request.Body))
body = sr.ReadToEnd();

Can anyone explain CreatedAtRoute() to me?

From the template for Web API 2, a post method is always like this:
[ResponseType(typeof(MyDTO))]
public IHttpActionResult PostmyObject(MyDTO myObject)
{
...
return CreatedAtRoute("DefaultApi", new { id = myObject.Id }, myObject);
}
I don't understand this CreatedAtRoute() method. Can anyone explain it to me?
The CreatedAtRoute method is intended to return a URI to the newly created resource when you invoke a POST method to store some new object.
So if you POST an order item for instance, you might return a route like 'api/order/11' (11 being the id of the order obviously).
BTW I agree that the MSDN article is of no use in understanding this. The route you actually return will naturally depend on your routing setup.
When you use CreatedAtRoute, the first argument is the route name of the GET to the resource. The trick that is not so obvious is that, even with the correct method name specified, you must thus use the Name param on the HttpGet attribute for it to work.
So if the return in your POST is this:
return CreatedAtRoute("Get", routeValues: new { id = model.Id }, value: model);
Then your Get method attribute should look like this even if your method is named Get:
[HttpGet("{id}", Name = "Get")]
Calls to your Post method will not only return the new object (normally as JSON), it will set the Location header on the response to the URI that would get that resource.
NOTE the field names in the routeValues field names need to match the binding names in the target route, i.e. there needs to be a field named id to match the {id} in HttpGet("{id}"
Finally, in some cases, it should be mentioned that the CreatedAtAction helper can be a more direct solution.
In .net core WebAPI, you use this method to return a 201 code, which means that the object was created.
[Microsoft.AspNetCore.Mvc.NonAction]
public virtual Microsoft.AspNetCore.Mvc.CreatedAtRouteResult CreatedAtRoute (string routeName, object routeValues, object content);
As you can see above, the CreatedAtRoute can receive 3 parameters:
routeName
Is the name that you must put on the method that will be the URI that would get that resource after created.
routeValues
It's the object containing the values that will be passed to the GET method at the named route. It will be used to return the created object
content
It's the object that was created.
The above example shows the implementation of two methods of a simple controller with a simple GET method with the bonded name and the POST method that creates a new object.
[Route("api/[controller]")]
[ApiController]
public class CompanyController : Controller
{
private ICompanyRepository _companyRepository;
public CompanyController(ICompanyRepository companyRepository)
{
_companyRepository = companyRepository;
}
[HttpGet("{id}", Name="GetCompany")]
public IActionResult GetById(int id)
{
Company company = _companyRepository.Find(id);
if (company == null)
{
return NotFound();
}
return new ObjectResult(company);
}
[HttpPost]
public IActionResult Create([FromBody] Company company)
{
if (company == null)
{
return BadRequest();
}
_companyRepository.Add(company);
return CreatedAtRoute(
"GetCompany",
new { id = company.CompanyID },
company);
}
}
IMPORTANT
Notice that the first parameter at CreatedAtRoute (routeName), must be the same at the definition of the Name at the Get method.
The object on the second parameter will need to have the necessary fields that you use to retrieve the resource on the Get method, you can say that it's a subset of the object created itself
The last parameter is the company object received in the body request in it's full form.
FINALY
As final result, when the Post to create a new company got made to this API, you will you return a route like 'api/company/{id}' that will return to you the newly created resource

Query string not working while using attribute routing

I'm using System.Web.Http.RouteAttribute and System.Web.Http.RoutePrefixAttribute to enable cleaner URLs for my Web API 2 application. For most of my requests, I can use routing (eg. Controller/param1/param2) or I can use query strings (eg. Controller?param1=bob&param2=mary).
Unfortunately, with one of my Controllers (and only one), this fails. Here is my Controller:
[RoutePrefix("1/Names")]
public class NamesController : ApiController
{
[HttpGet]
[Route("{name}/{sport}/{drink}")]
public List<int> Get(string name, string sport, string drink)
{
// Code removed...
}
[HttpGet]
[Route("{name}/{drink}")]
public List<int> Get(string name, string drink)
{
// Code removed...
}
}
When I make a request to either using routing, both work fine. However, if I use a query string, it fails, telling me that that path does not exist.
I have tried adding the following to my WebApiConfig.cs class' Register(HttpConfiguration config) function (before and after the Default route), but it did nothing:
config.Routes.MapHttpRoute(
name: "NameRoute",
routeTemplate: "{verId}/Names/{name}/{sport}/{drink}",
defaults: new { name = RouteParameter.Optional, sport = RouteParameter.Optional, drink = RouteParameter.Optional },
constraints: new { verId = #"\d+" });
So for clarity, I would like to be able to do both this:
localhost:12345/1/Names/Ted/rugby/coke
localhost:12345/1/Names/Ted/coke
and,
localhost:12345/1/Names?name=Ted&sport=rugby&drink=coke
localhost:12345/1/Names?name=Ted&drink=coke
but sadly the query string versions don't work! :(
Updated
I've removed the second Action altogether and now trying to use just a singular Action with optional parameters. I've changed my route attribute to [Route("{name}/{drink}/{sport?}")] as Tony suggested to make sport nullable, but this now prevents localhost:12345/1/Names/Ted/coke from being a valid route for some reason. Query strings are behaving the same way as before.
Update 2
I now have a singular action in my controller:
[RoutePrefix("1/Names")]
public class NamesController : ApiController
{
[HttpGet]
[Route("{name}/{drink}/{sport?}")]
public List<int> Get(string name, string drink, string sport = "")
{
// Code removed...
}
}
but still, using query strings does not find a suitable path, while using the routing method does.
I was facing the same issue of 'How to include search parameters as a query string?', while I was trying to build a web api for my current project. After googling, the following is working fine for me:
Api controller action:
[HttpGet, Route("search/{categoryid=categoryid}/{ordercode=ordercode}")]
public Task<IHttpActionResult> GetProducts(string categoryId, string orderCode)
{
}
The url I tried through postman:
http://localhost/PD/search?categoryid=all-products&ordercode=star-1932
http://localhost/PD is my hosted api
After much painstaking fiddling and Googling, I've come up with a 'fix'. I don't know if this is ideal/best practice/plain old wrong, but it solves my issue.
All I did was add [Route("")] in addition to the route attributes I was already using. This basically allows Web API 2 routing to allow query strings, as this is now a valid Route.
An example would now be:
[HttpGet]
[Route("")]
[Route("{name}/{drink}/{sport?}")]
public List<int> Get(string name, string drink, string sport = "")
{
// Code removed...
}
This makes both localhost:12345/1/Names/Ted/coke and localhost:12345/1/Names?name=Ted&drink=coke valid.
With the Attribute routing you need to specify default values so they would be optional.
[Route("{name}/{sport=Football}/{drink=Coke}")]
Assigning a value will allow it to be optional so you do not have to include it and it will pass the value to specify.
I have not tested the query string for this but it should work the same.
I just re-read the question and I see that you have 2 Get verbs with the same path, I believe this would cause conflict as routing would not know which one to utilize, perhaps using the optional params will help. You can also specify one can be null and do checking in the method as to how to proceed.
[Route("{name}/{sport?}/{drink?}")]
Then check the variables in the method to see if they are null and handle as needed.
Hope this helps, some? lol
If not perhaps this site will, it has more details about attribute routing.
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
Clip from that site:
Optional parameters and default values You can specify that a
parameter is optional by adding a question mark to the parameter, that
is:
[Route("countries/{name?}")]
public Country GetCountry(string name = "USA") { }
Currently, a default value must be specified on the optional parameter
for action selection to succeed, but we can investigate lifting that
restriction. (Please let us know if this is important.)
Default values can be specified in a similar way:
[Route("countries/{name=USA}")]
public Country GetCountry(string name) { }
The optional parameter '?' and the default values must appear after
inline constraints in the parameter definition.
Just a side note from my part as well. In order for queryString params to work, you need to provide a default value for your method parameters to make it optional. Just as you would also do when normally invoking a C# method.
[RoutePrefix("api/v1/profile")]
public class ProfileController : ApiController
{
...
[HttpGet]
[Route("{profileUid}")]
public IHttpActionResult GetProfile(string profileUid, long? someOtherId)
{
// ...
}
...
}
This allows me to call the endpoint like this:
/api/v1/profile/someUid
/api/v1/profile/someUid?someOtherId=123
Using Route("search/{categoryid=categoryid}/{ordercode=ordercode}") will enable you to use both Querystrings and inline route parameters as answered by mosharaf hossain. Writing this answer as this should be top answer and best way. Using Route("") will cause problems if you have multiple Gets/Puts/Posts/Deletes.
Here's a slight deviant of #bhargav kishore mummadireddy's answer, but an important deviation. His answer will default the querystring values to an actual non-empty value. This answer will default them to empty.
It allows you to call the controller through path routing, or using the querystring. Essentially, it sets the default value of the querystring to empty, meaning it will always be routed.
This was important to me, because I want to return 400 (Bad Request) if a querystring is not specified, rather than having ASP.NET return the "could not locate this method on this controller" error.
[RoutePrefix("api/AppUsageReporting")]
public class AppUsageReportingController : ApiController
{
[HttpGet]
// Specify default routing parameters if the parameters aren't specified
[Route("UsageAggregationDaily/{userId=}/{startDate=}/{endDate=}")]
public async Task<HttpResponseMessage> UsageAggregationDaily(string userId, DateTime? startDate, DateTime? endDate)
{
if (String.IsNullOrEmpty(userId))
{
return Request.CreateResponse(HttpStatusCode.BadRequest, $"{nameof(userId)} was not specified.");
}
if (!startDate.HasValue)
{
return Request.CreateResponse(HttpStatusCode.BadRequest, $"{nameof(startDate)} was not specified.");
}
if (!endDate.HasValue)
{
return Request.CreateResponse(HttpStatusCode.BadRequest, $"{nameof(endDate)} was not specified.");
}
}
}
I use FromUri attribute as solution
[Route("UsageAggregationDaily")]
public async Task<HttpResponseMessage> UsageAggregationDaily([FromUri] string userId = null, [FromUri] DateTime? startDate = null, [FromUri] DateTime? endDate = null)
Since you have [Route("{name}/{drink}/{sport?}")] as attribute routing, this code will never be hit.
config.Routes.MapHttpRoute(
name: "NameRoute",
routeTemplate: "{verId}/Names/{name}/{sport}/{drink}",
defaults: new { name = RouteParameter.Optional, sport = RouteParameter.Optional, drink = RouteParameter.Optional },
constraints: new { verId = #"\d+" });
So only the attribute route [Route("{name}/{drink}/{sport?}")] is going to be honored here. Since your request localhost:12345/1/Names?name=Ted&sport=rugby&drink=coke, doesn't have name, sport or drink in the URL it is not going to match this attribute route. We do not consider the query string parameters when matching the routes.
To solve this, you need to make all 3 optional in your attribute route. Then it will match the request.

Categories