Начальная загрузка приложения ASP.NET Core 2.1, содержащего несколько типов модулей, с использованием DI - PullRequest
0 голосов
/ 06 сентября 2018

Началось создание инфраструктуры для мультитенантного приложения 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(), я не смогу добавить новые сервисы, что я должен сделать после создания модулей. Какой подход, если это вообще возможно? Есть мнения, идеи?

Ух ты такая стена текста, спасибо, что прочли вообще!

1 Ответ

0 голосов
/ 06 сентября 2018

Ну, просто еще одно мнение, но мы используем тот же стандарт и команду Net Core, создаем методы расширения, поэтому нам просто нужно добавить службы, где они требуются:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddVtaeCommonServices();
        services.AddMyServiceMX(Configuration);
        services.AddOtherService(Configuration);

        return VtaeConfig.ConfigVtae(services);
    }
}

С помощью этого метода расширения, например:

public static class CommonServicesExtension
{

    public static IServiceCollection AddVtaeCommonServices(this IServiceCollection services)
    {
        services.AddNodaDateTime();
        services.AddMemoryCache();
        services.AddScoped<AuthorizedIpFilter>();
        services.AddScoped<HttpContextManager>();
        services.AddTransient<ProspectService>();
        services.AddTransient<TokenService>();
        services.AddTransient<TokenGenerator>();
        services.AddTransient<ProspectsRepository>();
        services.AddSingleton<UniqueIDGenerator>();
        services.AddSingleton<SchedulerService>();
        services.AddSingleton<ChecksumService>();
        return services;
    }
}

Таким образом, мы можем извлечь наши функции из пакетов nuget, а затем просто повторно использовать их, добавив AddXXXService() в нашем запуске

...