I am looking to inject a kafka producer as a singleton in my app. It currently has two steps required when disposing the instance. First, you must flush the buffer, second call dispose. To increase performance, this should only happen when messages are no longer being processed.
My solution for ASP.NET core, is to use the AddSingleton() method in DI and then use ApplicationLifetime.ApplicationStopping.Register to register a callback that will flush and dispose the producer. I followed the tutorial found here:https://andrewlock.net/four-ways-to-dispose-idisposables-in-asp-net-core/
putting together a quick test I did the following in my Startup class:
public void ConfigureServices(IServiceCollection services)
{
var producerConfig = new Dictionary<string, object>
{
{ "bootstrap.servers", "192.168.99.100:9092" },
{ "client.id", Dns.GetHostName() },
{ "api.version.request", true }
};
services.AddSingleton(new Producer<Null, string>(producerConfig, null, new StringSerializer(Encoding.UTF8)));
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime lifetime)
{
loggerFactory.AddConsole();
app.UseMvc();
app.UseWebSockets();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
lifetime.ApplicationStopping.Register(flushAndDispose, app.ApplicationServices.GetRequiredService<Producer>());
}
but when it runs I get the following error:
An exception of type 'System.InvalidOperationException' occurred in
Microsoft.Extensions.DependencyInjection.Abstractions.dll but was not
handled in user code: 'No service for type 'Confluent.Kafka.Producer'
has been registered.'
The assumption is also that Producer<T1,T2> is derived from Producer
you did not explicitly register a Producer with the service collection so the provider is unaware of how to resolve it.
services.AddSingleton<Producer>(
c => new Producer<Null, string>(producerConfig, null,
new StringSerializer(Encoding.UTF8)));
Related
I'm trying to register a singleton class, providing the constructor parameters in Startup.ConfigureServices method.
After several tries, I'm still not able to make the dbContext injection working
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddDbContext<EFContext>();
services.AddSingleton<OPCClient>(x =>
{
string endpointURL = "opc.tcp://xxx.yyy.zzz.nnn:12345";
bool autoAccept = false;
int stopTimeout = Timeout.Infinite;
var efContext = x.GetService<EFContext>();
OPCClient client = new OPCClient(endpointURL, autoAccept, stopTimeout, efContext);
client.Run();
return client;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// warmup
app.ApplicationServices.GetService<OPCClient>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<OPCService>();
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
When var efContext = x.GetService<EFContext>(); is executed, I'm getting the exception
System.InvalidOperationException: 'Cannot resolve scoped service 'EFContext' from root provider.'
Thanks for any help in injecting the DbContext in OPCClient class
It is not a good choice to use a scoped service (the EFContext) inside a singleton.
The DI container creates a new instance of a scoped service for every request, while it creates a singleton only once and this can lead to inconsistent states for your objects. Documentation here
I suggest to change the lifetime of OPCClient to scoped - using services.AddScoped instead of services.AddSingleton. If you cannot do this, pass a reference of IServiceProvider rather than EFContext and resolve that service from the container each time you need to use it:
public class OPCClient
{
private IServicePrivder _serviceProvider;
public OPCClient (IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void DoSomething() {
EfContext efContext = _serviceProvider.GetRequiredService<EfContext>();
}
}
In Asp.net core, inside the Startup class, I configured a class AccountService as an injection inside this method:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped(typeof(CharacterService));
}
I can successfully inject it on another class, but I want to also access CharacterService inside the Configure() method of Startup, because I want to call a method on the event of shut down of the server. Is it possible?
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime hostApplicationLifetime)
{
...
// var temp = app.ApplicationServices.GetService<CharacterService>();
hostApplicationLifetime.ApplicationStopping.Register(() =>
{
//CharacterService.Instance.SaveMemoryInDatabase();
});
}
How can I access CharacterService inside the Configure method?
Thanks,
You can access service using app.ApplicationServices.
In some cases you need to create a scope for services that are added scoped or transient. It often depends on where they were added. In case of the normal setup through ConfigureServices you need to create a scope. (Another place where services can be added is the host builder, which is often in the Program class.)
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<CharacterService>();
}
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env,
IHostApplicationLifetime hostApplicationLifetime)
{
using var scope = app.ApplicationServices.CreateScope();
var characterService = scope.ServiceProvider.GetRequiredService<CharacterService>();
hostApplicationLifetime.ApplicationStopping.Register(() =>
{
characterService.Instance.SaveMemoryInDatabase();
});
}
CreateScope returns an IServiceScope which is disposable. All IDisposable services that are created for a scope, such as scoped and transient services, will be disposed when the IServiceScope is disposed.
I'd like to send an email in my Configure method inside my Startup.cs file, when I receive exceptions.
I'm using IEmailSender and registering it in Startup.cs file like this for the rest of the application.
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IEmailSender, EmailSender>();
}
But now I want to use it below in the Configure method when I receive an exception, but I'm not sure how to instantiate it and/or use _emailSender, which is a IEmailSender that usually gets injected into the constructor of the class using it, but in this case (Startup.cs) I can't inject it because it hasn't been defined until the ConfigureServices method gets run and adds it as a service.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, Seed seeder)
{
app.UseExceptionHandler(builder => { builder.Run(async context => {
// where _emailSender is a IEmailSender emailSender
await _emailSender.SendEmailAsync("someone#gmail.com", "some subject", "hey an exception error!");
});
});
}
Extract the service provider from the context and use that to resolve the desired types
public void Configure(IApplicationBuilder app, IHostingEnvironment env, Seed seeder) {
//...
app.UseExceptionHandler(errorApp => {
errorApp.Run(async context => {
IServiceProvider services = context.RequestServices;
IEmailSender emailSender = services.GetRequiredService<IEmailSender>();
UserExceptionOptions options = services.GetRequiredService<IOptions<UserExceptionOptions>>().Value;
await emailSender.SendEmailAsync(options.ExceptionEmailAddress, "some subject", "hey an exception error!");
});
});
//...
}
The resolved services can then be used as needed
I'm developing a web app using ASP.net Core MVC 2.2, and in my Startup class I'm registering a dependency injection of type MyService, like so:
public void ConfigureServices(IServiceCollection services)
{
//Inject dependency
services.AddSingleton<MyService>();
//...other stuff...
}
This works correctly. However, I need to retrieve the instance of MyService during application shutdown, in order to do some cleanup operations before the app terminates.
So I tried doing this - first I injected IServiceProvider in my startup class, so it is available:
public Startup(IConfiguration configuration, IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
Configuration = configuration;
}
and then, in the Configure method, I configured a hook to the ApplicationStopping event, to intercept shutdown and call the OnShutdown method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime)
{
//Register app termination event hook
applicationLifetime.ApplicationStopping.Register(OnShutdown);
//...do stuff...
}
finally, in my OnShutdown method I try obtaining my dependency and using it:
private void OnShutdown()
{
var myService = ServiceProvider.GetService<MyService>();
myService.DoSomething(); //NullReference exception, myService is null!
}
However, as you can see from the comment in the code, this doesn't work: the returned dependency is always null. What am I doing wrong here?
I was able to make it work by explicitly passing your application services to your shutdown method like so.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime)
{
//Register app termination event hook
applicationLifetime.ApplicationStopping.Register(() => OnShutdown(app.ApplicationServices));
//...do stuff...
}
private void OnShutdown(IServiceProvider serviceProvider)
{
var myService = serviceProvider.GetService<MyService>();
myService.DoSomething();
}
Bare in mind that this will work for singleton services - you may have to CreateScope() if you want to resolve scoped services.
In my API project, Startup.cs file I have the following:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseMvc();
loggerFactory.AddAWSProvider(Configuration.GetAWSLoggingConfigSection(),
(logLevel, message, exception) => $"[{DateTime.UtcNow}] {logLevel}: {message}");
This works fine for API project. How would I register this in Structuremap Container?
var container = new StructureMap.Container(
c =>
{
c.For(typeof(ILogger<>)).Use(typeof(Logger<>));
c.For<ILoggerFactory>..../// use what?
}
If I understood you correctly, you need to register existing instance of loggerFactory. There is an overload of Use method that takes existing instance:
var container = new StructureMap.Container(
c =>
{
c.For(typeof(ILogger<>)).Use(typeof(Logger<>));
c.For(typeof(ILoggerFactory)).Use(loggerFactory);
});
See Registering Existing Objects for some more details.