Use HostBuilder.ConfigureServices with DryIoc container? - c#

I have some class libraries that provide services to the applications I create and for legacy reasons they are tightly bound to DryIoc. That is, the service registrations are tightly bound, not the actual services.
If I can I would rather not just go around changing that code if I don't have to.
Creating a new ASP.NET MVC Core application I was able to use DryIoc by changing the ConfigureServices method to return an IServiceProvider like this:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddControllersAsServices();
var container = new Container().WithDependencyInjectionAdapter(services);
return container.ConfigureServiceProvider<CompositionRoot>();
}
(this is from memory so may not be 100% correct but that is not important)
The important change was that the void method could be changed to return an IServiceProvider, which DryIoc can provide me with.
However, with HostBuilder, which I want to use for console applications, background services, etc. the method that configures services doesn't accept IServiceProvider so I'm not sure how to do it.
The important code is this:
var builder = new HostBuilder()
.ConfigureAppConfiguration((hostingContext, config) => { ... })
.ConfigureServices((hostContext, services) =>
{
services.AddOptions();
// configure services
})
.ConfigureLogging((hostingContext, logging) => { ... });
The ConfigureServices method above has one overload and an extension method:
ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
ConfigureServices(this IHostBuilder hostBuilder, Action<IServiceCollection> configureDelegate)
There doesn't seem to be any provisions for returning or using a IServiceProvider or anything else that DryIoc could provide for me in this regard.
Is this possible? Is there a way to bridge the gap? Or do I just have to switch to using the Microsoft IServiceCollection for my class libraries? Since they are in use in many projects I'd rather not change just because it seems easiest in this specific instance, but if I have to, I have to.

An answer, that was incorrect (but correct in the context of an ASP.NET application), was provided by #Nkosi, and the comment thread sparked a discussion about a method UseServiceProviderFactory that turned out to be the solution, so thanks guys.
To use UseServiceProviderFactory I had to implement one class myself, and add the appropriate nuget package references to my project.
Here are the steps:
Add a reference to DryIoc.dll (unsurprisingly)
Add a reference to DryIoc.Microsoft.DependencyInjection
Change the registration code
Provide a custom implementation of the framework required for UseServiceProviderFactory
The original code looked like this:
var builder = new HostBuilder()
.ConfigureAppConfiguration((hostingContext, config) => { ... })
.ConfigureServices((hostContext, services) =>
{
services.AddOptions();
// configure services
})
.ConfigureLogging((hostingContext, logging) => { ... });
Here's what to use instead:
var builder = new HostBuilder()
.ConfigureAppConfiguration((hostingContext, config) => { ... })
.ConfigureServices((hostContext, services) =>
{
services.AddOptions();
// configure services
})
//////////////////// ADD THIS vvvvvvvv
.UseServiceProviderFactory(new DryIocServiceProviderFactory())
.ConfigureContainer<Container>((hostContext, container) =>
{
container.Register<...>();
})
//////////////////// ADD THIS ^^^^^^^^
.ConfigureLogging((hostingContext, logging) => { ... });
Then supply this implementation of DryIocServiceProviderFactory:
internal class DryIocServiceProviderFactory : IServiceProviderFactory<IContainer>
{
public IContainer CreateBuilder(IServiceCollection services)
=> new Container().WithDependencyInjectionAdapter(services);
public IServiceProvider CreateServiceProvider(IContainer containerBuilder)
=> containerBuilder.ConfigureServiceProvider<CompositionRoot>();
}
The CompositionRoot class above is resolved during configuration and the constructor can be used to configure the container. A dummy-class that does nothing can be used.

Related

Exceptions are no longer shown in StackTrace in Application Insights Azure after switching from Instrumentation Key to Connection String

