Service Injected on startup is null in Extension Service Configuration aspnet core - asp.net-core

Service Injected on startup is null in Extension Service Configuration ASP.NET Core
We have one service for userservice to save user profiles and it is injected as scoped on startup.
In our extension, we add another service for students as singleton to insert update delete users' transactions. We want student info from userservice but in our student service it showing null.
In Startup.
services.AddMemoryCache();
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IUserService, UserService>();
In Extension
public void Execute(IServiceCollection serviceCollection, IServiceProvider serviceProvider)
{
serviceCollection.AddMvc();
serviceCollection.AddSingleton<IStudenService, StudenService>();
}
In Student Service
public class StudentSerivce : IStudentSerivce
{
private readonly IUserService _userService;
public StudentSerivce(IUserService userService)
{
_userService = userService; // is null
}
}

You could try this:
public StudentService(IServiceProvider services)
{
Services = services;
}
public IServiceProvider Services { get; }
public void SomeMethod()
{
using (var scope = Services.CreateScope())
{
var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
// Do Something
}
}
It would probably be better to make them both scoped or both singletons (then it would work without manually creating a scope).
It's dangerous to resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
Reference: Dependency injection in ASP.NET Core

Related

Implementing service worker in existing ASP.NET Core MVC application

I'm developing an ASP.NET Core MVC web application where I have these two tasks that should be running as background services:
Set the user status as "Expired" if EndOfSubscription date is == DateTime.Now
Before 1 month of EndOfSubscription date send a reminder e-mail to this user
After searching, I found that I can use service worker to implement this. But I'm totally confused how to use this service worker in existing ASP.NET Core MVC web application where I need to access my models and database.
Should I isolate these tasks in a separate service worker project? But in this case should I share the same database for both projects?
Can someone guide me with main steps in this kind of situations?
Thank you in advance.
Service worker or Worker service?
A Service Worker is a way to run background tasks in a browser and definitely unsuitable if you want to execute something on the server.
A Worker service is essentially a template with the (few) calls needed to run a BackgroundService/IHostedService in a console application and (optionally, through extensions) as a Linux daemon or Windows service. You don't need that template to create and run a BackgroundService.
The tutorial Background tasks with hosted services in ASP.NET Core shows how to create and use a BackgroundService but is a bit ... overengineered. The article tries to show too many things at the same time and ends up missing some essential things.
A better introduction is Steve Gordon's What are Worker Services?.
The background service
All that's needed to create a background service, is a class that implements the IHostedService interface. Instead of implementing all the interface methods, it's easier to inherit from the BackgroundService base class and override just the ExecuteAsync method.
The article's example shows this method doesn't need to be anything fancy:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
}
That's just a loop with a delay. This will run until the web app terminates and signals the stoppingToken. This service will be created by the DI container, so it can have service dependencies like ILogger or any other singleton service.
Registering the service
The background service needs to be registered as a service in ConfigureServices, the same way any other service is registered. If you have a console application, you configure it in the host's ConfigureServices call. If you have a web application, you need to register it in Startup.ConfigureServices:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<OrdersContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
...
//Register the service
services.AddHostedService<Worker>();
services.AddRazorPages();
}
This registers Worker as a service that can be constructed by the DI container and adds it to the list of hosted services that will start once .Run() is called in the web app's Main :
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
Using DbContext and other scoped services
Adding a DbContext as a dependency is trickier, since DbContext is a scoped service. We can't just inject a DbContext instance and store it in a field - a DbContext is meant to be used as a Unit-of-Work, something that collects all changes made for a single scenario and either commit all of them to the database or discard them. It's meant to be used inside a using block. If we dispose the single DbContext instance we injected though, where do we get a new one?
To solve this, we have to inject the DI service, IServiceProvider, create a scope explicitly and get our DbContext from this scope:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IServiceProvider _services;
//Inject IServiceProvider
public Worker(IServiceProvider services, ILogger<Worker> logger)
{
_logger = logger;
_services=services;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
//Create the scope
using (var scope = _services.CreateScope())
{
//Create OrdersContext in the scope
var ctx = scope.ServiceProvider.GetRequiredService<OrdersContext>();
var latestOrders = await ctx.Orders
.Where(o=>o.Created>=DateTime.Today)
.ToListAsync();
//Make some changes
if (allOK)
{
await ctx.SaveChangesAsync();
}
}
//OrdersContext will be disposed when exiting the scope
...
}
}
}
The OrdersContext will be disposed when the scope exits and any unsaved changes will be discarded.
Nothing says the entire code needs to be inside ExecuteAsync. Once the code starts getting too long, we can easily extract the important code into a separate method :
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
using (var scope = _services.CreateScope())
{
var ctx = scope.ServiceProvider.GetRequiredService<OrdersContext>();
await DoWorkAsync(ctx,stoppingToken);
}
await Task.Delay(1000, stoppingToken);
}
}
private async Task DoWorkAsync(OrdersContext ctx,CancellationToken stoppingToken)
{
var latestOrders = await ctx.Orders
.Where(o=>o.Created>=DateTime.Today)
.ToListAsync();
//Make some changes
if (allOK)
{
await ctx.SaveChangesAsync();
}
}

