Hangfire Autofac integration is not able to resolve service with registred dependency - c#

I'm using hangfire to lauch background job but I have a problem when I try to use the hangfire autofac integration with generic to resolve automaticly the task service it's not working because it's is not able to be resolve one of the dependency. I suppose because I don't receive any error.
BackgroundJob.Enqueue<IBackgroundTask>(x => x.RunAsync() );
If I use the other way around by resolving by myselft it's working.
var service = ApplicationContainer.Resolve<IBackgroundTask>();
BackgroundJob.Enqueue(() => service.RunAsync() );
I figured out that in my constructor I have a test service that cause the problem. If I remove the service in the constructor the service get resolved.
public class ConvertCarteCreditService : IBackgroundTask
{
private readonly ILogger logger;
private readonly ITest testService;
public BackgroundTask(ILogger logger, **ITest test**)
{
this.logger = logger;
this.testService = test;
// this.testService = Startup.Resolve<ITest>();
}
I have configured autofac in the startup class like this :
public void ConfigureServices(IServiceCollection services)
{
var builder = new ContainerBuilder();
ServiceLayerInstaller.ConfigureServices(builder);
DataLayerInstaller.ConfigureServices(builder, connectionString, readOnlyConnectionString);
builder.RegisterAssemblyTypes(typeof(WorkerRoleInstaller).
GetTypeInfo().Assembly).Where(t => t.Name.EndsWith("Test"))
.AsImplementedInterfaces();
WorkerRoleInstaller.ConfigureServices(builder);
builder.Populate(services);
ApplicationContainer = builder.Build();
var autofacJobActivator = new AutofacJobActivator(ApplicationContainer);
GlobalConfiguration.Configuration.UseActivator(autofacJobActivator);
}

I found my probleme was that I didn't return the service provider** from the configures services function but instead create the function as void and return nothing.
public **IServiceProvider** ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDirectoryBrowser();
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
var builder = new ContainerBuilder();
builder.Populate(services);
ServiceLayerInstaller.ConfigureServices(builder);
WorkerRoleInstaller.ConfigureServices(builder);
ApplicationContainer = builder.Build();
var autofacJobActivator = new AutofacJobActivator(ApplicationContainer, false);
GlobalConfiguration.Configuration.UseActivator(autofacJobActivator);
**return new AutofacServiceProvider(ApplicationContainer);**
}

Related

Understanding .net Core Dependency Injection in a console app

