Is I wanted to add a singleton service that is dependent on appsettings.
How would I do this in the .Net 6 worker service startup?
using AutoMapper;
var mapperConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
});
IMapper mapper = mapperConfig.CreateMapper();
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<Worker>();
services.AddSingleton(mapper);
services.AddSingleton<IOrderNumberGenerator, OrderNumberGenerator>();
services.AddSingleton(new ServiceIwantToCall(<--I need too use apsettings here-->));
})
.Build();
await host.RunAsync();
At the moment you're trying to register an instance of ServiceIwantToCall. You can instead register a factory method to resolve one, and once it's resolved it will be a singleton. For example, this anonymous method:
services.AddSingleton(serviceProvider => {
// resolve the configuration provider from the container
IConfiguration config = serviceProvider.GetRequiredService<IConfiguration>();
// get the config value and instantiate a ServiceIwantToCall
// return the instantiated service
string someValue = config["myValueKey"];
return new ServiceIwantToCall(someValue);
});
Related
I'm trying to work out what I'm missing in this snippet of code. I'm creating an Azure WebJob that I'm hoping to deploy to Azure, but I need to load a section of JSON from my appsettings.json file into a customer object I have created. I don't seem to have the GetSection method available on the builder object and I don't understand what I'm missing so that I can map a section of config to my HostOptions class (the POCO taking the config section).
Here's my code, I am using .NET Core 3.0.
private static void Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureWebJobs(config =>
{
config.AddTimers();
config.AddAzureStorageCoreServices();
})
.ConfigureAppConfiguration((builderContext, config) =>
{
config.AddJsonFile("appsettings.json", optional: true);
config.AddJsonFile($"appsettings.{builderContext.HostingEnvironment.EnvironmentName}.json", optional: true);
config.AddEnvironmentVariables();
})
.ConfigureServices(services =>
{
// Some IoC mappings...
})
.Build();
builder.Run();
}
Use the overload for IWebHostBuilder.ConfigureServices
.ConfigureServices((builderContext, services) => {
IConfiguration configuration = builderContext.Configuration;
HostOptions options = configuration.GetSection("MySection").Get<HostOptions>();
services.AddSingleton(options);
// Some IoC mappings...
})
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.
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.
I have a new .NET Core Web API project that has the following projects structure:
API -> Business / Domain -> Infrastructure
The API is very thin with only the API methods. The Business / Domain layer has all my business logic. And finally, my Infrastructure layer has my DB classes using EF Core 2.0.
I know using .NET Core built-in Dependency Injection I can add a reference from the API project to the Infrastructure project, then add the following code in the StartUp.cs file:
services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString));
However, I would like to maintain a more traditional separation of concerns. So far I have added a module in my Infrastructure layer that attempts to make the registration like so:
builder.Register(c =>
{
var config = c.Resolve<IConfiguration>();
var opt = new DbContextOptionsBuilder<MyContext>();
opt.UseSqlServer(config.GetSection("ConnectionStrings:MyConnection:ConnectionString").Value);
return new MyContext(opt.Options);
}).AsImplementedInterfaces().InstancePerLifetimeScope();
The DBContext, however, is not getting registered. Any class that attempts to access the injected DBContext cannot resolve the parameter.
Is there a way to register the DBContext in a separate project using AuftoFac in a .NET Core Web API Project?
I use Autofac to register both HttpContextAccessor and DbContext.
builder
.RegisterType<HttpContextAccessor>()
.As<IHttpContextAccessor>()
.SingleInstance();
builder
.RegisterType<AppDbContext>()
.WithParameter("options", DbContextOptionsFactory.Get())
.InstancePerLifetimeScope();
DbContextOptionsFactory
public class DbContextOptionsFactory
{
public static DbContextOptions<AppDbContext> Get()
{
var configuration = AppConfigurations.Get(
WebContentDirectoryFinder.CalculateContentRootFolder());
var builder = new DbContextOptionsBuilder<AppDbContext>();
DbContextConfigurer.Configure(
builder,
configuration.GetConnectionString(
AppConsts.ConnectionStringName));
return builder.Options;
}
}
DbContextConfigurer
public class DbContextConfigurer
{
public static void Configure(
DbContextOptionsBuilder<AppDbContext> builder,
string connectionString)
{
builder.UseNpgsql(connectionString).UseLazyLoadingProxies();
}
}
I think that the problem is that you're trying to register MyContext() using AsImplementedInterfaces(). This is not how DbContext are getting registered usually. You should register and resolve class itself.
Another simple solution for Autofac version 4.8.1
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddControllersAsServices();
services.AddDbContext<MyContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ConnectionStrings:MyConnection:ConnectionString")));
var builder = new ContainerBuilder();
builder.Populate(services);
//...
// Your interface registration
//...
builder.Build(Autofac.Builder.ContainerBuildOptions.None);
}
Here's an implementation I use - it mimics EF Core 3.1 registration with Autofac 4.9.4. Be sure to adjust scopes per your requirements.
public void RegisterContext<TContext>(ContainerBuilder builder)
where TContext : DbContext
{
builder.Register(componentContext =>
{
var serviceProvider = componentContext.Resolve<IServiceProvider>();
var configuration = componentContext.Resolve<IConfiguration>();
var dbContextOptions = new DbContextOptions<TContext>(new Dictionary<Type, IDbContextOptionsExtension>());
var optionsBuilder = new DbContextOptionsBuilder<TContext>(dbContextOptions)
.UseApplicationServiceProvider(serviceProvider)
.UseSqlServer(configuration.GetConnectionString("MyConnectionString"),
serverOptions => serverOptions.EnableRetryOnFailure(5, TimeSpan.FromSeconds(30), null));
return optionsBuilder.Options;
}).As<DbContextOptions<TContext>>()
.InstancePerLifetimeScope();
builder.Register(context => context.Resolve<DbContextOptions<TContext>>())
.As<DbContextOptions>()
.InstancePerLifetimeScope();
builder.RegisterType<TContext>()
.AsSelf()
.InstancePerLifetimeScope();
}
In the desired project you can create an extension method that adds the context to the collection
public static class MyDataExtensions {
public static IServiceCollection AddMyData(this IServiceCollection services) {
//...
services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString));
//...
}
}
with that then in your start up it is just a matter of calling the extension exposed from the other project
services.AddMyData();
//...other settings
The API project is the composition root, so it needs to know all the relevant dependencies anyway. At least with this extension you do not have to make direct reference of the used db context,
In the code below, serviceProvider.GetService<DocumentDbConnection>() is resolving to null:
public void ConfigureService(IServiceCollection services)
{
var serviceProvider = services.BuildServiceProvider();
services.AddSingleton<DocumentDbConnection>(
x => new DocumentDbConnection(uri, authKey));
// service is null?
var connection = serviceProvider.GetService<DocumentDbConnection>();
services.AddTransient<IStopRepository, StopRepository>(
x => new StopRepository(connection, databaseId, collectionId));
}
Why is this happening? The type is being registered before GetService is called so should it not resolve to the singleton?
You are building the service provider before you register the DocumentDbConnection. You should register the services you need first. Then BuildServiceProvider will build a service provider with the services registered until then:
services.AddSingleton<DocumentDbConnection>(x => new DocumentDbConnection(uri, authKey));
var serviceProvider = services.BuildServiceProvider();
// code using serviceProvider