RonC
RonC

Reputation: 33881

How to work around "A catch-all parameter can only appear as the last segment of the route template."

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

Answers (3)

Matthew Beck
Matthew Beck

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

Victor
Victor

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

Claudio
Claudio

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

Related Questions