Skip to content

Instantly share code, notes, and snippets.

@noseratio
Last active May 2, 2023 11:24
Show Gist options
  • Save noseratio/239918e05697fb5952865dd19a2ae3aa to your computer and use it in GitHub Desktop.
Save noseratio/239918e05697fb5952865dd19a2ae3aa to your computer and use it in GitHub Desktop.
A factory for ActivatorUtilities.CreateInstance
// https://stackoverflow.com/q/69207420/1768303
#nullable enable
using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
namespace App
{
static class Program
{
private static IServiceProvider BuildServices() => new ServiceCollection()
.AddFactory<ISomething, Something>()
.BuildServiceProvider();
static void Main()
{
var serviceProvider = BuildServices();
var something = serviceProvider.GetRequiredService<ISomething>();
serviceProvider.Inject<ISomething>();
serviceProvider.Inject<ISomething>(Guid.NewGuid());
serviceProvider.Inject<ISomething>(something);
using var scope = serviceProvider.CreateScope();
scope.ServiceProvider.Inject<ISomething>(Guid.NewGuid());
serviceProvider.GetRequiredService<IFactory<ISomething>>().CreateInstance(String.Empty);
}
}
/// <summary>
/// A sample interface for DI
/// </summary>
public interface ISomething
{
void DoSomething() => Console.WriteLine(nameof(DoSomething));
}
public class Something : ISomething
{
public Something() =>
Console.WriteLine($"{this.GetType()} created");
public Something(Guid context) =>
Console.WriteLine($"{this.GetType()} created with {context.GetType()}");
public Something(String context) =>
Console.WriteLine($"{this.GetType()} created with {context.GetType()}");
public Something(ISomething source) =>
Console.WriteLine($"{this.GetType()} created with {source.GetType()}");
public Something(IServiceProvider sp) =>
Console.WriteLine($"{this.GetType()} created with {sp.GetType()}");
public Something(IServiceProvider sp, Guid context) =>
Console.WriteLine($"{this.GetType()} created with {sp.GetType()}, {context.GetType()}");
public Something(IServiceProvider sp, ISomething context) =>
Console.WriteLine($"{this.GetType()} created with {sp.GetType()}, {context.GetType()}");
}
public interface IFactory<TService>
where TService : class
{
IServiceProvider ServiceProvider { get; }
TService CreateInstance(params object[] parameters);
TService Inject(params object[] parameters);
}
/// <summary>
/// DependencyInjectionExtensions
/// </summary>
public static class DependencyInjectionExtensions
{
private class Factory<TService, TInstance> :
IFactory<TService>
where TService : class
where TInstance : class, TService
{
public Factory(IServiceProvider serviceProvider) =>
this.ServiceProvider = serviceProvider;
public IServiceProvider ServiceProvider { get; }
public TService CreateInstance(params object[] parameters) =>
Activator.CreateInstance(typeof(TInstance), parameters) as TService ??
throw new InvalidCastException();
public TService Inject(params object[] parameters)
{
// is IServiceProvider supplied as any of params
if (!parameters.Any(p => p is IServiceProvider))
{
// if not, do we have a public constructor compatible with params + IServiceProvider?
var paramTypes = parameters.Select(p => p.GetType()).Append(typeof(IServiceProvider));
if (HasSuitableConstructor(paramTypes))
{
// if so, add the current IServiceProvider to the DI param list and create an instance
return ActivatorUtilities.CreateInstance<TInstance>(
this.ServiceProvider, parameters.Append(this.ServiceProvider).ToArray());
}
}
// otherwise, use the default behavior
return ActivatorUtilities.CreateInstance<TInstance>(this.ServiceProvider, parameters);
}
private static bool HasSuitableConstructor(IEnumerable<Type> paramTypes)
{
// go through all constructors
foreach (var c in typeof(TInstance)
.GetConstructors()
.Where(c => c.IsPublic))
{
// try to find a constructor that has matching parameters for paramTypes
var matched = true;
foreach (var paramType in paramTypes)
{
if (c.GetParameters()
.Select(p => p.ParameterType)
.Any(p => p.IsAssignableFrom(paramType)) == false)
{
matched = false;
break;
}
}
if (matched)
{
return true;
}
}
return false;
}
}
/// <summary>
/// Add a factory for parametrized DI injections
/// </summary>
public static IServiceCollection AddFactory<TService, TInstance>(
this IServiceCollection @this)
where TService : class
where TInstance : class, TService
{
return @this
.AddSingleton<IFactory<TService>>(serviceProvider =>
new Factory<TService, TInstance>(serviceProvider))
.AddTransient<TService>(serviceProvider =>
serviceProvider.GetRequiredService<IFactory<TService>>().Inject());
}
/// <summary>
/// Inject with parameters
/// </summary>
public static TService Inject<TService>(
this IServiceProvider @this, params object[] parameters)
where TService : class
{
return @this.GetRequiredService<IFactory<TService>>().Inject(parameters);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment