8

I was migrating an ASP.NET Core App to the Worker Service template and was intending to keep the Startup code. However after

within Program I kept the CreateHostBuilder as described on the MS Docs:

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

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseWindowsService()
        .ConfigureWebHostDefaults(
            webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            })
        .ConfigureServices(
            services =>
            {
                services.AddHostedService<Worker>();
            });

while debugging, the ConfigureServices is being called

public void ConfigureServices(IServiceCollection services)

but the Configure within the Startup, is not reached / called

public void Configure(IApplicationBuilder app)

before it crashes calling Run().

I also tried this with the same result:

public static IHostBuilder CreateHostBuilder(string[] args)
    => Host.CreateDefaultBuilder(args)
        .UseWindowsService()
        .ConfigureWebHostDefaults(webBuilder => webBuilder.Configure(Startup.Configure))
        .ConfigureServices(
            services =>
            {
                Startup.ConfigureServices(services);
                services.AddHostedService<Worker>();
            });

The interesseting part is that the following code actually calls the Startup.Configure(IApplicationBuilder app):

public static IHostBuilder CreateHostBuilder(string[] args)
    => Host.CreateDefaultBuilder(args)
        .UseWindowsService()
        .ConfigureWebHostDefaults(webBuilder => webBuilder.Configure(Startup.Configure));

As soon as I am adding ConfigureServices it skips the IApplicationBuilder configuration call.

Am I missing something or what is the suggested way to achieve this?

Edit:

the exact error is:

InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Builder.IApplicationBuilder' while attempting to activate 'Kledex.Extensions.KledexAppBuilder'.

stack trace:

at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable'1 serviceDescriptors, ServiceProviderOptions options) at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options) at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder) at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter'1.CreateServiceProvider(Object containerBuilder) at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider() at Microsoft.Extensions.Hosting.HostBuilder.Build()

the error happens as soon as it reaches CreateHostBuilder(args).Build().Run(); and tries to resolve the registered services, while the above one has a dependency to some config app.UseSomething(); within the Startup.Configure() method.

A breakpoint in Startup.Configure() doesn't get hit.

Raul
  • 2,745
  • 1
  • 23
  • 39
  • Show the error message when it crashes. Usually the code within Startup is not executed until the first request comes in – pinkfloydx33 May 13 '20 at 09:01
  • I added the error details and such. The **Startup** is being executed as soon as the app runs, which of course makes sense, since the service registration within the **Startup** happens _before_ services and dependencies are requested. What you probably ment is the resolution of services and dependencies, which happen as soon as these are requested. In example Controllers and DI. Nevertheless, the issue here is that `Startup.Configure(IApplicationBuilder app, IWebHostEnvironment env)` is never called, even when requested. – Raul May 13 '20 at 09:56
  • I meant in a normal web app, Startup.Configure isn't called until the first request. Anyways your error message indicates that it's all failing prior to that point because it cannot create an instance of KledexAppBuilder which appears to have a dependency on IApplicationBuilder, the latter of which I am pretty positive doesn't get added directly to the DI container (hence their method UseKledex... I think) – pinkfloydx33 May 13 '20 at 10:32
  • That is the case, however this works very well like this `public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args).UseStartup().UseIISIntegration()`. As such there must be a difference in the lifecycle of the Builder or the way it needs to be configured. – Raul May 13 '20 at 10:39
  • I think it might depend on where you are trying to use the KledexAppBuilder. Is it in the worker service? – pinkfloydx33 May 13 '20 at 10:41
  • I don't care too much where it gets called, it basically can be called as soon as the service starts or the the first time requested. I have no problem moving it to the worker, but I only see the KledexAppBuilder as an extension call to `IApplicationBuilder` which I have no idea how to inject into the service worker. If you give me a hint, I will try it out. – Raul May 13 '20 at 10:45
  • No--I was thinking that if you were trying to inject it into the worker service that was probably the reason the DI was failing. Anyways--per the exception, *somewhere* `KledexAppBuilder` is being injected and cannot be constructed itself because an `IApplicationBuilder` isn't in the container. I would track down where it's being injected and see if you can backtrack. I know nothing about `Kledex` so I can only go by the exception's message – pinkfloydx33 May 13 '20 at 11:29
  • Have you found a solution here @RaulSebastian ? – officer Feb 19 '21 at 10:34
  • @RaulSebastian any solution to this one? – MoonKnight Apr 10 '21 at 13:23
  • @MoonKnight What ASP.NET Core version are you using? – Alexander Apr 12 '21 at 11:41
  • Hi, for my particular case, I have managed to solve this after a long painful weekend. Mine was a threading issue... – MoonKnight Apr 12 '21 at 11:43
  • @RaulSebastian Could you please share you `Startup` code? – Alexander Apr 12 '21 at 11:44

4 Answers4

7

Here's your problem:

You are trying to use a pattern that was specifically designed for ASP.NET Core, but you aren't using ASP.NET Core and because of that you aren't getting the Startup pattern or any of it's features such as IApplicationBuilder or IWebHostEnvironment.

