.NET基于类名约定的自动依赖注入最佳实践指南
作者:得想办法娶到那个女人
本文详解.NET自动依赖注入技术,通过类名约定自动识别服务类与接口,支持Transient/Scoped/Singleton生命周期,提供注册示例与混合策略,简化服务注册流程,提升开发效率,本文给大家介绍的非常详细,感兴趣的朋友一起看看吧
🚀 .NET基于类名约定的自动依赖注入完整指南
基于类名约定的自动依赖注入可大幅减少手动注册服务的工作量,本文将通过清晰的结构、美观的排版和丰富的示例,帮助你快速掌握这一实用技术。
🌈 核心特性概览
特性 | 说明 |
---|---|
类名约定 | 自动识别以 Service 结尾的类(不区分大小写) |
接口优先匹配 | 优先注册到 I{ClassName} 形式的接口(如 UserService → IUserService ) |
多生命周期支持 | 支持 Transient /Scoped /Singleton 三种生命周期 |
灵活扫描控制 | 可指定任意程序集或默认扫描调用程序集 |
无接口自注册 | 自动注册未实现接口的类为自身类型 |
📦 完整代码实现(含所有重载)
using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; using System.Reflection; /// <summary> /// 依赖注入扩展方法集合(基于类名约定) /// </summary> public static class ServiceCollectionExtensions { /// <summary> /// 自动注册以"Service"结尾的类(默认Transient生命周期) /// </summary> public static IServiceCollection AutoRegisterServices( this IServiceCollection services, Assembly assembly = null) { assembly ??= Assembly.GetCallingAssembly(); // 扫描规则:公共类、非抽象、非泛型、类名以Service结尾 var serviceTypes = assembly.GetTypes() .Where(t => t.IsClass && !t.IsAbstract && t.IsPublic && !t.ContainsGenericParameters && t.Name.EndsWith("Service", StringComparison.OrdinalIgnoreCase)) .ToArray(); // 注册服务到容器 foreach (var implType in serviceTypes) { var interfaces = implType.GetInterfaces(); if (interfaces.Any()) { // 匹配I{ClassName}接口(如UserService→IUserService) var matchingInterface = interfaces.FirstOrDefault(i => i.Name == "I" + implType.Name); if (matchingInterface != null) { services.AddTransient(matchingInterface, implType); } else { // 注册到所有实现的接口 foreach (var @interface in interfaces) { services.AddTransient(@interface, implType); } } } else { // 无接口时注册自身 services.AddTransient(implType); } } return services; } /// <summary> /// 自动注册以"Service"结尾的类(支持自定义生命周期) /// </summary> public static IServiceCollection AutoRegisterServices( this IServiceCollection services, ServiceLifetime lifetime, Assembly assembly = null) { assembly ??= Assembly.GetCallingAssembly(); var serviceTypes = GetServiceTypes(assembly); foreach (var implType in serviceTypes) { var interfaces = implType.GetInterfaces(); var descriptor = CreateServiceDescriptor(implType, interfaces, lifetime); services.Add(descriptor); } return services; } // 辅助方法:获取服务类型(提取公共逻辑) private static Type[] GetServiceTypes(Assembly assembly) => assembly.GetTypes() .Where(t => t.IsClass && !t.IsAbstract && t.IsPublic && !t.ContainsGenericParameters && t.Name.EndsWith("Service", StringComparison.OrdinalIgnoreCase)) .ToArray(); // 辅助方法:创建服务描述符(提取公共逻辑) private static ServiceDescriptor CreateServiceDescriptor( Type implType, Type[] interfaces, ServiceLifetime lifetime) { if (interfaces.Any()) { var matchingInterface = interfaces.FirstOrDefault(i => i.Name == "I" + implType.Name); if (matchingInterface != null) { return new ServiceDescriptor(matchingInterface, implType, lifetime); } // 返回第一个接口(避免注册多个描述符) return new ServiceDescriptor(interfaces[0], implType, lifetime); } return new ServiceDescriptor(implType, implType, lifetime); } }
🚀 使用示例(清晰排版)
1. 在ASP.NET Core中注册(Program.cs
)
var builder = WebApplication.CreateBuilder(args); // 方式1:默认Transient(扫描调用程序集) builder.Services.AutoRegisterServices(); // 方式2:指定Scoped生命周期 builder.Services.AutoRegisterServices(ServiceLifetime.Scoped); // 方式3:扫描指定程序集(如业务层) var businessAssembly = Assembly.Load("MyBusinessLayer"); builder.Services.AutoRegisterServices(ServiceLifetime.Singleton, businessAssembly); var app = builder.Build();
2. 服务类示例(符合约定的实现)
// ✅ 示例1:接口匹配型服务 public interface IUserService { string GetInfo(); } public class UserService : IUserService { public string GetInfo() => "User Service Running"; } // ✅ 示例2:多接口实现服务 public interface IAuthService { void Login(); } public interface ILogService { void WriteLog(string msg); } public class AuthService : IAuthService, ILogService { public void Login() { /* 登录逻辑 */ } public void WriteLog(string msg) { /* 日志逻辑 */ } } // ✅ 示例3:无接口自注册服务 public class DataService { public void ProcessData() { /* 数据处理 */ } } // ❌ 示例4:不符合约定的类(不会被注册) public class ServiceHelper { } // 类名不以Service结尾 public abstract class BaseService { } // 抽象类
🔧 扩展优化方案(带emoji标记)
1. 🌐 基于类名的生命周期自动识别
private static ServiceLifetime GetLifetimeFromName(string className) { if (className.Contains("Singleton", StringComparison.OrdinalIgnoreCase)) return ServiceLifetime.Singleton; if (className.Contains("Scoped", StringComparison.OrdinalIgnoreCase)) return ServiceLifetime.Scoped; return ServiceLifetime.Transient; }
2. 📌 特性标记增强控制
[AttributeUsage(AttributeTargets.Class)] public class AutoRegisterAttribute : Attribute { public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Transient; public bool IsEnabled { get; set; } = true; }
3. ⚡ 缓存扫描结果提升性能
private static readonly object LockObj = new(); private static Type[] _cachedServiceTypes; private static Type[] GetCachedServiceTypes(Assembly assembly) { if (_cachedServiceTypes == null) { lock (LockObj) { _cachedServiceTypes = assembly.GetTypes() .Where(t => /* 扫描规则 */) .ToArray(); } } return _cachedServiceTypes; }
📝 最佳实践指南
📦 混合注册策略
- 核心服务(如DbContext)手动注册:
services.AddDbContext<AppDbContext>(options => {...});
- 业务服务自动注册:
builder.Services.AutoRegisterServices();
- 核心服务(如DbContext)手动注册:
🔍 精准扫描范围
// 仅扫描当前程序集中的服务 builder.Services.AutoRegisterServices(typeof(UserService).Assembly);
✅ 单元测试验证
[Fact] public void Should_Resolve_Service_By_Convention() { var services = new ServiceCollection(); services.AutoRegisterServices(typeof(IAuthService).Assembly); var provider = services.BuildServiceProvider(); var service = provider.GetService<IAuthService>(); Assert.NotNull(service); }
到此这篇关于.NET基于类名约定的自动依赖注入完整指南的文章就介绍到这了,更多相关.net自动依赖注入内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!