文章目錄 介紹 ABP的依賴註入系統是基於Microsoft的依賴註入擴展庫(Microsoft.Extensions.DependencyInjection nuget包)開發的。所以我們採用dotnet自帶的註入方式也是支持的。 由於ABP是一個模塊化框架,因此每個模塊都定義它自己的服務併在它自 ...
介紹
ABP的依賴註入系統是基於Microsoft的依賴註入擴展庫(Microsoft.Extensions.DependencyInjection nuget包)開發的。所以我們採用dotnet自帶的註入方式也是支持的。
- 由於ABP是一個模塊化框架,因此每個模塊都定義它自己的服務併在它自己的單獨模塊類中通過依賴註入進行註冊.例:
public class BlogModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
//在此處註入依賴項
// dotnet自帶依賴註入方式也是支持的
context.services.AddTransient
context.services.AddScoped
context.services.AddSingleton
}
}
Autofac
Autofac 是.Net世界中最常用的依賴註入框架之一. 相比.Net Core標準的依賴註入庫, 它提供了更多高級特性, 比如動態代理和屬性註入.
集成
1.安裝 Volo.Abp.Autofac nuget 包到你的項目 (對於一個多項目應用程式, 建議安裝到可執行項目或者Web項目中.)
2.模塊添加 AbpAutofacModule 依賴:
[DependsOn(typeof(AbpAutofacModule))]
public class MyModule : AbpModule
{
//...
}
}
3.配置 AbpApplicationCreationOptions 用 Autofac 替換預設的依賴註入服務. 根據應用程式類型, 情況有所不同
- ASP.NET Core 應用程式
public class Program
{
public static int Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseAutofac(); //Integrate Autofac!
}
- 控制台應用程式
namespace AbpConsoleDemo
{
class Program
{
static void Main(string[] args)
{
using (var application = AbpApplicationFactory.Create<AppModule>(options =>
{
options.UseAutofac(); //Autofac integration
}))
{
//...
}
}
}
}
依照約定的註冊
如果實現這些介面,則會自動將類註冊到依賴註入:
- ITransientDependency 註冊為transient生命周期.
- ISingletonDependency 註冊為singleton生命周期.
- IScopedDependency 註冊為scoped生命周期.
預設特定類型
一些特定類型會預設註冊到依賴註入.例子:
- 模塊類註冊為singleton.
- MVC控制器(繼承Controller或AbpController)被註冊為transient.
- MVC頁面模型(繼承PageModel或AbpPageModel)被註冊為transient.
- MVC視圖組件(繼承ViewComponent或AbpViewComponent)被註冊為transient.
- 應用程式服務(實現IApplicationService介面或繼承ApplicationService類)註冊為transient.
- 存儲庫(實現IRepository介面)註冊為transient.
- 域服務(實現IDomainService介面)註冊為transient.
手動註冊
在某些情況下,你可能需要向IServiceCollection手動註冊服務,尤其是在需要使用自定義工廠方法或singleton實例時.在這種情況下,你可以像Microsoft文檔描述的那樣直接添加服務.
public class BlogModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.services.AddTransient<ITestMicrosoftManager, TestMicrosoftManager>();
}
}
如何使用
構造函數註入
- 構造方法註入是將依賴項註入類的首選方式
屬性註入
- Microsoft依賴註入庫不支持屬性註入.但是,ABP可以與第三方DI提供商(例如Autofac)集成,以實現屬性註入。
- 屬性註入依賴項通常被視為可選依賴項.這意味著沒有它們,服務也可以正常工作.Logger就是這樣的依賴項,MyService可以繼續工作而無需日誌記錄.
public class MyService : ITransientDependency
{
public ILogger<MyService> Logger { get; set; }
public MyService()
{
Logger = NullLogger<MyService>.Instance;
}
public void DoSomething()
{
//...使用 Logger 寫日誌...
}
}
IServiceProvider
直接從IServiceProvider解析服務.在這種情況下,你可以將IServiceProvider註入到你的類並使用
public class MyService : ITransientDependency
{
private readonly IServiceProvider _serviceProvider;
public MyService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void DoSomething()
{
var taxCalculator = _serviceProvider.GetService<ITaxCalculator>();
//...
}
}
服務替換
在某些情況下,需要替換某些介面的實現.
- ITestManager有一個預設實現DefaultManager,但是我現在想替換成TestReplaceManager,該如何操作呢?
原生dotnet方式替換
services.Replace(ServiceDescriptor.Transient<ITestManager, TestReplaceManager>());
Abp支持
- 加上Dependency特性標簽
- 加上ExposeServices特性標簽
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(ITestManager))]
public class TestReplaceManager : ITestManager, ITransientDependency
{
public void Print()
{
Console.WriteLine("TestReplaceManager");
}
}
問題
- 有時候我們實現了ITransientDependency,ISingletonDependency,IScopedDependency但是再運行是還是提示依賴註入失敗?
- 實現類的名稱拼寫錯誤
- 比如介面名稱為ITestAppService,但是實現類為DefaultTessAppService,這個時候編譯不會報錯,但是運行報錯,下麵會基於源碼分析。
public class DefaultTessAppService : ApplicationService, ITestAppService
{
// ....
}
- 我通過[Dependency(ReplaceServices = true)]替換服務沒有生效?
- 請添加[ExposeServices(typeof(ITestManager))]顯示暴露服務,下麵會基於源碼分析。
源碼分析
- 進入到Startup.AddApplication源碼
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddApplication<xxxManagementHttpApiHostModule>();
}
}
- 進入到await app.ConfigureServicesAsync()源碼
public async static Task<IAbpApplicationWithExternalServiceProvider> CreateAsync(
[NotNull] Type startupModuleType,
[NotNull] IServiceCollection services,
Action<AbpApplicationCreationOptions>? optionsAction = null)
{
var app = new AbpApplicationWithExternalServiceProvider(startupModuleType, services, options =>
{
options.SkipConfigureServices = true;
optionsAction?.Invoke(options);
});
await app.ConfigureServicesAsync();
return app;
}
- 主要查看ConfigureServices下的Services.AddAssembly(assembly)方法。
public virtual async Task ConfigureServicesAsync()
{
// 省略...
var assemblies = new HashSet<Assembly>();
//ConfigureServices
foreach (var module in Modules)
{
if (module.Instance is AbpModule abpModule)
{
if (!abpModule.SkipAutoServiceRegistration)
{
var assembly = module.Type.Assembly;
if (!assemblies.Contains(assembly))
{
Services.AddAssembly(assembly);
assemblies.Add(assembly);
}
}
}
try
{
await module.Instance.ConfigureServicesAsync(context);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during {nameof(IAbpModule.ConfigureServicesAsync)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
}
// 省略...
}
4.進入下麵AddAssembly下AddType的邏輯
public class DefaultConventionalRegistrar : ConventionalRegistrarBase
{
public override void AddType(IServiceCollection services, Type type)
{
if (IsConventionalRegistrationDisabled(type))
{
return;
}
// 查看是否有DependencyAttribute特性標簽
var dependencyAttribute = GetDependencyAttributeOrNull(type);
// 判斷是否有實現介面,註入對於的類型。
var lifeTime = GetLifeTimeOrNull(type, dependencyAttribute);
if (lifeTime == null)
{
return;
}
var exposedServiceTypes = GetExposedServiceTypes(type);
TriggerServiceExposing(services, type, exposedServiceTypes);
foreach (var exposedServiceType in exposedServiceTypes)
{
var serviceDescriptor = CreateServiceDescriptor(
type,
exposedServiceType,
exposedServiceTypes,
lifeTime.Value
);
if (dependencyAttribute?.ReplaceServices == true)
{
services.Replace(serviceDescriptor);
}
else if (dependencyAttribute?.TryRegister == true)
{
services.TryAdd(serviceDescriptor);
}
else
{
services.Add(serviceDescriptor);
}
}
}
}
// GetLifeTimeOrNull
protected virtual ServiceLifetime? GetLifeTimeOrNull(Type type, DependencyAttribute? dependencyAttribute)
{
return dependencyAttribute?.Lifetime ?? GetServiceLifetimeFromClassHierarchy(type) ?? GetDefaultLifeTimeOrNull(type);
}
// abp 三個生命周期
protected virtual ServiceLifetime? GetServiceLifetimeFromClassHierarchy(Type type)
{
if (typeof(ITransientDependency).GetTypeInfo().IsAssignableFrom(type))
{
return ServiceLifetime.Transient;
}
if (typeof(ISingletonDependency).GetTypeInfo().IsAssignableFrom(type))
{
return ServiceLifetime.Singleton;
}
if (typeof(IScopedDependency).GetTypeInfo().IsAssignableFrom(type))
{
return ServiceLifetime.Scoped;
}
return null;
}
5.重點到了,看下為什麼名稱錯誤為什麼導致註入失敗。
- 通過介面的名稱去獲取實現。
- 也能解釋有時候不顯示指定ExposeServices可能替換失敗的問題
public class ExposeServicesAttribute : Attribute, IExposedServiceTypesProvider
{
// 省略...
private static List<Type> GetDefaultServices(Type type)
{
var serviceTypes = new List<Type>();
foreach (var interfaceType in type.GetTypeInfo().GetInterfaces())
{
var interfaceName = interfaceType.Name;
if (interfaceType.IsGenericType)
{
interfaceName = interfaceType.Name.Left(interfaceType.Name.IndexOf('`'));
}
// 查詢到實現類的名稱是否是移除I
if (interfaceName.StartsWith("I"))
{
interfaceName = interfaceName.Right(interfaceName.Length - 1);
}
// 查詢到實現類的名稱是否以介面名結尾
if (type.Name.EndsWith(interfaceName))
{
serviceTypes.Add(interfaceType);
}
}
return serviceTypes;
}
}
Abp vNext Pro
- Abp Vnext Pro Github地址 的 Vue3 實現版本 開箱即用的中後臺前端/設計解決方案.
- 文檔地址
- 演示地址:用戶名admin 密碼1q2w3E*
- Abp Vnext Pro Suite Github地址 代碼生成器。
- 演示地址:用戶名admin 密碼1q2w3E*
- 視頻教程