A while back, someone asked if we could figure out how to get the Startup pattern of ASP.NET Core in to something that uses the generic host builder without the use of ASP.NET Core. I came up with an answer.

The above answer doesn't give you the Configure(IApplicationBuilder app, IWebHostEnvironment env) method, and it shouldn't. That method is specifically for a Web Host. Not a worker. There is no reason you should need this method unless you are starting up a Web Host.

And if that's the case, then you should start over and create a brand new ASP.NET Core application... Not a Worker. This will configure your application to use Microsoft.NET.Sdk.Web SDK and not Microsoft.NET.Sdk.Worker SDK giving you the features you lost by going the Worker route.

You will not get an answer that will work for you unless you move away from the Worker SDK and move to the ASP.NET Core SDK. Maybe someone will come up with something, but more than likely it will be a kludge. And kludges break overtime and are a PITA to maintain.

There is nothing wrong with using Microsoft.NET.Sdk.Web for a Windows service. Microsoft even shows you how (Specifically pay attention to Deployment Type section).

Andy
  • 12,859
  • 5
  • 41
  • 56
4

Given that you are trying to configure web host defaults and use ASP.NET Core features like IApplicationBuilder, I believe that you are planning to run the service over some kind of web server. Is this correct?

If so, besides the other good suggestions, I have another one, which is to keep using an ASP.NET Core app - obviously enabling the usage of the Startup almost as is (with ConfigureServices and Configure methods) and perhaps most of your previous app.

To do so, in your Worker csproj, you could replace <Project Sdk="Microsoft.NET.Sdk.Worker"> by <Project Sdk="Microsoft.NET.Sdk.Web">.

Then, you can configure your CreateHostBuilder such as:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(
            webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Please note that I removed UseWindowsService() on purpose, since I am describing a case of using Worker services and not specifically Windows services.

Now you can keep using your Startup class, adding the registration of your background service(s):

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        // ...
    }
    
    public void ConfigureServices(IServiceCollection services)
    {
        // ...
        services.AddHostedService<Worker>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // ...
    }
}

Hope this helps :) However, if you specifically want to develop Windows services rather than Worker services, please let us know.

In this case, we keep having an ASP.NET Core WebApp/WebAPI with all its capabilities. We just added background services to it :)

nunohpinheiro
  • 2,169
  • 13
  • 14
  • 1
    This seems like the best answer. I'm guessing, but if OP _really_ needs the IApplicationBuilder then they need a web service with a background scheduler, not a worker service with a kludged together startup. – Ben Sampica Apr 16 '21 at 00:34
  • 1
    Thank you :) Perhaps both options can solve more or less the same use cases, yes... I've seen some nice examples regarding the use of worker services, like web apps running in containers, with background workers performing async tasks like publishing events or getting information from other services/message brokers. This also allows the use of observability tools for the worker services, like Health endpoints and telemetry, as well as taking advantage of containers orchestration capabilities (e.g. in Kubernetes). This becomes quite useful when working with SOA and microservices :) – nunohpinheiro Apr 16 '21 at 13:09
2

Yes, I had the same issue.

Answer for the question we can find in the Microsoft documentation here.

We need to use method ConfigureAppConfiguration for Host Services.

Please review my code examples. We use this code and it works fine:


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()
            .ConfigureAppConfiguration((context, builder) =>
            {
                var environment = context.HostingEnvironment.EnvironmentName;
                builder.AddAppSettings(environment);
            })
            .ConfigureWebHostDefaults(builder =>
            {
                builder.UseStartup<Startup>();
            });
}

HostedService

public class HostedService : IHostedService
{
    private readonly IServiceScopeFactory _serviceScopeFactory;
    private readonly ILogger<HostedService> _logger;

    public HostedService(IServiceScopeFactory serviceScopeFactory, ILogger<HostedService> logger)
    {
        _serviceScopeFactory = serviceScopeFactory;
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Service is starting...");

        // Run new task and use your business logic
        using (var scope = _serviceScopeFactory.CreateScope())
        {
            // Resolve business manager class with many dependencies. It is Scoped LifeTime.
            var manager = scope.ServiceProvider.GetRequiredService<IClientManager>();
        }

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Service is stopping...");
        return Task.CompletedTask;
    }
}

enter image description here


So we had only one trouble with this approach, we can inject only singletons to HostedService class. We will inject IServiceScopeFactory to constructor. And after it we create scope, resolve all needed dependencies and run our business logic.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Alexander I.
  • 2,380
  • 3
  • 17
  • 42
-3

You can move configuring of your HostedService from Program class to Startup. Just add this to ConfigureServices method before calling AddControllers:

services.AddHostedService<Worker>();

I'm including HostedService in my project in this way and it works.

That's my standart CreateHostBuilder:

public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
  • 1
    did you read the sample code above? That is exactly what I did, but the `Configure(IApplicationBuilder app)` is not being called at runtime. – Raul May 13 '20 at 09:39