🚀 .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); }