Skip to content

Endpoint information available before calling UseRouting() #50393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
1 task done
kimbell opened this issue Aug 29, 2023 · 5 comments
Open
1 task done

Endpoint information available before calling UseRouting() #50393

kimbell opened this issue Aug 29, 2023 · 5 comments
Labels
area-hosting Includes Hosting bug This issue describes a behavior which is not expected - a bug.
Milestone

Comments

@kimbell
Copy link

kimbell commented Aug 29, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

We are updating applications to net7 and discovered problems serving static files. We have a Razor convention that maps any unknown url to /index. Using debug logging, we figured out that it didn't match any static file, and used the Razor convention instead.

Logging output:

1 candidate(s) found for the request path '/bundles/shell-bundle.es.js'
Endpoint '/Index' with route pattern '{*url}' is valid for the request path '/bundles/shell-bundle.es.js'
Request matched endpoint '/Index'
Static files was skipped as the request already matched an endpoint.

I have created a small repro. This works as excepted on net6, but fails on net7 and net8.
If I remove UsePathBase(), things seem to be working again.

Expected Behavior

It should be possible to use

  • Razor Convention
  • UsePathBase()
  • UseStaticFiles()

at the same time

Steps To Reproduce

https://github.com/kimbell/RazorStatics

Exceptions (if any)

No response

.NET Version

8.0.100-preview.7.23376.3

Anything else?

No response

@ghost ghost added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label Aug 29, 2023
@martincostello martincostello added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates and removed area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates labels Aug 29, 2023
@kimbell
Copy link
Author

kimbell commented Aug 29, 2023

For additional context, I maintain an internal framework that is built on top of ASP.NET; handles things such as logging, metrics, authentication and ensures all our services run middleware in the correct order. The previous version of this framework had to support .NET Framework, so it was cross-compiled for net472 (ASP.NET Core 2.2) and .net6. Due to this we still used the Startup class system. This is the first time we are running applications built on the minimal api infrastructure.

@kimbell
Copy link
Author

kimbell commented Aug 30, 2023

One workaround I have tested for this is to run some custom middleware that checks if the file exists on disk, and if that is the case, sets the Endpoint information to null. The debug logging still shows the static file middleware is ignored, but it seems to return the file as expected.

Is this a bad idea? Something I haven't though of?

internal class FixEndpointMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IWebHostEnvironment _hostEnvironment;
    private readonly IOptions<StaticFileOptions> _options;

    public FixEndpointMiddleware(
        RequestDelegate next,
        IWebHostEnvironment hostEnvironment,
        IOptions<StaticFileOptions> options)
    {
        _next = next;
        _hostEnvironment = hostEnvironment;
        _options = options;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        var path = httpContext.Request.Path;

        // when using a combination of static file middleware, UsePathBase() and Razor wildcard convention
        // the Endpoint information is set before the static file middleware runs.
        // this middleware will exit when the Endpoint information has been set, even if it exists on disk.
        // this is a workaround that always ensures it's null when the file exists on disk

        if (path.StartsWithSegments(_options.Value.RequestPath, out var subpath))
        {
            var fileProvider = _options.Value.FileProvider ?? _hostEnvironment.WebRootFileProvider;

            var file = fileProvider.GetFileInfo(subpath);
            if (file.Exists)
            {
                var endpointFeature = httpContext.Features.Get<IEndpointFeature>();
                if (endpointFeature != null)
                {
                    endpointFeature.Endpoint = null;
                }
            }
        }
        await _next(httpContext).ConfigureAwait(false);
    }
}

@kimbell
Copy link
Author

kimbell commented Aug 30, 2023

We use Swashbuckle for SwaggerUI. This fails because it uses StaticFileMiddleware and EmbeddedFileProvider. When it requests the supporting js files, the content of my index razor page is returned.

@kimbell
Copy link
Author

kimbell commented Aug 30, 2023

After some more digging, it looks like the problematic code is located in the UsePathBase() method.

In #42876 this method was updated to include this code.

 if (app.Properties.TryGetValue(RerouteHelper.GlobalRouteBuilderKey, out var routeBuilder) && routeBuilder is not null)
        {
            return app.Use(next =>
            {
                var newNext = RerouteHelper.Reroute(app, routeBuilder, next);
                return new UsePathBaseMiddleware(newNext, pathBase).Invoke;
            });
        }

If we remove __GlobalEndpointRouteBuilder from Properties before calling UsePathBase(), then our code works as we except it to. Then we get static files, UsePathBase() and Razor wildcard to work at the same time.

@javiercn javiercn added area-hosting Includes Hosting and removed area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates labels Aug 31, 2023
@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@wtgodbe wtgodbe removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@wtgodbe wtgodbe removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 13, 2024
@dotnet dotnet deleted a comment from dotnet-policy-service bot Feb 13, 2024
@dotnet dotnet deleted a comment from dotnet-policy-service bot Feb 13, 2024
@mkArtakMSFT mkArtakMSFT added the bug This issue describes a behavior which is not expected - a bug. label Mar 6, 2024
@mkArtakMSFT mkArtakMSFT added this to the Backlog milestone Mar 6, 2024
@nsmithdev
Copy link

I'm having a similar problem with static files not being served properly when using UsePathBase("/App"); even when the file request does not have the prefix.

UseStaticFiles is before UseRouting so if the file exist it should serve it even if there is a route for that path.
After adding UsePathBase i get the route not the file since UsePathBase adds RerouteHelper.Reroute()
Then in the StaticFileMiddleware ValidateNoEndpointDelegate() finds the route added by UsePathBase so it does not serve the file.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) => {
    var logger = context.RequestServices.GetRequiredService<ILogger<Bla>>();
    var requestDelegate = context.GetEndpoint()?.RequestDelegate;
    var hasRequestDelegate = requestDelegate is not null;
    logger.LogInformation("before. Has Request Delegate {hasDelegate}", hasRequestDelegate);
    await next();
});

app.UsePathBase("/App");

app.Use(async (context, next) => {
    var logger = context.RequestServices.GetRequiredService<ILogger<Bla>>();
    var requestDelegate = context.GetEndpoint()?.RequestDelegate;
    var hasRequestDelegate = requestDelegate is not null;
    logger.LogInformation("after. Has Request Delegate {hasDelegate}", hasRequestDelegate);
    await next();
});

app.UseStaticFiles();

app.UseRouting();

app.MapGet("/bla.jpg", async (request) => {
    await request.Response.WriteAsync("You got the route");
});

app.Run();

class Bla {}

Output:

before. Has Request Delegate False
after. Has Request Delegate True

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-hosting Includes Hosting bug This issue describes a behavior which is not expected - a bug.
Projects
None yet
Development

No branches or pull requests

6 participants