Route matching from static Uri + route data extract - asp.net-core

I have a .Net Core 2.2 MVC app with routes defined as route attributes on my MVC actions.
I would like to find the matching route (if any) and what the route data are from a given Uri (i.e. not the current HTTP request but a static Uri coming from a database for instance).
I already use the LinkGenerator.GetPathByAction() method to get the "route URL" for a specific action with route data. What I am after would be the opposite: a method that takes a URL/Uri and return the matching route and its route data.
For instance if I have a route registered with the following template:
[Route("/my-action/{id:int}/{name}")]
the URL "/my-action/5/my-test-name" would return the following route data:
id: 5
name: my-test-name
I went through the routing documentation but I haven't found anything.
https://github.com/aspnet/AspNetCore.Docs/blob/master/aspnetcore/fundamentals/routing.md
The only option that I see would be to somehow call the RouteMiddleware (https://github.com/aspnet/AspNetCore/blob/master/src/Http/Routing/src/RouterMiddleware.cs) with a mock HttpContext which seems overkill if even doable?

I would like to find the matching route (if any) and what the route data are from a given Uri.
If you want to get the route data in the action with the matching route , you could directly use GetRouteData in the current HttpContext object like below :
[Route("/GetRouteData/{id:int}/{name}")]
public void GetRouteData()
{
var routeData = HttpContext.GetRouteData();
var routeCollection = routeData.Values;
var id = routeData?.Values["id"]?.ToString();
var name = routeData?.Values["name"]?.ToString();
}
About finding out if a URL matches an action in ASP.NET MVC Core , you could refer to the following links :
https://joonasw.net/view/find-out-if-url-matches-action
https://weblog.west-wind.com/posts/2019/May/15/Accessing-RouteData-in-an-ASPNET-Core-Controller-Constructor
https://rimdev.io/asp-net-core-routes-middleware/

Related

ASP.NET Core Endpoint - route all calls to specific route first

What do I need to change this to route all requests to /api/ShibAuth?
endpoints.MapGet("/", async context =>
{
context.Response.Redirect("/api/ShibAuth");
});
The code above obviously routes any calls to root URL and I've already tried what I though was appropriate wildcard.
What do I need to change this to route all requests to /api/ShibAuth?
Well, if I correctly understand the requirement, you would like all of your request to redirect to this /api/ShibAuth route at the begining.
Certainly, we can implement above scenario using UriBuilder class which provides the functionality to modify HttpRequest.Path. Finally, rebuild the request URI and redirect to your expected path. You can do as following
Solution:
app.MapGet("/", async context =>
{
var originalUrl = context.Request.GetDisplayUrl();
var routeToCallFirst = "api/ShibAuth";
var updatedUrl = (new UriBuilder(originalUrl) { Host = context.Request.Host.Host, Path = routeToCallFirst }).Uri;
context.Response.Redirect(updatedUrl.AbsoluteUri);
});
Output:
Note: Here, I am redirecting the all landing request to /api/ShibAuth controller from the middleware.

Check if request is made to Razor Page

How can I check within middleware code if current request is made to Razor Page not to any other resource (static file, or API)?
All my APIs are located within api folder, so if (!context.Request.Path.StartsWithSegments("/api")) {} filters out APIs, but that will not work for static content as these files and libraries are placed within number of folders what results in number of URL segments.
Could not find any relevant property in context.
First step - place the middleware after app.UseRouting(). Any request for a static file will never reach your middleware because the static files middleware will short circuit the request. Also, after this point, the routing middleware will have selected the endpoint and populated the endpoint metadata. Then you can test the endpoint metadata collection to see if it includes PageRouteMetaData, which tells you that this is a Razor page route:
app.Use((context, next) => {
var endpoint = context.GetEndpoint();
if (endpoint != null)
{
foreach(var md in endpoint.Metadata)
{
if( md is PageRouteMetadata)
{
// this is a page route
}
}
}
return next(context);
});

Adding Attribute Route on Controller adds Parameter to Url

I want to change the URL that will access my controller, but when I do, instead of getting controller/action/id, I get controller/action?id=(the id number) in the URL.
I am using default routing in my MVC .Net Core.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
On my controller I have [Route("DifferentName/{action=index}")] for attribute routing.
I have tried adding [Route("DifferentName/{action=index}/{id?}")]
but I get an exception "RoutePatternException: The route parameter name 'id' appears more than one time in the route template."
These attributes are incorrect according to the documentation:
[Route("DifferentName/{action=index}/{id?}")]
[Route("DifferentName/{action=index}")]
This is how it should look like for controller when we want to only change its part of URL:
[Route("DifferentName/[action]")]
[action]
[action] will be replaced by your action name when asp .net core will be resolving URLs.
More information about routing and can be found in the documentation
but I get an exception "RoutePatternException: The route parameter
name 'id' appears more than one time in the route template."
I have reproduced your error. I need to confirm with you whether the reason for your error is to add Route attribute to both controller and action.
If so, as the exception points out, you don't need to specify route repeatedly.
Modify the following settings to implement the url like 'Differentname/action/5':
[Route("DifferentName/{action=index}")]
public class ShowPdfController : Controller
{
public IActionResult Index()
{
return View();
}
[Route("{id?}")]
public IActionResult Action(int id)
{
return View();
}
}
Then, you can see the test result of this setting:

AssumeDefaultVersionWhenUnspecified is not working as expected

I have been using asp net core versioning component for my WebAPI. Need your help in understanding how AssumeDefaultVersionWhenUnspecified is working. (tried searching for documentation, but couldn't find one)
My startup looks like below
services.AddApiVersioning(o => {
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(2, 0);
o.ApiVersionReader = new UrlSegmentApiVersionReader();
});
When the route attribute is something like below
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/values")]
[ApiController]
public class ValuesV2Controller : ControllerBase
{
...
}
The above route works only when the api version is specified. ie: http://localhost:55401/api/v2/values
If I call like http://localhost:55401/api/values, getting 404 error
My question is this... How AssumeDefaultVersionWhenUnspecified works. Wouldn't it ignore the version in Route? Looks like Route attribute takes precedence over AssumeDefaultVersionWhenUnspecified. If I choose QueryString or Header versioning and when the Route looks like
[ApiVersion("2.0")]
[Route("api/values")]
the default routing reaches the API
Am I missing anything or is my understanding wrong? How shall I achieve default routing to the latest version API using url versioning?
I am also trying to achieve the same functionality. By looking into [https://github.com/Microsoft/aspnet-api-versioning/issues/351#issuecomment-425106940]
I am assuming that we can't achieve default API version AssumeDefaultVersionWhenUnspecified functionality with only a single style of versioning uses a URL segment [Route("api/v{version:apiVersion}/[controller]")]
We have to define two routes separately as follow
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]
and to hide the two implementations from swagger you can achieve using this link
Summarizing the the solution from the github issue linked by Athi S, here's what you need to do :
In ConfigureServices inside Startup.cs file :
services.AddApiVersioning(o =>
{
o.AssumeDefaultVersionWhenUnspecified = true;
o.ApiVersionSelector = new CurrentImplementationApiVersionSelector(o);
// o.DefaultApiVersion = new ApiVersion(1, 0);
});
You can optionally set ApiVersionSelector to a new instance of CurrentImplementationApiVersionSelector. What this does is, it automatically selects the highest api version registered in controllers. E.g. A controller decorated with [ApiVersion("1.2")] takes precedence over [ApiVersion("1.1")].
If you want to specify default api version explicitly, you can do so by leaving ApiVersionSelector to DefaultApiVersionSelector and setting DefaultApiVersion to your required api version.
In your controllers :
Register the required routes by decorating your controllers with the given Route attributes
[Route("api/[controller]")]
Or if you want the api to work both with and without the api version number specified, you can do so by declaring two routes for the controller.
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]

Asp.Net Web API action string route mapping

I have a contoller named customer and an action method for GetAllCustomers which recieves single string input parameter as FirstName. The issue is that I want the url to be like
MyAPI/Customer/MyFirstName
but the above stated URL is not working for me instead it working as below
MyAPI/Customer?firstname=MyFirstName
How can I make it work like the first url using ApplicationRouting (nuget package)? I have configured the attrbute route for the action method as below.
[GET("Customer/{firstname}")]
public List<Customer> GetAllCustomersFirstname(String firstname)
{
//code goes here
}
Edit
Here is the reference whatI am trying to achieve but it is not happening for me in ApiController
Attribute Routing Github
Open up ApiConfig in the AppStart folder. You need to add the route for that particular action. I think you'll need more than just what you showed there for route since you have to tell it which action to call and what methods to allow. E.g.
config.Routes.MapHttpRoute(
name: "yourRoute",
routeTemplate: "api/{controller}/{firstname}",
defaults: new { controller = "Customer", action = "GetAllCustomersFirstname" });