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
Related
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 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)
This is my first attempt creating microservices using Azure Service Fabric and .Net Core
In debug mode it works fine and I can hit the API end points using Postman. However when I tried to hit same endpoints after publishing the Service Fabric application to Service Fabric Local Cluster (single node), it throws exception below
Unable to resolve service for type 'UrlShortener.Services.Contracts.IUrlService' while attempting to activate 'UrlShortener.WebService.Controllers.UrlShortenerController'.
I am not sure what is missing here.
Here is my code snippet
Controller
public class UrlShortenerController : ControllerBase
{
private readonly IUrlService _urlService;
public UrlShortenerController(IUrlService urlService)
{
_urlService = urlService;
}
}
Service
public class UrlService : EntityService<Url>, IUrlService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IUrlRepository _repo;
private readonly IOptions<ShortenUrlConfig> _config;
public UrlService(IUnitOfWork unitOfWork, IUrlRepository repo, IOptions<ShortenUrlConfig> config)
: base(unitOfWork, repo)
{
_unitOfWork = unitOfWork;
_repo = repo;
_config = config;
}
}
Service Extension
public static IServiceCollection RegisterCustomServices(this IServiceCollection services)
{
services.AddScoped<IUrlService, UrlService>();
return services;
}
public static IServiceCollection RegisterRepositories(this IServiceCollection services)
{
services.AddScoped<IUrlRepository, UrlRepository>();
return services;
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.RegisterCustomContracts();
services.RegisterCustomServices();
services.RegisterRepositories();
// configure the system messages
services.Configure<ShortenUrlConfig>(Configuration.GetSection("ShortenUrlConfig"));
services.AddEntityFramework(Configuration.GetConnectionString("TestDBContext"));
}
The exception is thrown because the urlService was not resolved in controller constructor, UrlShortenerController(IUrlService urlService) :
I suggest to call services.AddMvc() Method after registering services like this :
public void ConfigureServices(IServiceCollection services)
{
services.RegisterCustomContracts();
services.RegisterCustomServices();
services.RegisterRepositories();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// configure the system messages
services.Configure<ShortenUrlConfig>(Configuration.GetSection("ShortenUrlConfig"));
services.AddEntityFramework(Configuration.GetConnectionString("TestDBContext"));
}
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();
I have an ASP.Net MVC 5 project using an Onion Architecture where I have repositories and services and I use Services from my controller. In my controller, I need to use the IGenericService variables I created, but how can I instantiate these variables? The problem being that my Service needs a IRepository for its constructor, and in turn IRepositoryneeds to be initialized too.
What I tried was AddSingleton(IGenericService<MyClass>, GenericService<MyClass>) in the method ConfigureServices(IServiceCollection services) in the Startup.cs file but it doesn't seem to help.
Edit As suggested my #Nkosi I am trying to resolve dependencies and followed this tutorial to do so : http://scottdorman.github.io/2016/03/17/integrating-asp.net-core-dependency-injection-in-mvc-4/ . My problem now is that I get an invalid operation exception :
Unable to resolve service for type 'Repository.PrincipalServerContext' while attempting to activate 'WebExploitv2.Controllers.NavigationController'
My startup.cs looks like this now:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
var services = new ServiceCollection();
ConfigureAuth(app);
ConfigureServices(services);
var resolver = new DefaultDependencyResolver(services.BuildServiceProvider());
DependencyResolver.SetResolver(resolver);
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllerAsServices(typeof(Startup).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && !t.IsGenericTypeDefinition)
.Where(t => typeof(IController).IsAssignableFrom(t)
|| t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)));
services.AddSingleton<IGenericRepository<Web_Documents>, GenericRepository<Web_Documents>>();
services.AddSingleton<IGenericService<Web_Documents>, GenericService<Web_Documents>>();
services.AddSingleton<IGenericRepository<Web_Categories>, GenericRepository<Web_Categories>>();
services.AddSingleton<IGenericService<Web_Categories>, GenericService<Web_Categories>>();
services.AddSingleton<IGenericService<Web_User_joint_Profils>, GenericService<Web_User_joint_Profils>>();
services.AddSingleton<IGenericRepository<Web_User_joint_Profils>, GenericRepository<Web_User_joint_Profils>>();
services.AddSingleton<IGenericRepository<Web_Group_joint_Profils>, GenericRepository<Web_Group_joint_Profils>>();
services.AddSingleton<IGenericService<Web_Group_joint_Profils>, GenericService<Web_Group_joint_Profils>>();
services.AddSingleton<IMenuService, MenuService>();
services.AddSingleton<IMenuRepository, MenuRepository>();
}
}
I also added a DefaultDependencyResolver class :
public class DefaultDependencyResolver : IDependencyResolver
{
protected IServiceProvider serviceProvider;
public DefaultDependencyResolver(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public object GetService(Type serviceType)
{
return this.serviceProvider.GetService(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return this.serviceProvider.GetServices(serviceType);
}
}
Next I have the ServiceProviderExtension class:
public static class ServiceProviderExtensions
{
public static IServiceCollection AddControllerAsServices(this IServiceCollection services, IEnumerable<Type> controllerTypes)
{
foreach(var type in controllerTypes)
{
services.AddTransient(type);
}
return services;
}
}
Finally in my controller, I have Interfaces of GenericService which allows me to access Repository and in turn access my DB. I use the followed interfaces for instantiation
private IGenericService<Web_User_joint_Profils> _userProfileService;
private IGenericService<Web_Group_joint_Profils> _groupProfileService;
private IGenericService<Web_Categories> _categoryService;
PrincipalServerContext context;
private NavigationController(PrincipalServerContext context, IGenericService<Web_User_joint_Profils> userProfileService, IGenericService<Web_Group_joint_Profils> groupProfileService, IGenericService<Web_Categories> categoryService)
{
_context = context;
_userProfileService = userProfileService;
_groupProfileService = groupProfileService;
_categoryService = categoryService;
}
Note that My GenericService takes POCOs as generics in order to know where to look in Database. So for each of these in Startup.ConfigureServices(IServiceCollection services) I added an AddSingleton method to register these services and repositories with the DI container.
Any ideas why I get this exception?
I wouldn't call services inside a startup.
Instance your IGenericService as a private readonly, then create the constructor to call in startup.cs or where ever you decide to use it.
private readonly IGenericService _genericService = new GenericService();
public IGenericService GenericService
{
get{ return _genericService; }
set{ _genericService = value; }
}
Now you call your classes like:
GenericService.Method();
It is rather simple, using IServiceCollection instance that is being passed to ConfigureServices method by the run time you do:
services.AddSingleton<IAbstraction, ConcreteImplementation>();
or, for a transient lifetime scope:
services.AddTransient<IAbstraction, ConcreteImplementation>();
or, in your case:
services.AddSingleton<IGenericService<MyClass>, GenericService<MyClass>>();