Replace ID part in relative path with a template string - c#

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.

Related

Differentiate routes by the querystring

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
}

WebApi - UrlHelper.Route to GET with [FromUri] object parameter

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.

webapi 5.2.3 Multiple controller types were found that match the URL - Attribute Routing

I've hit an issue with webapi attribute routing. I am calling the following route: assessment/assessments/assessmenttypes as an HttpPost. I'm getting the error that multiple controller types match the url. The problem is that they definitely don't match. Initially when I first got this issue I was using different route prefixes, however I read that webapi can ignore route prefixes, so I changed all the routes to make them unique, so the only HttpPosts which are in the "matching controllers" are defined as follows:
[Route("assessments"), HttpPost]
public async Task<System.Int32> PostCreate([FromBody]Assessment assessment)
and
[Route("assessments/assessmenttypes"), HttpPost]
public async Task<System.Int32> PostCreate([FromBody]AssessmentType assessmentType)
Both controllers have the same RoutePrefix of: [RoutePrefix("assessment")]
Can anyone help please, this is very frustrating.
Thanks in advance!
What you need to understand first at all is how routing works, that's the key to solve your problem. I can update my answer later if you give me more information (at this moment it looks like you didn't post all the required information)
As you know, routing is the process of matching requests to routes.
The handler that does the magic is the HttpRoutingDispatcher with some help of extra libraries, but basically the dispatcher resolves the request. The final goal of processing the route is to identify two elements:
Route
Values
You have two different ways of resolving routes, convention-based (templates) or by attributes. It's a good idea to be consistent to avoid confusions specially when the application grows.
The routing has two different parts, fixed segments (they must match exactly) and variables (denoted as you know by {})
The request verbs are matched in two different ways, with the attribute
[HttpGet] [HttpPost] [HttpPut]
Or confusingly in my opinion using the first part of the method, if you have 3 different methods not marked with attributes the WebApi assumes the first letters try to indicate the verb:
GetList --> HttpGet of List
GetDetail --> HttpGet of Detail
PostDetail --> HttpPost of Detail
From the official documentation if you try the following template:
routeTemplate: "api/{controller}/{id}"
And you make a request from Ajax like the following:
$.ajax("/api/today/dayofweek", {
The following controller will fail (nearly always)
public class TodayController : ApiController {
[HttpGet]
public string DayOfWeek() {
return DateTime.Now.ToString("dddd");
}
[HttpGet]
public int DayNumber() {
return DateTime.Now.Day;
}
Because it's nearly impossible to establish which one is the correct one (a human being can of course, but unfortunately machines need more programming to work with fuzzy logic) :)
The problem in your case it's not both start with the same fixed route, the problem is that it's not possible to identify whether you're saying the second part belongs to a variable or it's a fixed part (it's ambiguous)
Giving you an example; how could we establish the meaning of this?
/segmenta/segmentb
/segmenta/segmentb/segmentc (is segment c a fixed route or a parameter?)
From an architectural point of view the reality is (based on Api design) that I don't think /assestmenttypes is a subordinated resource of /assestments. If you want to design a RESTful ROA Api assestmenttypes are correctly indicated as a resource if the type depends on the assestment (as a child, not as an attribute).
I would suggest you to review your paths too.

WebAPI Controller Methods that Differ By Value Types

I have an existing API I'm moving over to WebAPI, so I'm not free to change the URL. Breaking existing clients is not an option for me.
Knowing that, the original API would accept either a Guid (an ID) or a string (a name) for a given action method. The old API handler would decipher the URL parameter and send the request to a controller action designed to accept the given parameter type.
As an example:
Get(Guid id)
versus
Get(string name)
With WebAPI, the parameter binding is greedy across value types, so depending on which is first in the controller source file, that action is the one invoked. For my needs, that's not working. I was hoping the binder would realize the conversion to a Guid would fail for a name and then select the more generic string-based action. No dice. The Guid simply comes across as a null value (interestingly since it's a value type, but that's what I'm getting in the debugger at a certain point in the processing).
So my question is how best to handle this? Do I need to go as far as implementing a custom IHttpActionSelector? I tried the attribute routing approach (with constraints), but that isn't working quite right (bummer as it looks cool). Is there a mechanism in WebAPI that accounts for this I don't (yet) know about? (I know I can hack it in by testing the string for Guid-ness and invoking the other controller method, but I'm hoping for a more elegant, WebAPI-based solution...)
I'd spent a lot of time trying to fit attribute-based routing in, but I've not got that to work. However, I did solve my particular issue using route constraints. If you register the more-constrained route first, WebAPI (like MVC) will apply the constraints and skip over more-constrained routes until it finds one that it can select, if any.
So, using my example, I'd set up routes like so:
_config.Routes.MapHttpRoute(name: "ById",
routeTemplate: "product/{id}",
defaults: new { controller = "ProductDetails" },
constraints: new { id = #"^\{?[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}?$" });
_config.Routes.MapHttpRoute(name: "ByName",
routeTemplate: "product/{name}",
defaults: new { controller = "ProductDetails" });
The first route accepts a constraint in the form of a regular expression for a Guid. The second accepts all other values, and the controller will deal with non-product names (returns a 404). I tested this in the self-hosted WebAPI server and it works fantastically.
I am sure attribute-based routing would be more elegant, but until I get that to work, it's routing the old way for me. At least I found a reasonable WebAPI-based solution.

How does ASP.NET Web.api handle two methods with names starting with GET?

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(){}

Categories