Skip to content
Draft
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
7 changes: 7 additions & 0 deletions VirtoCommerce.Platform.sln
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtoCommerce.Platform.Data
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtoCommerce.Platform.Data.SqlServer", "src\VirtoCommerce.Platform.Data.SqlServer\VirtoCommerce.Platform.Data.SqlServer.csproj", "{6B11F73A-4AF3-4EEF-99EE-A4B39134AF9C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtendedServiceProvider", "src\ExtendedServiceProvider\ExtendedServiceProvider.csproj", "{DB5AF8C4-C55D-1E90-8064-F217DA5FC908}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -122,6 +124,10 @@ Global
{6B11F73A-4AF3-4EEF-99EE-A4B39134AF9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B11F73A-4AF3-4EEF-99EE-A4B39134AF9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B11F73A-4AF3-4EEF-99EE-A4B39134AF9C}.Release|Any CPU.Build.0 = Release|Any CPU
{DB5AF8C4-C55D-1E90-8064-F217DA5FC908}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DB5AF8C4-C55D-1E90-8064-F217DA5FC908}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DB5AF8C4-C55D-1E90-8064-F217DA5FC908}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DB5AF8C4-C55D-1E90-8064-F217DA5FC908}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -143,6 +149,7 @@ Global
{9E100C75-7490-4A14-B74A-0F83CCA0C2A5} = {90F6E3EE-475A-4844-A4C4-078613591F81}
{4826F1CB-84E6-4932-9B61-B8AF467C3A4A} = {90F6E3EE-475A-4844-A4C4-078613591F81}
{6B11F73A-4AF3-4EEF-99EE-A4B39134AF9C} = {90F6E3EE-475A-4844-A4C4-078613591F81}
{DB5AF8C4-C55D-1E90-8064-F217DA5FC908} = {90F6E3EE-475A-4844-A4C4-078613591F81}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {01A94F38-27D1-412F-A4F4-F00991D1157C}
Expand Down
93 changes: 93 additions & 0 deletions src/ExtendedServiceProvider/ExtendedServiceProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System.Collections;
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace ExtendedServiceProvider
{
public interface IExtendedServiceProvider : IKeyedServiceProvider, IServiceProviderIsKeyedService, ISupportRequiredService, IServiceScopeFactory;

[DebuggerDisplay("ServiceDescriptors = {_services.Count}, Extended")]
internal class ExtendedServiceProvider : IExtendedServiceProvider, IEnumerable<ServiceDescriptor>
{
private readonly IServiceCollection _services;
private readonly ILogger<ExtendedServiceProvider> _logger;
private readonly IKeyedServiceProvider _serviceProvider;
private readonly IEnumerable<IServiceProviderResolver> _resolvers;
private readonly IEnumerable<IServiceProviderHook> _hooks;
private static readonly Type[] _selfTypes = [typeof(IExtendedServiceProvider), typeof(IServiceProvider), typeof(IKeyedServiceProvider), typeof(IServiceProviderIsService), typeof(IServiceProviderIsKeyedService), typeof(ISupportRequiredService), typeof(IServiceScopeFactory)];

public ExtendedServiceProvider(IServiceCollection services, ServiceProviderOptions? options = null, IServiceProviderResolver? resolver = null)
{
ArgumentNullException.ThrowIfNull(services);
_services = services;
_serviceProvider = services.BuildServiceProvider(options ?? ExtendedServiceProviderFactory.DefaultServiceProviderOptions);
_serviceProvider = (_serviceProvider.CreateScope().ServiceProvider as IKeyedServiceProvider)!;
_logger = _serviceProvider.GetRequiredService<ILogger<ExtendedServiceProvider>>();
var resolvers = _serviceProvider.GetServices<IServiceProviderResolver>();
_resolvers = (resolver != null) ? resolvers.Concat([resolver]) : resolvers;
_hooks = _serviceProvider.GetServices<IServiceProviderHook>();
}

public object? GetKeyedService(Type serviceType, object? serviceKey)
{
ArgumentNullException.ThrowIfNull(serviceType);

_logger.LogDebug("GetKeyedService: serviceType = {serviceType}, serviceKey = {serviceKey}", serviceType, serviceKey);

if (_selfTypes.Contains(serviceType) && serviceKey is null)
{
_logger.LogDebug("GetKeyedService: asking for {serviceType}, returning self", serviceType);
return this;
}

var service = ResolveKeyedService(serviceType, serviceKey) ?? _serviceProvider.GetKeyedService(serviceType, serviceKey);

if (service != null)
{
foreach (var hook in _hooks)
{
hook.ServiceResolved(serviceType, service);
}
}

return service;
}

private object? ResolveKeyedService(Type serviceType, object? serviceKey)
{
object? service = null;

foreach (var resolver in _resolvers)
{
service = resolver.Resolve(_serviceProvider, serviceType, serviceKey);
if (service != null)
{
break;
}
}

return service;
}

public object GetRequiredKeyedService(Type serviceType, object? serviceKey)
{
var service = GetKeyedService(serviceType, serviceKey);
return service ?? throw new InvalidOperationException($"No service for type '{serviceType}' has been registered.");
}

public object? GetService(Type serviceType) => GetKeyedService(serviceType, null);

public object GetRequiredService(Type serviceType) => GetRequiredKeyedService(serviceType, null);

public bool IsService(Type serviceType) => IsKeyedService(serviceType, null);

public bool IsKeyedService(Type serviceType, object? serviceKey) => _services.Any(x => x.ServiceType == serviceType && x.ServiceKey == serviceKey);

public IServiceScope CreateScope() => _serviceProvider.CreateScope();

public IEnumerator<ServiceDescriptor> GetEnumerator() => _services.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => _services.GetEnumerator();
}
}
26 changes: 26 additions & 0 deletions src/ExtendedServiceProvider/ExtendedServiceProvider.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Title>ExtendedServiceProvider</Title>
<Authors>Ricardo Peres</Authors>
<Copyright>Ricardo Peres</Copyright>
<PackageProjectUrl>https://github.com/rjperes/ExtendedServiceProvider</PackageProjectUrl>
</PropertyGroup>

