Hands-create the wheel: the realization of a simple dependency injection (two) --- registration service optimization
Intro
Before implementing dependency injection framework that the basic version is available, but the feeling is not flexible enough, and registration services and analytical services in the same place feels a little awkward, a little separation duties is not enough. So learn Autofac approach adds a ServiceContainerBuilder
responsible registration services, ServiceContainer
is responsible for parsing service, and adds a ServiceContainerModule
can support as Autofac in Module
/ RegisterAssemblyModules
as registration services
Implementation code
ServiceContainerBuilder
Increase ServiceContainerBuild
to specifically responsible for registration services, extension methods from those originally registered service IServiceContainer
extension methods become IServiceContainerBuilder
extensions
public interface IServiceContainerBuilder
{
IServiceContainerBuilder Add(ServiceDefinition item);
IServiceContainerBuilder TryAdd(ServiceDefinition item);
IServiceContainer Build();
}
public class ServiceContainerBuilder : IServiceContainerBuilder
{
private readonly List<ServiceDefinition> _services = new List<ServiceDefinition>();
public IServiceContainerBuilder Add(ServiceDefinition item)
{
if (_services.Any(_ => _.ServiceType == item.ServiceType && _.GetImplementType() == item.GetImplementType()))
{
return this;
}
_services.Add(item);
return this;
}
public IServiceContainerBuilder TryAdd(ServiceDefinition item)
{
if (_services.Any(_ => _.ServiceType == item.ServiceType))
{
return this;
}
_services.Add(item);
return this;
}
public IServiceContainer Build() => new ServiceContainer(_services);
}
IServiceContainer
Increase ServiceContainerBuilder
after it is no longer registered service support, and ServiceContainer
this type can also be turned into an internal class, and no longer exposed to the external
public interface IServiceContainer : IScope, IServiceProvider
{
IServiceContainer CreateScope();
}
internal class ServiceContainer : IServiceContainer
{
private readonly IReadOnlyList<ServiceDefinition> _services;
public ServiceContainer(IReadOnlyList<ServiceDefinition> serviceDefinitions)
{
_services = serviceDefinitions;
// ...
}
// 此处约省略一万行代码 ...
}
ServiceContainerModule
Defines a ServiceContainerModule
to achieve Autofac like that, in one assembly within the definition of a registered Module assembly services that require registration, registered in the local call service RegisterAssemblyModules
to scan all assembly and register the custom ServiceContainerModule
services that require registration
public interface IServiceContainerModule
{
void ConfigureServices(IServiceContainerBuilder serviceContainerBuilder);
}
public abstract class ServiceContainerModule : IServiceContainerModule
{
public abstract void ConfigureServices(IServiceContainerBuilder serviceContainerBuilder);
}
Custom ServiceContainerModule
Example of use:
public class TestServiceContainerModule : ServiceContainerModule
{
public override void ConfigureServices(IServiceContainerBuilder serviceContainerBuilder)
{
serviceContainerBuilder.AddSingleton<IIdGenerator>(GuidIdGenerator.Instance);
}
}
RegisterAssemblyModules
Extension method to achieve the following:
public static IServiceContainerBuilder RegisterAssemblyModules(
[NotNull] this IServiceContainerBuilder serviceContainerBuilder, params Assembly[] assemblies)
{
#if NET45
// 解决 asp.net 在 IIS 下应用程序域被回收的问题
// https://autofac.readthedocs.io/en/latest/register/scanning.html#iis-hosted-web-applications
if (null == assemblies || assemblies.Length == 0)
{
if (System.Web.Hosting.HostingEnvironment.IsHosted)
{
assemblies = System.Web.Compilation.BuildManager.GetReferencedAssemblies()
.Cast<Assembly>().ToArray();
}
}
#endif
if (null == assemblies || assemblies.Length == 0)
{
assemblies = AppDomain.CurrentDomain.GetAssemblies();
}
foreach (var type in assemblies.WhereNotNull().SelectMany(ass => ass.GetTypes())
.Where(t => t.IsClass && !t.IsAbstract && typeof(IServiceContainerModule).IsAssignableFrom(t))
)
{
try
{
if (Activator.CreateInstance(type) is ServiceContainerModule module)
{
module.ConfigureServices(serviceContainerBuilder);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
return serviceContainerBuilder;
}
Examples of Use
In addition to registering to use the service changes outside, other places are no different, look at the unit test code
public class DependencyInjectionTest : IDisposable
{
private readonly IServiceContainer _container;
public DependencyInjectionTest()
{
var containerBuilder = new ServiceContainerBuilder();
containerBuilder.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
containerBuilder.AddScoped<IFly, MonkeyKing>();
containerBuilder.AddScoped<IFly, Superman>();
containerBuilder.AddScoped<HasDependencyTest>();
containerBuilder.AddScoped<HasDependencyTest1>();
containerBuilder.AddScoped<HasDependencyTest2>();
containerBuilder.AddScoped<HasDependencyTest3>();
containerBuilder.AddScoped(typeof(HasDependencyTest4<>));
containerBuilder.AddTransient<WuKong>();
containerBuilder.AddScoped<WuJing>(serviceProvider => new WuJing());
containerBuilder.AddSingleton(typeof(GenericServiceTest<>));
containerBuilder.RegisterAssemblyModules();
_container = containerBuilder.Build();
}
[Fact]
public void Test()
{
var rootConfig = _container.ResolveService<IConfiguration>();
Assert.Throws<InvalidOperationException>(() => _container.ResolveService<IFly>());
Assert.Throws<InvalidOperationException>(() => _container.ResolveRequiredService<IDependencyResolver>());
using (var scope = _container.CreateScope())
{
var config = scope.ResolveService<IConfiguration>();
Assert.Equal(rootConfig, config);
var fly1 = scope.ResolveRequiredService<IFly>();
var fly2 = scope.ResolveRequiredService<IFly>();
Assert.Equal(fly1, fly2);
var wukong1 = scope.ResolveRequiredService<WuKong>();
var wukong2 = scope.ResolveRequiredService<WuKong>();
Assert.NotEqual(wukong1, wukong2);
var wuJing1 = scope.ResolveRequiredService<WuJing>();
var wuJing2 = scope.ResolveRequiredService<WuJing>();
Assert.Equal(wuJing1, wuJing2);
var s0 = scope.ResolveRequiredService<HasDependencyTest>();
s0.Test();
Assert.Equal(s0._fly, fly1);
var s1 = scope.ResolveRequiredService<HasDependencyTest1>();
s1.Test();
var s2 = scope.ResolveRequiredService<HasDependencyTest2>();
s2.Test();
var s3 = scope.ResolveRequiredService<HasDependencyTest3>();
s3.Test();
var s4 = scope.ResolveRequiredService<HasDependencyTest4<string>>();
s4.Test();
using (var innerScope = scope.CreateScope())
{
var config2 = innerScope.ResolveRequiredService<IConfiguration>();
Assert.True(rootConfig == config2);
var fly3 = innerScope.ResolveRequiredService<IFly>();
fly3.Fly();
Assert.NotEqual(fly1, fly3);
}
var flySvcs = scope.ResolveServices<IFly>();
foreach (var f in flySvcs)
f.Fly();
}
var genericService1 = _container.ResolveRequiredService<GenericServiceTest<int>>();
genericService1.Test();
var genericService2 = _container.ResolveRequiredService<GenericServiceTest<string>>();
genericService2.Test();
}
public void Dispose()
{
_container.Dispose();
}
}
Reference
- https://github.com/WeihanLi/WeihanLi.Common/tree/dev/src/WeihanLi.Common/DependencyInjection
- https://www.cnblogs.com/weihanli/p/implement-dependency-injection-01.html
- https://www.cnblogs.com/weihanli/p/implement-dependency-injection.html
- https://autofac.org/
- https://autofac.readthedocs.io/en/latest/register/scanning.html