Console apps don't use the Startup file with configure services like web apps do and I'm struggling to understand the crucial concept of Dependency Injection.
(Please note the below example does not compile)
Here is a basic example of how I think it should work (please do point out anything unconventional or wrong):
static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddUserSecrets<Settings>()
.Build();
var services = new ServiceCollection()
.AddLogging(b => b
.AddConsole())
.AddDbContext<UnderstandingDIContext>(options =>
options.UseSqlite(builder.GetConnectionString("DefaultConnection")))
.BuildServiceProvider();
var logger = services.GetService<ILoggerFactory>()
.CreateLogger<Program>();
logger.LogInformation("Starting Application");
var worker = new Worker();
logger.LogInformation("Closing Application");
}
But how do I use these services inside my 'Worker' class?:
public Worker(ILogger logger, IConfiguration configuration)
{
logger.LogInformation("Inside Worker Class");
var settings = new Settings()
{
Secret1 = configuration["Settings:Secret1"],
Secret2 = configuration["Settings:Secret2"]
};
logger.LogInformation($"Secret 1 is '{settings.Secret1}'");
logger.LogInformation($"Secret 2 is '{settings.Secret2}'");
using (var context = new UnderstandingDIContext())
{
context.Add(new UnderstandingDIModel()
{
Message = "Adding a message to the database."
});
}
}
UnderstandingDIContext
public class UnderstandingDIContext : DbContext
{
public UnderstandingDIContext(DbContextOptions<UnderstandingDIContext> options)
: base(options)
{ }
public DbSet<UnderstandingDIModel> UnderstandingDITable { get; set; }
}
The problems with this code are as follows:
Worker() is expecting to be passed ILogger and IConfiguration parameters but I thought Dependency Injection should cover that?
I cannot run 'dotnet ef migrations add Initial' because I'm not correctly passing in the connection string (error: 'Unable to create an object of type 'UnderstandingDIContext'.')
'using (var context = new UnderstandingDIContext())' won't compile because I'm misunderstanding the DbContext bit.
I've searched around A LOT and there's lots of examples for web apps but very little for Console apps. Am I just completely misunderstanding the entire concept of Dependency Injection?
When using constructor injection, dependencies will only be resolved when the object you are creating is actually created through dependency injection itself. So the key to make dependency injection work within your Worker is to actually resolve Worker through the dependency injection container as well.
This is actually pretty simple:
var services = new ServiceCollection()
.AddLogging(b => b.AddConsole())
.AddDbContext<UnderstandingDIContext>(options =>
options.UseSqlite(builder.GetConnectionString("DefaultConnection")));
// register `Worker` in the service collection
services.AddTransient<Worker>();
// build the service provider
var serviceProvider = services.BuildServiceProvider();
// resolve a `Worker` from the service provider
var worker = serviceProvider.GetService<Worker>();
var logger = serviceProvider.GetService<ILogger<Program>>();
logger.LogInformation("Starting Application");
worker.Run();
logger.LogInformation("Closing Application");
In addition, since you are using a database context which gets registered as a scoped dependency by default, I would recommend you to create a service scope as well—or alternatively change the lifetime of the database context when you register it.
var serviceProvider = services.BuildServiceProvider();
using (var scope = serviceProvider.CreateScope())
{
var worker = serviceProvider.GetService<Worker>();
worker.Run();
}
Note that I also made an explicit method Run on your worker, so that you don’t have the logic within the constructor.
public class Worker
{
private readonly ILogger<Worker> _logger = logger;
private readonly IConfiguration _configuration = configuration;
private readonly UnderstandingDIContext _dbContext = dbContext;
public Worker(ILogger<Worker> logger, IConfiguration configuration, UnderstandingDIContext dbContext)
{
_logger = logger;
_configuration = configuration;
_dbContext = dbContext;
}
public void Run()
{
_logger.LogInformation("Inside Worker Class");
var settings = new Settings()
{
Secret1 = configuration["Settings:Secret1"],
Secret2 = configuration["Settings:Secret2"]
};
_logger.LogInformation($"Secret 1 is '{settings.Secret1}'");
_logger.LogInformation($"Secret 2 is '{settings.Secret2}'");
_dbContext.Add(new UnderstandingDIModel()
{
Message = "Adding a message to the database."
});
_dbContext.SaveChanges();
}
}
Take a look at this API.
https://github.com/Akeraiotitasoft/ConsoleDriving
Nuget: Akeraiotitasoft.ConsoleDriving

How to use Property Injection in MVC Core and AutoFac

