Reputation: 5505
When I create the webhost for an ASP.NET Core application I can specify the Startup
class but not an instance.
The constructor of your own Startup class can take parameter which are provided through DI. I know how to register services for DI within ConfigureServices
but as that is a member of that class these services are not available for the constructor of my startup class.
How do I register services which will be available as constructor parameter of the Startup class?
The reason is that I have to supply an object instance which must be created outside/before the webhost is created and I do not want to pass it in a global-like style.
Code to create the IWebHost:
this.host = new WebHostBuilder()
.UseConfiguration(config)
.UseKestrel()
.UseIISIntegration()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<WebStartup>()
log.Debug("Run webhost");
this.host.Start();
Constructor of WebStartup
:
public WebStartup(IHostingEnvironment env, MyClass myClass)
{
var config = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
.Build();
...
}
So specifically, how to I register MyClass
in this example (which obviously must be done before WebStartup
is instanciated by the IWebHost
)?
Upvotes: 5
Views: 5458
Reputation: 21548
You have an instance of MyClass
called myClass
and you need to inject that instance into your Startup
class. I had a similar requirement and after some experimentation I came up with the following.
First, we inject the myClass
instance before we call UseStartup
:
var host = new WebHostBuilder()
// .... other code
.ConfigureServices(services => services.AddSingleton(myClass)) // Inject myClass instance
.UseStartup<WebStartup>()
Now we need to get hold of the object in Startup
(WebStartup
in your case). I wasn't able to find a way to access it from the constructor, but that shouldn't matter as it can be accessed from within the startup class' ConfigureServices()
and, if necessary, saved to a field or property from there if it need to be made available to Startup.Configure()
which is called later. Here is what I came up with:
// This goes into Startup.cs/WebStartup.cs
public virtual void ConfigureServices(IServiceCollection services)
{
var myClass = services.Single(s => s.ServiceType == typeof(MyClass)).ImplementationInstance as MyClass;
if (myClass == null)
throw new InvalidOperationException(nameof(MyClass) + " instance is null");
// Now do all the things
}
I suspect that there may be something in the Framework to retrieve the injected instance more easily, but if not - or until I find it - I can confirm that the above works perfectly!
Upvotes: 1
Reputation: 46531
Although Steven's concerns are valid and you should take note of them, it is technically possible to configure the DI container that is used to resolve your Startup class.
ASP.NET hosting uses dependency injection to wire up an instance of your Startup class and also let us add our own services to that container using the ConfigureServices
extension method on IWebHostBuilder
:
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.ConfigureServices(services => services.AddSingleton<IMyService, MyService>())
.UseStartup<Startup>()
.Build();
host.Run();
and:
public Startup(IMyService myService)
{
myService.Test();
}
In fact, all that UseStartup<WebStartup>
does is adding it as a service implementation of IStartup
to the hosting DI container (see this).
Please note that instances of your services will be resolved again in the application container as a new instance of the IServiceProvider
will be built. The registration of the services will, however, be passed to the application IServiceCollection
in your Startup class.
Upvotes: 9
Reputation: 172646
This is a 'chicken or the egg' problem: You can't let the DI container resolve an object graph before it is configured.
Your problem however should not exist, because just as you should strictly separate the registration phase of the container from the resolve-phase (as ASP.NET Core enforces upon you), the same way should you separate the registration phase from the phase before that where you load configuration values.
What this means is that classes that you require during the registration phase of the container should not be resolved by the container. because that could lead to common problems that are hard to track down.
Instead you should create the class by hand. For instance:
public class Startup
{
private static MyDependency dependency;
public Startup(IHostingEnvironment env)
{
dependency = new MyDependency();
var instance = new MyClass(dependency);
var builder = new ConfigurationBuilder()
.SetBasePath(instance.ContentRootPath)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// Register the dependency if it is required by other components.
services.AddSingleton<MyDependency>(dependency);
// Add framework services.
services.AddMvc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
// etc.
}
Upvotes: 7