Supplying IOptions to Autofac Module - asp.net-core

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.

Related

Asp.Net Core: creating a new scoped IServiceProvider

Objective
Create a asp.net core based solution that permits plugins loaded in runtime, way after IServiceCollection/IServiceProvider have been locked down to change.
Issue
IServiceCollection is configured at startup, from which IServiceProvider is developed, then both are locked for change before run is started.
I'm sure there are great reasons to do this....but I rue the day they came up with it being the only way to do things... so:
Attempt #1
Was based on using Autofac's ability to make child containers, falling back to parent containers for whatever is not specific to the child container,
where, right after uploading the new plugin, I create a new ILifetimeScope so that I can add Services given its containerBuilder:
moduleLifetimeScope = _lifetimeScope.BeginLifetimeScope(autoFacContainerBuilder =>
{
//can add services now
autoFacContainerBuilder.AddSingleton(serviceType, tInterface);
}
save the scope and its Container in a dictionary, against controllerTypes found in the dll, so that:
later can use a custom implementation of IControllerActivator to first try with the default IServiceProvider before falling back to try in the child plugin's child container.
The upside was, Holy cow, with a bit of hacking around, slowly got Controllers to work, then DI into Controllers, then OData....
The downside was that its custom to a specific DI library, and the Startup extensions (AddDbContext, AddOData) were not available as autoFacContainerBuilder doesn't implement IServiceCollection, so it became a huge foray into innards...that sooner or later couldn't keep on being pushed uphill (eg: couldn't figure out how to port AddDbContext)
Attempts #2
At startup, save a singleton copy of the original ISourceCollectionin theISourceCollection` (to easily re-get it later)
Later, upon loading a new plugin,
Clone the original ISourceCollection
Add to the clonedServiceCollection new Plugin Services/Controllers found in by Reflection
Use standard extension methods to AddDbContext and AddOData, etc.
Use a custom implementation of IControllerActivator as per above, falling back to the child IServiceProvider
Holy cow. Controllers work, OData works, DbContext works...
Hum...it's not working perfectly. Whereas the Controllers and being created new on every request, it's the same DbContext every time, because it's not being disposed, because it's not scoped by some form of scopefactory.
Attempt #3
Same thing as #2, but instead of making the IServiceProvider when the module is loaded, now -- in the custom IControllerActivator making a new IServiceProvider on each request.
No idea how much memory/time this is wasting, but I'm guessing its ...not brilliant
But sure...but I've really just pushed the problem a bit further along, not gotten rid of it:
A new IServiceProvider is being created...but nothing is actually disposing of it either.
backed by the fact that I'm watching memory usage increase slowly but surely....
Attempt #4
Same as above, but instead of creating a new IServiceProvider on every request, I'm keeping the IServiceProvider that i first built when I uploaded the module, but
using it to built a new Scope, and get its nested IServiceProvider,
hold on to the scope for later disposal.
It's a hack as follows:
public class AppServiceBasedControllerActivator : IControllerActivator {
public object Create(ControllerContext actionContext)
{
...
find the cached (ControllerType->module Service Provider)
...
var scope = scopeDictionaryEntry.ServiceProvider.CreateScope();
httpController = serviceProvider.GetService(controllerType);
actionContext.HttpContext.Items["SAVEMEFROMME"] = scope;
return httpController;
}
public virtual void Release(ControllerContext context, object controller)
{
var scope = context.HttpContext.Items["SAVEMEFROMME"] as IServiceScope;
if (scope == null){return;}
context.HttpContext.Items.Remove("SAVEMEFROMME");
scope.Dispose(); //Memory should go back down..but doesn't.
}
}
}
Attempt #5
No idea. Hence this Question.
I feel like I'm a little further along...but just not closing the chasm to success.
What would you suggest to permit this, in a memory safe way?
Background Musings/Questions in case it helps?
As I understand it, the default IServiceProvider doesn't have a notion of child lifespan/containers, like Autofac can create.
I see a IServiceScopeFactory makes a new IServiceProvider.
I understand there is some middleware (what name?) that invokes IServiceScopeFactory to make a IServiceProvider on every single request (correct?)
are these per-request IServiceProviders really separate/duplicate, and don't 'descend' from a parent one and falls back to parent if a asked for a singleton?
What is the Middleware doing different to dispose/reduce memory at the end of the call?
Should I be thinking about replacing the middleware? But even if it could -- it's so early that I only would have an url, not yet a Controller Type, therefore don't know what Plugin Assembly the Controller came from, therefore don't know what IServiceProvider to use for it...therefore too early to be of use?
Thank you
Getting a real grip on adding plugin sourced scoped services/controllers/DbContexts would be...wow. Been looking for this capability for several months now.
Thanks.
Other Posts
some similarity to:
Use custom IServiceProvider implementation in asp.net core
but I don't see how his disposing is any different to what I'm doing, so are they too having memory issues?

Autofac Multitenant Database Configuration

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...

Configuring options for middleware

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).

Lost scope using DependencyResolver in another class library, using Castle Windsor and FluentNhibernate and NHibernate

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.

RoleProvider injection with Ninject

First off I want to say there is a ton of answers on SO and google searches surrounding this, however I'm running into an issue that prevents those solutions from working. The answer here seems to be the way to go. (kernel.Inject(Roles.Provider);)
The issue that I'm having is that when I'm trying to inject the RoleProvider Roles.Provider is null, however my custom provider is found in the list within Roles.Providers. I am thinking that Ninject is trying to access the role provider too soon.
In my NinjectWebCommon class it appears that it's using WebActivator to start itself. [assembly: WebActivator.PreApplicationStartMethod(typeof(Admin.App_Start.NinjectWebCommon), "Start")]
It appears that all of the articles I've come across are using older versions of Ninject and are doing a lot of the heavy lifting in the Global.asax Application_Start... Using my implementation how can I get DI working for a custom role provider?
I'm using the [Inject] attribute within my custom provider.
The WebActivator pipeline runs way before even the standard ASP.NET stack is created. It means that you won't have access to anything created by ASP.NET during bootstrap in NinjectWebCommon.
Use that file only to declare bindings that do not depend on ASP.NET stuff to be up.
In order to get around this issue, you should use Global.asax Application_Start handler to load any additional modules/bindings that are dependend on ASP.NET stuff such as Roles.Provider.
Here is a suggestion that may solve your problem:
public void Application_Start()
{
var kernel = (new Bootstrapper()).Kernel;
kernel.Inject(Roles.Provider);
//Other initialization stuff
}
The Bootstrapper class is a lazy singleton that has a static IKernel initialized within your NinjectWebCommon.cs. So this is the proper way of retrieving the configured kernel instance from outside your NinjectWebCommon.
Give it a try.