How inject Logger in custom service - c#

I have ImportExportService.
In StartUp class in method ConfigureServices I use it as
services.AddImportExportService(Configuration.GetConnectionString("DefaultConnection"));
Extential method AddImportExportService:
public static class IServiceCollectionExtension
{
public static IServiceCollection AddImportExportService(this IServiceCollection services,
string connString,
ILogger<ImportExportService> logger
)
{
services.AddTransient<IImportExportService, ImportExportService>(provider => new ImportExportService(connString));
return services;
}
}
ExportImportService uses logging.
I tried to inject Logging in service as param in constructor like ILoger<ImportExportService> logger, but constructor includes only one param and extension method AddImportExportService get error.
How inject Logging in ExportImportService? Thank you

services.AddTransient<IImportExportService, ImportExportService>(provider => new ImportExportService(connString));
should be
services
.AddTransient<IImportExportService, ImportExportService>(
provider => new ImportExportService(connString, provider.GetRequiredService<ILogger<ImportExportService>>()));
assuming the constructor of ImportExportService has two arguments. Then the extension needs only two arguments:
public static IServiceCollection AddImportExportService(
this IServiceCollection services,
string connString)

Related

Add service to IServiceCollection NOT in ConfigureServices

I have Startup and IHosterService in witch I want to add a service to IServiceCollection. Problem is, after I add my service to IServiceCollection I can't get it from IServiceProvider
I tried to add my service to IServiceColection in IHostedService, but after I added my service I can't get it from IServiceProvider. Have I any chance to add service in IHostedService and after in get new service from IServiceProvider?
Startup
public class Startup {
...
public void ConfigureServices(IServiceCollection services) {
services.AddHostedService<InitHostedService>();
//without this line I can't resolve IServiceCollection in InitHostedService
services.AddSingleton<IServiceCollection>(services);
}
InitHostedService
public class InitHostedService : IHostedService {
private readonly IServiceCollection _services;
private readonly IServiceProvider _serviceProvider;
public InitHostedService(IServiceCollection services, IServiceProvider serviceProvider) {
_services = services;
_serviceProvider = serviceProvider;
}
public async Task StartAsync(CancellationToken cancellationToken) {
var serviceUri = // get actual uri for my service
if (serviceUri != null) {
// add service with uri to IServiceCollaction
_services.AddServiceClient<IIdMapperServiceClient, IdMapperServiceClient>(serviceUri);
// can't get here my added service
var a = _serviceProvider.GetRequiredService<IIdMapperServiceClient>();
}
...
}
AddServiceClient extension
public static void AddServiceClient<TServiceContract, TImplementation>(
this IServiceCollection services,
Uri serviceUri)
where TServiceContract : class
where TImplementation : class, TServiceContract {
services.AddHttpClient<TServiceContract, TImplementation>((sp, client) => { client.BaseAddress = serviceUri; });
}
You can't do it this way. IServiceProvider is built from IServiceCollection during startup process. When this happens - IServiceProvider copies services from IServiceCollection. So when your hosted service starts - IServiceProvider has already been built with services that you added during startup. Adding more services to IServiceCollection after that will have no effect, because IServiceProvider is "detached" from this collection already.
I think you trying to do something like that.
public static class ServiceCollectionExtension
{
public static IServiceCollection RegisterServices(this IServiceCollection servicesCollection, IConfiguration configuration)
{
servicesCollection.AddControllers();
servicesCollection.AddEndpointsApiExplorer();
servicesCollection.AppServices();
servicesCollection.AddIdentity();
servicesCollection.AddSwagger();
servicesCollection.AddAutoMapper(typeof(Program));
servicesCollection.AddDatabase(configuration);
var appSettings = servicesCollection.GetApplicationSettings(configuration);
servicesCollection.AddJwtTokenAtuhentication(appSettings);
return servicesCollection;
}
Hope it helps

How to add LinkGenerator to ASP.NET Core?

How do I add a LinkGenerator object to my IServiceCollection for DI in the Startup.cs ConfigureServices method?
public MyService(LinkGenerator linkGenerator) { }
Have tried:
public static void AddLinkGenerator(this IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IUrlHelper, UrlHelper>(implementationFactory =>
{
var actionContext = implementationFactory.GetService<IActionContextAccessor>().ActionContext;
return new UrlHelper(actionContext);
});
}
As far as I know, the LinkGenerator servides will be registered when you call the services.AddRouting(); methods and this codes will be called when you run .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); in the program.cs methods.
So if you use configure the asp.net core application as a web host, there is no need to call services.AddRouting(); methods again in your ConfigureServices method. This service will be registered before startup.cs's ConfigureServices method.
You could refer to below source codes to know how it has been registerd in RoutingServiceCollectionExtensions class.
Notice: Since the DefaultLinkGenerator is internal class, we couldn't use services.TryAddSingleton<LinkGenerator, DefaultLinkGenerator>(); to just register the LinkGenerator class.
public static IServiceCollection AddRouting(this IServiceCollection services)
{
//....
// Link generation related services
services.TryAddSingleton<LinkGenerator, DefaultLinkGenerator>();
services.TryAddSingleton<IEndpointAddressScheme<string>, EndpointNameAddressScheme>();
services.TryAddSingleton<IEndpointAddressScheme<RouteValuesAddress>, RouteValuesAddressScheme>();
services.TryAddSingleton<LinkParser, DefaultLinkParser>();
//....
}

Multiple hosts with the same DI container

In C#.NET Core you can create a generic host using the following code:
IHostBuilder builder = new HostBuilder()
.ConfigureServices((context, collection) => {
collection.AddSingleton<IMyClass, MyClass>();
collection.AddHostedService<MyService>();
});
await builder.RunConsoleAsync();
This creates a new instance of MyService with the default DI container.
Now, say that I want to create a new host inside MyService. This is easy enough (a web host in this case):
IWebHost webHost = WebHost.CreateDefaultBuilder()
.UseStartup<MyStartup>()
.Build();
.RunAsync();
This webhost will have its own Dependency Injection container, so it will not have access to all dependencies I've already added to the generic host container: i.e. it will not be able to have IMyClass injected into MyStartup.
I've also tried adding a custom IServiceProviderFactory<> using the following code (based on the .UseDefaultServiceProvider() code where they use IServiceCollection as the builder type):
public class CustomServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
private readonly IServiceProvider _provider;
public CustomServiceProviderFactory(IServiceProvider provider)
{
_provider = provider;
}
public IServiceCollection CreateBuilder(IServiceCollection services)
{
return services;
}
public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder)
{
return _provider;
}
}
Then in my HostBuilder I added it through .UseServiceProviderFactory(new CustomServiceProviderFactory(_serviceProvider)), but for some reason the HostedService is instantiated before this is created, causing DI exceptions about not finding the required objects.
However, seeing as WebHost.CreateDefaultBuilder() is now the preferred way to create a webhost (in .NET Core 3.0), and an IWebHostBuilder does not have an option to set a custom IServiceProviderFactory this does seem like a dead end.
How can I have the webhost use the same DI container as the initial generic host?
I've tried to do the same thing and this is what I have landed on. Not fully tested but it does appear to work.
First, in my base/first HostBuilder, add the service collection as a service so an IServiceCollection can be resolved via DI later on.
IHostBuilder builder = new HostBuilder()
.ConfigureServices((ctx, services) =>
{
services.AddSingleton<IMyService, MyService>();
services.AddHostedService<MyApp>();
services.AddSingleton(services);
});
In IHostedService.StartAsync() I create the WebHost. I copied the use of services.Replace from the functionality inside UseDefaultServiceProvider():
IWebHost host = WebHost
.CreateDefaultBuilder()
.ConfigureServices(services =>
{
var options = new ServiceProviderOptions();
services.Replace(ServiceDescriptor.Singleton<IServiceProviderFactory<IServiceCollection>>(new CustomServiceProviderFactory(_services, options)));
})
.UseStartup<MyStartup>()
.Build();
In the constructor of my CustomServicesProvider, I also need to remove any IHostedService services or else it appears you enter an infinite loop of the service starting. When creating the service provider, I add everything from the constructor-passed service collection to the local service collection.
class CustomServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
private readonly IServiceCollection _baseServices;
private readonly ServiceProviderOptions _options;
public CustomServiceProviderFactory(IServiceCollection baseServices, ServiceProviderOptions options)
{
_baseServices = baseServices;
_options = options;
_baseServices.RemoveAll<IHostedService>();
}
public IServiceCollection CreateBuilder(IServiceCollection services)
{
return services;
}
public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder)
{
foreach (var service in _baseServices)
{
containerBuilder.Add(service);
}
return containerBuilder.BuildServiceProvider(_options);
}
}
I was then able to create a Controller after adding app.UseRouting() and app.UseEndpoints(...) in my startup class. Injecting IMyService was successfully resolved and I could use it as normal.
You could also test it by just adding app.ApplicationServices.GetRequiredService<IMyService>() in your Startup.Configure() method and see that the correct service is returned.

