Devlooped.Extensions.DependencyInjection.Attributed 2.0.0
前缀已保留
dotnet add package Devlooped.Extensions.DependencyInjection.Attributed --version 2.0.0
NuGet\Install-Package Devlooped.Extensions.DependencyInjection.Attributed -Version 2.0.0
<PackageReference Include="Devlooped.Extensions.DependencyInjection.Attributed" Version="2.0.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Devlooped.Extensions.DependencyInjection.Attributed --version 2.0.0
#r "nuget: Devlooped.Extensions.DependencyInjection.Attributed, 2.0.0"
// Install Devlooped.Extensions.DependencyInjection.Attributed as a Cake Addin #addin nuget:?package=Devlooped.Extensions.DependencyInjection.Attributed&version=2.0.0 // Install Devlooped.Extensions.DependencyInjection.Attributed as a Cake Tool #tool nuget:?package=Devlooped.Extensions.DependencyInjection.Attributed&version=2.0.0
自动编译时在Microsoft.Extensions.DependencyInjection中注册服务,没有运行时依赖。
用法
安装nuget包后,将可使用新的[Service(ServiceLifetime)]
属性来注解您的类型
[Service(ServiceLifetime.Scoped)]
public class MyService : IMyService, IDisposable
{
public string Message => "Hello World";
public void Dispose() { }
}
public interface IMyService
{
string Message { get; }
}
ServiceLifetime
参数是可选的,默认为ServiceLifetime.Singleton。
注意:该属性是通过简单名称匹配的,因此您可以在自己的程序集中定义自己的属性。它只需提供一个接受ServiceLifetime参数的构造函数。
源生成器将在编译时生成一个针对IServiceCollection的AddServices
扩展方法,您可以从设置服务的启动代码中调用此方法,如下所示
var builder = WebApplication.CreateBuilder(args);
// NOTE: **Adds discovered services to the container**
builder.Services.AddServices();
// ...
var app = builder.Build();
// Configure the HTTP request pipeline.
app.MapGet("/", (IMyService service) => service.Message);
// ...
app.Run();
注意:因为调用了生成的
AddServices
来注册发现的服务,所以该服务对范围请求自动可用。
这就是所有内容。源生成器将发现当前项目及其所有引用中的注释类型。由于注册代码是在编译时生成的,所以没有任何运行时反射(或其他依赖)。
键值服务
键值服务也通过一个独立的泛型 [Service]
属性得到支持,例如:
public interface INotificationService
{
string Notify(string message);
}
[Service<string>("sms")]
public class SmsNotificationService : INotificationService
{
public string Notify(string message) => $"[SMS] {message}";
}
[Service<string>("email")]
public class EmailNotificationService : INotificationService
{
public string Notify(string message) => $"[Email] {message}";
}
希望消费特定键值服务的服务可以使用 [FromKeyedServices(object key)]
属性来指定键,例如:
[Service]
public class SmsService([FromKeyedServices("sms")] INotificationService sms)
{
public void DoSomething() => sms.Notify("Hello");
}
在这种情况下,从服务提供程序解析 SmsService
时,将根据提供的键注入正确的 INotificationService
。
工作原理
实现注册生成的代码如下所示:
static partial class AddServicesExtension
{
public static IServiceCollection AddServices(this IServiceCollection services)
{
services.AddScoped(s => new MyService());
services.AddScoped<IMyService>(s => s.GetRequiredService<MyService>());
services.AddScoped<IDisposable>(s => s.GetRequiredService<MyService>());
return services;
}
注意服务首先以自己的类型注册为作用域,其他两个注册只是检索相同的(根据其定义的生存期)。这意味着实例被重用,并自动在所有实现的接口下适当地注册。
注意:您可以通过在您的项目文件中将
EmitCompilerGeneratedFiles=true
设置为真来检查生成的代码,并在obj
下的generated
子文件夹中浏览。
如果服务类型有依赖项,实现工厂也将通过服务提供程序解决这些依赖项,例如:
services.AddScoped(s => new MyService(s.GetRequiredService<IMyDependency>(), ...));
MEF 兼容性
鉴于 .NET 中 MEF 属性(无论是 .NET MEF、NuGet MEF 还是 VS MEF)的(或多或少广泛)采用,生成器也支持 [Export]
属性来表示服务(类型参数以及程序集名称都将被忽略,因为这些在依赖于注入的容器中不受支持)。
为了在 MEF 中指定单例(共享)实例,您必须使用额外的属性注解类型:在 NuGet MEF 中为 [Shared]
(来自 System.Composition)或在 .NET MEF 中为 [PartCreationPolicy(CreationPolicy.Shared)]
(来自 System.ComponentModel.Composition)。
都支持 [Export("contractName")]
和 [Import("contractName")]
,并将分别用于注册和解析键值服务,这意味着通常可以仅依赖于 [Export]
和 [Import]
属性来实现所有依赖于注入的注解,并且在容器中自动执行时将正常工作。
高级场景
Lazy<T>
和 Func<T>
依赖
每个接口(和主要实现)提供了一个 Lazy<T>
,因此您可以自动从盒子里获取一个延迟依赖。在这种情况下,依赖项 T
的生存期与接受延迟依赖的组件的生存期相关联,这是显而易见的。此 Lazy<T>
仅通过服务提供程序进行延迟解决依赖项。这个延迟本身并不昂贵,并且由于底层服务的生存期以及消耗服务的生存期确定了最终的生存期,因此对它不需要额外的配置,因为它始终注册为一个瞬态组件。生成的代码如下所示
services.AddTransient(s => new Lazy<IMyService>(s.GetRequiredService<MyService>));
也自动注册了一个 Func<T>
,但仅仅是一个指向 IServiceProvider.GetRequiredService<T>
的委托。生成的代码如下所示
services.AddTransient<Func<IMyService>>(s => s.GetRequiredService<MyService>);
重复调用该功能将导致依赖于已注册生存期的所需服务的实例。例如,如果它被注册为单例,您将每次都得到相同的值,就好像您使用了 Lazy<T>
依赖项一样,但每次都调用服务提供程序,而不是只调用一次。这使得此模式更适合短期使用(并可能随后进行清理)的瞬态服务。
您自己的 ServiceAttribute
如果您想声明自己的 ServiceAttribute
并在项目中重复使用,以此避免在库项目中依赖这个(仅开发,仅编译时间)的包,您可以像这样声明:
[AttributeUsage(AttributeTargets.Class)]
public class ServiceAttribute : Attribute
{
public ServiceAttribute(ServiceLifetime lifetime = ServiceLifetime.Singleton) { }
}
同样适用于键值服务版本
[AttributeUsage(AttributeTargets.Class)]
public class ServiceAttribute<TKey> : Attribute
{
public ServiceAttribute(TKey key, ServiceLifetime lifetime = ServiceLifetime.Singleton) { }
}
注意:构造函数参数仅在源生成时用于确定注册风格(和键),但从未在运行时使用,因此您甚至不需要将其保留在字段或属性中!
有了这些,您只需将此包添加到包含服务的集合的顶层项目中即可!
该属性通过简单名称匹配,因此它可以存在于任何命名空间中。
如果您想避免将属性添加到引用此包的项目中,请通过 MSBuild 将 $(AddServiceAttribute)
设置为 true
。
<PropertyGroup>
<AddServiceAttribute>false</AddServiceAttribute>
</PropertyGroup>
选择构造函数
如果您想为服务实现工厂注册选择一个特定的构造函数(而不是默认的,即参数最多的那个),您可以使用来自 NuGet MEF(《System.Composition》)或 .NET MEF(《System.ComponentModel.Composition》)的 [ImportingConstructor]
进行注解。
自定义生成的类
您可以使用以下 MSBuild 属性自定义生成的类命名空间和名称:
<PropertyGroup>
<AddServicesNamespace>MyNamespace</AddServicesNamespace>
<AddServicesClassName>MyExtensions</AddServicesClassName>
</PropertyGroup>
它们的默认分别为 Microsoft.Extensions.DependencyInjection
和 AddServicesExtension
。
赞助商
-
.NETStandard 2.0
NuGet 包
此包没有被任何 NuGet 包使用。
GitHub 仓库
此包没有被任何流行的 GitHub 仓库使用。