Attribute routing with optional parameters in ASP.NET Web API - c#

I'm trying to use Web API 2 attribute routing to set up a custom API. I've got my route working such that my function gets called, but for some reason I need to pass in my first parameter for everything to work properly. The following are the URLs I want to support:
http://mysite/api/servicename/parameter1
http://mysite/api/servicename/parameter1?parameter2=value2
http://mysite/api/servicename/parameter1?parameter2=value2&parameter3=value3
http://mysite/api/servicename/parameter1?parameter2=value2&parameter3=value3&p4=v4
The last 3 URLs work but the first one says "No action was found on the controller 'controller name' that matches the request."
My controller looks like this:
public class MyServiceController : ApiController
{
[Route("api/servicename/{parameter1}")]
[HttpGet]
public async Task<ReturnType> Get(string parameter1, DateTime? parameter2, string parameter3 = "", string p4 = "")
{
// process
}
}

Web API requires to explicitly set optional values even for nullable types...so you can try setting the following and you should see your 1st request succeed
DateTime? parameter2 = null

Related

Asp Net core Controller URL parameters

I have Controller class like this:
namespace OptionsAPI.Controllers
{
[Route("api/[controller]")]
public class OptionsController : Controller
{
HttpGet("{symbol}/{date}")]
public IActionResult Chain(string symbol, string date)
{
DateTime quotedate = System.DateTime.Parse(date);
}
}
}
When I try to call the chain function through the URL like this:
http://127.0.0.1:5000/api/options/Chain/symbol=SPX&date=2019-01-03T10:00:00
I get this error:
FormatException: The string 'symbol=SPX&date=2019-01-03T10:00:00' was not recognized as a valid DateTime. There is an unknown word starting at index '0
It seems like "SPX" and the "date" are being concatenated as one string. What is the correct way to call this URL?
The given route template on the action
[HttpGet("{symbol}/{date}")]
along with the template on the controller
[Route("api/[controller]")]
expects
http://127.0.0.1:5000/api/options/SPX/2019-01-03T10:00:00
But the called URI
http://127.0.0.1:5000/api/options/Chain/symbol=SPX&date=2019-01-03T10:00:00
maps the Chain in the URL to symbol and the rest to date, which will fail when parsed.
To get the desired URI, the template would need to look something like
[Route("api/[controller]")]
public class OptionsController : Controller {
//GET api/options/chain?symbol=SPX&date=2019-01-03T10:00:00
[HttpGet("Chain")]
public IActionResult Chain(string symbol, string date) {
//...
}
}
Reference Routing to controller actions in ASP.NET Core
Reference Routing in ASP.NET Core

AmbiguousMatchException in ActionMethodSelectorAttribute when two or more requests are submitted at the same time

I have multiple actions methods in a Controller that share the same ActionName, like this:
[ActionName("Example")]
[DefinedParameter(Name = "xID")]
[HttpPost()]
public ActionResult ExampleX(Guid xID)
{
....
}
[ActionName("Example")]
[DefinedParameter(Name = "yID")]
[HttpPost()]
public ActionResult ExampleY(Guid yID)
{
....
}
In addition to this I use a action selector via the DefinedParameter attribute, like this:
public class DefinedParameterAttribute : ActionMethodSelectorAttribute
{
public string Name { get; set; }
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
HttpRequestBase request = controllerContext.RequestContext.HttpContext.Request;
return request[this.Name] != null;
}
}
In most cases this works perfectly.
In some cases however I get the following error:
"AmbiguousMatchException
Exception message: The current request for action 'Example' on controller type 'ExampleController' is ambiguous between the following action methods: ...."
I get this error when:
I submit a request (that takes some time) with example parameter xID.
While the request from step 1 is still processing on the server (no response returned yet), I submit a second request (example parameter yID).
I discovered that in those cases the request indeed contains both parameters; in my example xID and yID. That explains the error.
So it seems that the server somehow reuses the same request and adds the parameter from the second submit.
NB: Please know that I mostly use action methods with a unique name. That's the best way to go. But in some cases the described example can be a better approach.
How can I solve and/or avoid this error?
MVC doesn't support method overloading based solely on signature, so this will fail:
Same question posted here
Check this blog for same example

webapi2 Binding not working