<ItemGroup>
<Compile Remove="ApplicationBuilderExtensions.cs" />
<Compile Remove="ExtendedServiceProviderMiddleware.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

</Project>
32 changes: 32 additions & 0 deletions src/ExtendedServiceProvider/ExtendedServiceProviderFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.Extensions.DependencyInjection;

namespace ExtendedServiceProvider
{
internal class ExtendedServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
internal static readonly ServiceProviderOptions DefaultServiceProviderOptions = new() { ValidateOnBuild = true, ValidateScopes = true };

private IServiceProvider? _serviceProvider;
private readonly IServiceProviderResolver? _resolver;
private readonly ServiceProviderOptions? _options;

public ExtendedServiceProviderFactory(ServiceProviderOptions? options = null, IServiceProviderResolver? resolver = null)
{
_options = options;
_resolver = resolver;
}

public IServiceCollection CreateBuilder(IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
return services;
}

public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder)
{
ArgumentNullException.ThrowIfNull(containerBuilder);
_serviceProvider ??= new ExtendedServiceProvider(containerBuilder, _options, _resolver);
return _serviceProvider;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Features;

namespace ExtendedServiceProvider
{
public class ExtendedServiceProvidersFeatureFilter : IStartupFilter, IServiceProvidersFeature
{
public ExtendedServiceProvidersFeatureFilter(IServiceProvider serviceProvider)
{
RequestServices = serviceProvider;
}

public IServiceProvider RequestServices { get; set; }

public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return app =>
{
app.Use(async (context, nxt) =>
{
context.Features.Set<IServiceProvidersFeature>(this);
await nxt(context);
});
next(app);
};
}
}
}
19 changes: 19 additions & 0 deletions src/ExtendedServiceProvider/GenericLazy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.Extensions.DependencyInjection;

