HttpGetAttribute Order is ignored - c#

I have two methods in a .net6 web API that I want to expose that have similar routes.
This should not be a problem since one has the keyword ping to identify the route.
But from the looks of it and also from the documentation, the route stops evaluating routes as soon as it finds a match.
As the code runs by default it picks the GetDays method instead of Ping. Most likely because it is found first.
This should be easy to resolve by adding an order to the route.
By default, the route should get order 0 so by setting -1 the Ping route should be evaluated first. And if it does not match try next.
But it does not work. It seems that the Order attribute is ignored and it still picks the GetDays method.
[HttpGet("[controller]/ping", Order = -1)]
public async Task<IActionResult> Ping([FromQuery] string val)
[HttpGet("{from}/{to}")]
public async Task<ActionResult> GetDays(DateTime from, DateTime to)
Anyone have any tips on why the Order attribute is ignored and how to make this work?

Related

FromHeader attribute with multiple names

I want to use the FromHeader attribute with multiple names (e.g. for multi-language support) to the same model.
public async Task<IActionResult> Create([FromHeader(Name="Test-NLD"),FromHeader(Name="Test-ENG")] string Test)
You can not use multiple FromHeader attributes with another name to the same model (error CS0579, Duplicate 'FromHeader' attribute).
What is the best way to resolve this? I use Swagger, so it would be nice if the Swagger documentation is still correct.
In the short term, you'll need to make two different parameters on your action, one binding to each possible Header key.
public async Task<IActionResult> Create(
[FromHeader(Name="Test-NLD")] string testNld,
[FromHeader(Name="Test-ENG")] string testEng)
FromHeader doesn't accept multiple entries on a single parameter, or wildcards.
For now, if you're only going to support 2 languages, you might be able to squeak by with this solution. It's, understandably, very maintenance intensive and the definition of "Technical Debt".
ASP.Net and Swagger both work their best when the Header Keys are static. It's not possible to document "You need supply a header called Test-*, where * is 'NLD' or 'ENG'."
In the future, I still very much recommend that you split the choice of language into its own key. You could even use the "standard" Accept-Language header.
public async Task<IActionResult> Create(
[FromHeader(Name="Accept-Language")] string clientLanguages,
[FromHeader(Name="Test")] string test)
Accept-Languages is written so that it's expected the client gives you a CSV of languages they want, in priority order. You select the first one that you support, and inform the client of the selection with the Content-Language response header.
There are also more elegant methods to get the Accept-Language header, see this post.

Is it possible to change Request IP/Request Context in Action Filter or Module?

My scenario is that our already developed website (which relies a lot on User IP Address Request.UserHostAddress) is now deployed behind CloudFlare proxy. Therefore, now we need to check if there is CloudFlare's original IP header.
I am intending to write a Filter/Module that can run before Action takes place so we do not have to modify the code. I need the Filter/Module to replace the UserHostAddress with the value from the Header if it exist.
However, I do not know if Action Filter can actually modify the Requeset UserHostAddress, nor could I find anything to override the creation of the RequestContext object.
Is it possible to achieve this through Action Filter and/or IIS Module? If there is, what keyword should I be looking for?
The UserHostAddress property in the HttpRequest class is read only, so it can't be changed, but you can add an extension method in the HttpRequest class and make it read by default from the UserHostAddress and if a specific header or route data is present, ex: OriginalUserHostAddress, then return its value.
Inside the action filter, you can check the firewall headers and if it exist, then set a route data variable or temp data variable and read it from the extension method.

Unable to POST to Web API endpoint using attribute routing

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);

Url.Action return the current link in browser

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 = "" })

How to get all active parameters in ASP.NET MVC (2)

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

Categories

Resources