Looking on different libraries and even Microsoft code I've noticed two different ways of configuring options in code:
in ConfigureServices it can be done when registering DependencyInjection:
services.AddMvc(options => { });
or in Configure
app.UseStaticFiles(
new StaticFileOptions
{
ServeUnknownFileTypes = true
});
I tried to find out, which way use for which purpose and still don't know, assuming that creating your own middleware and registering both DI and usage.
Interesting issue you have found.
Looking into the source code i have found the following:
All the middleware registrations are an overload of this UseMiddleware function, which is an overload of IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);.
In these overloads you can specify hard your own parameters for the middle-ware. Say you have a few strings in the constructor, the DI container will not be able to resolve this.
If the parameters aren't set though, it does fallback to the IServiceProvider to resolve the specific type. see extension class: Microsoft.Extensions.Internal.ActivatorUtilities (inside: Microsoft.AspNetCore.Http.Abstractions.dll)
Tips
As for best practice think about the following (my opinion though):
Try to avoid using simple types in constructor but use an Options class.
for IOptions use services.Configure<>() as here you can specify options from a ConfigSection => appsettings.
As for Services: be aware that middleware is a singleton! so adding a transient Service, will only be resolved once for this middleware!
I think best practise is to: Register Middleware AND its dependencies as singletons in IOC on startup. then resolve it yourself and add it yourself with the method App.Use([yourDelegate]).
The advantage of this method is it is easilier understandable than use the hidden microsoft code + Microsoft recently published an upgrade to the DI container to check if the scopes of your registrations match properly (and warn you if not).
This tool basically does: it checks if a dependency of a service has a smaller scope then the service itself: say service is scope: Singleton and the dependency is scope Transient. this means that if Singleton is resolved Transient is also resolved for this singleton and thus not resolved again on next usage of this singleton. (so its a hidden singleton usage).
Related
I am implementing a Custom Configuration Provider in my application.
In that provider, I have to make a REST API call. That call needs a valid OAuth 2 Token to succeed. To get that token I need a semi complicated tree of class dependencies.
For the rest of my application, I just use dependency injection to get the needed instance. But a custom configuration provider is called well before dependency injection is setup.
I have thought about making a "Pre" instance of dependency injection. Like this:
IServiceCollection services = new ServiceCollection();
// Setup the DI here
IServiceProvider serviceProvider = services.BuildServiceProvider();
var myTokenGenerator = serviceProvider.GetService<IMyTokenGenerator>();
But I have read that when you make another ServiceCollection, it can cause problems. I would like to know the way to avoid those problems.
How do I correctly cleanup a "pre-DI" instance of ServiceCollection and ServiceProvider?
(Note: Neither one seems to implement IDisposable.)
Hm, I don't get the point why you want to do it that way.
I'd probably get the Serviceprovider fully build.
To avoid that retrieved services affect each other I'd would use nested containers/scopes which means that if you retrieve retrieve the same service you get different instances per container/scope.
Hopefully I understood what you want to achieve.
See
.NET Core IServiceScopeFactory.CreateScope() vs IServiceProvider.CreateScope() extension
Short version:
Simply put i would like to inject IOptions<TModuleOptions> (or just TModuleOptions) into an autofac module, but I cannot figure out how to do so without manually wiring up the options class (which sort of defeats the point).
Is this even possible, and how?
The longer version:
I have an ASP.NET Core 3.1 project using Autofac as the DI container, and a module that requires some configuration options. Like a name and a URL for instance.
In the startup i have something like:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<ModuleOptions>(Configuration.GetSection(ModuleOptions.ModuleSettings));
// Stuff remove for brevity
}
public void ConfigureContainer(ContainerBuilder builder)
{
ModuleOptions options = options; // It would be nice with a way to get this here or have it resolved "automagically".
builder.RegisterModule(new CustomModule());
}
I tried different things, but I can't really see how i might go about doing it in a "nice" manner.
The closet I have gotten is by doing the configuration binding manually like so:
var options = this.Configuration.GetSection("Something").Get<ModuleOptions>();
It works, but "feels" like its not the "idiomatic" .net core way of handling things.
Is it possible to achieve what I want using the DI container similar to how I would do it if I was using the MS DI or RegisterType(context => context.Resolve<IOptions<TModuleOptions>>()) ?
You can't supply IOptions<T> to a module like that because it's a circular dependency. A module executes registrations... but in order to resolve the IOptions<T> you need to build the container, which means you can't register things anymore.
If you think about it, that's actually correct behavior, because technically the IOptions<T> could end up causing something different to be registered, which would affect the IOptions<T>, which would change what gets registered, which would affect the IOptions<T>... yeah.
For bootstrap/app startup code, unfortunately you really can't over-DI it. Your mechanism of getting options from configuration is probably as good as it gets.
The reason you can kind of DI things into Startup is because internally the .NET Core hosting mechanism builds two containers. The first is super barebones and has config, logging, and hosting things in it; the second is the one you build as part of Startup and is your app container.
Anyway... yeah, the best you'll get is the config reading, and I'd recommend sticking with that.
I have a base abstract context which has a couple hundred shared objects, and then 2 "implementation" contexts which both inherit from the base and are designed to be used by different tenants in a .net core application. A tenant object is injected into the constructor for OnConfiguring to pick up which connection string to use.
public abstract class BaseContext : DbContext
{
protected readonly AppTenant Tenant;
protected BaseContext (AppTenant tenant)
{
Tenant = tenant;
}
}
public TenantOneContext : BaseContext
{
public TenantOneContext(AppTenant tenant)
: base(tenant)
{
}
}
In startup.cs, I register the DbContexts like this:
services.AddDbContext<TenantOneContext>();
services.AddDbContext<TenantTwoContext>();
Then using the autofac container and th Multitenant package, I register tenant specific contexts like this:
IContainer container = builder.Build();
MultitenantContainer mtc = new MultitenantContainer(container.Resolve<ITenantIdentificationStrategy>(), container);
mtc.ConfigureTenant("1", config =>
{
config.RegisterType<TenantOneContext>().AsSelf().As<BaseContext>();
});
mtc.ConfigureTenant("2", config =>
{
config.RegisterType<TenantTwoContext>().AsSelf().As<BaseContext>();
});
Startup.ApplicationContainer = mtc;
return new AutofacServiceProvider(mtc);
My service layers are designed around the BaseContext being injected for reuse where possible, and then services which require specific functionality use the TenantContexts.
public BusinessService
{
private readonly BaseContext _baseContext;
public BusinessService(BaseContext context)
{
_baseContext = context;
}
}
In the above service at runtime, I get an exception "No constructors on type 'BaseContext' can be found with the constructor finder 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'". I'm not sure why this is broken....the AppTenant is definitely created as I can inject it other places successfully. I can make it work if I add an extra registration:
builder.RegisterType<TenantOneContext>().AsSelf().As<BaseContext>();
I don't understand why the above registration is required for the tenant container registrations to work. This seems broken to me; in structuremap (Saaskit) I was able to do this without adding an extra registration, and I assumed using the built in AddDbContext registrations would take care of creating a default registration for the containers to overwrite. Am I missing something here or is this possibly a bug in the multitenat functionality of autofac?
UPDATE:
Here is fully runable repo of the question: https://github.com/danjohnso/testapp
Why is line 66 of Startup.cs needed if I have lines 53/54 and lines 82-90?
As I expected your problem has nothing to do with multitenancy as such. You've implemented it almost entirely correctly, and you're right, you do not need that additional registration, and, btw, these two (below) too because you register them in tenant's scopes a bit later:
services.AddDbContext<TenantOneContext>();
services.AddDbContext<TenantTwoContext>();
So, you've made only one very small but very important mistake in TenantIdentitifcationStrategy implementation. Let's walk through how you create container - this is mainly for other people who may run into this problem as well. I'll mention only relevant parts.
First, TenantIdentitifcationStrategy gets registered in a container along with other stuff. Since there's no explicit specification of lifetime scope it is registered as InstancePerDependency() by default - but that does not really matter as you'll see. Next, "standard" IContainer gets created by autofac's buider.Build(). Next step in this process is to create MultitenantContainer, which takes an instance of ITenantIdentitifcationStrategy. This means that MultitenantContainer and its captive dependency - ITenantIdentitifcationStrategy - will be singletons regardless of how ITenantIdentitifcationStrategy is registered in container. In your case it gets resolved from that standard "root" container in order to manage its dependencies - well, this is what autofac is for anyways. Everything is fine with this approach in general, but this is where your problem actually begins. When autofac resolves this instance it does exactly what it is expected to do - injects all the dependencies into TenantIdentitifcationStrategy's constructor including IHttpContextAccessor. So, right there in the constructor you grab an instance of IHttpContext from that context accessor and store it for using in tenant resolution process - and this is a fatal mistake: there's no http request at this time, and since TenantIdentitifcationStrategy is a singleton it means that there will not ever be one for it! So, it gets null request context for the whole application lifespan. This effectively means that TenantIdentitifcationStrategy will not be able to resolve tenant identifier based on http requests - because it does not actually analyze them. Consequently, MultitenantContainer will not be able to resolve any tenant-specific services.
Now when the problem is clear, its solution is obvious and trivial - just move fetching of request context context = _httpContextAccessor.HttpContext to TryIdentifyTenant() method. It gets called in the proper context and will be able to access request context and analyze it.
PS. This digging has been highly educational for me since I had absolutely no idea about autofac's multi-tenant concept, so thank you very much for such an interesting question! :)
PPS. And one more thing: this question is just a perfect example of how important well prepared example is. You provided very good example. Without it no one would be able to figure out what the problem is since the most important part of it was not presented in the question - and sometimes you just don't know where this part actually is...
In my WCF project I register my interface using Castle Windsor in the global.asax:
Component.For<IStrategy>()
.ImplementedBy<MyStrategy>()
.LifestylePerWcfOperation(),
Then later on in the same file I configure NHibernate using FluentNhibernate using a provider:
FluentConfiguration configuration = Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008.ConnectionString(myConnString)
.Provider<ConnectionProvider>())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<TenantMap>()) etc etc
However this ConnectionProvider is hosted in a common shared dll library as there are other WCF services that need to use it. I want to register this dependency as below but it doesn't work which means I have to manually new up a instance.
public ConnectionProvider()
{
// doesnt work
using (IDependencyScope scope = GlobalConfiguration.Configuration.DependencyResolver.BeginScope())
{
_myStrategy = scope.GetService<IStrategy>();
}
}
Is there anyway to make this work? Its like its lost scope as its in another assembly now. In the same assembly the DependencyScope works fine and creates an instance, but I want to keep it in a shared dll.
EDIT: The error I get is "System.Web.Http.Dependencies.EmptyResolver" on a watch on this bit of code: GlobalConfiguration.Configuration.DependencyResolver
I can see several problems with the code above:
1) You are registering IStrategy,MyStrategy with a per WcfOperation lifestyle. This means that windsor will create a strategy for each WcfOperation. On the otherhand you are trying to manually set the lifestyle of the component by using scope.GetService. For scope.GetService to work you will need a lifestyle scoped.
2) Assuming that the code for ConnectionProvider above is the constructor, it seems that the constructor is trying to get something from the container. This is in general a bad idea, and even worse when using an Ioc container like windsor. Instead pass the IStrategy to the constructor (inject it).
3) Seeing that you are calling the container a constructor here, probably means that you are not adhering to the principle that there should be only 3 calls to the container, 1 to register component, 1 to retrieve a top level component, and 1 to release the container.
I suggest you read a bit more about depedency injection and Ioc containers in general to fully understand how to work with this container.
I'm registering components that require special handling on release (namely, a WCF clients) via a config file.
<autofac>
<components>
<component
type="SomeType"
service="ISomeType"/>
</components>
</autofac>
However, via the configuration file, I don't see how I can specify an event.
I've looked at implementing the OnRelease event via a module, but I seem to lack access to some of the innards required to get it working. Mainly, in:
registration.Activating += (s, e) =>
{
var ra = new ReleaseAction(() => ReleaseWcfClient(e.Instance));
e.Context.Resolve<ILifetimeScope>().Disposer.AddInstanceForDisposal(ra);
};
The ReleaseAction class is not available outside Autofac.
Autofac does not support setting up events via XML configuration. You must do that in code.
However, one way you can do this in a more cross-cutting fashion is to create an Autofac module and override the AttachToComponentRegistration method. In there, you can test each registration for something (like whether it's a registration for a particular interface) and attach your Activating event handler there.
There is an example of using AttachToComponentRegistration on the Autofac wiki showing how you can use this event to wire up log4net.
Two other notes:
You might be interested in using the UseWcfSafeRelease() registration extension for WCF client proxies. I don't know what your ReleaseWcfClient() method is doing internally, but if the point of it is to handle the possible exceptions from WCF, Autofac has that in UseWcfSafeRelease(). You can read about that on the wiki.
If a component implements IDisposable (the way WCF clients do) then Autofac will automatically track it and handle disposal. If you have something specific you want to run when Autofac executes disposal, you'd want to use OnRelease for the registration. If it implements IDisposable and you want to set up your own disposal solution, you need to register the component using ExternallyOwned() so your component isn't double-disposed.
(I don't know if either of those latter points apply here, but thought I'd raise them since we were in the territory.)