Skip to content

Commit

Permalink
feat: 移除IScopedDependence等接口,添加KeyedService支持.
Browse files Browse the repository at this point in the history
  • Loading branch information
joesdu committed Jul 18, 2024
1 parent 70cda61 commit 1f4911f
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 123 deletions.
69 changes: 69 additions & 0 deletions sample/WebApi.Test.Unit/Controllers/KeyedServiceTestController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using EasilyNET.AutoDependencyInjection.Core.Attributes;
using EasilyNET.WebCore.Swagger.Attributes;
using Microsoft.AspNetCore.Mvc;

// ReSharper disable ClassNeverInstantiated.Global

namespace WebApi.Test.Unit.Controllers;

/// <summary>
/// KeyedServiceTest
/// </summary>
[Route("api/[controller]"), ApiController, ApiGroup("KeyedServiceTest", "v1", "KeyedServiceTestController")]
public class KeyedServiceTestController(IServiceProvider sp, IKeyedServiceTest kst2) : ControllerBase
{
/// <summary>
/// ShowHello
/// </summary>
/// <returns></returns>
[HttpGet("HelloKeyedService")]
public string ShowHello()
{
var kst = sp.GetRequiredKeyedService<KeyedServiceTest>("helloKey");
return kst.ShowHello();
}

/// <summary>
/// ShowHello2
/// </summary>
/// <returns></returns>
[HttpGet("HelloKeyedService2")]
public string ShowHello2() => kst2.ShowHello();

/// <summary>
/// ShowHello3
/// </summary>
/// <returns></returns>
[HttpGet("HelloKeyedService3")]
public string ShowHello3()
{
var kst = sp.GetRequiredKeyedService<IKeyedServiceTest>("helloKey");
return kst.ShowHello();
}
}

/// <summary>
/// KeyedServiceTest
/// </summary>
[DependencyInjection(ServiceLifetime.Transient, ServiceKey = "helloKey")]
public sealed class KeyedServiceTest : IKeyedServiceTest
{
/// <summary>
/// ShowHello
/// </summary>
/// <returns></returns>
// ReSharper disable once MemberCanBeMadeStatic.Global
public string ShowHello() => "Hello, KeyedServiceTest!";
}

