Reputation: 33881
If I have a controller with an action method that uses attribute based routing and declare it like this, all is well:
[HttpGet]
[Route("/dev/info/{*somevalue}")]
public IActionResult Get(string somevalue) {
return View();
}
I can route to the above action method for example by specifying a url that ends in /dev/info/hello-world
or /dev/info/new-world
However my business requirement is to have a urls that look like this: /dev/hello-world/info
or /dev/new-world/info
And there is an endless set of such urls that all need to route to the same action method on the controller.
I thought to set up the attribute based route on the action method as follows:
[HttpGet]
[Route("/dev/{*somevalue}/info/")]
public IActionResult Get(string somevalue) {
return View();
}
But when I do that I get the following error as soon as the web project runs:
An unhandled exception occurred while processing the request. RouteCreationException: The following errors occurred with attribute routing information:
For action: 'App.SomeController.Get (1-wwwSomeProject)' Error: A catch-all parameter can only appear as the last segment of the route template. Parameter name: routeTemplate Microsoft.AspNetCore.Mvc.Internal.AttributeRoute.GetRouteInfos(IReadOnlyList actions)
There has to be some way to work around this error. Know a way?
Upvotes: 9
Views: 2601
Reputation: 596
Just helping anyone else having this issue with a Razor Pages app - if using a regex string matching pattern to load a Razor Page endpoint, be sure to add this little guy here in your Startup.cs, or top-level file, or whatever Microsoft is doing whenever you're reading this:
services.AddRazorPages(options =>
{
// Add as many of these as you'd like
options.Conventions.AddPageRoute("/Path/To/Razor/Template", "/{somevalue}/info");
});
Last note: if using a regex in your route logic, Microsoft warns to use a regex timeout, lest the webpage would be vulnerable to regex DDOS attacks. One way to achieve this is described by this Stack Overflow post here
Upvotes: 0
Reputation: 8950
It is possible to achieve this by using the regular expression:
[HttpGet]
[Route(@"/dev/{somevalue:regex(^.*$)}/info/")]
public IActionResult Get(string somevalue)
{
return View();
}
About routing constrain using the regular expressions see in the documentation: Route constraint reference
The regular expression tokens explanation:
Token | Explanation |
---|---|
^ | Asserts position at start of a line |
. | Matches any character (except for line terminators) |
* | Matches the previous token between zero and unlimited times, as many times as possible |
$ | Asserts position at the end of a line |
If it's required to have the “world”
suffix in the second segment then add this suffix to the pattern like the following: [Route(@"/dev/{somevalue:regex(^.*world$)}/info/")]
.
Upvotes: 4
Reputation: 3095
Middleware is the way to achieve this.
If you need an api response is easy to implement inline.
if (app.Environment.IsDevelopment())
{
app.Use(async (context, next) =>
{
Console.WriteLine(context.Request.Path);
if (context.Request.Path.ToString().EndsWith("/info"))
{
// some logic
await context.Response.WriteAsync("Terminal Middleware.");
return;
}
await next(context);
});
}
If you need to call a controller you can simply edit request path via middleware to achieve your requirement.
You can find an example here: https://stackoverflow.com/a/50010787/3120219
Upvotes: 2