.net core , how to handle route with extra leading slash
Asked Answered
G

4

6

I need to handle an incoming request which is of the form: //ohif/study/1.1/series Note the exta slash at the front

My controller signature is:

[Route("ohif/study/{studyUid}/series")]
[HttpGet]
public IActionResult GetStudy(string studyUid)

If I modify the incoming request to /ohif/study/1.1/series it works fine

however when I use //ohif/study/1.1/series, the route is not hit

Additionally I also tried: [Route("/ohif/study/{studyUid}/series")] and [Route("//ohif/study/{studyUid}/series")]

Both fail. I unfortunately cannot change the incoming request as it is from an external application. Is there some trick to handle this route? I am working in .NET Core 3.0.

Update NOTE: I have logging activated and I see that asp.net core is analyzing the route, I have the message: No candidates found for the request path '//ohif/study/1.1/series' for the logger Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware

Goldner answered 12/12, 2019 at 12:1 Comment(5)
maybe you can try and override controllers, onactionexecuting method and apply some logic thereSharrisharron
I wonder if you could grab it with a regular expression?Greeting
Route("ohif/study/{studyUid:regex(^(\\d*\\.)?\\d+(?!.\\\\series))}") possibly something like that. I have no way to test it.Greeting
@FelixCastor that regex is handling slashes at the end the issue is really the extra slash "/" at the start of the url, I will try some regexes and provide feedbackGoldner
doh, Yeah, I thought the slash at the end was the problem...Greeting
L
4

Rewrite the URL at the web server-level, e.g. for IIS, you can use the URL Rewrite Module to automatically redirect //ohif/study/1.1/series to /ohif/study/1.1/series. This isn't a job for your application.

Loverly answered 12/12, 2019 at 15:25 Comment(2)
I think this is the correct approach, I also found a quick workaround using route constraints with a catchall route as described here #22665424Goldner
That will make it fun when you migrate your app elsewhere and you forget.Mondrian
P
9

What about the middleware to handle double slash?

app.Use((context, next) =>
            {
                if (context.Request.Path.Value.StartsWith("//"))
                {
                    context.Request.Path = new PathString(context.Request.Path.Value.Replace("//", "/"));
                }
                return next();
            });
Plod answered 26/4, 2020 at 15:13 Comment(2)
When I looked up this middleware approach in the MS documentation they had it with async. app.Use( async ( context, next ) => { var path = context.Request.Path.Value; /* do something */ await next.Invoke(); });Marston
In .Net 6, app.UseRouting() can be implicit, and if so will be at the start of the pipeline so this won't appear to work. Add an explicit UseRouting() call after the above. More details here https://mcmap.net/q/193562/-what-are-the-differences-between-app-userouting-and-app-useendpointsMondrian
L
4

Rewrite the URL at the web server-level, e.g. for IIS, you can use the URL Rewrite Module to automatically redirect //ohif/study/1.1/series to /ohif/study/1.1/series. This isn't a job for your application.

Loverly answered 12/12, 2019 at 15:25 Comment(2)
I think this is the correct approach, I also found a quick workaround using route constraints with a catchall route as described here #22665424Goldner
That will make it fun when you migrate your app elsewhere and you forget.Mondrian
B
3

This SO answer to a similar question describes another, simpler solution: use built-in URL Rewriting Middleware. For example a double slash can be replaced with a single one via:

var options = new RewriteOptions().AddRewrite(@"(.*)\/\/(.*)", "$1/$2", false);
app.UseRewriter(options);
Bylaw answered 5/10, 2023 at 23:52 Comment(0)
V
2

I took Ravi's answer and fleshed out a middleware. The middleware is nice because it is encapsulated, easily testable, can inject a logger, more readable, etc.

app.UseDoubleSlashHandler();

The code and tests:

public class DoubleSlashMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<DoubleSlashMiddleware> _logger;

    public DoubleSlashMiddleware(RequestDelegate next, ILogger<DoubleSlashMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        _logger.LogInformation($"Invoking {nameof(DoubleSlashMiddleware)} on {context.Request.Path}");

        context.Request.Path = context.Request.Path.FixDoubleSlashes();

        // Call the next delegate/middleware in the pipeline.
        await _next(context);
    }
}

public static class DoubleSlashMiddlewareExtensions
{
    public static IApplicationBuilder UseDoubleSlashHandler(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<DoubleSlashMiddleware>();
    }
}

[TestClass()]
public class DoubleSlashMiddlewareTests
{
    private DoubleSlashMiddleware _sut;
    private ILogger<DoubleSlashMiddleware> _logger;
    private bool _calledNextMiddlewareInPipeline;

    [TestInitialize()]
    public void TestInitialize()
    {
        _logger = Substitute.For<ILogger<DoubleSlashMiddleware>>();
        Task Next(HttpContext _)
        {
            _calledNextMiddlewareInPipeline = true;
            return Task.CompletedTask;
        }
        _sut = new DoubleSlashMiddleware(Next, _logger);
    }

    [TestMethod()]
    public async Task InvokeAsync()
    {
        // Arrange
        _calledNextMiddlewareInPipeline = false;

        // Act
        await _sut.InvokeAsync(new DefaultHttpContext());

        // Assert
        _logger.ReceivedWithAnyArgs(1).LogInformation(null);
        Assert.IsTrue(_calledNextMiddlewareInPipeline);
    }
}

String method to do the replacement:

public static class RoutingHelper
{
    public static PathString FixDoubleSlashes(this PathString path)
    {
        if (string.IsNullOrWhiteSpace(path.Value))
        {
            return path;
        }
        if (path.Value.Contains("//"))
        {
            return new PathString(path.Value.Replace("//", "/"));
        }
        return path;
    }
}

[TestClass()]
public class RoutingHelperTests
{
    [TestMethod()]
    [DataRow(null, null)]
    [DataRow("", "")]
    [DataRow("/connect/token", "/connect/token")]
    [DataRow("//connect/token", "/connect/token")]
    [DataRow("/connect//token", "/connect/token")]
    [DataRow("//connect//token", "/connect/token")]
    [DataRow("/connect///token", "/connect/token")]
    public void FixDoubleSlashes(string input, string expected)
    {
        // Arrange
        var path = new PathString(input);

        // Act
        var actual = path.FixDoubleSlashes();

        // Assert
        Assert.AreEqual(expected, actual.Value);
    }
}
Varmint answered 13/9, 2022 at 12:21 Comment(2)
This is certainly more code, but do you really want untested code in your startup that is executed for every request? (with no logging) ;)Varmint
Here's one more test case: [DataRow("/connect///token", "/connect/token")]Aerospace

© 2022 - 2024 — McMap. All rights reserved.