/// <summary>
/// IKeyedServiceTest
/// </summary>
public interface IKeyedServiceTest
{
/// <summary>
/// ShowHello
/// </summary>
/// <returns></returns>
string ShowHello();
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,9 @@ public sealed class DependencyInjectionAttribute(ServiceLifetime lifetime) : Att
/// 仅注册自身类型,而不注册接口
/// </summary>
public bool SelfOnly { get; set; }

/// <summary>
/// 设置服务的键,适配KeyedService
/// </summary>
public string ServiceKey { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ReSharper disable ClassNeverInstantiated.Global

namespace EasilyNET.AutoDependencyInjection.Core.Attributes;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
using System.Collections.Frozen;
using System.Reflection;
using EasilyNET.AutoDependencyInjection.Contexts;
using EasilyNET.AutoDependencyInjection.Core.Abstractions;
using EasilyNET.AutoDependencyInjection.Core.Attributes;
using EasilyNET.Core.Misc;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Frozen;
using System.Reflection;

// ReSharper disable UnusedType.Global

Expand Down Expand Up @@ -32,26 +31,31 @@ public override void ConfigureServices(ConfigureServicesContext context)
/// <param name="services"></param>
private static void AddAutoInjection(IServiceCollection services)
{
var baseTypes = new[] { typeof(IScopedDependency), typeof(ITransientDependency), typeof(ISingletonDependency) };
var types = AssemblyHelper.FindTypes(type =>
(type is { IsClass: true, IsAbstract: false } && baseTypes.Any(b => b.IsAssignableFrom(type))) ||
type.GetCustomAttribute<DependencyInjectionAttribute>() is not null);
var types = AssemblyHelper.FindTypes(type => type.GetCustomAttribute<DependencyInjectionAttribute>() is not null);
foreach (var implementedType in types)
{
var attr = implementedType.GetCustomAttribute<DependencyInjectionAttribute>();
var lifetime = GetServiceLifetime(implementedType);
if (lifetime is null) continue;
// 优化:直接从属性或特性获取AddSelf和SelfOnly的值,这里的名称属于约定项
var addSelf = attr?.AddSelf ?? GetPropertyValue<bool?>(implementedType, "DependencyInjectionSelf");
var serviceTypes = GetServiceTypes(implementedType);
if (serviceTypes.Count is 0 || addSelf is true)
if (serviceTypes.Count is 0 || attr?.AddSelf is true)
{
services.Add(new(implementedType, implementedType, lifetime.Value));
var selfOnly = attr?.SelfOnly ?? GetPropertyValue<bool?>(implementedType, "DependencyInjectionSelfOnly");
if (selfOnly is true || serviceTypes.Count is 0) continue;
if (!string.IsNullOrWhiteSpace(attr?.ServiceKey))
{
services.Add(new(implementedType, attr.ServiceKey, implementedType, lifetime.Value));
}
else
{
services.Add(new(implementedType, implementedType, lifetime.Value));
}
if (attr?.SelfOnly is true || serviceTypes.Count is 0) continue;
}
foreach (var serviceType in serviceTypes.Where(o => !o.HasAttribute<IgnoreDependencyAttribute>()))
{
if (!string.IsNullOrWhiteSpace(attr?.ServiceKey))
{
services.Add(new(serviceType, attr.ServiceKey, implementedType, lifetime.Value));
}
services.Add(new(serviceType, implementedType, lifetime.Value));
}
}
Expand All @@ -65,13 +69,6 @@ private static FrozenSet<Type> GetServiceTypes(Type implementation)
.Select(t => t.GetRegistrationType(typeInfo)).ToFrozenSet();
}

// 优化:提取获取静态属性值的通用方法,减少重复代码
private static T? GetPropertyValue<T>(Type type, string name)
{
var property = type.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
return property is null ? default : (T?)property.GetValue(type);
}

/// <summary>
/// 获取服务生命周期
/// </summary>
Expand All @@ -80,14 +77,7 @@ private static FrozenSet<Type> GetServiceTypes(Type implementation)
private static ServiceLifetime? GetServiceLifetime(Type type)
{
var attr = type.GetCustomAttribute<DependencyInjectionAttribute>();
return attr?.Lifetime ??
(typeof(IScopedDependency).IsAssignableFrom(type)
? ServiceLifetime.Scoped
: typeof(ITransientDependency).IsAssignableFrom(type)
? ServiceLifetime.Transient
: typeof(ISingletonDependency).IsAssignableFrom(type)
? ServiceLifetime.Singleton
: null);
return attr?.Lifetime;
}

/// <summary>
Expand Down
67 changes: 8 additions & 59 deletions src/EasilyNET.AutoDependencyInjection/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
#### EasilyNET.AutoDependencyInjection

- 新增KeyedService支持,可在 DependencyInjectionAttribute 中看到对应的 ServiceKey 属性,用于标识服务的 Key 值.
- 新增 WPF 项目支持,理论上也支持 WinForm 项目,但是没有测试,使用时请注意.(仅限于 .NET 的项目,不支持 .NET Framework)
- 经测试是支持 WinUI 3 类型的项目的,但是需要注意的是,WinUI 3 项目的启动方式和 WPF 项目不一样,需要自行调整.

##### 中断性变更

- 移除使用 IScopedDependency, ISingletonDependency, ITransientDependency 接口的方式,仅使用特性注入的方式.保持设计简洁性.

##### 变化

由于新增了 WPF 项目支持,所以在使用时需要注意以下几点:
Expand Down Expand Up @@ -48,15 +53,14 @@ internal sealed class AppServiceModules : AppModule { }
在 WPF 项目中,使用依赖注入,需要在 MainWindow.xaml.cs 中继承接口或者添加 DependencyInjection 特性如下代码:

```csharp
// 这里特性和接口二选一,推荐使用特性
// 使用特性配置注入信息
[DependencyInjection(ServiceLifetime.Singleton, AddSelf = true, SelfOnly = true)]
public partial class MainWindow : Window, IXXXXDependency
public partial class MainWindow : Window

```

##### 注意事项

- 接口的实现类,需要显示的继承 IScopedDependency, ISingletonDependency, ITransientDependency 接口.
- 需要注意的是,在 WPF 项目中,请将 AddSelf 属性设置为 true,否则会出现服务无法找到的问题,因为默认会注册实现类的父类,导致使用 ```host.Services.GetRequiredService<MainWindow>()``` 的方式无法找到服务.WinForm 项目中,没有测试,但是理论上也是一样的.
- 由于新增 WPF 项目支持,所以调整了 IApplicationBuilderIHost,因此 WEB 项目中的使用方式有细微的变化.
```csharp
Expand All @@ -70,30 +74,10 @@ WebApplication app = context.GetApplicationHost() as WebApplication;
IHost app = context.GetApplicationHost();
```

**使用接口注入时需要注意**
- 由于无法通过接口的方式来约束类中的静态成员,所以我们这里需要做一个约定.在类中写入如下代码来实现和特性相同的功能.(所以更推荐使用特性的方式注入)
- 若是不声明这两个属性,可能会导致注入了其实现的类或接口,影响获取服务的结果.如在 WPF 中注册 MainWindow.cs,会注册其实现的接口类型.导致无法获取到正确的实现.
- 这里采用较长的名字,避免和类中别的成员出现名称冲突.
- DependencyInjectionSelf 对应 DependencyInjection 特性中的 AddSelf
- DependencyInjectionSelfOnly 对应 DependencyInjection 特性中的 SelfOnly
```csharp
/// <summary>
/// 是否添加自身
/// </summary>
// ReSharper disable once UnusedMember.Global
public static bool? DependencyInjectionSelf => true;

/// <summary>
/// 仅注册自身,而不注其父类或者接口
/// </summary>
// ReSharper disable once UnusedMember.Global
public static bool? DependencyInjectionSelfOnly => true;
```

##### 如何使用

- 使用 Nuget 包管理工具添加依赖包 EasilyNET.AutoDependencyInjection
- a.使用特性注入服务
- 使用特性注入服务

```csharp
[DependencyInjection(ServiceLifetime.Singleton, AddSelf = true, SelfOnly = true)]
Expand All @@ -105,41 +89,6 @@ public class XXXService : IXXXService
}
```

- b.使用接口的方式,继承 IScopedDependency, ISingletonDependency, ITransientDependency

```csharp
/// <summary>
/// 测试模块
/// </summary>
public class MyTestModule : ITest, IScopedDependency
{
/// <summary>
/// Show
/// </summary>
public void Show()
{
Console.WriteLine("Test");
}
}

/// <summary>
/// 测试
/// </summary>
//[IgnoreDependency]
public interface ITest
{
/// <summary>
/// Show函数
/// </summary>
void Show();
}

// 在其他地方获取服务,并执行方法
var test = context.Services.GetService<MyTestModule>();
test?.Show();
...
```

- 3.继承 AppModule 类,然后显示加入到 AppWebModule 配置中
- Step1.创建 CorsModule.cs

Expand Down

0 comments on commit 1f4911f

Please sign in to comment.