I found today that using Instrumentation Key in Application Insights will be deprecated in future. Microsoft recommends switching to Connection String. The issue is that if I'm trying to switch to ConnectionString, the exception are no longer shown in Application Insights in Azure.
That was my code before switching that is working
private static IServiceCollection ConfigureServices()
{
//here I just read app settings.json file
IConfiguration config = LoadConfiguration();
IServiceCollection services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddConfiguration(config);
builder.AddConsole();
builder.AddApplicationInsights(config.GetSection("InstrumentationKey").Value)
.AddFilter("", LogLevel.Trace);
})
.AddSingleton<IExampleService, ExampleService>();
return services;
}
And this is my code now, after switching to ConnectionString
private static IServiceCollection ConfigureServices()
{
IConfiguration config = LoadConfiguration();
IServiceCollection services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddConfiguration(config);
builder.AddConsole();
builder.AddApplicationInsights(
telConfig => telConfig.ConnectionString = config.GetSection("AIConnectionString").Value),
ailoptions => {}).AddFilter("", LogLevel.Trace);
})
.AddSingleton<IExampleService, ExampleService>();
return services;
}
If I log a message with the ILogger, the trace appears in AppInsights, but if I throw an exception, then nothing is shown in Azure. Also I have LogLevel configured in appsettings.json.
Also, tried multiple others methods and still doesn't work (used ApplicationInsightsTelemetryWorkerService, waiting a few seconds before flushing the TelemetryClient, etc)
Now everything is okay. I don't know what caused this issue.
If you want to implement Logger with Application Insights using ConnectionString here is a code example. The Microsoft documentation is not explicit on this:
private static IServiceCollection ConfigureServices()
{
IConfiguration config = LoadConfiguration();
IServiceCollection services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddConfiguration(config);
builder.AddConsole();
builder.AddApplicationInsights(
telConfig => telConfig.ConnectionString = config.GetSection("AIConnectionString").Value),
ailoptions => {}).AddFilter("", LogLevel.Trace);
})
.AddSingleton<IExampleService, ExampleService>();
return services;
}

Calling 'BuildServiceProvider' from application code results in copy of Singleton warning. How do I avoid this?

