Search code examples
c#.net-corewebsocketmiddleware

How to add Middleware-based support for WebSockets to .net core server?


I'm working on a .net core project which currently offers a REST API and I would like to add websocket functionality to this server. According to this developer page by microsoft it should be possible to do the following:

public class MyWebSocketMiddleware : IMiddleware {
    
    private readonly IMyService _myService;

    // I need injection of request-scoped services, which is why I use IMiddleware.
    public MyWebSocketMiddleware(IMyService myService){
        this._myService = myService;
    }

    public async Task InvokeAsync(HttpContext context, RequestDelegate requestDelegate) {
            if (!context.WebSockets.IsWebSocketRequest) {
                // this case works perfectly fine for regular REST, middleware gets called.
                await requestDelegate.Invoke(context);
                return;
            }
            
            // the execution NEVER gets here
            Log.Info("Received websocket request.");
    }
}

... and register it in Startup.cs like so:

public void ConfigureServices(IServiceCollection services) {

    // ... lots of services...

    // according to the microsoft page linked above, we should use AddTransient here
    services.AddTransient<MyWebSocketMiddleware>();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
    // for now, to make sure CORS isn't the problem
    app.UseCors(
        options => options.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()
    );

    // for the REST controllers
    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();

    // for Websockets
    app.UseWebSockets(new WebSocketOptions {
        KeepAliveInterval = TimeSpan.FromSeconds(180)
    });
    app.UseMiddleware<MyWebSocketMiddleware>();

    app.UseEndpoints(endpoints => {
        endpoints.MapControllers();
    });
}

It all compiles and boots nicely without any problems on my Windows 10 machine.

My issue is: No matter which client I try to use, I never get to the "Received websocket request" log output. All HTTP(s) calls work as intended, but my server seems to outright refuse any connections with ws://localhost:5000 (or wss on port 5001 or any permutations thereof).

Am I missing something? Did I mess up the ordering of middleware registration?


Solution

  • After much tinkering and comparing my code to a working minimal sample project from microsoft, I finally got it working in my project as well.

    The key is that all REST-specific configurations (in particular app.UseHttpsRedirection()) must occur after the websocket middleware, otherwise the requests might end up blocked and/or redirected before the websocket middleware even has a chance to fire.

    The following ordering worked for me:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
        // first, register everything websocket-related...
        app.UseWebSockets(new WebSocketOptions {
            KeepAliveInterval = TimeSpan.FromSeconds(180)
        });
        app.UseMiddleware<MyWebSocketMiddleware>();
    
        // ... then everything related to REST
        app.UseHttpsRedirection();
        
        app.UseRouting();
        
        app.UseCors(...);
    
        app.UseAuthentication();
        app.UseAuthorization();
    
        app.UseEndpoints(endpoints => {
            endpoints.MapControllers();
        });
    }
    

    Bonus tip: if you're trying to get your own websockets to work, the example project linked above contains a single self-contained HTML page that can act as an interactive testing tool.