Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ public class DefaultBootstrapHostHandler<TModule> where TModule : class

protected virtual void ConfigureServices(HostBuilderContext hostBuilderContext, IServiceCollection services, IServiceProvider serviceProvider)
{
IFilter<IModulesHostBuilderContext, IServiceCollection> moduleServicesFilterAdapter =
new GenericModuleHostBuilderContextAdapter<IModuleServicesFilter<TModule>, TModule, IServiceCollection>();

Filters.BuildFilterPipeline(
BootstrapContext.Advanced.FrameworkServices.GetServices<IModuleServicesFilter>()
.Append(GenericModuleHostBuilderContextAdapter<IServiceCollection>.Create(typeof(IModuleServicesFilter<>)))
.Append(moduleServicesFilterAdapter)
, (ctx, s) =>
{
ModuleMethodInvoker.CallOptionalMethod(BootstrapContext.ToModuleContext(serviceProvider), "ConfigureServices", serviceProvider, s);
Expand Down
65 changes: 65 additions & 0 deletions src/Hosuto.Hosting/Modules/Hosting/Internal/ModuleRegistration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Dbosoft.Hosuto.Modules.Hosting.Internal
{
/// <summary>
/// Non-generic handle for a registered module. Concrete instances are
/// <see cref="ModuleRegistration{TModule}"/>, which keeps the module type as a static generic
/// parameter so all closed generic instantiations (module host, hosted service, module type) are
/// visible to the (AOT) compiler instead of being reconstructed at runtime via reflection.
/// </summary>
internal interface IModuleRegistration
{
Type ModuleType { get; }

ModuleHostingOptions Options { get; }

void RegisterModule(IServiceCollection services, IModuleHostServiceProviderFactory serviceProviderFactory);

void RegisterHost(IServiceCollection services, IServiceProvider frameworkServices);

void Bootstrap(IServiceProvider moduleHostServices, IServiceProvider frameworkServices);
}

internal sealed class ModuleRegistration<TModule> : IModuleRegistration where TModule : class
{
public ModuleRegistration(ModuleHostingOptions options)
{
Options = options;
}

public Type ModuleType => typeof(TModule);

public ModuleHostingOptions Options { get; }

public void RegisterModule(IServiceCollection services, IModuleHostServiceProviderFactory serviceProviderFactory)
{
if (serviceProviderFactory != null)
{
serviceProviderFactory.ConfigureModule(typeof(TModule), Options.ModuleFactory);
return;
}

if (Options.ModuleFactory == null)
services.AddSingleton<TModule>();
else
services.AddSingleton(sp => (TModule)Options.ModuleFactory(sp));
}

public void RegisterHost(IServiceCollection services, IServiceProvider frameworkServices)
{
var internalHost = frameworkServices.GetRequiredService<IModuleHost<TModule>>();

services.AddSingleton<Dbosoft.Hosuto.Modules.Hosting.IModuleHost<TModule>>(_ => internalHost);
services.AddTransient<IHostedService, ModulesHostService<TModule>>();
}

public void Bootstrap(IServiceProvider moduleHostServices, IServiceProvider frameworkServices)
{
frameworkServices.GetRequiredService<IModuleHost<TModule>>()
.Bootstrap(moduleHostServices, Options);
}
}
}
71 changes: 31 additions & 40 deletions src/Hosuto.Hosting/Modules/Hosting/ModulesHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,48 @@ public class ModulesHostBuilder : IModulesHostBuilder

public IDictionary<object, object> Properties => _innerBuilder.Properties;

private readonly Dictionary<Type, ModuleHostingOptions> _registeredModules = new Dictionary<Type, ModuleHostingOptions>();
private readonly Dictionary<Type, IModuleRegistration> _registeredModules = new Dictionary<Type, IModuleRegistration>();
private readonly List<Action<HostBuilderContext, IServiceCollection>> _configureFrameworkActions = new List<Action<HostBuilderContext, IServiceCollection>>();
private bool _hostBuilt;
private readonly IHostBuilder _innerBuilder = new HostBuilder();

public IModulesHostBuilder HostModule<TModule>(Action<IModuleHostingOptions> options = null) where TModule : class
{
HostModule(typeof(TModule), options);
// Generic overload: the closed ModuleRegistration<TModule> keeps the module type as a
// static generic parameter, so no reflection/MakeGenericType is needed to host it.
AddModuleRegistration(new ModuleRegistration<TModule>(CreateHostingOptions(options)));
return this;
}

public IModulesHostBuilder HostModule(Type moduleType, Action<IModuleHostingOptions> options = null)
{
if (_registeredModules.ContainsKey(moduleType))
throw new InvalidOperationException($"Module of type {moduleType} is already used.");
if (moduleType == null) throw new ArgumentNullException(nameof(moduleType));

var hostingOptions = new ModuleHostingOptions();
options?.Invoke(hostingOptions);
// Type-based overload: the module type is only known at runtime, so the closed
// registration has to be created reflectively here. Prefer the generic HostModule<TModule>
// overload, which is trimming/AOT friendly.
var registration = (IModuleRegistration)Activator.CreateInstance(
typeof(ModuleRegistration<>).MakeGenericType(moduleType), CreateHostingOptions(options));

_registeredModules.Add(moduleType, hostingOptions);
AddModuleRegistration(registration);
return this;
}

private void AddModuleRegistration(IModuleRegistration registration)
{
if (_registeredModules.ContainsKey(registration.ModuleType))
throw new InvalidOperationException($"Module of type {registration.ModuleType} is already used.");

_registeredModules.Add(registration.ModuleType, registration);
}

private static ModuleHostingOptions CreateHostingOptions(Action<IModuleHostingOptions> options)
{
var hostingOptions = new ModuleHostingOptions();
options?.Invoke(hostingOptions);
return hostingOptions;
}

public IModulesHostBuilder ConfigureHostConfiguration(
Action<IConfigurationBuilder> configureDelegate)
{
Expand Down Expand Up @@ -127,19 +146,12 @@ public IHost Build()
var moduleHostServicesFactory = frameworkServices.GetService<IModuleHostServiceProviderFactory>();
moduleHostServicesFactoryState = moduleHostServicesFactory?.ConfigureServices(services);

foreach (var module in _registeredModules)
foreach (var registration in _registeredModules.Values)
{
moduleHostServicesFactory?.ConfigureModule(module.Key, module.Value.ModuleFactory);
if (moduleHostServicesFactory != null) continue;

if (module.Value.ModuleFactory == null)
services.AddSingleton(module.Key);
else
services.AddSingleton(module.Key, module.Value.ModuleFactory);
registration.RegisterModule(services, moduleHostServicesFactory);
registration.RegisterHost(services, frameworkServices);
}

RegisterModulesAndHosts(services, frameworkServices);

}).Build();

var moduleHostServiceProvider = host.Services;
Expand All @@ -159,20 +171,6 @@ public IHost Build()
return host;
}

private void RegisterModulesAndHosts(IServiceCollection services, IServiceProvider frameworkServices)
{
foreach (var module in _registeredModules)
{
var internalHostType = typeof(Internal.IModuleHost<>).MakeGenericType(module.Key);
var serviceType = typeof(IModuleHost<>).MakeGenericType(module.Key);
var hostedServiceType = typeof(ModulesHostService<>).MakeGenericType(module.Key);

var internalHost = frameworkServices.GetRequiredService(internalHostType);
services.AddSingleton(serviceType, sp => internalHost);
services.AddTransient(typeof(IHostedService), hostedServiceType);
}
}

IHostBuilder IHostBuilder.ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
{
return ConfigureHostConfiguration(configureDelegate);
Expand Down Expand Up @@ -235,18 +233,11 @@ public IModulesHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuil
}


private static IModuleHost GetModuleHost(Type moduleType, IServiceProvider frameworkServices)
{
var hostType = typeof(Internal.IModuleHost<>).MakeGenericType(moduleType);
var internalHost = frameworkServices.GetRequiredService(hostType) as IModuleHost;
return internalHost;
}

private void BootstrapModuleHosts(IServiceProvider moduleHostServices, IServiceProvider frameworkServices)
{
foreach (var module in _registeredModules)
foreach (var registration in _registeredModules.Values)
{
GetModuleHost(module.Key, frameworkServices)?.Bootstrap(moduleHostServices, module.Value);
registration.Bootstrap(moduleHostServices, frameworkServices);
}
}

Expand Down
10 changes: 8 additions & 2 deletions src/Hosuto.SimpleInjector/Modules/Hosting/BootstrapHostFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ public class BootstrapHostFilter<TModule> : IBootstrapHostFilter<TModule> where

private static void UseSimpleInjector(IModuleContext moduleContext, SimpleInjectorUseOptions options)
{
IFilter<IModuleContext, SimpleInjectorUseOptions> useSimpleInjectorAdapter =
new GenericModuleContextFilterAdapter<IUseSimpleInjectorFilter<TModule>, TModule, SimpleInjectorUseOptions>();

Filters.BuildFilterPipeline(
moduleContext.Advanced.FrameworkServices.GetServices<IUseSimpleInjectorFilter>()
.Append(GenericModuleContextFilterAdapter<SimpleInjectorUseOptions>.Create(typeof(IUseSimpleInjectorFilter<>))),
.Append(useSimpleInjectorAdapter),
(ctx, o) =>
{
ModuleMethodInvoker.CallOptionalMethod(ctx, "UseSimpleInjector", o);
Expand All @@ -25,9 +28,12 @@ private static void UseSimpleInjector(IModuleContext moduleContext, SimpleInject

private static void ConfigureContainer(IModuleContext moduleContext, Container container)
{
IFilter<IModuleContext, Container> configureContainerAdapter =
new GenericModuleContextFilterAdapter<IConfigureContainerFilter<TModule>, TModule, Container>();

Filters.BuildFilterPipeline(
moduleContext.Advanced.FrameworkServices.GetServices<IConfigureContainerFilter>()
.Append(GenericModuleContextFilterAdapter<Container>.Create(typeof(IConfigureContainerFilter<>))),
.Append(configureContainerAdapter),
(ctx, c) =>
{
ModuleMethodInvoker.CallOptionalMethod(ctx, "ConfigureContainer", container);
Comment on lines 35 to 39

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing behavior, not changed by this PR — the diff here only swaps the adapter construction, not this delegate (same pattern in UseSimpleInjector/ConfigureServices). In practice the container/options filters mutate the passed instance rather than substituting a different one, so c == the captured container. Leaving it out of scope of this no-behavior-change refactor; can be cleaned up separately.

Expand Down