Explicit Accessing Intance of Options and Passing it to Method within ConfigureServices with ASP.NET CORE API

I need to pass instance of IOptions to a method as parameter, like below: any idea?
services.SetWaitAndRetryPolicy<CustomHttpClient>(); //how to retrieve and pass instance of IOptions<MyConfig>
I follow the links below and at the bottom:
How to read AppSettings values from .json file in ASP.NET Core
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Add functionality to inject IOptions<T>
services.AddOptions();
// Add our Config object so it can be injected
services.Configure<MyConfig>(Configuration.GetSection("MyConfig"));
services.SetWaitAndRetryPolicy<CustomHttpClient>(); //how to retrieve and pass instance of IOptions<MyConfig>
}
public static class IServiceCollectionExtension
{
public static void SetWaitAndRetryPolicy<T>(this IServiceCollection services, IOptions<MyConfig> config) where T : class
{
}
}
How to get an instance of IConfiguration in asp.net core?
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.2
ASP.NET CORE 2.2
If you are using the extension method to register your CustomHttpClient class then you access the options within the configure method.
public static class IServiceCollectionExtension
{
public static void SetWaitAndRetryPolicy<T>(this IServiceCollection services) where T : class
{
services.AddHttpClient<T>((sp, client) =>
{
var options = sp.GetService<IOptions<MyConfig>>();
...
});
}
}
One of the parameters to the configure action is the IServiceProvider. From here you can get access to any of the registered services, in this case the IOptions<MyConfig> settings.

