9

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?

RonC
  • 31,330
  • 19
  • 94
  • 139
  • Do you need to include the `'/'` character in your parameter? Neither of the examples you've shown include it. – Richard Deeming Feb 11 '22 at 15:55
  • @RichardDeeming The two examples that I provide of how the end can be a wildcard don't need a / to work. The whole trailing part of the url can be anything (ie ending in a / or not). But the example of what I'd like to be able to do does end in a / and I really need it to be able to end in a / – RonC Feb 11 '22 at 18:28

2 Answers2

3

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/")].

Jackdaw
  • 7,626
  • 5
  • 15
  • 33
2

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

Claudio
  • 3,060
  • 10
  • 17