I can use Constructor Parameter Injection easily In MVC Core. But Property Injection is not supported.I try use AutoFac but fail too.
So how to use Property Injection in MVC Core.
Here is the code with AutoFac
services.AddMvc();
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<Test2>().As<ITest>();
builder.RegisterType<HomeController>().PropertiesAutowired();
builder.Populate(services);
var container = builder.Build();
//The following code works
HomeController test2 = container.Resolve<HomeController>();
return new AutofacServiceProvider(container);
In dotnet core, you need to make the following changes to make Autofac work:
Add a public Autofac IContainer in your application Startup.cs
public IContainer ApplicationContainer { get; private set; }
Change ConfigureServices in Startup.cs to return IServiceProvider, do all your registrations, populate the framework services in your container by using builder.Populat(services);. Please note that there is no need for you to do builder.RegisterType<HomeController>().PropertiesAutowired();
public IServiceProvider ConfigureServices(IServiceCollection services)
{
builder.Populate(services);
ApplicationContainer = container;
return new AutofacServiceProvider(ApplicationContainer);
}
You will also need to make sure you dispose the container on application stopped by doing this in your Configure method.
public void Configure(IApplicationBuilder app, IApplicationLifetime appLifetime)
{
app.UseMvc();
appLifetime.ApplicationStopped.Register(() => ApplicationContainer.Dispose());
}
After you do this - your controllers should automatically get the properties autowired.
Property injection requires some additional setup. Let's Assume in your case we have this hierarchy of classes:
public class HomeController
{
public ITest Test {get; set;}
}
public class Test : ITest
{
public IRepository Repository {get;set;}
}
public class Repository: IRepository
{
}
The first things needed to be done are changing the return type to IServiceProvider for ConfigureServices(IServiceCollection services) and building new container using Populate method from 'Autofac.Extensions.DependencyInjection' method in order to return AutofacServiceProvider
New ConfigureServices method:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddControllersAsServices();
ContainerBuilder builder = new ContainerBuilder();
builder.Populate(services);//Autofac.Extensions.DependencyInjection
/*Here we are going to register services for DI*/
return new AutofacServiceProvider(builder.Build());
}
Next step is registration of classes in DI container. Property-injection for 'service classes' requires less action than for 'controllers'.
To setup property injection for service classes you just need to:
Register type in the container:
builder.RegisterType<Repository>().As<IRepository>();
Register type in which you are going to inject dependencies through
properties with PropertiesAutowired():
builder.RegisterType<Test>.As<ITest>().PropertiesAutowired()
To setup property injection for a controllers you need a little more steps:
Execute AddControllersAsServices() on services.AddMvc()
Regiser DI for controllers with call of PropertiesAutowired() for all controllers:
//in case you just need to allow registration for several specific controllers change this line
var controllersTypesInAssembly = typeof(Startup).Assembly
.GetExportedTypes()
.Where(type => typeof(ControllerBase).IsAssignableFrom(type)).ToArray();
builder.RegisterTypes(controllersTypesInAssembly).PropertiesAutowired();
As a result here is ConfigureServices() for predetermined hierarchy:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddControllersAsServices();
ContainerBuilder builder = new ContainerBuilder();
builder.Populate(services);//Autofac.Extensions.DependencyInjection
builder.RegisterType<Repository>().As<IRepository>()
.InstancePerLifetimeScope();
var controllersTypesInAssembly = typeof(Startup).Assembly.GetExportedTypes()
.Where(type => typeof(ControllerBase).IsAssignableFrom(type)).ToArray();
builder.RegisterTypes(controllersTypesInAssembly).PropertiesAutowired();
builder.RegisterType<Test>().As<ITest>().PropertiesAutowired();
return new AutofacServiceProvider(builder.Build());
}

How to fake declared services in Startup.cs during testing?