namespace ExtendedServiceProvider
{
public class GenericLazy<T> : Lazy<T>
where T : notnull
{
public GenericLazy() { }
public GenericLazy(bool isThreadSafe) : base(isThreadSafe) { }
public GenericLazy(Func<T> valueFactory) : base(valueFactory) { }
public GenericLazy(LazyThreadSafetyMode mode) : base(mode) { }
public GenericLazy(T value) : base(value) { }
public GenericLazy(Func<T> valueFactory, bool isThreadSafe) : base(valueFactory, isThreadSafe) { }
public GenericLazy(Func<T> valueFactory, LazyThreadSafetyMode mode) : base(valueFactory, mode) { }
public GenericLazy(IServiceProvider serviceProvider) : this(() => serviceProvider.GetRequiredService<T>()) { }

public static implicit operator T(GenericLazy<T> value) => value.Value;
}
}
25 changes: 25 additions & 0 deletions src/ExtendedServiceProvider/HostBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ExtendedServiceProvider
{
public static class HostBuilderExtensions
{
public static IHostBuilder UseExtendedServiceProvider(this IHostBuilder builder, ServiceProviderOptions? options = null, IServiceProviderResolver? resolver = null)
{
ArgumentNullException.ThrowIfNull(builder);
var serviceProviderFactory = new ExtendedServiceProviderFactory(options ?? ExtendedServiceProviderFactory.DefaultServiceProviderOptions, resolver);
return builder
.UseServiceProviderFactory(serviceProviderFactory)
.ConfigureServices((ctx, services) =>
{
services.AddSingleton<IStartupFilter>(_ =>
{
var serviceProvider = serviceProviderFactory.CreateServiceProvider(services);
return new ExtendedServiceProvidersFeatureFilter(serviceProvider);
});
});
}
}
}
53 changes: 53 additions & 0 deletions src/ExtendedServiceProvider/IServiceProviderHook.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace ExtendedServiceProvider
{
public interface IServiceProviderHook
{
void ServiceResolved(Type serviceType, object service);
}

public sealed class LoggerServiceProviderHook : IServiceProviderHook
{
private readonly ILogger<LoggerServiceProviderHook> _logger;

public LoggerServiceProviderHook(ILogger<LoggerServiceProviderHook> logger)
{
_logger = logger;
}

public void ServiceResolved(Type serviceType, object service)
{
_logger.LogDebug("ServiceResolved: serviceType = {serviceType}, service = {service}", serviceType, service);
}
}

public sealed class PropertyInitializerServiceProviderHook : IServiceProviderHook
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<PropertyInitializerServiceProviderHook> _logger;

public PropertyInitializerServiceProviderHook(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_logger = serviceProvider.GetRequiredService<ILogger<PropertyInitializerServiceProviderHook>>();
}

public void ServiceResolved(Type serviceType, object service)
{
foreach (var prop in service.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.GetProperty))
{
var injectPropertyAttribute = prop.GetCustomAttribute<InjectAttribute>();

if (injectPropertyAttribute != null && prop.GetValue(service) == null)
{
_logger.LogDebug("ServiceResolved: serviceType = {serviceType}, setting {propertyName}", serviceType, prop.Name);
var propertyService = _serviceProvider.GetRequiredKeyedService(prop.PropertyType, injectPropertyAttribute.ServiceKey);
prop.SetValue(service, propertyService);
}
}
}
}
}
9 changes: 9 additions & 0 deletions src/ExtendedServiceProvider/IServiceProviderResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Microsoft.Extensions.DependencyInjection;

namespace ExtendedServiceProvider
{
public interface IServiceProviderResolver
{
object? Resolve(IKeyedServiceProvider serviceProvider, Type serviceType, object? serviceKey);
}
}
24 changes: 24 additions & 0 deletions src/ExtendedServiceProvider/InjectAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace ExtendedServiceProvider
{
[Serializable]
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class InjectAttribute : Attribute
{
public object? ServiceKey { get; init; }

public override bool Equals(object? obj) => Match(obj);

public override bool Match(object? obj)
{
if (obj is InjectAttribute attribute)
{
return object.Equals(ServiceKey, attribute.ServiceKey);
}
return false;
}

public override int GetHashCode() => ServiceKey?.GetHashCode() ?? 0;

public override bool IsDefaultAttribute() => ServiceKey == null;
}
}
25 changes: 25 additions & 0 deletions src/ExtendedServiceProvider/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Microsoft.Extensions.DependencyInjection;

namespace ExtendedServiceProvider
{
public static class ServiceCollectionExtensions
{
public static IServiceProvider BuildExtendedServiceProvider(this IServiceCollection services, ServiceProviderOptions? options = null, IServiceProviderResolver? resolver = null)
{
ArgumentNullException.ThrowIfNull(services);
return new ExtendedServiceProvider(services, options, resolver);
}

public static IServiceCollection AddServiceProviderHook<THook>(this IServiceCollection services) where THook : class, IServiceProviderHook
{
ArgumentNullException.ThrowIfNull(services);
return services.AddSingleton<IServiceProviderHook, THook>();
}

public static IServiceCollection AddServiceProviderResolver<TResolver>(this IServiceCollection services) where TResolver : class, IServiceProviderResolver
{
ArgumentNullException.ThrowIfNull(services);
return services.AddSingleton<IServiceProviderResolver, TResolver>();
}
}
}
Loading
Loading