How can I inject dependencies into a custom ILogger in asp.net core 2.0?

In asp.net core 1.1 I could inject the IServiceProvider into the logger provider and resolve my logger when CreateLogger was called, but it all changed in asp.net core 2.0
My ILogger implementation needs dependencies injected.
How can I achieve this?
ASP.NET core provides possibility to replace built-in DI container with custom one (see this article for details). You could use this possibility to obtain instance of IServiceProvider earlier for logging bootstrapping while still using standard .Net core DI container.
To do this you should change return value of Startup.ConfigureServices(IServiceCollection services) method from void to IServiceProvider. You can use this possibility to build instance of IServiceProvider in ConfigureServices, use it for logging bootstrapping and then return from the method.
Sample code:
public interface ISomeDependency
{
}
public class SomeDependency : ISomeDependency
{
}
public class CustomLogger : ILogger
{
public CustomLogger(ISomeDependency dependency)
{
}
// ...
}
public class CustomLoggerProvider : ILoggerProvider
{
private readonly IServiceProvider serviceProvider;
public CustomLoggerProvider(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public ILogger CreateLogger(string categoryName)
{
return serviceProvider.GetRequiredService<ILogger>();
}
// ...
}
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
return ConfigureLogging(services);
}
private IServiceProvider ConfigureLogging(IServiceCollection services)
{
services.AddTransient<ISomeDependency, SomeDependency>();
services.AddSingleton<ILogger, CustomLogger>();
IServiceProvider serviceProvider = services.BuildServiceProvider();
var loggerFactory = new LoggerFactory();
loggerFactory.AddProvider(new CustomLoggerProvider(serviceProvider));
return serviceProvider;
}
// ...
}
Starting of with that dependency thing you need in various places
public class SomeDependency : ISomeDependency
{
}
An extension file so we can configure logging on the ServiceCollection as per MSDN
Pretty standard stuff you can find on various sources
public static class ApplicationLoggerFactoryExtensions
{
public static ILoggingBuilder CustomLogger(this ILoggingBuilder builder)
{
builder.Services.AddSingleton<ILoggerProvider, CustomLoggerProvider>();
//Be careful here. Singleton may not be OK for multi tenant applications - You can try and use Transient instead.
return builder;
}
}
The logger provider is the part that gets called AFTER services are built when you are working in your business code and need to log stuff.
So in the context of application the DI is built and available here. And it probably makes sense now why ILoggerProvider exists now.
public class CustomLoggerProvider : ILoggerProvider
{
private ISomeDependency someDependency;
public CustomLoggerProvider(ISomeDependency someDependency)
{
this.someDependency = someDependency;
}
public ILogger CreateLogger(string categoryName)
{
return new CustomeLogger(someDependency);
}
}
The concrete custom logger pretty simple stuff
public class CustomLogger : ILogger
{
public CustomLogger(ISomeDependency dependency)
{
}
}
And in the place where you are configuring your ServiceCollection.. as in the OP's question in Startup.cs
private void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ISomeDependency, SomeDependency>();
//services.AddSingleton<ILogger, CustomLogger>(); <== NO
var loggerFactory = new LoggerFactory(); //??? newer DotNet gives you LoggerFactory in startup this may be unnecessary.
//Add some console printer
services.AddLogging(configure => configure.AddConsole())
.Configure<LoggerFilterOptions>(options => options.MinLevel = LogLevel.Trace);
//Add our custom logger
services.AddLogging(configure => configure.CustomLogger()); // <== our extension helping out!
}
So just a note for usage of ILogger
✘ DO NOT - Do not add any ILogger to your services
The whole point of LoggerFactory and LoggerProvider configuration is to simplify using ILogger
public MyBusinessService(ILogger<BusinessServiceClass> log)
{
log.Information("Please tell all registered loggers I am logging!);
}
In my example it will print out message to console if available and the CustomLogger that took a Dependency we injected. If you register more.. it will go to all of them
If you are configuring logging in program.cs you can create a function to configure logging and get an instance of logging provider like this:
private static void ConfigureApplicationLogging(WebHostBuilderContext context, ILoggingBuilder loggingBuilder)
{
loggingBuilder.AddConfiguration(context.Configuration.GetSection("Logging"));
loggingBuilder.AddDebug();
loggingBuilder.AddConsole();
var serviceProvider = loggingBuilder.Services.BuildServiceProvider();
loggingBuilder.AddProvider(new DoxErrorLoggerProvider(serviceProvider, null));
}
Then in BuildWebHost you will configure logging as follows:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(ConfigureApplicationLogging)
.UseNLog()
.UseStartup<Startup>()
.Build();

Categories

Resources