I would like to write integration tests for my Asp .net core application, but I don't want my tests to use real implemetation of some services.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient<IExternalService,ExternalService>();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
}
}
public interface IExternalService
{
bool Verify(int id);
}
public class ExternalService : IExternalService
{
public bool Verify(int id)
{
//Implemetation is here.
//I want to fake this implemetation during testing.
}
}
[Fact]
public void TestCase()
{
//Stub out service
var myExtService = new Mock<IExternalService>();
//Setup response by stub
myExtService
.Setup(p => p.Verify(It.IsAny<int>()))
.Returns(false);
var host = new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureServices((services) =>
{
//Setup injection
services.AddTransient<IExternalService>((a) =>
{
return myExtService.Object;
});
});
var server = new TestServer(host);
var client = server.CreateClient();
var response = client.GetAsync("/").Result;
var responseString = response.Content.ReadAsStringAsync().Result;
Assert.Contains("Your service returned: False", responseString);
}
Current injection setup in test case does not work, because ExternalService is injected over the mock.
However the test will pass when I remove services.AddTransient<IExternalService,ExternalService>; from Startup.
Most likely the one in Startup is called later and all the setup in that class is preferred by application.
What options do I have to setup some dependecies in tests, but use everything else as they are declared in Startup?
UPDATE
Application code should be unaware of tests.
Tests should be aware of:
(weakly typed) Endpoint - if this changes then test should fail
IExternalService interface
Tests should not care if application has razor pages or uses mvc or how the application is wired between endpoint and IExternalService.
Tests should not have to setup or configure application (apart from stubbing IExternalService) in order to make it work.
I understand that WebHostBuilder still has to be created, but my point is that configuration should be bare minimum in test case and majority of configuration should still be described on application side.
The only option I know of is to setup WebHostBuilder with UseEnvironment:
var host = new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureServices(services =>
{
//Setup injection
services.AddTransient<IExternalService>(provider =>
{
return myExtService.Object;
});
})
.UseEnvironment("IntegrationTest");
And then add a condition in the ConfigureServices method in the Startup:
public void ConfigureServices(IServiceCollection services)
{
if (Configuration["Environment"] != "IntegrationTest")
{
services.AddTransient<IExternalService, ExternalService>();
}
services.AddMvc();
// ...
}
UPDATE:
I did some more poking around and another option is to not use UseStartup extension method but rather configure the WebHostBuilder directly. You can do this a number of ways but I thought that you could possibly create your own extension method to create a template in your tests:
public static class WebHostBuilderExt
{
public static WebHostBuilder ConfigureServicesTest(this WebHostBuilder #this, Action<IServiceCollection> configureServices)
{
#this.ConfigureServices(services =>
{
configureServices(services);
services.AddMvc();
})
.Configure(builder =>
{
builder.UseMvc();
});
return #this;
}
}
Now your tests can be setup like the following:
var host = new WebHostBuilder()
.ConfigureServicesTest(services =>
{
//Setup injection
services.AddTransient<IInternalService>(provider =>
{
return myExtService.Object;
});
});
var server = new TestServer(host);
This means that you will have to explicitly setup all the implementations that the container will resolve for the specific endpoint you are calling. You can choose to mock or use the the concrete implementations.
The only thing yo need to change is to use ConfigureTestServices instead of ConfigureServices. ConfigureTestServices runs after your Startup, therefor you can override real implementations with mocks/stubs. ConfigureServices was newer intended for that purpose, rather, it configures "host services", which are used during the host-building phase of the application, and copied into the application's DI container.
ConfigureTestServices is available in ASP Core version 2.1 and higher.
var host = new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureTestServices((services) =>
{
//Setup injection
services.AddTransient<IExternalService>((a) =>
{
return myExtService.Object;
});
});
So after hours of research I found a solution.
I could not find a way to solely use built-in dependency injection solution, so I opted to choose 3rd party DI solution - Autofac
Idea is to use WebHostBuilder (declared Main Program) and add necessary options so I can fake some services during testing.
Something that I learned:
If you use Startup as host.UseStartup<Startup> it will be created after host.ConfigureServices()
You cannot inject something to Startup like host.UseStartup<Startup>(new Dependency())
However if you have registred dependency in host.ConfigureServices(services => services.AddTransient<IDependency, MyDependency>()), then it will be resolved before Startup is created and constructor public Startup(IDependency dependency) is used to create Startup.
My application side:
public class Program
{
public static void Main(string[] args)
{
CreateWebHost(args)
.Build()
.Run();
}
public static IWebHostBuilder CreateWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureServices((services) =>
{
//Setup autofac.
services.AddAutofac();
//Register module dependency that Startup requires.
services.AddTransient<Module, MyAutofacModule>();
////It would a bit cleaner to use autofac to setup Startup dependency,
////but dependency did not get resolved for Startup.
//services.AddAutofac((builder) =>
//{
// builder.RegisterModule(new AutofacModule());
//});
})
.UseStartup<Startup>();
}
public class MyAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
//Register all application dependencies in this module.
builder.Register((c) => new ExternalService()).As<IExternalService>();
}
}
public class Startup
{
private Module applicationDIModule;
public Startup(Module applicationDIModule)
{
this.applicationDIModule = applicationDIModule;
}
public void ConfigureServices(IServiceCollection services)
{
//We can add build-in services such as mvc and authorization,
//but I would not use Add(Transient/Scoped/Singleton) here.
//You should register domain specific dependecies in MyAutofacModule,
//since it will be added after this method call.
services.AddMvc();
}
//This method is called after ConfigureServices (refer to Autofac link).
public void ConfigureContainer(ContainerBuilder builder)
{
//We will register injected module.
builder.RegisterModule(applicationDIModule);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvcWithDefaultRoute();
}
}
Test case:
public class IntegrationTests
{
[Fact]
public void TestCase()
{
//Create and setup moq object as usual.
var service = new Mock<IExternalService>();
service
.Setup(p => p.Verify(It.IsAny<int>()))
.Returns(false);
//Bundle moq objects together for registration.
var attachFakes = new Action<ContainerBuilder>((builder) =>
{
builder.Register(c => service.Object);
});
//Use host builder that application uses.
var host = Program.CreateWebHost(new string[] { })
.UseContentRoot(GetContentRoot()) //Adjust content root since testproject.csproj is not in same folder as application.csproj
.ConfigureServices((services) =>
{
//We re-configure Module registration,
//so Startup is injected with our TestModule.
services.AddTransient<Module>((a) =>
{
return new TestModule(attachFakes);
});
});
//Create server to use our host and continue to test.
var server = new TestServer(host);
var client = server.CreateClient();
var response = client.GetAsync("/").Result;
var responseString = response.Content.ReadAsStringAsync().Result;
Assert.Contains("External service result: False", responseString);
}
private static string GetContentRoot()
{
var current = Directory.GetCurrentDirectory();
var parent = Directory.GetParent(current).Parent.Parent.Parent;
return Path.Combine(parent.FullName, "src");
}
}
public class TestModule : MyAutofacModule
{
private Action<ContainerBuilder> attachFakes;
public TestModule(Action<ContainerBuilder> attachFakes)
{
this.attachFakes = attachFakes;
}
protected override void Load(ContainerBuilder builder)
{
//We register everything in MyAutoFacModule before adding our fakes.
base.Load(builder);
//We add fakes and everything that is re-registered here will be used instead.
attachFakes.Invoke(builder);
}
}
Although it feels a bit brittle, but I still prefer this solution over what #ODawg suggested. His solution would work, but I see it would cause troubles in future when new test cases are added.

