Skip to content

Commit

Permalink
#75 - use child container service collection
Browse files Browse the repository at this point in the history
  • Loading branch information
dazinator committed Sep 25, 2020
1 parent 51c1921 commit 21418b0
Show file tree
Hide file tree
Showing 13 changed files with 86 additions and 74 deletions.
2 changes: 1 addition & 1 deletion src/Dotnettency.AspNetCore.Modules/ModuleShell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public async Task EnsureStarted(Func<Task<ITenantContainerAdaptor>> containerFac

if (Options.OnConfigureModuleServices != null)
{
container = container.CreateChildContainerAndConfigure($"Module:{Module?.GetType().Name}", (services) =>
container = container.CreateChildContainerAndConfigure($"Module:{Module?.GetType().Name}", sharedServices, (services) =>
{
services.AddRouting(); //it's assumed routing is required for a routed module!
Options.OnConfigureModuleServices(services);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static AdaptedContainerBuilderOptions<TTenant> Autofac<TTenant>(
// Update the root container with a service that can be used to build per tenant container!
ContainerBuilder updateBuilder = new ContainerBuilder();
var defaultServices = options.DefaultServices;
var defaultServices = options.ParentServices;
updateBuilder.RegisterInstance(new DelegateActionTenantContainerBuilder<TTenant>(defaultServices, adaptedContainer, configureTenant, containerEventsPublisher)).As<ITenantContainerBuilder<TTenant>>();
updateBuilder.Update(container);
Expand Down Expand Up @@ -71,7 +71,7 @@ public static AdaptedContainerBuilderOptions<TTenant> AutofacAsync<TTenant>(
// Update the root container with a service that can be used to build per tenant container!
ContainerBuilder updateBuilder = new ContainerBuilder();
var defaultServices = options.DefaultServices;
var defaultServices = options.ParentServices;
updateBuilder.RegisterInstance(new DelegateTaskTenantContainerBuilder<TTenant>(defaultServices, adaptedContainer, configureTenant, containerEventsPublisher)).As<ITenantContainerBuilder<TTenant>>();
updateBuilder.Update(container);
Expand Down
39 changes: 22 additions & 17 deletions src/Dotnettency.Container.Autofac/AutofacTenantContainerAdaptor.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Dazinator.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
Expand Down Expand Up @@ -62,7 +63,7 @@ public void Configure(Action<IServiceCollection> configure)
configure(services);

ContainerBuilder builder = new ContainerBuilder();
builder.Populate(services);
builder.Populate(services);
builder.Update(_container.ComponentRegistry);

_logger.LogDebug("Configured container: {id}, {containerNAme}, {role}", _id, ContainerName, Role);
Expand All @@ -71,9 +72,9 @@ public void Configure(Action<IServiceCollection> configure)
public ITenantContainerAdaptor CreateNestedContainer(string Name)
{

// return new AutofacServiceScope(this._lifetimeScope.BeginLifetimeScope());
return CreateNestedContainerAndConfigure(Name, null);
// throw new NotImplementedException();
// return new AutofacServiceScope(this._lifetimeScope.BeginLifetimeScope());
return CreateNestedContainerAndConfigure(Name);
// throw new NotImplementedException();
//_logger.LogDebug("Creating nested container from container: {id}, {containerNAme}, {role}", _id, ContainerName, _container.Role);
//ILifetimeScope perRequestScope = _container.BeginLifetimeScope()

Expand All @@ -91,27 +92,30 @@ public ITenantContainerAdaptor CreateChildContainer(string Name)
public new void Dispose()
{
_logger.LogDebug("Disposing of container: {id}, {containerNAme}, {role}", _id, ContainerName, Role);
// _container.Dispose();
// _container.Dispose();
base.Dispose();
}

public ITenantContainerAdaptor CreateChildContainerAndConfigure(string Name, Action<IServiceCollection> configure)
public ITenantContainerAdaptor CreateChildContainerAndConfigure(string Name, IServiceCollection parentServices, Action<IServiceCollection> configure)
{
var scope = _container.BeginLifetimeScope(TenantLifetimeScopeTag, (builder) =>
{
ServiceCollection services = new ServiceCollection();
var services = parentServices.CreateChildServiceCollection(ParentSingletonOpenGenericRegistrationsBehaviour.ThrowIfNotSupportedByContainer);
//todo check difference between these two behaviours..
// ServiceCollection services = new ServiceCollection();
configure(services);
builder.Populate(services);
builder.Populate(services.ChildDescriptors);
});

_logger.LogDebug("Creating child container from container: {id}, {containerNAme}, {role}", _id, ContainerName, Role);
return new AutofacTenantContainerAdaptor(_logger, scope, ContainerRole.Child, Name);
_logger.LogDebug("Creating child container from container: {id}, {containerNAme}, {role}", _id, ContainerName, Role);
return new AutofacTenantContainerAdaptor(_logger, scope, ContainerRole.Child, Name);
}

public async Task<ITenantContainerAdaptor> CreateChildContainerAndConfigureAsync(string Name, Func<IServiceCollection, Task> configure)
public async Task<ITenantContainerAdaptor> CreateChildContainerAndConfigureAsync(string Name, IServiceCollection parentServices, Func<IServiceCollection, Task> configure)
{
var services = parentServices.CreateChildServiceCollection(ParentSingletonOpenGenericRegistrationsBehaviour.ThrowIfNotSupportedByContainer);

ServiceCollection services = new ServiceCollection();
// ServiceCollection services = new ServiceCollection();
await configure(services);

var scope = _container.BeginLifetimeScope(TenantLifetimeScopeTag, (builder) =>
Expand All @@ -123,13 +127,14 @@ public async Task<ITenantContainerAdaptor> CreateChildContainerAndConfigureAsync
return new AutofacTenantContainerAdaptor(_logger, scope, ContainerRole.Child, Name);
}

public ITenantContainerAdaptor CreateNestedContainerAndConfigure(string Name, Action<IServiceCollection> configure)
{
public ITenantContainerAdaptor CreateNestedContainerAndConfigure(string Name)
{
var scope = _container.BeginLifetimeScope((builder) =>
{
ServiceCollection services = new ServiceCollection();
configure?.Invoke(services);
builder.Populate(services);
//ServiceCollection services = new ServiceCollection();
//configure?.Invoke(services);
//builder.Populate(services);
});

_logger.LogDebug("Creating nested container from container: {id}, {containerNAme}, {role}", _id, ContainerName, Role);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static AdaptedContainerBuilderOptions<TTenant> WithStructureMap<TTenant>(
var containerEventsPublisher = container.TryGetInstance<ITenantContainerEventsPublisher<TTenant>>();
// add ITenantContainerBuilder<TTenant> service to the host container
// This service can be used to build a child container (adaptor) for a particular tenant, when required.
var defaultServices = options.DefaultServices;
var defaultServices = options.ParentServices;
container.Configure(_ =>
_.For<ITenantContainerBuilder<TTenant>>()
.Use(new DelegateActionTenantContainerBuilder<TTenant>(defaultServices, adaptedContainer, configureTenant, containerEventsPublisher))
Expand Down
2 changes: 1 addition & 1 deletion src/Dotnettency.Owin/HttpRequestWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static Uri GetUri(IReadOnlyDictionary<string, object> environment)
var scheme = (string)environment["owin.RequestScheme"];

// PERF: Calculate string length to allocate correct buffer size for StringBuilder.
var length = scheme + SchemeDelimiter.Length + host.Length
var length = scheme.Length + SchemeDelimiter.Length + host.Length
+ pathBase.Length + path.Length + queryString.Length;

return new Uri(new StringBuilder(length)
Expand Down
8 changes: 4 additions & 4 deletions src/Dotnettency.Tests/Options/AutofacOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ public async Task CanResolveTOptionsFromTenantRequestContainer()
{
ILogger<AutofacOptionsTests> logger = _loggerFactory.CreateLogger<AutofacOptionsTests>();

//NOTE add logging and options were added to default services..
IServiceCollection services = new ServiceCollection() as IServiceCollection;
services.AddLogging();

services.AddOptions();

//var configBuilder = new ConfigurationBuilder();
Expand Down Expand Up @@ -135,7 +135,7 @@ public async Task CanResolveTOptionsFromTenantRequestContainer()
{
// var defaultServices = services.RemoveOptions();
// var defaultServices = services.RemoveOptions();
containerBuilder.SetDefaultServices(services);
// containerBuilder.SetDefaultServices(services);
containerBuilder.Events((events) =>
{
// callback invoked after tenant container is created.
Expand Down Expand Up @@ -289,7 +289,7 @@ public async Task CanUpdateOptions()
{
// var defaultServices = services.RemoveOptions();
// var defaultServices = services.RemoveOptions();
containerBuilder.SetDefaultServices(services);
//containerBuilder.SetDefaultServices(services);
//containerBuilder.Events((events) =>
//{
// // callback invoked after tenant container is created.
Expand Down Expand Up @@ -446,7 +446,7 @@ public async Task CanGetReloadedOptionsAfterConfigChanged()
{
// var defaultServices = services.RemoveOptions();
// var defaultServices = services.RemoveOptions();
containerBuilder.SetDefaultServices(services);
// containerBuilder.SetDefaultServices(services);
// Extension methods available here for supported containers. We are using structuremap..
// We are using an overload that allows us to configure structuremap with familiar IServiceCollection.
containerBuilder.AutofacAsync(async (tenant, tenantServices) =>
Expand Down
43 changes: 23 additions & 20 deletions src/Dotnettency/Container/ContainerBuilderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ namespace Dotnettency.Container
public class ContainerBuilderOptions<TTenant>
where TTenant : class
{

public ContainerBuilderOptions(MultitenancyOptionsBuilder<TTenant> builder)
{
Builder = builder;
ContainerEventsOptions = new TenantContainerEventsOptions<TTenant>(this);

builder.Services.AddSingleton<ITenantContainerFactory<TTenant>, TenantContainerBuilderFactory<TTenant>>();
builder.Services.AddScoped<ITenantContainerAccessor<TTenant>, TenantContainerAccessor<TTenant>>();
builder.Services.AddScoped<ITenantRequestContainerAccessor<TTenant>, TenantRequestContainerAccessor<TTenant>>();
builder.Services.AddScoped<ITenantRequestContainerAccessor<TTenant>, TenantRequestContainerAccessor<TTenant>>();

builder.Services.AddSingleton<ITenantContainerEventsPublisher<TTenant>>((sp) =>
{
{
var events = new TenantContainerEventsPublisher<TTenant>();
var containerEventsOptions = this.ContainerEventsOptions;
Expand All @@ -34,36 +34,39 @@ public ContainerBuilderOptions(MultitenancyOptionsBuilder<TTenant> builder)
return events;
});
builder.Services.AddSingleton<ITenantContainerEvents<TTenant>>((sp) =>
{
{
return sp.GetRequiredService<ITenantContainerEventsPublisher<TTenant>>();
});

}

public MultitenancyOptionsBuilder<TTenant> Builder { get; set; }

public TenantContainerEventsOptions<TTenant> ContainerEventsOptions { get; set; }

public ContainerBuilderOptions<TTenant> Events(Action<TenantContainerEventsOptions<TTenant>> containerLifecycleOptions)
{
{
containerLifecycleOptions?.Invoke(ContainerEventsOptions);
return this;
}

public IServiceCollection DefaultServices { get; set; }

/// <summary>
/// Services that will be added to the tenants IServiceCollection by default. Sometimes when an IServiceCollection.AddXYZ() method is called,
/// the particular implementation may depend upon other services being already registered in the IServiceCollection for it to behave correctly.
/// You can specify these default services here, and they will be added into the tenants IServiceCollection, prior to it then being passed into the tenants Configure() method.
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public ContainerBuilderOptions<TTenant> SetDefaultServices(IServiceCollection services)
{
DefaultServices = services;
return this;
public IServiceCollection ParentServices
{
get { return Builder.Services; }
}


///// <summary>
///// Services that will be added to the tenants IServiceCollection by default. Sometimes when an IServiceCollection.AddXYZ() method is called,
///// the particular implementation may depend upon other services being already registered in the IServiceCollection for it to behave correctly.
///// You can specify these default services here, and they will be added into the tenants IServiceCollection, prior to it then being passed into the tenants Configure() method.
///// </summary>
///// <param name="services"></param>
///// <returns></returns>
//public ContainerBuilderOptions<TTenant> SetDefaultServices(IServiceCollection services)
//{
// ParentServices = services;
// return this;
//}

}
}
24 changes: 13 additions & 11 deletions src/Dotnettency/Container/DelegateActionTenantContainerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@ namespace Dotnettency.Container
public class DelegateActionTenantContainerBuilder<TTenant> : ITenantContainerBuilder<TTenant>
where TTenant : class
{
private readonly IServiceCollection _defaultServices;
// private readonly IServiceCollection _defaultServices;
private readonly IServiceCollection _parentServices;

private readonly ITenantContainerAdaptor _parentContainer;
private readonly Action<TenantShellItemBuilderContext<TTenant>, IServiceCollection> _configureTenant;
private readonly ITenantContainerEventsPublisher<TTenant> _containerEventsPublisher;

public DelegateActionTenantContainerBuilder(
IServiceCollection defaultServices,
IServiceCollection parentServices,
ITenantContainerAdaptor parentContainer,
Action<TenantShellItemBuilderContext<TTenant>, IServiceCollection> configureTenant,
ITenantContainerEventsPublisher<TTenant> containerEventsPublisher)
{
_defaultServices = defaultServices;
_parentServices = parentServices;
_parentContainer = parentContainer;
_configureTenant = configureTenant;
_containerEventsPublisher = containerEventsPublisher;
Expand All @@ -27,17 +29,17 @@ public DelegateActionTenantContainerBuilder(
public Task<ITenantContainerAdaptor> BuildAsync(TenantShellItemBuilderContext<TTenant> tenantContext)
{
var name = tenantContext.Tenant?.ToString();
var tenantContainer = _parentContainer.CreateChildContainerAndConfigure("Tenant: " + (name ?? "NULL"), config =>
var tenantContainer = _parentContainer.CreateChildContainerAndConfigure("Tenant: " + (name ?? "NULL"), _parentServices, config =>
{
// add default services to tenant container.
// see https://github.com/aspnet/AspNetCore/issues/10469 and issues linked with that.
if (_defaultServices != null)
{
foreach (var item in _defaultServices)
{
config.Add(item);
}
}
//if (_defaultServices != null)
//{
// foreach (var item in _defaultServices)
// {
// config.Add(item);
// }
//}
// As tenantContext.Services is provided from current scope, we prefer that.
// otherwise fallback to parent scope (i.e this could mean falling back to application services from current scoped services etc).
Expand Down
21 changes: 11 additions & 10 deletions src/Dotnettency/Container/DelegateTaskTenantContainerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ namespace Dotnettency.Container
public class DelegateTaskTenantContainerBuilder<TTenant> : ITenantContainerBuilder<TTenant>
where TTenant : class
{
private readonly IServiceCollection _defaultServices;
private readonly IServiceCollection _parentServices;
private readonly ITenantContainerAdaptor _parentContainer;
private readonly Func<TenantShellItemBuilderContext<TTenant>, IServiceCollection, Task> _configureTenant;
private readonly ITenantContainerEventsPublisher<TTenant> _containerEventsPublisher;

public DelegateTaskTenantContainerBuilder(IServiceCollection defaultServices,
public DelegateTaskTenantContainerBuilder(IServiceCollection parentServices,
ITenantContainerAdaptor parentContainer,
Func<TenantShellItemBuilderContext<TTenant>, IServiceCollection, Task> configureTenant,
ITenantContainerEventsPublisher<TTenant> containerEventsPublisher)
{
_defaultServices = defaultServices;
_parentServices = parentServices;
_parentContainer = parentContainer;
_configureTenant = configureTenant;
_containerEventsPublisher = containerEventsPublisher;
Expand All @@ -26,17 +26,18 @@ public DelegateTaskTenantContainerBuilder(IServiceCollection defaultServices,
public async Task<ITenantContainerAdaptor> BuildAsync(TenantShellItemBuilderContext<TTenant> tenantContext)
{
var tenantContainer = await _parentContainer.CreateChildContainerAndConfigureAsync("Tenant: " + (tenantContext?.Tenant?.ToString() ?? "NULL").ToString(),
_parentServices,
async config =>
{
// add default services to tenant container.
// see https://github.com/aspnet/AspNetCore/issues/10469 and issues linked with that.
if (_defaultServices != null)
{
foreach (var item in _defaultServices)
{
config.Add(item);
}
}
//if (_defaultServices != null)
//{
// foreach (var item in _defaultServices)
// {
// config.Add(item);
// }
//}
// As tenantContext.Services is provided from current scope, we prefer that.
// otherwise fallback to parent scope (i.e this could mean falling back to application services from current scoped services etc).
Expand Down
Loading

0 comments on commit 21418b0

Please sign in to comment.