Unable to resolve service for type 'SignalR.XXXX' while attempting to activate 'YYYYAPIController'

I have two services.
services.AddControllers();
and
services.AddSignalR()
.AddHubOptions<OutputMessages>(options =>
{
options.EnableDetailedErrors = true;
});
services.AddScoped<IOutputMessages, OutputMessages>();
second services is also confured:
app.UseEndpoints(endpoints =>
endpoints.MapHub<OutputMessages>("/OutputMessages", options =>
{
options.Transports =
HttpTransportType.WebSockets |
HttpTransportType.LongPolling;
});
I have interface for my SignalR service:
public interface ISignalRHandler
{
public void RestartProcessor(Guid containerId, string userId, string message);
....
}
and I inherits this interface and SignalR interface in my SignalR service realization
public class OutputMessages : Hub, IOutputMessages
{
public OutputMessages(IHubContext<OutputHub> hubContext, ILogger<OutputMessages> logger, ApplicationDbContext dbContext)
{
_hubContext = hubContext;
_db = dbContext;
_logger = logger;
}
public void SendUserMessage(string discordId, Guid containerId, string message)
....
}
I try to inject my SignalR service to controller service:
public class ApplicationAPIController : ControllerBase
{
public ApplicationAPIController(ILogger<ApplicationAPIController> logger, ApplicationDbContext dbContext, IConfiguration Configuration, CoreObjectDumper.CoreObjectDumper dump, OutputMessages _outputMessages)
{
But receive error message
Unable to resolve service for type 'SignalR.OutputMessages' while attempting to activate 'ApplicationAPIController'.
How is possible to solver this problem?
You don't need to add the OutputMessages as transient because you already doing that when you map your hub and add/use signalr.
app.UseSignalR(routes =>
{
routes.MapHub<OutputMessages>("/OutputMessages");
});
Then when you injecting to controller, it is recommended to inject the HubContext, and not the hub itself.
Example:
private IHubContext<NotificationsHub, INotificationsHub> NotificationsHub
{
get
{
return this.serviceProvider.GetRequiredService<IHubContext<NotificationsHub, INotificationsHub>>();
}
}
or in your case:
public ApplicationAPIController(ILogger<ApplicationAPIController> logger, ApplicationDbContext dbContext, IConfiguration Configuration, CoreObjectDumper.CoreObjectDumper dump, IHubContext<OutputMessages, IOutputMessages> _outputMessages)
You generally shouldn't resolve the Hub out of DI. If you need to share code between your Hub and some other component, I'd suggest using either IHubContext or putting the shared code in a separate DI service instead.

How to validate all registered types in ASP.NET Core DI?

I want to check that the type registrations I established in Startup.cs are all valid at runtime (either when starting up the service or as part of a test suite). There's a feature like this in Lamar and other containers.
ASP.NET Core 3.x actually introduced a feature for scope and provider validation. Both of these are useful in different contexts (see the below post and sample code).
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
options.ValidateOnBuild = true;
});
https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/
You can iterate the services that you want to validate, and try to initialize a service with GetRequiredService<T>. It will throw an exception if there is something wrong. More info on
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.serviceproviderserviceextensions.getrequiredservice?view=aspnetcore-2.2
The IServiceCollection is actually enumerable over ServiceDescriptor which contains type information on the registered service and implementation. The service collection isn't usually registered, but it should be possible to capture both the service collection and service provider in a hosted service.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddServicesValidation();
}
The right extension method can capture the services collection without actually registering it (which feels "safer").
public static class ValidateServicesExtensions
{
public static IServiceCollection AddServicesValidation(this IServiceCollection services)
{
services.AddHostedService<ValidateServices>(provider => new ValidateServices(services, provider));
return services;
}
}
Now, the hosted service can iterate over the registered services & implementations. Although, this code bombs on the first generic IOptions<TOption>, but I'm sure we can figure something out?
ValidateServices.cs
public class ValidateServices : BackgroundService
{
private readonly IServiceCollection services;
private readonly IServiceProvider provider;
public ValidateServices(
IServiceCollection services,
IServiceProvider provider
)
{
this.services = services;
this.provider = provider;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
using var scope = provider.CreateScope();
foreach (var service in services)
{
_ = scope.ServiceProvider.GetRequiredService(service.ServiceType);
}
return Task.CompletedTask;
}
}

