简体   繁体   中英

Implemented IHostedService in an asp.net core app, How to run it without the first request on IIS?

I've implemented IHostedService in an asp.net core website. It works great but the problem is that I want it to be started when the hosting server boots up or the IIS service restarts but it won't unless the first request to the website comes in.

  • The website is hosted on IIS version 10.0.18
  • The AppPool is in "AlwaysRunning" mode
  • "PreloadEnabled" is "True" on the website.
  • Setting ".NET CLR Version" to "No Managed Code" or "v4.0.xxxxxx" did not helped.
  • dotnet core version is 2.2 and dotnet core hosting bundle is installed.

UPDATE 1: "Application Initialization Module", Suggested by @Arthur did not help. Neither on site nor server level.

The configuration I used:

    <applicationInitialization
         doAppInitAfterRestart="true"
         skipManagedModules="false"
         remapManagedRequestsTo="init.htm">
        <add initializationPage="/init.htm" hostName="localhost"/>
    </applicationInitialization>

UPDATE 2: Here is how I implemented the interface

internal class PaymentQueueService : IHostedService, IDisposable
{
    private readonly ILogger _logService;
    private Timer _timerEnqueue;

    public PaymentQueueService(ILogger logService)
    {
        _logService = logService;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logService.LogInformation("Starting processing payments.");

        _timerEnqueue = new Timer(EnqueuePayments, null, TimeSpan.Zero,
            TimeSpan.FromSeconds(10));

        return Task.CompletedTask;
    }

    private void EnqueuePayments(object state)
    {
        _logService.LogInformation("Enqueueing Payments.");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logService.LogInformation("Stopping processing payments.");

        _timerEnqueue?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timerEnqueue?.Dispose();
    }
}

The Program class in main.cs file:

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

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args).ConfigureServices(services =>
            {
                services.AddHostedService<PaymentQueueService>();
            }).Configure((IApplicationBuilder app) =>
            {

                app.UseMvc();
            })
                .UseStartup<Startup>();
    }

The Startup class:

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostEnvironment env)
        {

        }
    }

Since the suggested "Application Initialization Module" did not work, you could consider making the call yourself with a client since

The module starts the process for the ASP.NET Core app when the first request arrives and restarts the app if it shuts down or crashes.

public class Program {
    static Lazy<HttpClient> client = new Lazy<HttpClient>();
    public static async Task Main(string[] args) {
        var host = CreateWebHostBuilder(args).Start();//non blocking start
        using (host) {
            bool started = false;
            do {
                var response = await client.Value.GetAsync("site root");
                started = response.IsSuccessStatusCode;
                await Task.Delay(someDelayHere);
            } while (!started);
            
            host.WaitForShutdown();
        }
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .ConfigureServices(services => {
                services.AddHostedService<PaymentQueueService>();
            })
            .Configure((IApplicationBuilder app) => {
                app.UseMvc();
            })
            .UseStartup<Startup>();
}

NOTE:

To prevent apps hosted out-of-process from timing out, use either of the following approaches:

  • Ping the app from an external service in order to keep it running.
  • If the app only hosts background services, avoid IIS hosting and use a Windows Service to host the ASP.NET Core app.

Hosting it "OutOfProcess" does the trick, but bear in mind it's not the preferred way. I've created a repo on GitHub with the given code and you can check the change on the last commit.

Source: https://github.com/tvdias/SO58831661

For a deep explanation on the differences between InProcess and OutOfProcess I recommend reading this page: https://weblog.west-wind.com/posts/2019/Mar/16/ASPNET-Core-Hosting-on-IIS-with-ASPNET-Core-22 .

Here is how I create my webservices . In the Startup.cs I inject my IHostedServices . Container is a class from StructureMap . I have copied it from an existing project, so it does not fit 100% to your sample.

public class Program
{
    public static void Main(string[] args)
    {
        Config.Directories.EnsureDirectoryTree();

        var isService = !(Debugger.IsAttached || args.Contains("--console"));
        var webHostService = MyWebHostService.BuildWebHostService(args.Where(arg => arg != "--console").ToArray());

        if (isService)
        {
            ServiceBase.Run(webHostService);
        }
        else
        {
            webHostService.InitializeBackend();
            webHostService.Host.Run();
        }
    }
}

public class MyWebHostService : WebHostService
{
    public static readonly Logger Logger = LogManager.GetCurrentClassLogger();

    public IWebHost Host { get; }

    public static ZlsWebHostService BuildWebHostService(string[] args)
    {
        ConfigureNLog();

        Logger.Info("{0} (v{1}) starting...", Config.ServiceName, GetApplicationVersion());

        // restore config files
        Config.Files.EnsureRestored();

        var host = CreateWebHostBuilder(args).Build();
        return new ZlsWebHostService(host);
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args)
    {
        var pathToExe = Assembly.GetExecutingAssembly().Location;
        var pathToContentRoot = Path.GetDirectoryName(pathToExe);

        return WebHost.CreateDefaultBuilder()
            .UseKestrel()
            .UseContentRoot(pathToContentRoot)
            .ConfigureAppConfiguration((context, config) =>
            {
                config.SetBasePath(Config.Directories.ActiveConfig);
                config.AddJsonFile(Config.Files.KestrelConfig.RelativePath, true, true);})
            .ConfigureLogging((hostingContext, logging) =>
            {
                logging.ClearProviders();
                logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                logging.AddConsole();
                if(hostingContext.HostingEnvironment.IsDevelopment())
                    logging.AddDebug();
            })
            .UseNLog()
            .UseStartup<Startup>();
    }

    public MyWebHostService(IWebHost host) : base(host)
    {
        this.Host = host;
    }

    protected override void OnStarting(string[] args)
    {
        InitializeBackend();

        base.OnStarting(args);
    }

    protected override void OnStarted()
    {
        Logger.Info("{0} started...", Config.ServiceName);
        base.OnStarted();
    }

    protected override void OnStopped()
    {
        Logger.Info("{0} stopped...", Config.ServiceName);
        base.OnStopped();
    }

...

}

public class Startup
{
...

    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        ...

        services.AddSingleton<PaymentQueueService>();

        ...

        var container = new Container(c =>
        {
            c.IncludeRegistry<MyFooRegistry>();
            c.Populate(services);
        });
        return container.GetInstance<IServiceProvider>();
    }

...

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM