I have an API action defined as the following:
[Route(Name="GetMembersTest"), HttpGet, ResponseType(typeof(MemberHeadersDto))]
public IHttpActionResult GetMembers[FromUri]MemberFilterDto filter, [FromUri]PagingOptionsDto paging)
This method works as expected, routing and all, requests are flowing through just fine. However, I'd like to supply a "NextUri" for paging so that the caller can just keep following NextUri until it is null to get all the results. I have to send back a uri to the same action, 1 page ahead, if that makes sense.
So I tried using UrlHelper.Route. This route is named "GetMembers" for the purpose of this example.
NextUri = Url.Route("GetMembers", new { filter, paging });
The problem is that instead of getting something like
/v1/members?filter.q=&filter.otherproperty=&paging.count=10&paging.startRow=11
I get
/v1/members?filter=WebApi.Models.MemberFilterDto&paging=WebApi.Models.PagingOptionsDto
It looks like UrlHelper.Route doesn't support complex types in the [FromUri] parameter of a GET Request. Is there anything I can do to get this functionality? My workaround right now is to take in all the Dto properties as individual parameters then build my Dtos from them on the server. This isn't ideal because if I ever add any more options I'd have to add more parameters to the action, which also makes the route value dictionary more fragile as well because it has to match with the method signature in UrlHelper.Route(routeName,routeValues).
Unfortunately, there is no way to pass in complex object to routing. Instead, you will need to pass in the simple properties individually.
I was not able to find a way to extend Url.Route, but that would be/have been your best option.
Related
I have a requirement to inspect routes in .Net Core middleware and perform different actions based on different route. For this I have to do route comparisons for below sample routes returned from HttpContext.Request.Path:
/api/employees/2d50f670c72911ec861760be36a6c36a/authorise
/api/employees/9d50f670c72911ec861760be36a6c36a/decline
Because of dynamic ID present in the path, I have to split and join the string using below code:
route = "/api/employees";
var pathSegments = request.Path.Value.Split('/');
if (pathSegments.Length > 3 && Guid.TryParse(pathSegments[3], out Guid employeeId))
{
route += #"/{employeeId}/"+string.Join("/", pathSegments.Skip(4));
}
While this works okay for URL comparison, I wanted to know if this can be done in a simpler way like UriTemplate class provides in WCF?
Instead of the first two lines you wrote which you tried to partition the url, You can use the property Segments of the HttpContext.Url object. It gives you a string array contains all segments.
BUT, I guess you're performing a wrong logic. Why don't you just do the desired action in an actual Action?
I mean the urls you mentioned to compare, surely call an Action(Controller Method), right? If so, why don't you just execute your desired actions in them?
For instance, if the /api/employees/2d50f670c72911ec861760be36a6c36a/authorise request must do a logic before the requested action (and this logic is done for every request), why should we do it in a middleware?
Because as far as I know, the middlewares are used for cross-cutting concerns.
I need to be able to have the same route to get two different kinds of output.
/api/v1/items?ids=1,2,3
should retrieve the list of items containing those three entries, however
/api/v1/items?from=142523&limit=4
should retrieve a cursor paginated response, where from would be the id of the item.
I know that in the past it was possible to create route constraints based on querystring, but that has been removed according to the answer posted here:
Query parameter route constraints
What would be the best way to tackle this? Attribute routing is a no-go, as we don't want to have a items/{list-of-ids} routes in the application. I could merge the methods into one with optional parameters, but then the API output is inconsistent in the automatically generated documentation (paginated vs non-paginated response). Is it possible to achieve what I want using custom route constraints?
You can use Optional Parameter to define your API endpoint like below. Your query string values will automatically get parameter binded if you keep the parameter name same and don't have to define separate route for them
[HttpGet]
public IActionResult items(string ids, int from = 0, int limit = 0)
{
//code here
}
I'm trying to add AttributeRouting to my WebAPI project.
On one controller I currently have three GET methods defined:
[GET("dictionaries")]
public IEnumerable<Dictionary> Get()
[GET("dictionaries/{id}")]
public Dictionary GetByID(int id)
[GET("dictionaries/{dictionaryID}/{page}")]
public Dictionary Browse(long dictionaryID, int page)
The first two routes are working as I expect them to, but the third always returns a 405 METHOD NOT ALLOWED.
I've tried sending the parameters in the URL, and the query-string, and it's the same response for both. When I've tried the query-string version, I've modified the route to be
[GET("dictionaries?dictionaryID={dictionaryID}&page={page}
I've also tried changing the initial word from dictionaries to dictionary to avoid any ambiguity with the other two GET routes, but still no success.
The documentation for AttributeRouting only mentions query-strings in relation to parameter constraints (which aren't available for me because of the WebHost framework) and doesn't mention how query-strings can be used in routing.
Can anyone tell me how I can achieve this third route with those two parameters, or do I have to drop AttributeRouting or try on a controller of its own?
Web API action selector implicitly thinks that the third action here is a POST, since it doesn't start with verbs like GET, POST, PUT etc. Try adding HttpGet attribute also and see if this works.
[HttpGet, GET("dictionaries/{dictionaryID}/{page}")]
public Dictionary Browse(long dictionaryID, int page)
Currently we use the below routing rules to cater for all our controllers; the advantage being that we don't have to define a route for each action in each controller:
routes.MapHttpRoute("3", "{controller}/{action}/{arg1}/{arg2}/{arg3}");
routes.MapHttpRoute("2", "{controller}/{action}/{arg1}/{arg2}");
routes.MapHttpRoute("1", "{controller}/{action}/{arg1}");
routes.MapHttpRoute("0", "{controller}/{action}");
However due to this the parameter names in the methods must match; like so:
// Example method signature
public ResponseDto GetResponse(int arg1, int arg2)
If the parameter names are changed to something more friendly (eg: a name that would show the intention of each parameter instead of an ambiguous "arg1" name) like so:
// Better example method signature
public ResponseDto GetResponse(int userId, int itemId)
The binding would break unless:
The routes and parameter names are explicitly defined
The arguments are passed in using a query string
Is there are way to set up WebApi routing so it will automatically use the correct action based on the number of parameters; rather than the parameter names?
It seems you're trying to do something similar to what is described in this good blog post on custom parameter binding. As you've found out, there is nothing out-of-the-box to support what you're trying to do.
At a conceptual level, HTTP is built around the concept of resources. A URI should identify a specific resource. Using generic positional "parameters" seems to blur the specificity of a resource. It seems that your use case might be better served by using the query string of the URI. That way, your HTTP API expresses a clear intent of what parameters are expected for a given resource.
I am looking at the following tutorial from Microsoft. As per this tutorial,
In the first example, "products" matches the controller named
ProductsController. The request is a GET request, so the framework
looks for a method on ProductsController whose name starts with
"Get...". Furthermore, the URI does not contain the optional {id}
segment, so the framework looks for a method with no parameters. The
ProductsController::GetAllProducts method meets all of these
requirements.
What happens if there are two methods like GetAllProducts() and GetSoldProducts()? Both have no parameters.
Your First Web API Tutorial
There are two possible solutions to this specific problem:
Alter MapHttpRoute calls to require specifying the name of the action. (I'm using Self-hosting syntax):
config.Routes.MapHttpRoute(
"API Route 1",
"api/{controller}/{action}");
config.Routes.MapHttpRoute(
"API Route 2",
"api/{action}",
new { controller = "products" });
So your http client would call:
api/products/GetAllProducts OR api/GetAllProducts
api/products/GetSoldProducts OR api/GetSoldProducts
See:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Place each method in a separate controller (ProductsController, SoldProductsController). So you would call api/products and api/soldproducts to get your results.
Related topic... in the situation where you have a multiple Get actions that have a single primitive argument of the same type, ASP.NET Web API will look at the name of the argument to resolve which overloaded action to call.
For example, if you have two actions:
GetProductByName(string name)
GetProductByCategory(string category)
your http client can call
api/products?name=hammer
api/products?category=toys
and the routing engine will call the correct action.
Assuming you're using the default routes the short answer is : the method defined first (at the top) of your class will be called. the other method is inaccessible.
NOTE : the beta behaved as above for 'matching multiple methods' - the RC & Release version is a bit more OCD. It throws an error if there are multiple potential matches. This change removes the confusion of multiple ambiguous matches. At the same time, it reduces our ability to mix REST and RPC style interfaces in the same controller, relying on the order & overlapping routes.
Stealing liberally from another post I wrote on the topic:
WebAPI Matching Semantic
The matching semantic used by WebAPI is fairly simple.
It matches the name of the action with the verb (verb = get? look for method starting with "get")
if a parameter is passed, the api seeks an action with a parameter
So in your code sample a GET request without a parameter matches the Get*( ) function without an parameters. A Get containing and ID looks for a Get***(int id).
Examples
While the matching semantic is simple, it creates some confusion for MVC developers (well at least this developer). Lets look at some examples :
Odd Names - Your get method can be named anything, so long as it starts with "get". So in the case of a widget controller you can name your functions GetStrawberry() and it will still be matched. Think of the matching as something like : methodname.StartsWith("Get")
Multiple Matching Methods - What happens if you have two Get methods with no parameters? GetStrawberry() and GetOrange(). As best I can tell, the function defined first (top of the file) in your code wins ...strange. This has the side effect of making some methods in your controller unreachable (at least with the default routes)....stranger.
UPDATE
#WinFXGuy - This was a bit long to put in a comment, but ...
Don't jump to conclusions! I tried to answer the question you posed, but that's only half the story. There is plenty you can do to change the default behavior.
First, WebAPI supports much of the oData spec. If you bubble an IQueryable up to your controller, oData paramaters are automatically integrated with the query object. It takes parameters like $filter, $top, and $skip. So you in your case you can write one method and pass something like $filter=sale_date neq null.
Additionally, you can apply the [ResultLimit] attribute to prevent people asking for 15 billion records.
Second you can modify the routes. The default routes aim towards a RESTful api, where you generally have 1 controller per entity. You can change the routes and make it RPC style.
If you look at my linked post I explain how I kept the default route binding, added 'sub folders' and also allowed additional method calls for scenarios where i needed GetAllProducts() and GetSoldProducts().
Adding an answer to reflect that the latest version of Web API supports [Route] attribute natively
[Route("api/products")]
public IEnumerable<Product> GetAllProducts(){}
[Route("api/products/sold")]
public IEnumerable<Product> GetSoldProducts(){}