I just pasted the 4 lines at the end from another project and it works but I get a warning.. I clearly do not understand DI well enough ... What does it want me to change ?
public void ConfigureServices(IServiceCollection services)
{
if (HostingEnvironment.EnvironmentName == "Local")
{
services.AddHealthChecksUI()
.AddHealthChecks()
.AddCheck<TestWebApiControllerHealthCheck>("HomePageHealthCheck")
.AddCheck<DatabaseHealthCheck>("DatabaseHealthCheck");
}
services.Configure<PwdrsSettings>(Configuration.GetSection("MySettings"));
services.AddDbContext<PwdrsContext>(o => o.UseSqlServer(Configuration.GetConnectionString("PwdrsConnectionRoot")));
services.AddMvc(o =>
{
o.Filters.Add<CustomExceptionFilter>();
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", b => b
.SetIsOriginAllowed((host) => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddSwaggerDocument();
services.AddHttpContextAccessor();
services.AddAutoMapper(typeof(ObjectMapperProfile));
services.AddTransient<IEmailSender, EmailSender>();
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
services.AddScoped(typeof(IAsyncRepository<>), typeof(Repository<>));
services.AddScoped<IRfReportTypeRepository, RfReportTypeRepository>();
services.AddScoped<IRfReportRepository, RfReportRepository>();
services.AddScoped<IRfReportLookupsService, RfReportLookupsService>();
services.AddScoped<IRfReportService, RfReportService>();
services.Configure<RAFLogging>(Configuration.GetSection("RAFLogging"));
ServiceProvider serviceProvider = services.BuildServiceProvider(); //WARNING IS HERE
IOptions<RAFLogging> RAFLogger = serviceProvider.GetRequiredService<IOptions<RAFLogging>>();
RegisterSerilogLogger logger = new RegisterSerilogLogger(RAFLogger);
}
If called BuildServiceProvider() in ConfigureServices, shown warning "Calling 'BuildServiceProvider' from application code results in a additional copy of Singleton services being created"
I solved this issue:
Create another function (which passed argument is IServiceCollection) and into the function call BuildServiceProvider()
For example your code it should be:
public void ConfigureServices(IServiceCollection services)
{
if (HostingEnvironment.EnvironmentName == "Local")
{
services.AddHealthChecksUI()
.AddHealthChecks()
.AddCheck<TestWebApiControllerHealthCheck>("HomePageHealthCheck")
.AddCheck<DatabaseHealthCheck>("DatabaseHealthCheck");
}
services.Configure<PwdrsSettings>(Configuration.GetSection("MySettings"));
services.AddDbContext<PwdrsContext>(o => o.UseSqlServer(Configuration.GetConnectionString("PwdrsConnectionRoot")));
services.AddMvc(o =>
{
o.Filters.Add<CustomExceptionFilter>();
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", b => b
.SetIsOriginAllowed((host) => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddSwaggerDocument();
services.AddHttpContextAccessor();
services.AddAutoMapper(typeof(ObjectMapperProfile));
services.AddTransient<IEmailSender, EmailSender>();
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
services.AddScoped(typeof(IAsyncRepository<>), typeof(Repository<>));
services.AddScoped<IRfReportTypeRepository, RfReportTypeRepository>();
services.AddScoped<IRfReportRepository, RfReportRepository>();
services.AddScoped<IRfReportLookupsService, RfReportLookupsService>();
services.AddScoped<IRfReportService, RfReportService>();
RegisterSerilogLogger logger = CreateRegisterSerilogLogger(services);
}
private RegisterSerilogLogger CreateRegisterSerilogLogger(IServiceCollection services){
services.Configure<RAFLogging>(Configuration.GetSection("RAFLogging"));
ServiceProvider serviceProvider = services.BuildServiceProvider(); //No warning here ))
IOptions<RAFLogging> RAFLogger = serviceProvider.GetRequiredService<IOptions<RAFLogging>>();
RegisterSerilogLogger logger = new RegisterSerilogLogger(RAFLogger);
return logger;
}
Or use ApplicationServices of IApplicationBuilder. ApplicationSerivces's type is IServiceProvider.
I mention this solution is only for remove warning.
Calling BuildServiceProvider creates a second container, which can create torn singletons and cause references to object graphs across multiple containers.
UPDATED 24.01.2021
I read Adam Freeman's Pro ASP.NET Core 3 8th book. Adam Freeman used app.ApplicationServices instead of services.BuildServiceProvider() in page 157 for this purpose, that app is Configure method's parameter that this method located in Startup.cs
I thinks correct version is to use ApplicationServices property of app, which app is IApplicationBuilder in Configure method's parameter. ApplicationServices's type is IServiceProvider.
Adam Freeman's Pro ASP.NET Core 3 8th book : Pro ASP.NET Core 3
Adam Freeman's example project: SportStore project's Startup.cs, SportStore project's SeedData.cs
Microsoft's recommendations about DI : Dependency injection in ASP.NET Core
Similar questions' answers in Stackoverflow: https://stackoverflow.com/a/56058498/8810311, https://stackoverflow.com/a/56278027/8810311
The ONLY purpose of calling 'BuildServiceProvider' is to get a service provider instance,
To remove this call and still be able to use IServiceProvider, change Configure method to get it as parameter:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider provider)

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.

Simple Injector integration with HostBuilder in .NET Core 2.x

We are developing a windows service that runs .net core 2.x. Following this blog post by Steve Gordon running .netcore generic host applications as a service things seem to be working beautifully... as long as we use the IServiceCollection. I prefer SimpleInjector but I'm not sure how I can use it like I do in asp.net core. I there's a way to replace the built in DI as described here Default service container replacement and I know the SI team doesn't recommend the approach ASP.NET Core MVC Integration Guide so is there a better way in this use case?
Here is what I have so far but it's uncomfortable
--main program
internal class Program
{
private static async Task Main(string[] args)
{
var isService = !(Debugger.IsAttached || args.Contains("--console"));
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Runner>();
//configure SimpleInjector here???
});
if (isService)
{
await builder.RunAsServiceAsync();
}
else
{
await builder.RunConsoleAsync();
}
}
}
Configuring the container here works more or less but the first class being created by the host (i.e. Runner in this case) gets created by the Host and injects any dependencies via the IServicesCollection. So my question is how do I have it inject from my SI container instead?
The obvious answer here is... Don't have any dependencies injected into the Runner. Instead Runner is the class that represents your application entry point so I configure my container there and dispose of it when the Runner is stopped. The complete code for runner...
public class Runner : IHostedService, IDisposable
{
private Container _container;
public Runner()
{
_container = new Container();
_container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
}
public Task StartAsync(CancellationToken cancellationToken)
{
Bootstrapper.Bootstrap(_container);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public void Dispose()
{
_container.Dispose();
_container = null;
}
}
I would hook onto the ConfigureContainer method of the HostBuilder and setup simpleinjectore there likke this:
HostBuilder()
.ConfigureContainer<ServiceCollection>((builder, services) =>
{
var container = new Container();
container.RegisterSingleton<IJobRepository, JobRepository>();
services.AddTransient<IHostedService, TimedService>();
})
.ConfigureServices((hostContext, services) =>
{
// Originally we would have done this
//services.AddHostedService<Service>();
})
.Build();
using (host)
{
await host.StartAsync();
await host.WaitForShutdownAsync();
}
While you could use your IHostedService implementation indeed I think it may hide what is going on. I believe the infrastructure bootstrapping should be done in one place or orchestrated at least in one place. I consider the container to be infrastructure and would set it all up with the rest of the app via the HostBuilder methods.
An added advantage may also be that you do not entirely replace the ServiceCollection as it works well with doing other framework related things. An example of some stuff I would still do with the ServiceCollection:
HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
services.AddOptions();
})
This is in line with what is stated in the simpleinjector docs about setting the container up with ASP.NET Core:
The practice with Simple Injector is to use Simple Injector to build up object graphs of your application components and let the built-in container build framework and third-party components,The practice with Simple Injector is to use Simple Injector to build up object graphs of your application components and let the built-in container build framework and third-party components
The same should apply with just .net core and the generic HostBuilder.
Generic host resolves hosted services from services collection, so the solution is to register hosted services in Simple Injector and then resolve them from Simple Injector to register in Services collection:
var container = new Container();
var host = new HostBuilder()
//...
.ConfigureServices((context, services) =>
{
container.Collection.Append(typeof(IHostedService), typeof(Runner));
services.AddSingleton(_ => container.GetAllInstances<IHostedService>());
})
//...
.Build();
container.Verify();
await host.RunAsync();

ASP.NET Core View Injection problems

Has anyone tried to use the new View Injection from ASP.NET Core?
I'm trying to use straight forward as described on the documentation (https://docs.asp.net/en/latest/mvc/views/dependency-injection.html) but no success at all.
The unique diference from my implementation and the documentation is that I'm using AutoFac for DI.
When I try to use the injection on my view I get an exception that my Service has not been registered.
#inject Domain.Service.LevelService LevelService
Error Message:
ComponentNotRegisteredException: The requested service 'Domain.Service.LevelService' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
Btw, the service is correctly registered and can be accessed from the controller for example.
Edit to include Startup:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddMemoryCache();
services.AddSession();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
return new AutofacServiceProvider(DependencyInjection.RegisterServices(services));
}
Code of the method RegisterServices:
public static IContainer RegisterServices(IServiceCollection services)
{
// Create the container builder.
var builder = new ContainerBuilder();
var assembly = Assembly.GetExecutingAssembly();
assembly.GetTypes()
.Where(x => x.IsSubclassOf(typeof(ServiceInjectionModule)))
.ToList()
.ForEach(x =>
{
var t = (ServiceInjectionModule)Activator.CreateInstance(x, new object[] { true });
t.AddtoContainer(builder);
});
// Add automapper configurations
var mapperConfiguration = AutoMapperConfig.Configure();
var mapper = mapperConfiguration.CreateMapper();
builder.RegisterInstance(mapper).As<IMapper>();
// Populate default services
builder.Populate(services);
return builder.Build();
}
The problem is in the assembly scanning section you've written. It's much easier to use the built in functionality of AutoFac. Not sure your code is .Net Core just based on the fact you're not using GetTypeInfo. GetTypeInfo is backwards compatible so will work with .Net 4.x
public static IContainer RegisterServices(IServiceCollection services)
{
// Create the container builder.
var builder = new ContainerBuilder();
var assembly = Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.GetTypeInfo().IsSubclassOf(typeof(ServiceInjectionModule)))
.AsSelf();
// Add automapper configurations
var mapperConfiguration = AutoMapperConfig.Configure();
var mapper = mapperConfiguration.CreateMapper();
builder.RegisterInstance(mapper).As<IMapper>();
// Populate default services
builder.Populate(services);
return builder.Build();
}
OK, I solved the problem.
Well, I didn't paid attention and seems that no one too :p.
The problem is that I'm trying to inject an instance and not an interface. Just changed the implementation and everything started working.
Final code:
#inject Domain.Service.Interfaces.ILevelService LevelService

Categories

Resources