Add a self hosted SignalR server to a .Net Core Worker Service

Viewed 1989

I'm trying to extend a .NET Core Worker Service (<Project Sdk="Microsoft.NET.Sdk.Worker">) with SignalR (self hosted web app).
All the examples/tutorials/docs I have found are based on web applications, so they don't fit my case.

This is what I've done until now:
MyService Program.cs:

    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>                       

            Host.CreateDefaultBuilder(args)
                .UseWindowsService()            
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<Worker>();
                    DependencyBuilder.Build(hostContext, services);  // inject the stuff I need in my service

                    // create a SignalR Web host               
                    SignalRWebHostCreator.CreateHost(services, "http://localhost:8090", (endpoints) => {
                        endpoints.MapHub<MyHub>("/result");
                    });
                });
    }

and the class I want to use to "extend" the servie with a SignalR server application.


public class SignalRWebHostCreator
    {
        public static void CreateHost(IServiceCollection services, string serviceUrl, Action<IEndpointRouteBuilder> mapHubs)
        {
            services.AddSignalR();  // is it ok here ?

            WebHost.CreateDefaultBuilder()
                .UseUrls(serviceUrl)
                .Configure((IApplicationBuilder app) => {
                    app.UseRouting();

                    app.Map("/check", config => {  // just a test: it works!
                        config.Run(async context =>
                        {   
                            context.Response.ContentType = "text/plain";
                            byte[] data = System.Text.Encoding.UTF8.GetBytes("OK");
                            await context.Response.Body.WriteAsync(data, 0, data.Length);
                            await context.Response.Body.FlushAsync();
                        });
                    });

                    app.UseEndpoints(endpoints =>
                    {
                        //endpoints.MapHub<ClockHub>("/hubs/clock");   // ERROR
                        //endpoints.MapHub<PingHub>("/ping");  // ERROR
                        //mapHubs(endpoints);    // ERROR
                    });
            })
            .Build().Run();
        }
    }

(ClockHub is taken from MS example and PingHub is another simple Hub I tried to use instead of my "injected" Hubs)

It starts the web application properly and it responds properly to the url http://localhost:8090/check.

When I uncomment the calls to enpoint.MapHub() or my cusom Actions I have this error:

System.InvalidOperationException: 'Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddSignalR' inside the call to 'ConfigureServices(...)' in the application startup code.'

2nd try:
Seems like service.AddSignalR() is not doing its job, so I added this in SignalRWebHostCreator:

.Configure((IApplicationBuilder app) => {
      app.ApplicationServices = services.BuildServiceProvider();

and now I have this error:
System.InvalidOperationException: 'Unable to resolve service for type 'System.Diagnostics.DiagnosticListener' while attempting to activate 'Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware'.'

that at least has a callstack:
_This exception was originally thrown at this call stack:
Microsoft.Extensions.Internal.ActivatorUtilities.ConstructorMatcher.CreateInstance(System.IServiceProvider)
Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(System.IServiceProvider, System.Type, object[])
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.UseMiddleware.AnonymousMethod__0(Microsoft.AspNetCore.Http.RequestDelegate)
Microsoft.AspNetCore.Builder.ApplicationBuilder.Build()
Microsoft.AspNetCore.Hosting.WebHost.BuildApplication()
Microsoft.AspNetCore.Hosting.WebHost.StartAsync(System.Threading.CancellationToken)
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()_

If I add services.AddSingleton(new System.Diagnostics.DiagnosticListener("diagnostic listener")); I can use endpoints.MapHub(..) without errors but now a call to http://8090/check returns a 500 internal error, so I don't think this is the right way to solve the issue.

I found some example using WebApp from Microsoft.Owin.hosting.
It requires Microsoft.Owin.4.1.0, Microsoft.Owin.Hosting.4.1.0 and Owin.1.0.0 and the last one require Net Framework 4.6.1, I don't want this.
I have included Microsoft.AspNetCore.Owin.3.1.2 (100% .NET Core) but that does not offer WebApp or something similar.

1 Answers

I started experiencing same error when I upgraded nuget EFCore package to new version.

I noticed, that in my bin directory, a System.Diagnostics.DiagnosticSource.dll appeared, while when I downgraded - it disappeared.

I suspect, that DiagnosticListener type from old assembly version is registered in DI container, while on activation newer version is expected. Or reverse - I didn't dig that deep.

My solution was to revert EFCore to match Product version of System.Diagnostics.DiagnosticSource.dll so it will not appear in bin folder.

Related