I'm new to webapi 2 and trying to get my api to bind to this call below.
Can anyone see what I'm doing wrong?
Call:
https://blabla.azurewebsites.net/myjohndeere/authenticated/e33dd8f74c97474d86c75b00dd28ddf8?oauth_token=1539dccf-d935-4d9e-83be-e00f76cabbb9&oauth_verifier=B22dWL
[RoutePrefix("myjohndeere")]
public class ApiMyJohnDeereController : ApplicationController
{
[HttpGet, Route("authenticated/{callbackId}")]
[SwaggerResponse(HttpStatusCode.OK, Type = typeof(ApiResponseModel))]
[SwaggerResponse(HttpStatusCode.InternalServerError, "An unknown error occurred")]
[SwaggerResponse(HttpStatusCode.BadRequest, "Missing FieldMappings")]
public IHttpActionResult Authenticated(string callbackId,[FromUri]string oauth_token, [FromUri]string oauth_verifier)
{
...
}
First of, you missed the 'api' in the route.
Try it like this
https://blabla.azurewebsites.net/api/myjohndeere/authenticated/e33dd8f74c97474d86c75b00dd28ddf8?oauth_token=1539dccf-d935-4d9e-83be-e00f76cabbb9&oauth_verifier=B22dWL
Then, remove the FromUri attribute from your controller method. The attribute is not needed when just reading querystring of value types.
Try it like this
public IHttpActionResult Authenticated(string callbackId, string oauth_token, string oauth_verifier)
The issue was the Azure API gateway was still set to use PUT rather than GET.

"GetBy" Methods in Web API

We are using .NET Core to build a Web API. We need to support "GetBy" functionality, e.g. GetByName, GetByType, etc. but the issue we are running into is how to depict this through routes in a Restful way as well as the method overloading not properly working with how we think the routes should be. We are using MongoDB so our IDs are strings.
I'm assuming our routes should be something like this:
/api/templates?id=1
/api/templates?name=ScienceProject
/api/templates?type=Project
and the issue is that all our methods in our controller have a single string parameter and isn't properly mapped. Should the routes me different or is there a way to properly map these routes to the proper method?
If the parameters are mutually exclusive, i.e. you only search by name or type but not by name and type, then you can have the parameter be a part of the path instead of the query-params.
Example
[Route("templates")]
public class TemplatesController : Controller
{
[HttpGet("byname/{name}")]
public IActionResult GetByName(string name)
{
return Ok("ByName");
}
[HttpGet("bytype/{type}")]
public IActionResult GetByType(string type)
{
return Ok("ByType");
}
}
This example would lead to routes like:
/api/templates/byname/ScienceProject
/api/templates/bytype/Project
If there parameters are not mutually eclusive then you should do it like suggested in the answer by Fabian H.
You can make a TemplatesController with a single get method, that can take all the arguments.
[Route("api/templates")]
public class TemplatesController : Controller
{
[HttpGet]
public IActionResult Get(int? id = null, string name = null, string type = null)
{
// now handle you db stuff, you can check if your id, name, type is null and handle the query accordingly
return Ok(queryResult);
}
}

Web API 2 Attribute Routing Controller Selection

I use Web API 2 Attribute Routing in my project to provide JSON interface over my data. I am facing weird behaviour of controller selection, not decided yet whether it's a bug or a feature :)
Let me describe my approach.
I would like to simulate OData syntax with help of attribute routing (direct OData usage has been refused due to design principles). For example, to get entity with id=5 I use HTTP GET request to URI http://mydomain.com/api/Entity(5) . I expect to use the same URI with HTTP PUT verb to update the entity. This is where the journey begins...
I would like to have separate controller for getting entities (FirstController in the example provided below) and another one for modifying entities (SecondController). Both controllers handles the same URI (e.g. http://mydomain.com/api/Entity(5)) the only difference is HTTP verb used with the URI - GET should be handled by FirstController, PUT should be handled by SecondController. But the URI is handled by none of them; instead HTTP 404 error is returned.
When I "merge" GET and PUT actions to only one controller (commented out in FirstController), both verbs are handled correctly.
I am using IIS Express and all conventional routes are disabled, only attribute routing is in charge.
It looks like the controller selection process does not work with HTTP verb. In another words, HttpGet and HttpPut attributes just limit action usage but they do not serve as criteria during controller selection. I am not so familiar with MVC / Web API fundamentals, so let me ask you my big question:
Is the behaviour, described herein before, a feature intentionally implemented by MVC / Web API 2 or a bug to be fixed?
If it is considered as a feature, it prevents me to follow design principles. I can live with "merged" controllers but still considering it as a bad practice...
Or am I missing something in my train of thought?
My environment setup:
Windows 7 (virtual machine using Oracle VirtualBox)
Visual Studio 2013
.NET 4.5.1
Web API 2
The following is implementation of FirstController class:
public class FirstController : ApiController
{
[HttpGet]
[Route("api/Entity({id:int})")]
public Output GetEntity(int id)
{
Output output = new Output() { Id = id, Name = "foo" };
return output;
}
//[HttpPut]
//[Route("api/Entity({id:int})")]
//public Output UpdateEntity(int id, UpdateEntity command)
//{
// Output output = new Output() { Id = id, Name = command.Name };
// return output;
//}
}
The following is implementation of SecondController class:
public class SecondController : ApiController
{
[HttpPut]
[Route("api/Entity({id:int})")]
public Output UpdateEntity(int id, UpdateEntity command)
{
Output output = new Output() { Id = id, Name = command.Name };
return output;
}
}
The following is implementation of a console application to test the described behaviour:
class Program
{
static void Main(string[] args)
{
// HTTP client initialization
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("http://localhost:1567");
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HTTP GET - FirstController.GetEntity
HttpResponseMessage getEntityResponse = httpClient.GetAsync("/api/Entity(5)").Result;
Output getOutput = getEntityResponse.Content.ReadAsAsync<Output>().Result;
// HTTP PUT - SecondController.UpdateEntity
UpdateEntity updateCommand = new UpdateEntity() { Name = "newEntityname" };
HttpResponseMessage updateEntityResponse = httpClient.PutAsJsonAsync("/api/Entity(10)", updateCommand).Result;
Output updateOutput = updateEntityResponse.Content.ReadAsAsync<Output>().Result;
}
}
For completion, the following are used DTOs:
public class UpdateEntity
{
public string Name { get; set; }
}
public class Output
{
public int Id { get; set; }
public string Name { get; set; }
}
Thanks in advance for your responses,
Jan Kacina
This design was intentional as we thought it to be an error case where a user would be having same route template on different controllers which can cause ambiguity in the selection process.
Also if we keep aside attribute routing, how would this work with regular routing? Let's imagine we have 2 regular routes where first one is targeted for FirstController and the second to SecondController. Now if a request url is like api/Entity(5), then Web API would always match the 1st route in the route table which would always hit the FirstController and would never reach SecondController. Remember that once Web API matches a route it tries to go till the action selection process and if the action selection process doesn't result in an action being selected, then an error response is sent to the client. You probably are assuming that if an action is not selected in one controller then Web API would route it to the next one in the route configuration. This is incorrect.
Route probing occurs only once and if it results in a match, then the next steps take place...that is controller and action selection. Hope this helps.

Categories

Resources