ASP.NET Core 6 - How do I get required service
Asked Answered
L

2

15

I'm following the example for queued services on MSDN but I don't know how to get an instance for MonitorLoop, because host.Services.GetRequiredService is undefined. How can I retrieve it in ASP.NET Core 6?

Or may a better practice be to use a background scoped service for the MonitorLoop? https://learn.microsoft.com/en-us/dotnet/core/extensions/scoped-service

public static class QueueServiceExtensions
{
    public static void AddQueueService(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddSingleton<MonitorLoop>();
        services.AddHostedService<QueuedHostedService>();
        services.AddSingleton<IBackgroundTaskQueue>(_ =>
        {
            if (!int.TryParse(configuration["QueueCapacity"], out var queueCapacity))
            {
                queueCapacity = 100;
            }

            return new DefaultBackgroundTaskQueue(queueCapacity);
        });
        
        // TODO: host.Services is undefined
        MonitorLoop monitorLoop = host.Services.GetRequiredService<MonitorLoop>()!;
        monitorLoop.StartMonitorLoop();
    }
}

This is what my Program.cs looks like

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
using QSGEngine.Web.QueueService;
using QSGEngine.Web.SignalR;
using Serilog;

var builder = WebApplication.CreateBuilder(args);

// Logging
builder.Host.UseSerilog((context, configuration) =>
{
    configuration
        .ReadFrom.Configuration(context.Configuration)
        .Enrich.FromLogContext()
        .Enrich.WithMachineName()
        .Enrich.WithProperty("Environment", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"))
        .WriteTo.Console(
#if DEBUG
            restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Verbose,
#else
            restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information,
#endif
            outputTemplate: builder.Configuration["Serilog:OutputTemplateConsole"]
        )
        .WriteTo.File(
            path: builder.Configuration["Serilog:LogFileLocation"], retainedFileCountLimit: 7, rollingInterval: RollingInterval.Day, buffered: false,
            outputTemplate: builder.Configuration["Serilog:OutputTemplateFile"]
        );
});

// CORS
builder.Services.AddCors(options =>
{
    options.AddPolicy("CorsPolicy", configurePolicy => configurePolicy
        .WithOrigins(builder.Configuration["Configuration:FrontEndUrl"])
        .AllowAnyMethod()
        .AllowAnyHeader()
        .AllowCredentials());
});

// Authentication & Authorization
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

// Services
//builder.Services.AddHostedService<PortfolioService>();
builder.Services.AddQueueService(builder.Configuration);

builder.Services.AddControllers();

// Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// SignalR
//builder.Services.AddSignalR();
builder.Services.AddPortfolioSignalRService();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    // Swagger
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

// CORS
app.UseCors("CorsPolicy");

// Web Sockets
var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120)
};
app.UseWebSockets(webSocketOptions);

// Authentication & Authorization
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

// SignalR
//app.MapHub<PortfolioHub>("/portfolios");
app.MapPortfolioSignalR();

app.Run();

Lack answered 15/11, 2021 at 22:33 Comment(1)
I would say that the ideomatic way is to split AddQueueService into AddQueueService and UseMonitoring which will be called on the build app.Pilgrimage
F
8

I have a similar situation and this is my exact working implementation:

    private static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        // I Create a new Service Colleciton
        var services = new ServiceCollection();
        
        // I Configure my services
        services.ConfigureServices();
        
        // I build a new service provider from the services collection
        using (ServiceProvider serviceProvider = services.BuildServiceProvider())
        {
            // Review the FormMain Singleton.
            var formMain = serviceProvider.GetRequiredService<FormMain>();
            Application.Run(formMain);
        }
    }

    private static void ConfigureServices(this IServiceCollection services)
    {
        services.AddSingleton<FormMain>();
        ...
        services.AddSingleton<MonitorService>();
    }

This also works for web based applications. Here is an example of a Blazor application that uses the same pattern:

Program.cs

    public static void Main(string[] args)
    {
        var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
        logger.Debug($"Application Started {DateTime.Now}");

        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder
                .UseStartup<Startup>()
                .ConfigureLogging(logging =>
                {
                    logging.ClearProviders();
                    logging.SetMinimumLevel(LogLevel.Trace);
                })
                .UseNLog();
            }).UseWindowsService();

Do the following is from a Web Application but uses same concept:

    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddScopred<ILoginService, LoginService>();
        
        using (ServiceProvider serviceProvider = services.BuildServiceProvider())
        {
            var loginService = serviceProvider.GetRequiredService<LoginService>();
            var task = loginService.LoginAsync("Test", "Test");

            var result = task.GetAwaiter().GetResult();
        }
        ...
    }
