Началось создание инфраструктуры для мультитенантного приложения ASP.NET Core 2.1, которое состоит и будет состоять из множества модулей.
Идея состоит в том, чтобы разрешить любые модули, которые будут добавлены в будущем, просто подключаться к системе, регистрируя свои собственные необходимые зависимости при запуске приложения и используя уже зарегистрированные зависимости (зависимости, зарегистрированные другими модулями), если это необходимо .
Давайте сначала посмотрим на пример кода того, как я представлял, как это будет выглядеть - с макушки головы.
Допустим, у нас есть какой-то менеджер модулей, который мы назовем просто так - ModuleManager
.
public class ModuleManager
{
// Let's store all of our module types here
private List<Type> moduleTypes;
void RegisterModules(Type webHostModuleType, IServiceCollection services)
{
// Find all module dependencies recursively
// (i.e. all modules that are specified in some, let's say 'DependsOn' attribute
// which decorates webHostModuleType)
moduleTypes = FindDependencies();
// Now we need to register all dependencies
foreach (Type moduleType in moduleTypes)
{
services.AddSingleton(moduleType);
}
// ... and we shouldn't forget to register the webHostModuleType too
services.AddSingleton(webHostModuleType);
}
}
Давайте пока остановимся и сначала определим тип модуля. Я думаю, что предполагается, что каждый тип модуля может иметь различные свойства и поля в зависимости от их потребностей, но мы должны сделать так, чтобы все они были производными от базового типа модуля. Давайте назовем это BaseModule
, и вот как я себе это представлял:
public abstract class BaseModule
{
// I've currently got no idea on how this method should be defined
// Because it's supposed to register the concrete module's dependencies
// after the module has been instantiated... is that even possible?
// Should it be some kind of a factory method rather than void
// and serve as a lazy initializer? Is that also even possible?
// Something that should make use of IOptions<>?
public virtual void Init()
{
}
// Maybe some post-init code will be needed too
public virtual void PostInit()
{
}
}
И тогда у нас будут определенные типы модулей, определенные следующим образом:
public class CoreModule : BaseModule
{
// some dependencies that need to be injected...
private readonly IHostingEnvironment hostingEnvironment;
private readonly IDontKnowSomeOtherDependency someOtherDependency;
public CoreModule(IHostingEnvironment hostingEnvironment, IDontKnowSomeOtherDependency someOtherDependency)
{
this.hostingEnvironment = hostingEnvironment;
this.someOtherDependency = someOtherDependency;
}
public override void Init()
{
// Somehow register additional services defined by this module,
// after it's been instantiated
}
}
И, скажем, ради полноты наш webHostModuleType
определяется следующим образом:
[DependsOn(typeof(CoreModule))]
public class WebHostModule : BaseModule
{
private readonly ISomeDependencyRegisteredInCoreModule someDependency;
public WebHostModule(ISomeDependencyRegisteredInCoreModule someDependency)
{
this.someDependency = someDependency;
}
public override void Init()
{
// Register some other service based on something from 'someDependency' field
}
}
И, наконец, вернемся к менеджеру модулей. Теперь у него должен быть другой метод, выполняемый после RegisterModules
, который должен создавать экземпляры каждого модуля в правильном порядке, а затем вызывать Init()
и PostInit()
в правильном порядке, начиная с CoreModule
и заканчивая WebHostModule
. Что-то похожее на это:
public void(?) LoadModules()
{
// Sort our modules first so dependencies come in first and webHostModuleType the last
SortEm(moduleTypes);
// Now we need to instantiate them.
// Can't do it manually as all of them might have different constructors
// So need to do it using our service collection
IServiceProvider serviceProvider = serviceCollection.BuildServicePRovider();
foreach (Type moduleType in moduleTypes)
{
BaseModule moduleInstance = serviceProvider.GetRequiredService(moduleType) as BaseModule;
// This is where we can register everything needed by the module instance.
// But can we?
moduleInstance.Init();
moduleInstance.PostInit();
}
// Maybe return the IServiceProvider instance we've built
// so we can return in to the `ConfigureServices` and return to ASP.NET Core from there?
}
Как видите, этот подход вызывает много вопросов. Я иду в правильном направлении вообще? Есть ли способ правильно зарегистрировать сервисы, используя методы модулей Init()
и PostInit()
?
Если я вызову BuildServiceProvider()
и затем создаю экземпляры синглтона, мне придется вернуть этот IServiceProvider
экземпляр обратно в ConfigureServices()
, чтобы ASP.NET Core мог его использовать. Если я этого не сделаю, он создаст новый, а затем все эти синглтоны будут созданы снова.
Но если я позвоню ConfigureServices()
, я не смогу добавить новые сервисы, что я должен сделать после создания модулей. Какой подход, если это вообще возможно? Есть мнения, идеи?
Ух ты такая стена текста, спасибо, что прочли вообще!