4

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

shelbypereira
  • 2,097
  • 3
  • 27
  • 50

3 Answers3

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();
            });
Ravi
  • 398
  • 3
  • 11
  • 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(); });` – StackOverflowUser Feb 10 '23 at 20:35
  • 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://stackoverflow.com/a/59966192/960380 – LMK May 23 '23 at 02:09
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.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • I think this is the correct approach, I also found a quick workaround using route constraints with a catchall route as described here https://stackoverflow.com/questions/22664924/net-mvc-routing-catchall-at-start-of-route – shelbypereira Dec 13 '19 at 06:58
  • That will make it fun when you migrate your app elsewhere and you forget. – LMK May 23 '23 at 02:11
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);
    }
}
Jess
  • 23,901
  • 21
  • 124
  • 145