Reputation: 3131
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.
Upvotes: 11
Views: 8090
Reputation: 1
I get the following configuration. I'm using .net Core 9 (but it also works on .net core 8), the first thing is to load the following packages:
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Settings.AppSettings" Version="3.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.4" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
Then the Worker Service configuration is done as follows:
var builder = Host.CreateApplicationBuilder(args);
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext()
.CreateLogger();
Log.Logger = logger;
Log.Information("Starting host");
Log.Information($"Evironment [{builder.Environment.EnvironmentName}]");
builder.Services.AddSerilog(logger);
builder.Services.AddCronJob<ScopedProcessingSignService>(options =>
{
options.CronExpression = "* * * * *";
options.TimeZone = TimeZoneInfo.Local;
});
var host = builder.Build();
host.Run();
when doing a var builder = Host.CreateApplicationBuilder(args); passing it as a parameter to .ReadFrom.Configuration(builder.Configuration), the Serilog configuration takes it without problems.
Important point, you must pass the newly generated logger as parameter to builder.Services.AddSerilog(logger);
Finally the configuration in the appsettings is the following:
},
"Serilog": {
"Using": [ "Serilog.Sinks.File", "Serilog.Sinks.Console" ],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
"System": "Information"
}
},
"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ],
"WriteTo": [
{
"Name": "Console",
"Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level}] {MachineName} ({ThreadId}) <{SourceContext}> {Message}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "C:\\Logs\\Sign\\Sign-.log",
"rollingInterval": "Day",
"retainedFileCountLimit": 14,
"shared": true,
"buffered": false,
"flushToDiskInterval": "00:00:10",
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level}] {MachineName} ({ThreadId}) <{SourceContext}> {Message}{NewLine}{Exception}"
}
}
]
}
Upvotes: 0
Reputation: 13527
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).
Upvotes: 8
Reputation: 2269
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 :)
Upvotes: 5
Reputation: 2714
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;
}
}
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.
Upvotes: 2
Reputation: 111
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>();
});
Upvotes: -3