Fenton answered 15/11, 2021 at 22:57 Comment(8)
> System.InvalidOperationException: 'Unable to resolve service for type 'Microsoft.Extensions.Hosting.IHostApplicationLifetime' while attempting to activate 'QSGEngine.Web.QueueService.MonitorLoopLack
@Lack Not sure how your minitor service looks, however, if you just want to call the StartLoop function you can do something like this: .AddSingleton<MonitorLoop>(new MonitorLoop()) then call the StartMonitorLoop in the contructor.Fenton
I also changed my answer. Try that.Fenton
This code is for WinForms, not ASP.NET Core - so this might not help due to the differences in environment.Lamonicalamont
@user15119845, MonitorLoop service is literally on Microsoft's page. I haven't changed anything. learn.microsoft.com/en-us/dotnet/core/extensions/…Lack
It still doesn't work. You can create a default ASP.NET Core 6 project and try to add it there. The whole code for the queue service is literally at Microsoft's page.Lack
I made it pastebin.com/YMFPfT8R.. I had to split the code in between ConfigureService and Configure. I still believe that if I make MonitorLoop service just like here: learn.microsoft.com/en-us/dotnet/core/extensions/scoped-service, it will be better. What do you think?Lack
Beware. In my solution, ReSharper warns that, "Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created." I used app.Services.CreateScope().ServiceProvider.GetRequiredService<xx> to solve this.Isochronal
C
24

You get the required services from the app instance,

var app = builder.Build();
var monitorLoop = app.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Consolute answered 7/6, 2022 at 0:50 Comment(0)
F
8

I have a similar situation and this is my exact working implementation:

    private static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        // I Create a new Service Colleciton
        var services = new ServiceCollection();
        
        // I Configure my services
        services.ConfigureServices();
        
        // I build a new service provider from the services collection
        using (ServiceProvider serviceProvider = services.BuildServiceProvider())
        {
            // Review the FormMain Singleton.
            var formMain = serviceProvider.GetRequiredService<FormMain>();
            Application.Run(formMain);
        }
    }

    private static void ConfigureServices(this IServiceCollection services)
    {
        services.AddSingleton<FormMain>();
        ...
        services.AddSingleton<MonitorService>();
    }

This also works for web based applications. Here is an example of a Blazor application that uses the same pattern:

Program.cs

    public static void Main(string[] args)
    {
        var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
        logger.Debug($"Application Started {DateTime.Now}");

        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder
                .UseStartup<Startup>()
                .ConfigureLogging(logging =>
                {
                    logging.ClearProviders();
                    logging.SetMinimumLevel(LogLevel.Trace);
                })
                .UseNLog();
            }).UseWindowsService();

Do the following is from a Web Application but uses same concept:

    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddScopred<ILoginService, LoginService>();
        
        using (ServiceProvider serviceProvider = services.BuildServiceProvider())
        {
            var loginService = serviceProvider.GetRequiredService<LoginService>();
            var task = loginService.LoginAsync("Test", "Test");

            var result = task.GetAwaiter().GetResult();
        }
        ...
    }
Fenton answered 15/11, 2021 at 22:57 Comment(8)
> System.InvalidOperationException: 'Unable to resolve service for type 'Microsoft.Extensions.Hosting.IHostApplicationLifetime' while attempting to activate 'QSGEngine.Web.QueueService.MonitorLoopLack
@Lack Not sure how your minitor service looks, however, if you just want to call the StartLoop function you can do something like this: .AddSingleton<MonitorLoop>(new MonitorLoop()) then call the StartMonitorLoop in the contructor.Fenton
I also changed my answer. Try that.Fenton
This code is for WinForms, not ASP.NET Core - so this might not help due to the differences in environment.Lamonicalamont
@user15119845, MonitorLoop service is literally on Microsoft's page. I haven't changed anything. learn.microsoft.com/en-us/dotnet/core/extensions/…Lack
It still doesn't work. You can create a default ASP.NET Core 6 project and try to add it there. The whole code for the queue service is literally at Microsoft's page.Lack
I made it pastebin.com/YMFPfT8R.. I had to split the code in between ConfigureService and Configure. I still believe that if I make MonitorLoop service just like here: learn.microsoft.com/en-us/dotnet/core/extensions/scoped-service, it will be better. What do you think?Lack
Beware. In my solution, ReSharper warns that, "Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created." I used app.Services.CreateScope().ServiceProvider.GetRequiredService<xx> to solve this.Isochronal

© 2022 - 2024 — McMap. All rights reserved.