Multiple hosts with the same DI container - c#

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.

Related

.NET Core 5 console app with IOC and Serilogging without microservice?

I need to create a simple .NET Core 5 Console App that uses IOC(Autofac), ILogging(Serilog) and a Message Queue(MQRabbit). I have done this before(exception Microsoft IOC instead of Autofac) in Console Apps that run microservices, this time I however need just a basic console app without hosting a service. How is this done?
I have this code from my microservice :
public static void Main(string[] args) {
Log.Logger = new LoggerConfiguration().DefaultLoggerSetup<Program>();
var serviceName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
var configurationBuilder = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var appSettings = configurationBuilder.Get<AppSettings>();
Log.Information("{#serviceName} microservice starting up.", serviceName);
Host.CreateDefaultBuilder(args)
.UseMQ(context => context.UseSettings(appSettings.MQSettings))
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration))
.ConfigureServices((hostContext, services) =>
{
services
.AddHostedService<MyService>()
.Configure<MQSettings>(configurationBuilder.GetSection("MQSettings"))
.AddTransient<IController, Controller>()
...
})
.Build().Run();
Log.Information("{#serviceName} microservice closing down.", serviceName);
}
But I dont need to host anything in this case, it should be a just basic console app with logging, injection and some I/O on a MQ.
Edit :
This is as far as I manage to get :
public class Program
{
static void Main(string[] args)
{
var serviceName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
var configurationBuilder = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var appSettings = configurationBuilder.Get<AppSettings>();
CompositionRoot(configurationBuilder).Resolve<ITestController>().PlaceTrestment1OnMQTest();
}
private static IContainer CompositionRoot(IConfigurationRoot configurationRoot)
{
var serilog = new LoggerConfiguration().ReadFrom.Configuration(configurationRoot).CreateLogger();
var loggerFactory = new LoggerFactory().AddSerilog(serilog);
Microsoft.Extensions.Logging.ILogger logger = loggerFactory.CreateLogger("Logger");
var builder = new ContainerBuilder();
builder.Register<Microsoft.Extensions.Logging.ILogger>((c, p) =>
{ return logger; });
//builder.Register<Microsoft.Extensions.Logging.ILogger>((c, p) =>
//{ return new LoggerConfiguration().ReadFrom.Configuration(configurationRoot).CreateLogger(); });
builder.RegisterType<TestController>().As<ITestController>();
return builder.Build();
}
}
}
public class TestController : ITestController
{
private ILogger<TestController> _logger;
public TestController(ILogger<TestController> logger)
{
_logger = logger;
}
public void PlaceTrestment1OnMQTest()
{
}
}
I get the exception when running it :
Cannot resolve parameter 'Microsoft.Extensions.Logging.ILogger1[ConnectorMockTest.BusinessLogicLayer.TestController] logger' of constructor 'Void .ctor(Microsoft.Extensions.Logging.ILogger1[ConnectorMockTest.BusinessLogicLayer.TestController])'.
Registering non-generic ILogger does not automatically register generic ILogger<T> and this is what you are trying to do.
Inject ILogger into TestController constructor instead ILogger and it should work properly, assuming the rest of the code is valid.
I was under the impression that a Service host was mainly used when hosting webservice/webpage or likewise. There is however really nothing wrong to create a service in a "closed" console application that serves the application with Dependency Injection, Configuration and startup routines. Even if the application is just a fire and forget app.

Unable to resolve service for type 'System.Lazy`1[System.Net.Http.IHttpClientFactory]' while attempting to activate service

I am getting the below error while trying to inject Lazy.
It works fine without "Lazy<>"
I have registered it like below in startup.cs.
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
..
..
}
I am trying to inject it in a controller like below:
private readonly Lazy<IHttpClientFactory> _clientFactory;
protected IHttpClientFactory ClientFactory => _clientFactory.Value;
public ValuesController(Lazy<IHttpClientFactory> clientFactory)
{
_clientFactory = clientFactory;
}
Here, I am getting an error:
System.InvalidOperationException: 'Unable to resolve service for type 'System.Lazy`1[System.Net.Http.IHttpClientFactory]' while attempting to activate 'POC.Core.Controllers.ValuesController'.'
Any idea, what could be the issue in lazy initialization?
Just don't use Lazy<IHttpClientFactory>. HttpClientFactory itself is the type that creates and caches instances of HttpClients, or rather, HttpClientHandlers.
It's a singletonmanaged by the DI container itself so Lazy<IHttpClientFactory> wouldn't have any effect, even if you got it to compile.
The source code of AddHttpClient explicitly registers a default HttpClientFactory as a singleton.
public static IServiceCollection AddHttpClient(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddLogging();
services.AddOptions();
//
// Core abstractions
//
services.TryAddTransient<HttpMessageHandlerBuilder, DefaultHttpMessageHandlerBuilder>();
services.TryAddSingleton<DefaultHttpClientFactory>();

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>();
//....
}

Using autofac in asp.net core 2.2

I am trying to use latest Autofac with asp.net core 2.2 project. However documentation of autofac seems to be outdated.
I am trying to use autofac without ConfigureContainer:
// ConfigureServices is where you register dependencies. This gets
// called by the runtime before the Configure method, below.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add services to the collection.
services.AddMvc();
// Create the container builder.
var builder = new ContainerBuilder();
// Register dependencies, populate the services from
// the collection, and build the container.
//
// Note that Populate is basically a foreach to add things
// into Autofac that are in the collection. If you register
// things in Autofac BEFORE Populate then the stuff in the
// ServiceCollection can override those things; if you register
// AFTER Populate those registrations can override things
// in the ServiceCollection. Mix and match as needed.
builder.Populate(services);
builder.RegisterType<MyType>().As<IMyType>();
this.ApplicationContainer = builder.Build();
// Create the IServiceProvider based on the container.
return new AutofacServiceProvider(this.ApplicationContainer);
}
but the ConfigureServices method signature is different in asp.net core 2.2
public void ConfigureServices(IServiceCollection services)
{
}
there is not return type.
Anyone knows how to use autofac in asp.net core 2.2?
You need to use this package:
Autofac.Extensions.DependencyInjection
In your program.cs, just use these lines of code:
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureServices(services => services.AddAutofac());
}
In your startup.cs, create a method called
public void ConfigureContainer(ContainerBuilder builder)
{
}
And use "builder" to register whatever you want. Autofac will find this method itself.
You don't need to call any builder.Build() anymore.
Following remarks in order to execute reccurent code with injected values, you need to add:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHostedService<MyHostedService>();
...
}
public class MyHostedService : IHostedService
{
private Timer _timer;
private IInjectedService _myInjectedService;
public MyHostedService(IServiceProvider services)
{
var scope = services.CreateScope();
_myInjectedService = scope.ServiceProvider.GetRequiredService<IInjectedService>();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private async void DoWork(object state)
{
_myInjectedService.DoStuff();
}
}

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