一、背景
你說我 Castle Windsor 庫用得好好的,為啥要大費周章的替換成 DryIoc 庫呢?那就是性能,DryIoc 是一款優秀而且輕量級的 DI 框架,整個項目代碼就兩個文件,加起來代碼 1 萬行左右(PS: 大部分都是注釋)。
在各個 Ioc 容器的 性能評測 當中,DryIoc 以其優異的性能成為我選擇使用他的原因。Abp 使用的 Castle Windsor 在解析復雜對象的時候,速度非常慢,而替換為 DryIoc 之后速度可以提升 150% 以上。
【注意】
本文僅對 .NET Core 相關的庫進行更改並測試,.NET Framework 相關的庫並沒有進行修改測試。
二、准備
你需要准備如下原料:
- Abp 源碼 一份。
- 測試用的項目一份。
- Visual Studio 2017 或者 Rider 一份。
- .NET 程序猿一枚。
三、分析
首先,Abp 框架的大部分動作基本上都是通過 IIocManager
這個接口對象進行實現的,它抽象了一層作為一個 DI 框架的操作類。它的默認實現是使用的 Castle Windsor 來進行組件的注入與解析,所以我們只需要將其改為使用 DryIoc 的容器其進行操作即可。
其次,在 Abp 框架的很多地方都有用到 Castle Windsor 的 IWindsorContainer
對象,但一般用到該方法的地方都是注入或者綁定組件注冊事件,這些我們都可以重新實現的。
做完了以上的工作僅僅是代表我們的 Abp 的所有組件都可以通過 DryIoc 來進行注冊和解析,不過要和 ASP.NET Core 集成的話,還需要 IServiceProvider
的適配器,針對於適配器 DryIoc 也給我們提供了,拿來用即可。
所以,我們基本確定了需要變更的項目主要是 Abp 這個核心庫,還有 Abp.AspNetCore 這個子模塊。除了前面兩個比較重要的模塊之外,還有 Abp.EntityFrameworkCore 相關的庫也需要變更,這是因為他們內部都直接使用到了 IWindsorContainer
對象對容器進行操作的。
四、開擼
4.1 Abp 庫改造
Abp 本身庫里面需要改動的地方基本集中在 Dependency 文件夾里面,這個文件夾我們之前有講過,基本所有依賴注入相關的類型與接口都存放在這里面的。
除了依賴注入相關的類型需要更改以外,我們還需要更改各個攔截器注入的地方。因為在之前 Abp 如果需要為某個類型注入攔截器的話,是使用到了 IWindsorContainer
接口所提供的組件注入事件來進行攔截器注入的。
首先我們針對於 Abp 庫添加 DryIoc 庫的 NuGet 包引用,這里我是安裝的 3.1.0-preview-06 版本。
4.1.1 IocManger 改造
首先看一下 IIocManager
接口,該接口定義如下:
/// <summary>
/// This interface is used to directly perform dependency injection tasks.
/// </summary>
public interface IIocManager : IIocRegistrar, IIocResolver, IDisposable
{
/// <summary>
/// Reference to the Castle Windsor Container.
/// </summary>
IWindsorContainer IocContainer { get; }
/// <summary>
/// Checks whether given type is registered before.
/// </summary>
/// <param name="type">Type to check</param>
new bool IsRegistered(Type type);
/// <summary>
/// Checks whether given type is registered before.
/// </summary>
/// <typeparam name="T">Type to check</typeparam>
new bool IsRegistered<T>();
}
可以看到他定義了一個 IWindsorContainer
的屬性,我們將其改為 IContainer
。基本上做了這一步之后,在 Abp 的其他項目會出現一堆錯誤提示,先不慌,一步一步來。
接着我們轉到 IIocManager
的實現類 IocManager
,一樣的更改 IocContainer
的類型為 IContainer
之后,我們繼續來到其構造函數,可以看到有如下代碼:
public IocManager()
{
IocContainer = CreateContainer();
_conventionalRegistrars = new List<IConventionalDependencyRegistrar>();
//Register self!
IocContainer.Register(
Component
.For<IocManager, IIocManager, IIocRegistrar, IIocResolver>()
.Instance(this)
);
}
因為我們的 IocContainer
跟着變更了,這里也不能使用 CreateContainer()
方法來創建 DryIoc 的容器。其次,在下面注冊自己的時候,也是使用到了 IWindsorContainer
的注冊方法,一樣的需要進行更改。變更好的構造函數如下:
public IocManager()
{
// 這里通過 Rules 啟用了瞬態對象跟蹤,默認是不啟動的。
IocContainer = new Container(Rules.Default.WithTrackingDisposableTransients());
_conventionalRegistrars = new List<IConventionalDependencyRegistrar>();
// 注冊自身
IocContainer.UseInstance(typeof(IocManager),this);
IocContainer.UseInstance(typeof(IIocManager),this);
IocContainer.UseInstance(typeof(IIocRegistrar),this);
IocContainer.UseInstance(typeof(IIocResolver),this);
}
接着就需要繼續看一下報錯的方法,另一個需要改的則是注冊方法的一個輔助私有方法 ApplyLifestyle
,該方法主要作用就是將 Abp 定義的生命周期轉換為具體 Ioc 容器的生命周期常量。而且該方法原來是返回的一個 ComponentRegistration<T>
對象,這個對象是 Castle Windsor 的一個專屬類,所以需要改造一下,變更之后如下:
private static IReuse ApplyLifestyle(DependencyLifeStyle lifeStyle)
{
switch (lifeStyle)
{
case DependencyLifeStyle.Transient:
return Reuse.Transient;;
case DependencyLifeStyle.Singleton:
return Reuse.Singleton;
default:
return Reuse.Transient;
}
}
做了這個改動之后,剩下的就是需要針對注冊與解析方法進行一些改動了,因為 IocManger
提供的注冊與解析方法也是調用的具體 Ioc 容器所提供的方法,而 IWindsorContainer
提供的,DryIoc 的 IContainer
基本上也都有提供 ,只是個別特殊的方法有一些不同而已。
下面是改造完成的部分注冊與解析接口(詳細的可以查看 Github 代碼):
public void Register(Type type, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton)
{
IocContainer.Register(type,ApplyLifestyle(lifeStyle));
}
// ... 其他接口
public void Register(Type type, Type impl, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton)
{
if (type == impl)
{
// 這里通過 made 參數指定了解析對象時優先解析帶有參數的構造函數
IocContainer.Register(type,impl,ApplyLifestyle(lifeStyle),
made: Made.Of(FactoryMethod.ConstructorWithResolvableArguments));
RegisterTypeEventHandler?.Invoke(this,type,impl);
}
else
{
IocContainer.RegisterMany(new[]
{
type,
impl
},
impl,
made: Made.Of(FactoryMethod.ConstructorWithResolvableArguments),
reuse: ApplyLifestyle(lifeStyle));
RegisterTypeEventHandler?.Invoke(this,type,impl);
RegisterTypeEventHandler?.Invoke(this,impl,impl);
}
}
// ... 其他接口
這里需要注意一點的是帶參數的解析方法 public T Resolve<T>(object argumentsAsAnonymousType)
,DryIoc 與 Castle Windsor 不同的是,它能夠接收的只能是參數數組,而不能接收一個參數集合的匿名對象。所以我們需要將入參改為 object[]
,當然也因為變更了方法簽名,所以我們需要更改 ScopedIocResolver
、IIocResolver
、IocResolverExtensions
定義里面帶參數的解析方法簽名。
public T Resolve<T>(object[] argumentsAsAnonymousType)
{
return IocContainer.Resolve<T>(argumentsAsAnonymousType);
}
其次,還有一個 public T[] ResolveAll<T>()
內部調用了 IocContainer.ResolveAll
方法,而 DryIoc 是沒有提供這個方法的,但是有一個 ResolveMany()
方法是一樣的作用。下面是進行更改之后的 ResolveAll()
方法的所有重載:
///<inheritdoc/>
public T[] ResolveAll<T>()
{
return IocContainer.ResolveMany<T>().ToArray();
}
///<inheritdoc/>
public T[] ResolveAll<T>(object[] argumentsAsAnonymousType)
{
return IocContainer.ResolveMany<T>(args:argumentsAsAnonymousType).ToArray();
}
///<inheritdoc/>
public object[] ResolveAll(Type type)
{
return IocContainer.ResolveMany(type).ToArray();
}
///<inheritdoc/>
public object[] ResolveAll(Type type, object[] argumentsAsAnonymousType)
{
return IocContainer.ResolveMany(type, args:argumentsAsAnonymousType).ToArray();
}
除了解析方法之外,還有對象釋放的方法 Release
,由於 DryIoc 沒有提供釋放方法,所以這里只能顯式地調用對象的 Dispose()
方法來進行釋放。
public void Release(object obj)
{
if(obj is IDisposable disposeObj)
{
disposeObj.Dispose();
}
}
做了以上變更之后,還有一個地方在提示錯誤:
public void RegisterAssemblyByConvention(Assembly assembly, ConventionalRegistrationConfig config)
{
var context = new ConventionalRegistrationContext(assembly, this, config);
foreach (var registerer in _conventionalRegistrars)
{
registerer.RegisterAssembly(context);
}
if (config.InstallInstallers)
{
// 這里仍然使用了 IWindsorContainr 的方法
IocContainer.Install(FromAssembly.Instance(assembly));
}
}
看過博主之前更新的 Abp 源碼分析的同學應該知道,這個 Install()
的作用其實很簡單,就是直接遍歷指定程序集的類型,查找是否有實現了 IWindsorInstaller
接口的對象,如果有則調用其 Install()
方法。而在其 Install()
方法里面,一般都是通過傳入的 IIocContainer
或者是 IIocManager
對象來進行組件注冊的功能。
在這里,我們可以針對 IocManager
寫兩個擴展方法 Intsall()
和一個 IDryIocInstaller
接口用於實現相似的功能。
namespace Abp.Dependency
{
public interface IDryIocInstaller
{
void Install(IIocManager iocManager);
}
}
擴展方法:
using System;
using System.Linq;
using System.Reflection;
namespace Abp.Dependency
{
public static class IocManagerExtensions
{
public static void Install(this IIocManager iocManager,IDryIocInstaller installer)
{
installer.Install(iocManager);
}
public static void Install(this IIocManager iocManager, Assembly assembly)
{
// 獲得指定程序集內部所有的 Installer 類型
var installers = assembly.GetTypes().Where(type => type.GetInterfaces().Any(@interface => @interface == typeof(IDryIocInstaller)));
// 遍歷類型並通過 Activator 進行構造並調用
foreach (var installer in installers)
{
(Activator.CreateInstance(installer) as IDryIocInstaller)?.Install(iocManager);
}
}
}
}
現在我們回到最開始報錯的地方,將其 Install()
方法改為調用我們新的擴展方法。
public void RegisterAssemblyByConvention(Assembly assembly, ConventionalRegistrationConfig config)
{
var context = new ConventionalRegistrationContext(assembly, this, config);
foreach (var registerer in _conventionalRegistrars)
{
registerer.RegisterAssembly(context);
}
if (config.InstallInstallers)
{
// 調用之前編寫的擴展方法
this.Install(assembly);
}
}
4.1.2 依賴注入輔助接口改造
Abp 庫本身提供了兩個接口 (ITransientDependency
、ISingletonDependency
) 來幫助用戶快速地注入某個對象,然后通過注冊規約結合 IocManager
提供的 AddConventionalRegistrar()
方法和 RegisterAssemblyByConvention()
方法能夠快速地將某個程序集內部符合規則的類型進行注入。(PS: 這里其實流程很像之前 Installer 的做法)
在使用 Castle Windsor 的時候,Abp 本身並不需要做太多的工作,就可以實現上述的功能。而 DryIoc 本身是沒有提供這些比較高級的特性的,但原理其實並不復雜, 就是掃描整個程序集的所有類型,然后挨個進行判斷即可。
在原來的 BasicConventionalRegistrar
類型內部,對實現了 ITransientDependency
、 ISingletonDependency
、 IInterceptor
接口的類型進行了自動注冊。所以我們就有了以下的實現代碼:
using System;
using System.Linq;
using System.Reflection;
using Abp.Extensions;
using Castle.DynamicProxy;
namespace Abp.Dependency
{
public class AssemblyType
{
public Type ServiceType { get; set; }
public Type ImplType { get; set; }
}
/// <summary>
/// 本類用於注冊實現了 <see cref="ITransientDependency"/> 和 <see cref="ISingletonDependency"/> 接口的類型。
/// </summary>
public class BasicConventionalRegistrar : IConventionalDependencyRegistrar
{
public void RegisterAssembly(IConventionalRegistrationContext context)
{
// 瞬時對象注冊
var waitRegisterTransient = GetTypes<ITransientDependency>(context.Assembly).ToList();
foreach (var transientType in waitRegisterTransient)
{
context.IocManager.RegisterIfNot(transientType.ServiceType,transientType.ImplType,DependencyLifeStyle.Transient);
}
// 單例對象注冊
var waitRegisterSingleton = GetTypes<ISingletonDependency>(context.Assembly).ToList();
foreach (var singletonType in waitRegisterSingleton)
{
context.IocManager.RegisterIfNot(singletonType.ServiceType,singletonType.ImplType,DependencyLifeStyle.Singleton);
}
// Castle.DynamicProxy 攔截器注冊
var waitRegisterInterceptor = GetTypes<IInterceptor>(context.Assembly).ToList();
foreach (var interceptorType in waitRegisterInterceptor)
{
context.IocManager.RegisterIfNot(interceptorType.ServiceType,interceptorType.ImplType,DependencyLifeStyle.Transient);
}
}
private ParallelQuery<AssemblyType> GetTypes<TInterface>(Assembly assembly)
{
Type GetServiceType(Type type)
{
var interfaces = type.GetInterfaces().Where(i => i != typeof(TInterface));
// 優先匹配去除 I 之后的接口
var defaultInterface = interfaces.FirstOrDefault(i => type.Name.Equals(i.Name.RemovePreFix("I")));
if (defaultInterface != null) return defaultInterface;
if (interfaces.FirstOrDefault() != null) return interfaces.FirstOrDefault();
return type;
}
return assembly.GetTypes()
.AsParallel()
.Where(type => typeof(TInterface).IsAssignableFrom(type))
.Where(type => type.GetInterfaces().Any() && !type.IsInterface)
.Where(type => !type.IsGenericTypeDefinition)
.Where(type => !type.IsAbstract)
.Select(type => new AssemblyType
{
ServiceType = GetServiceType(type),
ImplType = type
});
}
}
}
在我們實現的新的注冊規約當中可以看到,其實最核心的代碼在於 GetTypes()
方法內部,在其內部進行了比較復雜的判斷邏輯,其余的瞬時對象與單例對象的注入,都是直接調用的 IIocManager
接口所提供的注冊方法。
4.1.3 攔截器綁定
因為沒有使用 Castle Windsor ,那么我們攔截器如何使用?又如何與類型進行綁定的呢?
在 DryIoc 官方文檔已經說明,DryIoc 本身的攔截功能也是通過 Castle Dynamic Proxy 來實現的,所以我們只需要編寫一個輔助的靜態擴展類即可。
using System;
using System.Linq;
using Castle.DynamicProxy;
using DryIoc;
using ImTools;
public static class DryIocInterception
{
static readonly DefaultProxyBuilder ProxyBuilder = new DefaultProxyBuilder();
public static void Intercept(this IRegistrator registrator,Type serviceType,Type interceptorType,Type implType, object serviceKey = null)
{
// 判斷傳入的類型是接口還是類型,以便建立代理類
Type proxyType;
if (serviceType.IsInterface())
proxyType = ProxyBuilder.CreateInterfaceProxyTypeWithTargetInterface(
serviceType, ArrayTools.Empty<Type>(), ProxyGenerationOptions.Default);
else if (serviceType.IsClass())
proxyType = ProxyBuilder.CreateClassProxyTypeWithTarget(
serviceType, ArrayTools.Empty<Type>(), ProxyGenerationOptions.Default);
else
throw new ArgumentException(
$"Intercepted service type {serviceType} is not a supported, cause it is nor a class nor an interface");
// 創建 DryIoc 裝飾器
var decoratorSetup = serviceKey == null
? Setup.DecoratorWith(useDecorateeReuse: true)
: Setup.DecoratorWith(r => serviceKey.Equals(r.ServiceKey), useDecorateeReuse: true);
// 替換注冊原來接口的解析,解析到新的代理類
registrator.Register(serviceType, proxyType,
made: Made.Of((Type type) => type.GetConstructors().SingleOrDefault(c => c.GetParameters().Length != 0),
Parameters.Of.Type<IInterceptor[]>(interceptorType.MakeArrayType()),
// 一定要加上這個,不然屬性注入無法使用
PropertiesAndFields.Auto),
setup: decoratorSetup);
}
public static void Intercept<TService,TImplType, TInterceptor>(this IRegistrator registrator, object serviceKey = null)
where TInterceptor : class, IInterceptor
{
Intercept(registrator,typeof(TService),typeof(TInterceptor),typeof(TImplType),serviceKey);
}
}
這個擴展類的用法,在后面就有體現。
4.1.4 攔截器注冊器綁定事件
最開始 Abp 攔截器是在什么時候與具體類型綁定的呢?其實就是在 Castle Windsor 注入組件的時候,各個攔截器注冊器都會監聽這個組件注入事件。當事件被觸發的時候,Abp 各個攔截器注冊器都會執行一系列的判斷來確保當前類型應該綁定哪一個攔截器。
Abp 自帶的攔截器一共有 5 種:工作單元攔截器、參數驗證攔截器、授權攔截器、審計日志攔截器、實體歷史攔截器。這五種攔截器都是在 AbpBootstrapper
執行創建方法的時候會被調用,調用的時候會監聽組件注冊事件。
現在的問題是,我們已經沒有使用 Castle Windsor 也就沒有辦法使用 IWindsorContainer
來監聽組件注冊事件。而 DryIoc 本身也是沒有提供這種注入事件的,所以這里我們就只有抽象到 IocManager
類型當中,當 IocManager
的幾個注冊方法被調用的時候,顯式觸發一個事件通知這些攔截器注冊器對象。
首先我們來到 IIocManager
接口,為其添加一個公開的委托屬性,該委托的定義也在下面給出來了。
委托定義:
using System;
namespace Abp.Dependency
{
public delegate void RegisterTypeEventHandler(IIocManager iocManager, Type registerType,Type implementationType);
}
IIocManager
接口處新增的屬性:
using System;
using DryIoc;
namespace Abp.Dependency
{
/// <summary>
/// 依賴注入容器管理器,
/// 本接口用於執行注入操作
/// </summary>
public interface IIocManager : IIocRegistrar, IIocResolver, IDisposable
{
IContainer IocContainer { get; }
new bool IsRegistered(Type type);
new bool IsRegistered<T>();
event RegisterTypeEventHandler RegisterTypeEventHandler;
}
}
之后呢,我們在 IocManager
的 Register()
注冊方法內部都顯式地觸發這個事件。
public void Register(Type type, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton)
{
IocContainer.Register(type,ApplyLifestyle(lifeStyle),
made: Made.Of(FactoryMethod.ConstructorWithResolvableArguments));
RegisterTypeEventHandler?.Invoke(this,type,type);
}
就如同這樣,實現的效果也是每當有組件注冊的時候,都會觸發該事件。而各個注冊器內部的 Initialize()
方法都傳入了一個 IIocManager
對象,所以我們只需要將原有的監聽事件改為綁定我們自己定義的事件即可。
下面以工作單元的攔截器注冊器為例:
using System.Linq;
using System.Reflection;
using Abp.Dependency;
using Castle.Core;
using Castle.MicroKernel;
namespace Abp.Domain.Uow
{
/// <summary>
/// This class is used to register interceptor for needed classes for Unit Of Work mechanism.
/// </summary>
internal static class UnitOfWorkRegistrar
{
/// <summary>
/// Initializes the registerer.
/// </summary>
/// <param name="iocManager">IOC manager</param>
public static void Initialize(IIocManager iocManager)
{
iocManager.RegisterTypeEventHandler += (manager, type, implementationType) =>
{
var implType = implementationType.GetTypeInfo();
HandleTypesWithUnitOfWorkAttribute(implType,manager);
HandleConventionalUnitOfWorkTypes(iocManager, implType);
};
}
private static void HandleTypesWithUnitOfWorkAttribute(TypeInfo implementationType,IIocManager iocManager)
{
if (IsUnitOfWorkType(implementationType) || AnyMethodHasUnitOfWork(implementationType))
{
// 使用的是上面寫的擴展方法
iocManager.IocContainer.Intercept(implementationType,typeof(UnitOfWorkInterceptor));
}
}
private static void HandleConventionalUnitOfWorkTypes(IIocManager iocManager, TypeInfo implementationType)
{
if (!iocManager.IsRegistered<IUnitOfWorkDefaultOptions>())
{
return;
}
var uowOptions = iocManager.Resolve<IUnitOfWorkDefaultOptions>();
if (uowOptions.IsConventionalUowClass(implementationType.AsType()))
{
// 使用的是上面寫的擴展方法
iocManager.IocContainer.Intercept(implementationType,typeof(UnitOfWorkInterceptor));
}
}
private static bool IsUnitOfWorkType(TypeInfo implementationType)
{
return UnitOfWorkHelper.HasUnitOfWorkAttribute(implementationType);
}
private static bool AnyMethodHasUnitOfWork(TypeInfo implementationType)
{
return implementationType
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Any(UnitOfWorkHelper.HasUnitOfWorkAttribute);
}
}
}
按照上面這種步驟,完成剩余攔截器注冊器的更改。
4.1.5 收尾工作
如果上述操作都已經完成了的話,那么基本上只剩下 AbpBootstrapper
類型與 AbpKernelModule
幾處細小的錯誤了。
首先我們看一下 AbpBootstrapper
還提示哪些錯誤,然后我們進行更改。
// 第一處
public virtual void Initialize()
{
ResolveLogger();
try
{
RegisterBootstrapper();
// IocManager.IocContainer.Install(new AbpCoreInstaller());
// 此處使用的仍然是 IWindsorContainer 的 Install 方法,改為最新的
IocManager.Install(new AbpCoreInstaller());
IocManager.Resolve<AbpPlugInManager>().PlugInSources.AddRange(PlugInSources);
IocManager.Resolve<AbpStartupConfiguration>().Initialize();
_moduleManager = IocManager.Resolve<AbpModuleManager>();
_moduleManager.Initialize(StartupModule);
_moduleManager.StartModules();
}
catch (Exception ex)
{
_logger.Fatal(ex.ToString(), ex);
throw;
}
}
上面仍然報錯,我們繼續來到 AbpCoreInstaller
將其接口由 IWindsorInstaller
改為 IDryIocInstaller
並重新實現接口的方法。
using Abp.Application.Features;
using Abp.Auditing;
using Abp.BackgroundJobs;
using Abp.Configuration.Startup;
using Abp.Domain.Uow;
using Abp.EntityHistory;
using Abp.Localization;
using Abp.Modules;
using Abp.Notifications;
using Abp.PlugIns;
using Abp.Reflection;
using Abp.Resources.Embedded;
using Abp.Runtime.Caching.Configuration;
using DryIoc;
namespace Abp.Dependency.Installers
{
/// <summary>
/// ABP 框架核心類安裝器
/// 本類用於注冊 ABP 框架當中核心組件
/// </summary>
internal class AbpCoreInstaller : IDryIocInstaller
{
public void Install(IIocManager iocManager)
{
iocManager.IocContainer.RegisterMany(new[] {typeof(IUnitOfWorkDefaultOptions), typeof(UnitOfWorkDefaultOptions)}, typeof(UnitOfWorkDefaultOptions), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(INavigationConfiguration), typeof(NavigationConfiguration)}, typeof(NavigationConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(ILocalizationConfiguration), typeof(LocalizationConfiguration)}, typeof(LocalizationConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IAuthorizationConfiguration), typeof(AuthorizationConfiguration)}, typeof(AuthorizationConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IValidationConfiguration), typeof(ValidationConfiguration)}, typeof(ValidationConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IFeatureConfiguration), typeof(FeatureConfiguration)}, typeof(FeatureConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(ISettingsConfiguration), typeof(SettingsConfiguration)}, typeof(SettingsConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IModuleConfigurations), typeof(ModuleConfigurations)}, typeof(ModuleConfigurations), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IEventBusConfiguration), typeof(EventBusConfiguration)}, typeof(EventBusConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IMultiTenancyConfig), typeof(MultiTenancyConfig)}, typeof(MultiTenancyConfig), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(ICachingConfiguration), typeof(CachingConfiguration)}, typeof(CachingConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IAuditingConfiguration), typeof(AuditingConfiguration)}, typeof(AuditingConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IBackgroundJobConfiguration), typeof(BackgroundJobConfiguration)}, typeof(BackgroundJobConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(INotificationConfiguration), typeof(NotificationConfiguration)}, typeof(NotificationConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IEmbeddedResourcesConfiguration), typeof(EmbeddedResourcesConfiguration)}, typeof(EmbeddedResourcesConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IAbpStartupConfiguration), typeof(AbpStartupConfiguration)}, typeof(AbpStartupConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IEntityHistoryConfiguration), typeof(EntityHistoryConfiguration)}, typeof(EntityHistoryConfiguration), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(ITypeFinder), typeof(TypeFinder)}, typeof(TypeFinder), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IAbpPlugInManager), typeof(AbpPlugInManager)}, typeof(AbpPlugInManager), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IAbpModuleManager), typeof(AbpModuleManager)}, typeof(AbpModuleManager), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(IAssemblyFinder), typeof(AbpAssemblyFinder)}, typeof(AbpAssemblyFinder), Reuse.Singleton);
iocManager.IocContainer.RegisterMany(new[] {typeof(ILocalizationManager), typeof(LocalizationManager)}, typeof(LocalizationManager), Reuse.Singleton);
}
}
}
而 AbpBootstrapper
類型還有一處問題一樣是使用了 IWindsorContainer
提供的方法,這里改為 DryIoc 提供的方法即可。
private void RegisterBootstrapper()
{
if (!IocManager.IsRegistered<AbpBootstrapper>())
{
// IocManager.IocContainer.Register(
// Component.For<AbpBootstrapper>().Instance(this)
// );
IocManager.IocContainer.UseInstance(this);
}
}
第二個問題則是 AbpKernelModule
當中的報錯,其實與上一個類型的錯誤一樣,第一個是調用了之前的 Install
的方法,並且 Intsaller
也不是繼承自 IDryIocInstaller
,另一個問題則是使用了 IWindsorContainer
里面的注冊方法。
public override void Initialize()
{
foreach (var replaceAction in ((AbpStartupConfiguration)Configuration).ServiceReplaceActions.Values)
{
replaceAction();
}
// IocManager.IocContainer.Install(new EventBusInstaller(IocManager));
IocManager.Install(new EventBusInstaller(IocManager));
IocManager.Register(typeof(IOnlineClientManager<>), typeof(OnlineClientManager<>), DependencyLifeStyle.Singleton);
IocManager.RegisterAssemblyByConvention(typeof(AbpKernelModule).GetAssembly(),
new ConventionalRegistrationConfig
{
InstallInstallers = false
});
}
EventBusInstaller
的變更:
using System.Reflection;
using Abp.Configuration.Startup;
using Abp.Dependency;
using Abp.Events.Bus.Factories;
using Abp.Events.Bus.Handlers;
using Castle.MicroKernel;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using DryIoc;
namespace Abp.Events.Bus
{
/// <summary>
/// Installs event bus system and registers all handlers automatically.
/// </summary>
internal class EventBusInstaller : IDryIocInstaller
{
private readonly IIocResolver _iocResolver;
private readonly IEventBusConfiguration _eventBusConfiguration;
private IEventBus _eventBus;
public EventBusInstaller(IIocResolver iocResolver)
{
_iocResolver = iocResolver;
_eventBusConfiguration = iocResolver.Resolve<IEventBusConfiguration>();
}
public void Install(IIocManager iocManager)
{
if (_eventBusConfiguration.UseDefaultEventBus)
{
iocManager.IocContainer.UseInstance<IEventBus>(EventBus.Default);
}
else
{
iocManager.IocContainer.Register<IEventBus,EventBus>(Reuse.Singleton);
}
_eventBus = iocManager.Resolve<IEventBus>();
iocManager.RegisterTypeEventHandler += (manager, type, implementationType) =>
{
if (!typeof(IEventHandler).GetTypeInfo().IsAssignableFrom(implementationType))
{
return;
}
var interfaces = implementationType.GetTypeInfo().GetInterfaces();
foreach (var @interface in interfaces)
{
if (!typeof(IEventHandler).GetTypeInfo().IsAssignableFrom(@interface))
{
continue;
}
var genericArgs = @interface.GetGenericArguments();
if (genericArgs.Length == 1)
{
_eventBus.Register(genericArgs[0], new IocHandlerFactory(_iocResolver, implementationType));
}
}
};
}
}
}
另外一處的變更如下:
private void RegisterMissingComponents()
{
if (!IocManager.IsRegistered<IGuidGenerator>())
{
// IocManager.IocContainer.Register(
// Component
// .For<IGuidGenerator, SequentialGuidGenerator>()
// .Instance(SequentialGuidGenerator.Instance)
// );
IocManager.IocContainer.UseInstance<IGuidGenerator>(SequentialGuidGenerator.Instance);
IocManager.IocContainer.UseInstance<SequentialGuidGenerator>(SequentialGuidGenerator.Instance);
}
IocManager.RegisterIfNot<IUnitOfWork, NullUnitOfWork>(DependencyLifeStyle.Transient);
IocManager.RegisterIfNot<IAuditingStore, SimpleLogAuditingStore>(DependencyLifeStyle.Singleton);
IocManager.RegisterIfNot<IPermissionChecker, NullPermissionChecker>(DependencyLifeStyle.Singleton);
IocManager.RegisterIfNot<IRealTimeNotifier, NullRealTimeNotifier>(DependencyLifeStyle.Singleton);
IocManager.RegisterIfNot<INotificationStore, NullNotificationStore>(DependencyLifeStyle.Singleton);
IocManager.RegisterIfNot<IUnitOfWorkFilterExecuter, NullUnitOfWorkFilterExecuter>(DependencyLifeStyle.Singleton);
IocManager.RegisterIfNot<IClientInfoProvider, NullClientInfoProvider>(DependencyLifeStyle.Singleton);
IocManager.RegisterIfNot<ITenantStore, NullTenantStore>(DependencyLifeStyle.Singleton);
IocManager.RegisterIfNot<ITenantResolverCache, NullTenantResolverCache>(DependencyLifeStyle.Singleton);
IocManager.RegisterIfNot<IEntityHistoryStore, NullEntityHistoryStore>(DependencyLifeStyle.Singleton);
if (Configuration.BackgroundJobs.IsJobExecutionEnabled)
{
IocManager.RegisterIfNot<IBackgroundJobStore, InMemoryBackgroundJobStore>(DependencyLifeStyle.Singleton);
}
else
{
IocManager.RegisterIfNot<IBackgroundJobStore, NullBackgroundJobStore>(DependencyLifeStyle.Singleton);
}
}
4.1.6 測試
做完以上變更之后,新建一個控制台程序,引用這個 Abp 庫項目,然后鍵入以下代碼進行測試即可。
using System;
using Abp;
using Abp.Modules;
using Abp.Runtime.Session;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
// Abp 框架測試
using (var bootstarp = AbpBootstrapper.Create<StartupModule>())
{
bootstarp.Initialize();
// 解析 IAbpSession 看是否正常地進行了注入
var session = bootstarp.IocManager.Resolve<IAbpSession>();
if (session != null && session is ClaimsAbpSession claimsSession)
{
Console.WriteLine("當前 Session 已經成功被注入為 ClaimAbpSession");
}
}
Console.ReadLine();
}
}
[DependsOn(typeof(AbpKernelModule))]
public class StartupModule : AbpModule
{
}
}
4.2 EFCore 庫與相關庫改造
針對 Abp 庫進行測試之后,基本上我們 Abp 現在所有組件都是通過 DryIoc 來進行注冊與解析的了。不過僅僅針對 Abp 做這些更改其實是不夠的,除了 Abp 核心庫之外,我們最常用的就是數據庫操作了。因為在 Abp.EntityFrameworkCore 庫 和 Abp.EntityFramework.Common 的內部也有部分代碼在之前是直接通過 IWindsorContainer
進行注冊與解析操作的,所以我們也得繼續改報錯的地方。
4.2.1 倉儲類注冊
在 Abp.EntityFramework.Common 庫的 EfGenericRepositoryRegistrar
類型內部,有使用到 IWindsorContainer
的組件注冊方法,用於注入 IRepository<,>
泛型倉儲。下面代碼展示的更改后的結果:
private void RegisterForDbContext(
Type dbContextType,
IIocManager iocManager,
Type repositoryInterface,
Type repositoryInterfaceWithPrimaryKey,
Type repositoryImplementation,
Type repositoryImplementationWithPrimaryKey)
{
foreach (var entityTypeInfo in _dbContextEntityFinder.GetEntityTypeInfos(dbContextType))
{
var primaryKeyType = EntityHelper.GetPrimaryKeyType(entityTypeInfo.EntityType);
if (primaryKeyType == typeof(int))
{
var genericRepositoryType = repositoryInterface.MakeGenericType(entityTypeInfo.EntityType);
if (!iocManager.IsRegistered(genericRepositoryType))
{
var implType = repositoryImplementation.GetGenericArguments().Length == 1
? repositoryImplementation.MakeGenericType(entityTypeInfo.EntityType)
: repositoryImplementation.MakeGenericType(entityTypeInfo.DeclaringType,
entityTypeInfo.EntityType);
// iocManager.IocContainer.Register(
// Component
// .For(genericRepositoryType)
// .ImplementedBy(implType)
// .Named(Guid.NewGuid().ToString("N"))
// .LifestyleTransient()
// );
iocManager.IocContainer.Register(genericRepositoryType,implType,Reuse.Transient);
}
}
var genericRepositoryTypeWithPrimaryKey = repositoryInterfaceWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType,primaryKeyType);
if (!iocManager.IsRegistered(genericRepositoryTypeWithPrimaryKey))
{
var implType = repositoryImplementationWithPrimaryKey.GetGenericArguments().Length == 2
? repositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType, primaryKeyType)
: repositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType, primaryKeyType);
// iocManager.IocContainer.Register(
// Component
// .For(genericRepositoryTypeWithPrimaryKey)
// .ImplementedBy(implType)
// .Named(Guid.NewGuid().ToString("N"))
// .LifestyleTransient()
// );
iocManager.IocContainer.Register(genericRepositoryTypeWithPrimaryKey,implType,Reuse.Transient);
}
}
}
按照以上方法更改之后,Abp.EntityFramework.Common 應該可以正常地進行編譯了。
4.2.2 DbContext 配置類更改
在 AbpEfCoreConfiguration
類型當中,也有使用到 IWindsorContainer
接口的地方,進行如下變更即可:
using System;
using Abp.Dependency;
using Castle.MicroKernel.Registration;
using DryIoc;
using Microsoft.EntityFrameworkCore;
namespace Abp.EntityFrameworkCore.Configuration
{
public class AbpEfCoreConfiguration : IAbpEfCoreConfiguration
{
private readonly IIocManager _iocManager;
public AbpEfCoreConfiguration(IIocManager iocManager)
{
_iocManager = iocManager;
}
public void AddDbContext<TDbContext>(Action<AbpDbContextConfiguration<TDbContext>> action)
where TDbContext : DbContext
{
// _iocManager.IocContainer.Register(
// Component.For<IAbpDbContextConfigurer<TDbContext>>().Instance(
// new AbpDbContextConfigurerAction<TDbContext>(action)
// ).IsDefault()
// );
_iocManager.IocContainer.UseInstance<IAbpDbContextConfigurer<TDbContext>>(new AbpDbContextConfigurerAction<TDbContext>(action));
}
}
}
4.2.3 EFCore 庫模塊變更
該錯誤在 AbpEntityFrameworkCoreModule
模塊的 Initialize()
方法里面,一樣的是因為使用了 IWndsorContainer
的注冊方法導致的。
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(AbpEntityFrameworkCoreModule).Assembly);
// IocManager.IocContainer.Register(
// Component.For(typeof(IDbContextProvider<>))
// .ImplementedBy(typeof(UnitOfWorkDbContextProvider<>))
// .LifestyleTransient()
// );
IocManager.IocContainer.Register(typeof(IDbContextProvider<>),typeof(UnitOfWorkDbContextProvider<>),Reuse.Transient);
RegisterGenericRepositoriesAndMatchDbContexes();
}
而另一處錯誤則是在 RegisterGenericRepositoriesAndMatchDbContexes()
方法內部:
private void RegisterGenericRepositoriesAndMatchDbContexes()
{
// ... 其他的代碼
using (IScopedIocResolver scope = IocManager.CreateScope())
{
foreach (var dbContextType in dbContextTypes)
{
Logger.Debug("Registering DbContext: " + dbContextType.AssemblyQualifiedName);
scope.Resolve<IEfGenericRepositoryRegistrar>().RegisterForDbContext(dbContextType, IocManager, EfCoreAutoRepositoryTypes.Default);
// IocManager.IocContainer.Register(
// Component.For<ISecondaryOrmRegistrar>()
// .Named(Guid.NewGuid().ToString("N"))
// .Instance(new EfCoreBasedSecondaryOrmRegistrar(dbContextType, scope.Resolve<IDbContextEntityFinder>()))
// .LifestyleTransient()
// );
IocManager.IocContainer.UseInstance<ISecondaryOrmRegistrar>(new EfCoreBasedSecondaryOrmRegistrar(dbContextType,
scope.Resolve<IDbContextEntityFinder>()));
}
scope.Resolve<IDbContextTypeMatcher>().Populate(dbContextTypes);
}
}
4.2.4 DbContext 解析器變更
這個解析器的主要問題則與前面的不一樣,這里報錯是因為在構造 DbContext
的時候需要傳入構造參數。根據我們之前的改動,現在 Resolve()
方法傳入的是一個 object[]
數組,而不是原來的 object
對象,所以這里需要進行一些細微的改動。
using Abp.Dependency;
using Abp.EntityFramework;
using Abp.EntityFrameworkCore.Configuration;
using Microsoft.EntityFrameworkCore;
using System;
using System.Data.Common;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using System.Linq;
namespace Abp.EntityFrameworkCore
{
public class DefaultDbContextResolver : IDbContextResolver, ITransientDependency
{
// ... 其他代碼
public TDbContext Resolve<TDbContext>(string connectionString, DbConnection existingConnection)
where TDbContext : DbContext
{
// ... 其他代碼
try
{
if (isAbstractDbContext)
{
// return (TDbContext) _iocResolver.Resolve(concreteType, new
// {
// options = CreateOptionsForType(concreteType, connectionString, existingConnection)
// });
return (TDbContext) _iocResolver.Resolve(concreteType, new object[]
{
CreateOptionsForType(concreteType, connectionString, existingConnection)
});
}
// return _iocResolver.Resolve<TDbContext>(new
// {
// options = CreateOptions<TDbContext>(connectionString, existingConnection)
// });
return _iocResolver.Resolve<TDbContext>(new object[]
{
CreateOptions<TDbContext>(connectionString, existingConnection)
});
}
catch (Castle.MicroKernel.Resolvers.DependencyResolverException ex)
{
// ... 其他代碼
}
// ... 其他代碼
}
// ... 其他代碼
}
}
至此,針對於 EFCore 相關的庫改造就已經成功完成了。
4.3 ASP .NET Core 相關改造
到目前,我們已經針對 Abp 的核心庫和 EF Core 庫都進行了一些不算大的改動,現在就只剩 Abp.AspNetCore 庫了。因為 .NET Core 自己使用了一套 DI 框架。而我們在之前的源碼分析也有講到過,通過更改 Startup
類的 ConfigureService()
方法的返回值為 IServiceProvider
,就可以將原來內部的 DI 框架替換為其他的 DI 框架。
在原來 Abp.AspNetCore 庫的 AbpServiceCollectionExtensions
擴展類當中可以看到以下代碼:
public static IServiceProvider AddAbp<TStartupModule>(this IServiceCollection services, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
where TStartupModule : AbpModule
{
var abpBootstrapper = AddAbpBootstrapper<TStartupModule>(services, optionsAction);
ConfigureAspNetCore(services, abpBootstrapper.IocManager);
return WindsorRegistrationHelper.CreateServiceProvider(abpBootstrapper.IocManager.IocContainer, services);
}
這里我們可以看到,Abp 通過 WindsorRegistrationHelper
類創建並返回了一個 IServiceProvider
對象。那么 DryIoc 是否也為我們提供了這樣的擴展方法呢?答案是有的,DryIoc 通過 DryIoc.Microsoft.DependencyInjection 給我們提供了一個適配器,該適配器可以基於 DryIoc 創建一個 IServiceProvier
來替換掉默認的 DI 框架。
首先我們為 Abp.AspNetCore 庫添加 DryIoc.Microsoft.DependencyInjection 的 NuGet 包,然后編輯上述方法:
public static IServiceProvider AddAbp<TStartupModule>(this IServiceCollection services, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
where TStartupModule : AbpModule
{
var abpBootstrapper = AddAbpBootstrapper<TStartupModule>(services, optionsAction);
ConfigureAspNetCore(services, abpBootstrapper.IocManager);
var newContainer = new Container(rules =>
rules.WithAutoConcreteTypeResolution())
.WithDependencyInjectionAdapter(services);
abpBootstrapper.IocManager.InitializeInternalContainer(newContainer);
return abpBootstrapper.IocManager.IocContainer.BuildServiceProvider();
}
4.3.1 視圖組件與其他組件的自動注冊
除了更改上述問題之外,在 Abp.AspNetCore 庫還有一個注冊器 AbpAspNetCoreConventionalRegistrar
,在里面也使用了 IWindsorContainer
接口的注冊方法,此處也需要進行更改。
using System.Linq;
using Abp.Dependency;
using Microsoft.AspNetCore.Mvc;
namespace Abp.AspNetCore
{
public class AbpAspNetCoreConventionalRegistrar : IConventionalDependencyRegistrar
{
public void RegisterAssembly(IConventionalRegistrationContext context)
{
//ViewComponents
var types = context.Assembly.GetTypes()
.AsParallel()
.Where(type => typeof(ViewComponent).IsAssignableFrom(type))
.Where(type => !type.IsGenericTypeDefinition)
.Where(type => !type.IsAbstract)
.AsSequential();
foreach (var type in types)
{
context.IocManager.Register(type);
}
}
}
}
完成以上操作之后,我們新建 4 個項目,分別是 AspNetCoreApp(Web 項目) 、 AspNetCoreApp.Core(庫項目) 、AspNetCore.Application(庫項目) 、 AspNetCoreApp.EntityFrameworkCore(庫項目) ,並且配置好各自的依賴關系。
4.3.2 IServiceProvider 適配器
首先我們更改 AspNetCoreApp 下面的 ConfigureService()
方法與 Configure()
方法如下:
using System;
using Abp.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace AspNetCoreApp
{
public class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
return services.AddAbp<AspNetCoreAppModule>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc();
app.UseAbp(op=>op.UseCastleLoggerFactory = false);
}
}
}
不出意外的話,會拋出以下異常信息:
上述異常的意思是說無法解析 Microsoft.AspNetCore.Hosting.Internal.WebHostOptions
對象,這說明我們的 DryIoc 容器並沒有將 MVC 服務初始化注入的對象獲取到。
我們在 AddAbp<TStartupModule>()
方法內打一個斷點,看一下在 ConfigureAspNetCore()
方法內部注入的對象是否放在 IContainer
里面,結果發現並沒有。
所以之后呢,我經過測試,只有 new
一個新的 Container
對象,然后對其調用 WithDependencyInjectionAdapter()
方法才會正常的獲取到注入的 MVC 組件。
效果:
那么就需要將 IocManager
內部的 IocContainer
賦值為這里創建的 newContainer
對象,而 IIocManager
接口所定義的 IocContainer
屬性是只讀的。所以這里我為 IIocManager
接口新增了一個 InitializeInternalContainer()
方法用於初始化 IocContainer
屬性。
public interface IIocManager : IIocRegistrar, IIocResolver, IDisposable
{
// ... 其他代碼
/// <summary>
/// 類型注冊事件
/// </summary>
event RegisterTypeEventHandler RegisterTypeEventHandler;
/// <summary>
/// 初始化 IocManager 內部的容器
/// </summary>
void InitializeInternalContainer(IContainer dryIocContainer);
}
IocManager
需要實現該方法,並且將其構造器內的相關注冊方法移動到 InitializeInternalContainer()
內部。
public class IocManager : IIocManager
{
// ... 其他代碼
public IocManager()
{
_conventionalRegistrars = new List<IConventionalDependencyRegistrar>();
}
public void InitializeInternalContainer(IContainer dryIocContainer)
{
IocContainer = dryIocContainer;
//Register self!
IocContainer.UseInstance(typeof(IocManager),this);
IocContainer.UseInstance(typeof(IIocManager),this);
IocContainer.UseInstance(typeof(IIocRegistrar),this);
IocContainer.UseInstance(typeof(IIocResolver),this);
}
// ... 其他代碼
}
之后再回到最開始的地方,我們最終 AddAbp<TStartupModule>()
方法的內部實現是下面這個樣子的:
public static IServiceProvider AddAbp<TStartupModule>(this IServiceCollection services, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
where TStartupModule : AbpModule
{
var abpBootstrapper = AddAbpBootstrapper<TStartupModule>(services, optionsAction);
ConfigureAspNetCore(services, abpBootstrapper.IocManager);
var newContainer = new Container().WithDependencyInjectionAdapter(services);
abpBootstrapper.IocManager.InitializeInternalContainer(newContainer);
return abpBootstrapper.IocManager.IocContainer.BuildServiceProvider();
}
運行 AspNetCoreApp 項目,我們可以看到正常運行了。
五、存在的問題
5.1 ApplicationService 屬性注入失效
在示例項目當中,我在 AspNetCoreApp.Application 庫當中建立了一個 TestApplicationService
服務,該服務用有一個 GetJson()
方法。
在其內部,我調用了父類提供的 AbpSession
屬性,按照正常的情況下,該屬性的實現應該是 ClaimsAbpSession
類型,不過通過測試之后我得到了以下結果:
可以看到,它填充的是默認的空實現,造成這個問題的原因是,DryIoc 本身在注冊對象的時候,需要顯式提供屬性注入的選項,否則默認是不啟用屬性注入的。
鑒於此,我們為 IIocRegistrar
與 IocManager
內所提供的 Register()
方法增加一個 isAutoInjectProperty
字段,用於判斷是否在注冊的使用啟用屬性注入。
public interface IIocRegistrar
{
/// <summary>
/// Registers a type as self registration.
/// </summary>
/// <typeparam name="T">Type of the class</typeparam>
/// <param name="lifeStyle">Lifestyle of the objects of this type</param>
void Register<T>(DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton,bool isAutoInjectProperty = false)
where T : class;
/// <summary>
/// Registers a type as self registration.
/// </summary>
/// <param name="type">Type of the class</param>
/// <param name="lifeStyle">Lifestyle of the objects of this type</param>
void Register(Type type, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton,bool isAutoInjectProperty = false);
/// <summary>
/// Registers a type with it's implementation.
/// </summary>
/// <typeparam name="TType">Registering type</typeparam>
/// <typeparam name="TImpl">The type that implements <see cref="TType"/></typeparam>
/// <param name="lifeStyle">Lifestyle of the objects of this type</param>
void Register<TType, TImpl>(DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton,bool isAutoInjectProperty = false)
where TType : class
where TImpl : class, TType;
/// <summary>
/// Registers a type with it's implementation.
/// </summary>
/// <param name="type">Type of the class</param>
/// <param name="impl">The type that implements <paramref name="type"/></param>
/// <param name="lifeStyle">Lifestyle of the objects of this type</param>
void Register(Type type, Type impl, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton,bool isAutoInjectProperty = false);
}
而具體實現則需要使用 isAutoInjectProperty
來判斷是否需要屬性注入功能,下面隨便以一個 Register()
方法為例。
public void Register(Type type, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton,bool isAutoInjectProperty = false)
{
IocContainer.Register(type,
ApplyLifestyle(lifeStyle),
made: Made.Of(FactoryMethod.ConstructorWithResolvableArguments,
propertiesAndFields: isAutoInjectProperty
? PropertiesAndFields.Auto
: null));
RegisterTypeEventHandler?.Invoke(this,
type,
type);
}
寫好之后,我們再回到 BasicConventionalRegistrar
注冊器當中,因為應用服務類型個都是瞬時對象,並且應用服務都會繼承 IApplicationService
接口。所以我們加一個判斷,如果是應用服務的話,則在注冊的時候,允許進行屬性注入。
public class BasicConventionalRegistrar : IConventionalDependencyRegistrar
{
// ... 其他代碼
public void RegisterAssembly(IConventionalRegistrationContext context)
{
// 瞬時對象注冊
var waitRegisterTransient = GetTypes<ITransientDependency>(context.Assembly).ToList();
foreach (var transientType in waitRegisterTransient)
{
if (typeof(IApplicationService).IsAssignableFrom(transientType.ImplType))
{
context.IocManager.Register(transientType.ServiceType,transientType.ImplType,DependencyLifeStyle.Transient,true);
continue;
}
context.IocManager.RegisterIfNot(transientType.ServiceType,transientType.ImplType,DependencyLifeStyle.Transient);
}
// ... 其他代碼
}
}
進行了上述更改之后,再次調用接口進行測試可以看到屬性已經被正常地注入了。
PS:
這里一定要注意 AspNetCoreApp.Application 庫里面的
AspNetCoreAppAppicationModule
模塊一定要在Initialize()
方法調用IocManager.RegisterAssemblyByConvention(typeof(AspNetCoreAppApplicationModule).Assembly);
否則應用服務不會被注入到 Ioc 容器當中的。
5.2 無法獲取攔截器真實類型
該問題主要出在攔截器里面,因為在 DryIoc 當中如果某個類型綁定了多個攔截器,那么就會形成一個層級關系。類似於下面截圖的這樣:
所以如果你需要在外層的攔截器獲取真實對象,目前只能通過遞歸來解決該問題。
public static Type GetUnproxiedType(object instance)
{
if (instance is IProxyTargetAccessor proxyTargetAccessor)
{
var newInstance = proxyTargetAccessor.DynProxyGetTarget();
return GetUnproxiedType(newInstance);
}
return instance.GetType();
}
然后使用方式如下:
public void Intercept(IInvocation invocation)
{
_authorizationHelper.Authorize(invocation.MethodInvocationTarget, TypeExtensions.GetUnproxiedType(invocation.Proxy));
invocation.Proceed();
}
該問題我在 Github 上面已經向作者提出,作者反饋正在解決。
六、結語
雖然通過文章看起來整個過程十分簡單輕松,但是博主當時在操作的時候遇到了不少的坑。結合博主之前關於 Abp 源碼分析的文章,你可以更加地了解 Abp 整個框架的結構。
通過這種方式,你除了可以將 DI 框架換成 DryIoc 之外,你也可以替換成你喜歡的其他 DI 框架。
在 Abp vNext 當中的設計Ioc 容器是可以很方便替換的,你可以更加方便地替換 Ioc 容器,就不需要像現在這樣麻煩。
PS: 官方就有針對於 AutoFac 與 Castle Windsor 的擴展。
改造完成的代碼與 DEMO 的 GitHub 地址:https://github.com/GameBelial/Abp-DryIoc.git