How to pass IOptions through Dependency injection to another Project

I have a WebApplication targetting .net core.
I have also created a Class Library targetting .net core as well.
I am creating a Users Repository following this Dapper tutorial Here
It would be nice to be able to provide the option that was injected in start up of the WebApplication into the project that will be the data access layer.
Here is the code for the Users Repository in a separate project.
class UsersRepository
{
private readonly MyOptions _options;
private string connectionString;
public UsersRepository(IOptions iopt/// insert Option here )
{
_options = iopt.Value;
connectionString = _options.connString;
}
public IDbConnection Connection
{
get
{
return new SqlConnection(connectionString);
}
}
The WebApplication Project Startup looks as follows.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<MyOptions>(Configuration);
services.AddMvc();
}
and of course MyOptions is a class in the web application that has only one property connString
One possible design is to make a new interface for your repository configuration inside your class library, and have your MyOptions type implement that interface.
For example, in your class library you can do the following:
public interface IRepositoryConfig
{
string ConnectionString { get; }
}
public class UserRepository
{
public UserRepository(IRepositoryConfig config)
{
// setup
}
}
And in your WebAPI Startup class you can wire this up as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<MyOptions>(Configuration);
services.AddMvc();
services.AddScoped<IRepositoryConfig>(s =>
s.GetService<IOptions<MyOptions>>().Value
);
services.AddScoped<UserRepository>();
}
Doing this will allow you to use the Asp.Net Core configuration/options framework without having to reference any Asp.Net DLLs in your class library directly.

looking for samples on how to user services.add* in asp.vnext

I would like to know where can I find samples the explains the differences among services.AddInstance, services.AddScoped, services.AddSingleton and service.AddTransient.
I found some articles that explain the point in a generic way, but I think a source sample is much more clear.
The scope of this questions is rather large, but since it seems you are specifically looking for AddScoped information I narrowed the sample down to scoping inside a web application.
Inside a web application AddScoped will mean pretty much the scope of the request. EntityFramework is using scoping internally, but it doesn't affect the user code in most cases so I'm sticking with the user code as shown below.
If you register a DbContext as a service, and also register a scoped service, for each request you will get a single instance of the scoped service where you resolve the DbContext.
The example code below should make it clearer. In general I would recommend just trying it out the way I'm showing it below to familiarize yourself with the behavior, by stepping through the code in the debugger. Start from an empty web application. Note the code I'm showing is from Beta2 (since in Beta2 we added the [FromServices] attribute which makes it easier to demonstrate, the underlying behavior is the same regardless of version.
startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add EF services to the services container.
services.AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<UserDbContext>();
services.AddScoped<UserService>();
// Add MVC services to the services container.
services.AddMvc();
}
UserDbContext.cs
public class UserDbContext : DbContext
{
public UserService UserService { get; }
public UserDbContext(UserService userService)
{
_userService = userService;
}
}
HomeController.cs
public class HomeController : Controller
{
private UserDbContext _dbContext;
public HomeController(UserDbContext dbContext)
{
_dbContext = dbContext;
}
public string Index([FromServices]UserDbContext dbContext, [FromServices]UserService userService)
{
// [FromServices] is available start with Beta2, and will resolve the service from DI
// dbContext == _ctrContext
// and of course dbContext.UserService == _ctrContext.UserService;
if (dbContext != _dbContext) throw new InvalidOperationException();
if (dbContext.UserService != _dbContext.UserService) throw new InvalidOperationException();
if (dbContext.UserService != userService) throw new InvalidOperationException();
return "Match";
}
}
Alternatively if you resolve the user service from another service, this time registered as transient the transient service will have a new instance everytime it is resolved, but the scoped service will remain the same within the scope of the request.
Create the new service
public class AnotherUserService
{
public UserService UserService { get; }
public AnotherUserService(UserService userService)
{
UserService = userService;
}
}
Add the following lines to startup.cs
services.AddTransient<AnotherUserService>();
And rewrite the HomeController.cs as follows
public class HomeController : Controller
{
private AnotherUserService _anotherUserService;
public HomeController(AnotherUserService anotherUserService)
{
_anotherUserService = anotherUserService;
}
public string Index([FromServices]AnotherUserService anotherUserService,
[FromServices]UserService userService)
{
// Since another user service is tranient we expect a new instance
if (anotherUserService == _anotherUserService)
throw new InvalidOperationException();
// but the scoped service should remain the same instance
if (anotherUserService.UserService != _anotherUserService.UserService)
throw new InvalidOperationException();
if (anotherUserService.UserService != userService)
throw new InvalidOperationException();
return "Match";
}
}