How to get an instance of IServiceProvider in .NET Core?

IServiceProvider is an interface with single method:
object GetService(Type serviceType);
It's used to create instances of types registered in .NET Core native DI container.
An instance of IServiceProvider itself can be obtained by calling a BuildServiceProvider method of an IServiceCollection. IServiceCollection is a parameter of ConfigureServices method in a Startup class. It seems to be magically called with an instance of IServiceCollection by the framework.
I want to create an instance of IServiceProvider without having Setup method at all. I need it to resolve dependencies in an integration test assembly. Is it possible to get it at all in this scenario?
As goaty mentioned it's enough to create new ServiceCollection. Here's example class which can be used to access DI container in .NET Core:
public static class ServiceProviderFactory
{
public static IServiceProvider ServiceProvider { get; }
static ServiceProviderFactory()
{
HostingEnvironment env = new HostingEnvironment();
env.ContentRootPath = Directory.GetCurrentDirectory();
env.EnvironmentName = "Development";
Startup startup = new Startup(env);
ServiceCollection sc = new ServiceCollection();
startup.ConfigureServices(sc);
ServiceProvider = sc.BuildServiceProvider();
}
}
Startup class is taken from tested project so the service registrations don't need to be repeated.
Then in test class simply use:
var foo = ServiceProviderFactory.ServiceProvider.GetServices(typeof(IFoo));
This is the default implementation of IServiceCollection from Microsoft:
https://github.com/aspnet/DependencyInjection/blob/master/src/DI/ServiceCollection.cs
Looking at the code then you should be able to get an IServiceCollection simply by calling:
var serviceCollection = new Microsoft.Extensions.DependencyInjection.ServiceCollection();
Hope that helps :)
To get access to existing DI of ASP.NET Core application e.g. in some controller, you should just resolve it in a constructor. Example with some manager and workers:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IFooManager, FooManager>();
services.AddTransient<IFooWorker, FooWorker>();
}
Manually resolve workers for manager:
public class FooManager: IFooManager
{
private readonly IServiceProvider _serviceProvider;
public FooManager(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Start()
{
var w1 = _serviceProvider.GetRequiredService<IFooWorker>(); // new instance of FooWorker
var w2 = _serviceProvider.GetRequiredService<IFooWorker>(); // new instance of FooWorker
}
}
First you need to install the Microsoft.Extensions.DependencyInjection NuGet package. (docs, API, API)
Then you create a new ServiceCollection and method chain it with the BuildServiceProvider method. In between that you can also register any service providers.
var serviceProvider = new ServiceCollection()
.AddSingleton<IFooService, FooService>()
.BuildServiceProvider();
Here is an updated approach:
var host = Host.CreateDefaultBuilder().ConfigureWebHostDefaults(builder =>
{
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
env.ContentRootPath = Directory.GetCurrentDirectory();
env.EnvironmentName = "Development";
});
builder.UseStartup<Startup>();
}).Build();
Example usage:
host.Services.GetService<IFoo>();
You can find it in Program.cs
public static IServiceProvider ServiceProvider { get; private set; }
public static void Main(string[] args)
{
IHost build = CreateHostBuilder(args).Build();
ServiceProvider = build.Services;
build.Run();
}
use this
ServiceProviderA = new ServiceCollection(). . . . . .BuildServiceProvider() .GetRequiredService<IServiceProvider>();
this ServiceProviderA contain itself

