Within my project I have 2 versions of an API. From this Post I understand that a custom control selector needs writing so it get get the different versions (as webapi looks for controller name match and ignores the namespaces they are in).
This all works OK and I can make the calls to the different versions.
However, I also utilise the new data attribute routing from web api 2 and when using this the customer control selector does not handle the routing correctly.
The routing data attributes I am using are as follows.
(The V01 differs between the versions so it can be V02)
[Route("api/v01/enumeration/{type}/{subtype}")]
This can contain some additional query string params at the end as well
[Route("api/V01/user/readbyreference")]
this takes a query string of ?id=EMAIL|email.domain.com
The code for the customer control selector can be found here
I can see the issue is with GetRouteVariable to get the needed namespace part and controller, but I was wondering if anyone else has had to do something like this and if they have any way around it.
I will be looking into so if I find something I will update on here but if you have anything please let me know.
Thanks
Mark
After a bit of digging I have found out that attribute routing goes via a different path.
So to handle attribute routing in the GetRouteVariable you need to grab the MS_SubRoutes values and then perform the needed action on the result to get the namespace and controller.
The below needs tidying up but it at least gives you the idea of what is done to process data attribute routing in your custom control selector
var subroutes = (IEnumerable<IHttpRouteData>)routeData.Values["MS_SubRoutes"];
var routeBreakDown= subroutes.First().Route.RouteTemplate.Split('/');
if (name == "namespace")
{
return (T)(object)routeBreakDown[1]; //namespace
}
else if (name == "controller")
{
return (T)(object)routeBreakDown[2]; //controller
}
Cheers
Mark
Related
Using ASP.NET Core 3 Razor Pages, suppose you would like to set up pages with the following URL structure:
http://contoso.com/course/4231/details
http://contoso.com/course/4231/students
where 4231 is the course ID. This can be achieved by using the #page directive:
#page "/course/{courseId}/details"
Now suppose you want to create links between the /details and the /students pages. You then want the courseId parameter to be added to every link, which can be accomplished using the anchor tag helper as follows:
<a asp-page="./students" asp-route-courseid="#RouteData.Values["courseId"]>Students</a>
As far as I've understood it, in ASP.NET Core 2.1 and earlier versions it was not necessary to add the asp-route-* attribute for this purpose, because route values would 'propagate' to other pages, being added automatically to any anchor tag unless manually overridden ("ambient route values", as it was called). This was apparently removed in version 2.2, but I'm not quite sure why.
Having to always remember to manually propagate route values using the above scheme seems like a very error-prone workflow, and could get out of hand quick if you have many route values that always needs to be added to anchor tags.
Is it possible to manually enable ambient route values in later versions of ASP.NET Core, at the very least for individual parameters? Is there any reason not to do this?
Essentially, I'd like for the anchor tag helpers to be relative, so that just linking to ./students from /course/4231/details automatically resolves to /course/4231/details.
Since I managed to find a solution for this, I'm going to answer my own question.
As mentioned in this GitHub issue (credit to Oliver Weichhold), you can override the default anchor tag helper (and form action tag helper) to achieve the desired behaviour of having route values automatically added to links.
I ended up implementing derived versions of the tag helpers that will automatically add route values if they are part of the route template, because that's the behaviour I was looking for. To keep it short, the Process method of the tag helpers looks like this:
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var routeTemplate = ViewContext.ActionDescriptor.AttributeRouteInfo.Template;
Regex regex = new Regex(#"\{(\w+)(:.+)?\??\}");
var matches = regex.Matches(routeTemplate);
if (matches.Count > 0)
foreach (var routeValueKvp in ViewContext.HttpContext.Request.RouteValues)
if (matches.Any(x => x.Groups[1].Value == routeValueKvp.Key))
RouteValues[routeValueKvp.Key] = routeValueKvp.Value.ToString();
base.Process(context, output);
}
This solution works, but it's not optimal. One of the reasons is that the custom tag helpers needs to be decorated with the HtmlTargetElement attributes manually, which means that if the base versions of the tag helpers gets extended with new attributes in future releases of the framework, the custom versions will need to be updated manually.
IMO, it is expected. When you redirect to the same action/page, the current ambient route values are being reused.
<a asp-page="details">Studentdetails</a>
It generates url: /course/4231/details.
When you redirect to a different action/page, the ambient values are correctly ignored.
<a asp-page="./students">Students</a>
it will not generate correct url.
Refer to https://github.com/dotnet/aspnetcore/issues/3746
I've been trying to figure this out for about a week now. It's time to ask S.O.
I have 4 overall goals here:
The controller code needs to use ViewModel request inputs for validation reasons. (Controller Snippet)
Client code for my API should use a nice model syntax. (Client Code Snippet)
For the swagger UI page, I would like the "Try me" interface to be usable. Either a bunch of text boxes, or a text area for a json blob to serialize and send over.
GET request
Client Code Snippet:
var response = client.GetUserProductHistory(new Models.UserProductHistoryRequest() {
Locale = "en-US",
UserPuid = "FooBar"
});
Controller Snippet
[HttpGet]
[HasPermission(Permissions.CanViewUserProductHistory)]
public JsonPayload<UserProductHistoryResponse> GetUserProductHistory([FromUri]UserProductHistoryRequest model)
{
JsonPayload<UserProductHistoryResponse> output = new JsonPayload<UserProductHistoryResponse>();
return output;
}
I have tried using [FromBody]. It looks great, but I get an error that says 'GET requests do not support FromBody'.
I tried using [FromUri], but then the generated client gives me like 15 method parameters per call in the generated client.
I tried using [FromUri], and operation filters so that the parameters would be condensed into Ref parameters (complex objects as defined by the spec). This actually worked decently for the client generation and the server side. Problem is, the UI for swagger looks really lame. A single TEXT box that you can't actually use very well. If I can figure out how to get the Swagger UI to change the appearance of the [FromUri] request to more closely match the [FromBody] UI, I will be in good shape here. Any ideas or pre-existing content that would point me in the right direction here?
Swagger is not the limitation - REST itself is. By definition of REST, web servers should ignore the incoming request body on all HTTP GET methods. ASP.NET enforces this convention, which is why you it doesn't allow you to use [FromBody] on the GET method.
When designing a REST API, the better practice is to use POST methods for an actual search. This will allow to use [FromBody], and as a bonus, Swagger will behave the way you want it to. See here for a supporting opinion: https://stackoverflow.com/a/18933902/66101
I have a Web API project that uses a default route of "api/{controller}/{id}" where "id" is optional. In most cases this is sufficient but in some cases I need attribute routing. In this specific case, I have "api/Customers/{customerID}/Orders/{orderID}" where customer ID is required and orderID is optional. Originally my code only required the Order ID but I needed a way to pull the orders for a particular customer so I used an attribute route on my methods to allow this.
I am able to perform GET operations without problem, but when I try to do a POST operation, I get a 500 error. What's odd though is my object gets created so the exception that gets thrown must be coming after the database insert is created but I cannot confirm this since my debugger doesn't work. My API is in a separate project from my UI and for whatever reason I cannot get my debugger to work in the API project so the breakpoints I have set don't work.
The last line of code in my POST method is this:
return CreatedAtRoute("DefaultApi", new { id = order.ID }, order);
The first argument of this method is the route name and the one listed above is for the default route specified in WebApiConfig.cs. This particular route however is different from the default:
[Route("api/Customers/{customerID:int}/Orders")]
Could this be the problem? Since the route in question uses two arguments, I would assume that I'd need to specify them in the routeValues (second) argument to the CreatedAtRoute method.
What do I need to do to make this work? I suspect I may have problems performing PUT and DELETE operations as well, but I need to create an object before I can modify or delete it.
Okay, I solved this myself. I need to set the Name attribute on the route and use that as the first argument for the CreatedAtRouteMethod. I just needed to specify the two route values corresponding to the two method arguments. I was also able to perform PUT and DELETE operations without problems.
[Route("api/Customers/{customerID:int}/Orders", Name = "Test")]
return CreatedAtRoute("Test", new { customerID = customer.customerID, orderID = order.ID }, order);
I am currently on the page /Customer/Edit/13244.
When I use #Url.Action("Edit", "Customer") on the same page it returns me /Customer/Edit/13244, but I want it to return /Customer/Edit/.
Kindly tell me how to fix this issue.
This is a "feature" of MVC that many people find unnatural and was previously reported as a bug.
Microsoft's official response:
Ultimately if you want the most control over what gets generated for a URL there are a few options to consider:
Use named routes to ensure that only the route you want will get used to generate the URL (this is often a good practice, though it won't help in this particular scenario)
Specify all route parameters explicitly - even the values that you want to be empty. That is one way to solve this particular problem.
Instead of using Routing to generate the URLs, you can use Razor's ~/ syntax or call Url.Content("~/someurl") to ensure that no extra (or unexpected) processing will happen to the URL you're trying to generate.
Actually, this bug only rears its ugly head when you try to re-purpose an action method name. If you use a different action method name other than Edit in the case where it is not followed by id, this problem will magically disappear.
You will need to use (assuming your using the default route with id = UrlParameter.Optional
#Url.Action("Edit", "Customer", new { id = "" })
I was wondering whether there is a way to create an ActionLink or similar, that changes only a few parameters of the actual query, and keeps all the other parameters intact. For example if I'm on an URL like http://example.com/Posts/Index?Page=5&OrderBy=Name&OrderDesc=True I want to change only the Page, or OrderBy parameter and keep all other parameters the same, even those I don't yet know of (like when I want to add a Search parameter or something similar too).
The header of my current action looks like this:
public ActionResult Index(int? Page, string OrderBy, bool? Desc)
and I'm only interested in the values that this controller "eats". I want however that when I extend this action (for example with a string Search parameter) the links should work the same way as before.
Here is what I did already:
Create a new RouteValueDictionary and fill it with everything from RouteData.Values
Problem: This only fills the parameters that are used in the Routing, so all other optional parameters (like Page) to the controller are lost
Add everything from HttpContext.Request.QueryString to the previous dictionary
This is what I am currently using
Problem: It might have some junk stuff, that the Controller didn`t ask for, and it doesn't work if the page was loaded using POST. You also don't have any ModelBindings (but this isn't much of a problem, because we are re-sending everything anyway)
Use HttpContext.Request.Params
Problem: this has too much junk data which imho one shouldn't add to a RouteValueDictionary that is passed to an ActionLink
So the questions:
Is there an RVD that has all the data that was passed to the Controller and was used by it?
Is this solution good, or are there any caveats I didn't think about (mainly in the context of changing a few query parameters while keeping the others intact)?
Is there a way to filter out the "junk" data from the Params object?
EDIT: Checked the RouteData.DataTokens variable, but it's usually empty, and doesn't contain everything I need. It seems to only contain parameters that are needed for the routing somewhere, but not all of the parameters.
Have a look in RouteData.DataTokens.
RouteData.DataTokens # MSDN documentation:
Gets a collection of custom values that are passed to the route handler but are not used when ASP.NET routing determines whether the route matches a request.
HTHs,
Charles