aoven
aoven

Reputation: 2332

ASP.NET Core MVC route that matches any URL containing a pattern that's possibly in the middle

Typically, MVC routing is done by matching patterns from the start of the URL, e.g.:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "MyRoute", pattern: "my.io/{*suffix}", defaults: new { controller = "MyController" });
});

But what if I wanted to define a route that would catch any request that contains a certain pattern, regardless of where in the URL path it may be found? Something like this:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "MyRoute", pattern: "{*prefix}/my.io/{*suffix}", defaults: new { controller = "MyController" });
});

As you can hopefully see, I would like to catch any request that happens to include the pattern "/my.io/", regardless of whether it's at the start of URL (like it typically is), in the middle of the URL, or at the end of the URL. In all cases, I would also like to capture the prefix and suffix, if any. Needless to say, these could vary unpredictably, which is big part of the reason I want to do this using wildcards.

Is there a way to do this using MVC endpoint routing?

If yes, how would the [Route()] attributes on MyController and its methods have to be written in order to be discovered by the routing subsystem?

If no, what are my alternatives?

UPDATE

After some research, I determined that my problem is similar to this one. Unfortunately, there was no definitive answer there...

Upvotes: 1

Views: 5774

Answers (3)

Fanetic
Fanetic

Reputation: 682

I had a similiar requirement and it was easily solved using the ASP.NET Core Middleware MapWhen feature.

See the example for MapWhen at this link: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0#branch-the-middleware-pipeline

As an example:

app.MapWhen(context => context.Request.Path.HasValue && context.Request.Path.Value.Contains("my.io", StringComparison.InvariantCultureIgnoreCase), HandleMyIORequestsOnAllPaths);

and then in HandleMyIORequestsOnAllPaths

    private static void HandleMyIORequestsOnAllPaths(IApplicationBuilder app)
    {
        app.Run(context =>
        {
            context.Response.Redirect("/redirect-path");
            return Task.CompletedTask;
        });
    }

Ensure you add app.MapWhen before app.UseRouting() and app.UseEndpoints()

Upvotes: 2

aoven
aoven

Reputation: 2332

After some experimenting, I discovered that perhaps @panagiotis-kanavos was pointing me in the right direction, after all!

While the prefixes and suffixes really can differ wildly (both in content and length), I realized that there still are some final limits to the lengths.

I then defined a number of routes like this:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "Api0", pattern: "my.io", defaults: new { controller = "MyController" });
    endpoints.MapControllerRoute(name: "Api1", pattern: "{part1}/my.io", defaults: new { controller = "MyController" });
    endpoints.MapControllerRoute(name: "Api2", pattern: "{part1}/{part2}/my.io", defaults: new { controller = "MyController" });
    endpoints.MapControllerRoute(name: "Api3", pattern: "{part1}/{part2}/{part3}/my.io", defaults: new { controller = "MyController" });
    endpoints.MapControllerRoute(name: "Api4", pattern: "{part1}/{part2}/{part3}/{part4}/my.io", defaults: new { controller = "MyController" });
    endpoints.MapControllerRoute(name: "Api5", pattern: "{part1}/{part2}/{part3}/{part4}/{part5}/my.io", defaults: new { controller = "MyController" });
    
    // ... and a couple more along the same vein.
});

Next, I defined multiple matching [Route] attributes on MyController:

[Route("my.io")]
[Route("{part1}/my.io")]
[Route("{part1}/{part2}/my.io")]
[Route("{part1}/{part2}/{part3}/my.io")]
[Route("{part1}/{part2}/{part3}/{part4}/my.io")]
[Route("{part1}/{part2}/{part3}/{part4}/{part5}/my.io")]
...

This successfully handles any URL containing my.io that has a prefix of at most 5 levels. Naturally, this can be realistically extended up to the limit I require. See the Important Edit below!

Once I hit my action method, I can then simply read the entire suffix in one go like this:

[Route("{*suffix}")]

The only work I need to do in the method is stitching together all the {partN} segments, which I can also get straight from Request.Url.

Important Edit

It would seem I spoke just a little bit too soon. As luck would have it, above solution works OK for up to 5 segment-prefixes. I did notice a slight increase in memory usage, but otherwise everything was working smoothly.

But once I added a 6-segment prefix, the app startup started to lag noticeably, and memory usage doubled to that from before.

Adding a 7-segment prefix causes my app to consume over 4 GB of memory on startup (which should be under 300 MB normally) and serving requests becomes extremely slow.

There seem to be some exponential shenanigans going on. This unfortunately means that this solution is a lot more limited than I first believed.

Important Edit 2

After further experiments, I determined that it's not the endpoints.MapControllerRoute() calls that are to blame for slowness and memory-eating, but rather the [Route] attributes on the controller. When these rise above 5, things quickly grind to a halt. I wonder why this is?

How can I make it better?

Important Edit 3

I've raised the issue with the ASP.NET Core team in this issue. We'll see where it goes.

Upvotes: 1

Manoj
Manoj

Reputation: 79

you can apply the regex on Controller name and action name as below

app.UseEndpoints(endpoints =>
 {
     // Regex constraint
     endpoints.MapControllerRoute(
         name: "regexConstraint1",
         pattern: "{controller:regex(^M.*)=MyApp}/{action:regex(^Index$|^About$)=Index}/{id?}");
});

In this route I have applied constraint on both controller and action segment of the URL. The controller segment specifies that it should start with letter M.

The action segment specifies that the action segment should be either Index or About. You can change regex expression as per your requirement.

Upvotes: 0

Related Questions