Resolving instances with ASP.NET Core DI from within ConfigureServices

How do I manually resolve a type using the ASP.NET Core MVC built-in dependency injection framework?
Setting up the container is easy enough:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddTransient<ISomeService, SomeConcreteService>();
}
But how can I resolve ISomeService without performing injection? For example, I want to do this:
ISomeService service = services.Resolve<ISomeService>();
There are no such methods in IServiceCollection.
The IServiceCollection interface is used for building a dependency injection container. After it's fully built, it gets composed to an IServiceProvider instance which you can use to resolve services. You can inject an IServiceProvider into any class. The IApplicationBuilder and HttpContext classes can provide the service provider as well, via their ApplicationServices or RequestServices properties respectively.
IServiceProvider defines a GetService(Type type) method to resolve a service:
var service = (IFooService)serviceProvider.GetService(typeof(IFooService));
There are also several convenience extension methods available, such as serviceProvider.GetService<IFooService>() (add a using for Microsoft.Extensions.DependencyInjection).
Resolving services inside the startup class
Injecting dependencies
The runtime's hosting service provider can inject certain services into the constructor of the Startup class, such as IConfiguration,
IWebHostEnvironment (IHostingEnvironment in pre-3.0 versions), ILoggerFactory and IServiceProvider. Note that the latter is an instance built by the hosting layer and contains only the essential services for starting up an application.
The ConfigureServices() method does not allow injecting services, it only accepts an IServiceCollection argument. This makes sense because ConfigureServices() is where you register the services required by your application. However you can use services injected in the startup's constructor here, for example:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// Use Configuration here
}
Any services registered in ConfigureServices() can then be injected into the Configure() method; you can add an arbitrary number of services after the IApplicationBuilder parameter:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IFooService>();
}
public void Configure(IApplicationBuilder app, IFooService fooService)
{
fooService.Bar();
}
Manually resolving dependencies
If you need to manually resolve services, you should preferably use the ApplicationServices provided by IApplicationBuilder in the Configure() method:
public void Configure(IApplicationBuilder app)
{
var serviceProvider = app.ApplicationServices;
var hostingEnv = serviceProvider.GetService<IHostingEnvironment>();
}
It is possible to pass and directly use an IServiceProvider in the constructor of your Startup class, but as above this will contain a limited subset of services, and thus has limited utility:
public Startup(IServiceProvider serviceProvider)
{
var hostingEnv = serviceProvider.GetService<IWebHostEnvironment>();
}
If you must resolve services in the ConfigureServices() method, a different approach is required. You can build an intermediate IServiceProvider from the IServiceCollection instance which contains the services which have been registered up to that point:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IFooService, FooService>();
// Build the intermediate service provider
var sp = services.BuildServiceProvider();
// This will succeed.
var fooService = sp.GetService<IFooService>();
// This will fail (return null), as IBarService hasn't been registered yet.
var barService = sp.GetService<IBarService>();
}
Please note:
Generally you should avoid resolving services inside the ConfigureServices() method, as this is actually the place where you're configuring the application services. Sometimes you just need access to an IOptions<MyOptions> instance. You can accomplish this by binding the values from the IConfiguration instance to an instance of MyOptions (which is essentially what the options framework does):
public void ConfigureServices(IServiceCollection services)
{
var myOptions = new MyOptions();
Configuration.GetSection("SomeSection").Bind(myOptions);
}
Or use an overload for AddSingleton/AddScoped/AddTransient:
// Works for AddScoped and AddTransient as well
services.AddSingleton<IBarService>(sp =>
{
var fooService = sp.GetRequiredService<IFooService>();
return new BarService(fooService);
}
Manually resolving services (aka Service Locator) is generally considered an anti-pattern. While it has its use-cases (for frameworks and/or infrastructure layers), you should avoid it as much as possible.
Manually resolving instances involves using the IServiceProvider interface:
Resolving Dependency in Startup.ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMyService, MyService>();
var serviceProvider = services.BuildServiceProvider();
var service = serviceProvider.GetService<IMyService>();
}
Resolving Dependencies in Startup.Configure
public void Configure(
IApplicationBuilder application,
IServiceProvider serviceProvider)
{
// By type.
var service1 = (MyService)serviceProvider.GetService(typeof(MyService));
// Using extension method.
var service2 = serviceProvider.GetService<MyService>();
// ...
}
Resolving Dependencies in Startup.Configure in ASP.NET Core 3
public void Configure(
IApplicationBuilder application,
IWebHostEnvironment webHostEnvironment)
{
application.ApplicationServices.GetService<MyService>();
}
Using Runtime Injected Services
Some types can be injected as method parameters:
public class Startup
{
public Startup(
IHostingEnvironment hostingEnvironment,
ILoggerFactory loggerFactory)
{
}
public void ConfigureServices(
IServiceCollection services)
{
}
public void Configure(
IApplicationBuilder application,
IHostingEnvironment hostingEnvironment,
IServiceProvider serviceProvider,
ILoggerFactory loggerfactory,
IApplicationLifetime applicationLifetime)
{
}
}
Resolving Dependencies in Controller Actions
[HttpGet("/some-action")]
public string SomeAction([FromServices] IMyService myService) => "Hello";
If you generate an application with a template you are going to have something like this on the Startup class:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
}
You can then add dependencies there, for example:
services.AddTransient<ITestService, TestService>();
If you want to access ITestService on your controller you can add IServiceProvider on the constructor and it will be injected:
public HomeController(IServiceProvider serviceProvider)
Then you can resolve the service you added:
var service = serviceProvider.GetService<ITestService>();
Note that to use the generic version you have to include the namespace with the extensions:
using Microsoft.Extensions.DependencyInjection;
ITestService.cs
public interface ITestService
{
int GenerateRandom();
}
TestService.cs
public class TestService : ITestService
{
public int GenerateRandom()
{
return 4;
}
}
Startup.cs (ConfigureServices)
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
services.AddTransient<ITestService, TestService>();
}
HomeController.cs
using Microsoft.Extensions.DependencyInjection;
namespace Core.Controllers
{
public class HomeController : Controller
{
public HomeController(IServiceProvider serviceProvider)
{
var service = serviceProvider.GetService<ITestService>();
int rnd = service.GenerateRandom();
}
If you just need to resolve one dependency for the purpose of passing it to the constructor of another dependency you are registering, you can do this.
Let's say you had a service that took in a string and an ISomeService.
public class AnotherService : IAnotherService
{
public AnotherService(ISomeService someService, string serviceUrl)
{
...
}
}
When you go to register this inside Startup.cs, you'll need to do this:
services.AddScoped<IAnotherService>(ctx =>
new AnotherService(ctx.GetService<ISomeService>(), "https://someservice.com/")
);
You can inject dependencies in attributes like AuthorizeAttribute in this way
var someservice = (ISomeService)context.HttpContext.RequestServices.GetService(typeof(ISomeService));
I know this is an old question but I'm astonished that a rather obvious and disgusting hack isn't here.
You can exploit the ability to define your own ctor function to grab necessary values out of your services as you define them... obviously this would be ran every time the service was requested unless you explicitly remove/clear and re-add the definition of this service within the first construction of the exploiting ctor.
This method has the advantage of not requiring you to build the service tree, or use it, during the configuration of the service. You are still defining how services will be configured.
public void ConfigureServices(IServiceCollection services)
{
//Prey this doesn't get GC'd or promote to a static class var
string? somevalue = null;
services.AddSingleton<IServiceINeedToUse, ServiceINeedToUse>(scope => {
//create service you need
var service = new ServiceINeedToUse(scope.GetService<IDependantService>())
//get the values you need
somevalue = somevalue ?? service.MyDirtyHack();
//return the instance
return service;
});
services.AddTransient<IOtherService, OtherService>(scope => {
//Explicitly ensuring the ctor function above is called, and also showcasing why this is an anti-pattern.
scope.GetService<IServiceINeedToUse>();
//TODO: Clean up both the IServiceINeedToUse and IOtherService configuration here, then somehow rebuild the service tree.
//Wow!
return new OtherService(somevalue);
});
}
The way to fix this pattern would be to give OtherService an explicit dependency on IServiceINeedToUse, rather than either implicitly depending on it or its method's return value... or resolving that dependency explicitly in some other fashion.
You can inject dependencies using IApplicationBuilder instance in this way
public void Configure(IApplicationBuilder app)
{
//---------- Your code
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var resultLogic = serviceScope.ServiceProvider.GetService<IResultLogic>();
resultLogic.YourMethod();
}
//---------- Your code
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ISelfServiceConfigLoad, SelfServiceConfigLoader>();
var sp = services.BuildServiceProvider();
var configservice = sp.GetServices<ISelfServiceConfigLoad>();
services.AddSingleton<IExtractor, ConfigExtractor>( sp =>
{
var con = sp.GetRequiredService<ISelfServiceConfigLoad>();
var config = con.Load();
return new ConfigExtractor(config.Result);
});
services.AddSingleton<IProcessor<EventMessage>, SelfServiceProcessor>();
services.AddTransient<ISolrPush, SolrDataPush>();
services.AddSingleton<IAPICaller<string, string>, ApiRestCaller<string, string>>();
services.AddSingleton<IDataRetriever<SelfServiceApiRequest, IDictionary<string, object>>, SelfServiceDataRetriever>();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<ConfigurationRepository>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SqlConnectionString")));
services.AddScoped<IConfigurationBL, ConfigurationBL>();
services.AddScoped<IConfigurationRepository, ConfigurationRepository>();
}

Categories