Cursor rule
.cursor/rules/dependency-injection.mdcCodeSpirit 依赖注入规范 - Scrutor自动注册、生命周期管理
Cursor rules
Quality
54/100
Scores the file, not the repository.Length
821 words
28 headings · 19 code blocksRepository
56
— · pushed 134 days agoLast changed
3 days ago
First indexed 3 days ago.12345678910111213# 依赖注入规范(Scrutor 自动注册)1415## 概述1617项目使用 Scrutor 库实现基于标记接口的自动依赖注入,无需手动注册服务。1819## 标记接口2021位于 `CodeSpirit.Core.DependencyInjection` 命名空间:2223| 接口 | 生命周期 | 适用场景 |24|-----|---------|---------|25| `IScopedDependency` | Scoped | 业务服务、数据库操作、请求相关 |26| `ITransientDependency` | Transient | 无状态工具类、轻量操作 |27| `ISingletonDependency` | Singleton | 配置服务、缓存、ID生成器 |2829### 生命周期说明3031```csharp32// IScopedDependency - 作用域注入33// 同一个请求中是同一个实例,不同请求是不同实例34// 推荐:大多数业务服务、DbContext 相关操作3536// ITransientDependency - 瞬时注入37// 每次注入都创建新实例38// 推荐:无状态工具类、不持有资源的服务3940// ISingletonDependency - 单例注入41// 整个应用生命周期只有一个实例42// 推荐:配置服务、缓存管理、ID生成器43```4445## 标记方式4647### 方式一:接口继承标记接口(推荐)4849接口继承标记接口,实现类无需再次标记:5051```csharp52// 接口定义 - 继承 IScopedDependency53public interface IAuthService : IScopedDependency54{55 Task<AuthResultDto> LoginAsync(LoginDto input);56 Task<bool> LogoutAsync(long userId);57}5859// 实现类 - 无需标记接口60public class AuthService : IAuthService61{62 private readonly IRepository<User> _userRepository;6364 public AuthService(IRepository<User> userRepository)65 {66 _userRepository = userRepository;67 }6869 public async Task<AuthResultDto> LoginAsync(LoginDto input)70 {71 // 实现逻辑72 }73}74```7576### 方式二:实现类标记接口7778适用于无业务接口的服务类:7980```csharp81// 无接口的服务类,直接实现标记接口82public class SeederService : IScopedDependency83{84 private readonly IServiceProvider _serviceProvider;85 private readonly ILogger<SeederService> _logger;8687 public SeederService(IServiceProvider serviceProvider, ILogger<SeederService> logger)88 {89 _serviceProvider = serviceProvider;90 _logger = logger;91 }9293 public async Task SeedAsync()94 {95 // 初始化种子数据96 }97}98```99100### 方式三:同时实现业务接口和标记接口101102适用于需要明确指定生命周期的服务:103104```csharp105public interface IUserService : IBaseCRUDService<User, long, CreateUserDto, UpdateUserDto, UserQueryDto>106{107 Task<UserDto> GetByUsernameAsync(string username);108}109110public class UserService : BaseCRUDService<User, long, CreateUserDto, UpdateUserDto, UserQueryDto>,111 IUserService, IScopedDependency112{113 public async Task<UserDto> GetByUsernameAsync(string username)114 {115 // 实现逻辑116 }117}118```119120## 生命周期选择指南121122### IScopedDependency(作用域 - 最常用)123124```csharp125// ✅ 业务服务126public interface IQuestionService : IScopedDependency127{128 Task<QuestionDto> GetByIdAsync(long id);129 Task CreateAsync(CreateQuestionDto dto);130}131132// ✅ 数据访问服务133public interface IExamRepository : IScopedDependency134{135 Task<Exam> GetWithQuestionsAsync(long examId);136}137138// ✅ 种子数据服务139public class TenantSeeder : IScopedDependency140{141 public async Task SeedAsync() { }142}143```144145### ISingletonDependency(单例)146147```csharp148// ✅ ID 生成器149public interface IIdGenerator : ISingletonDependency150{151 long NewId();152}153154// ✅ 缓存服务155public interface IConfigCacheService : ISingletonDependency156{157 Task<string> GetAsync(string key);158 Task SetAsync(string key, string value, TimeSpan? expiry = null);159}160161// ✅ 端点扫描器(应用启动时扫描一次)162public class AiFormFillEndpointScanner : ISingletonDependency163{164 public void ScanAssemblies(params Assembly[] assemblies) { }165}166167// ✅ 本地化设置初始化器168public class LocalizationSettingsInitializer : ISingletonDependency169{170 public void Initialize() { }171}172```173174### ITransientDependency(瞬时)175176```csharp177// ✅ 无状态工具类178public interface IPasswordHasher : ITransientDependency179{180 string HashPassword(string password);181 bool VerifyPassword(string password, string hash);182}183184// ✅ 存储提供器工厂(每次创建新实例)185public interface IStorageProviderFactory : ITransientDependency186{187 IStorageProvider CreateProvider(string providerType);188}189190// ✅ 配置变更通知器191public interface IConfigChangeNotifier : ITransientDependency192{193 Task NotifyChangeAsync(string configKey);194}195```196197## Scrutor 自动注册扩展方法198199### 基础注册方法200201```csharp202// 位于 CodeSpirit.Shared.DependencyInjection.ServiceCollectionExtensions203204// 自动扫描并注册标记接口的服务205services.AddDependencyInjectionWithScrutor(Assembly.GetExecutingAssembly());206207// 可同时扫描多个程序集208services.AddDependencyInjectionWithScrutor(209 Assembly.GetExecutingAssembly(),210 typeof(SharedService).Assembly);211```212213### 高级注册方法214215```csharp216// 按命名约定自动注册(Service、Repository 后缀)217services.AddAdvancedDependencyInjection(Assembly.GetExecutingAssembly());218```219220### 装饰器模式221222```csharp223// 使用装饰器包装现有服务224services.AddDecorator<IUserService, CachingUserServiceDecorator>();225services.AddDecorator<ILogger<UserService>, AuditLoggerDecorator<UserService>>();226```227228## 注册行为229230Scrutor 自动完成以下注册:2312321. **接口注册**:服务注册为其实现的业务接口2332. **自身注册**:服务同时注册为自身类型(可直接注入具体类)234235```csharp236// 给定服务类237public class UserService : IUserService, IScopedDependency { }238239// Scrutor 自动注册:240// services.AddScoped<IUserService, UserService>(); // 接口注册241// services.AddScoped<UserService>(); // 自身注册242243// 两种方式都可以注入:244public class UserController245{246 public UserController(247 IUserService userService, // ✅ 接口注入248 UserService userServiceImpl) // ✅ 具体类注入249 { }250}251```252253## API 配置类中的服务注册254255### 自动注册(BaseApiConfiguration 已处理)256257```csharp258public class ExamApiConfiguration : BaseApiConfiguration259{260 public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)261 {262 base.ConfigureServices(services, configuration);263264 // Scrutor 自动注册已在 BaseApiConfiguration 中完成265 // 无需再调用 AddDependencyInjectionWithScrutor266 }267}268```269270### 手动注册特殊服务271272```csharp273public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)274{275 base.ConfigureServices(services, configuration);276277 // 手动注册:特殊配置、外部库服务、条件注册278 services.AddScoped<ISpecialService>(sp =>279 new SpecialService(sp.GetRequiredService<IOptions<SpecialOptions>>()));280281 // 注册外部库服务282 services.AddHttpClient<IExternalApiClient, ExternalApiClient>();283284 // 条件注册285 if (configuration.GetValue<bool>("Features:EnableNewFeature"))286 {287 services.AddScoped<INewFeatureService, NewFeatureService>();288 }289}290```291292## 依赖注入最佳实践293294### ✅ 推荐做法295296```csharp297// 1. 构造函数注入(推荐)298public class QuestionService : IQuestionService, IScopedDependency299{300 private readonly IRepository<Question> _repository;301 private readonly IMapper _mapper;302303 public QuestionService(IRepository<Question> repository, IMapper mapper)304 {305 _repository = repository;306 _mapper = mapper;307 }308}309310// 2. 接口定义继承标记接口311public interface IExamService : IScopedDependency312{313 Task<ExamDto> GetByIdAsync(long id);314}315316// 3. 使用 IServiceProvider 延迟解析(避免循环依赖)317public class CrudDialogHandler : IScopedDependency318{319 private readonly IServiceProvider _serviceProvider;320321 public CrudDialogHandler(IServiceProvider serviceProvider)322 {323 _serviceProvider = serviceProvider;324 }325326 private ColumnHelper ColumnHelper => _serviceProvider.GetRequiredService<ColumnHelper>();327}328```329330### ❌ 禁止做法331332```csharp333// 1. 不要手动注册标记接口的服务(会重复注册)334services.AddScoped<IUserService, UserService>(); // ❌ Scrutor 已自动注册335336// 2. 不要使用服务定位器反模式337public class BadService338{339 public void DoWork()340 {341 var service = ServiceLocator.GetService<IOtherService>(); // ❌342 }343}344345// 3. 不要在 Singleton 服务中注入 Scoped 服务346public class BadSingletonService : ISingletonDependency347{348 private readonly IUserService _userService; // ❌ Scoped 服务不能注入到 Singleton349}350351// 4. 不要创建多余的包装接口352public interface IUserServiceWrapper : IUserService { } // ❌ 不必要353```354355## 常见问题356357### 循环依赖处理358359```csharp360// 使用 IServiceProvider 延迟解析361public class ServiceA : IScopedDependency362{363 private readonly IServiceProvider _serviceProvider;364365 public ServiceA(IServiceProvider serviceProvider)366 {367 _serviceProvider = serviceProvider;368 }369370 // 延迟获取依赖371 private IServiceB ServiceB => _serviceProvider.GetRequiredService<IServiceB>();372}373```374375### 条件服务注册376377```csharp378// 在 API 配置类中进行条件注册379public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)380{381 base.ConfigureServices(services, configuration);382383 var storageType = configuration["Storage:Type"];384 services.AddScoped<IStorageService>(sp => storageType switch385 {386 "S3" => sp.GetRequiredService<S3StorageService>(),387 "Azure" => sp.GetRequiredService<AzureBlobStorageService>(),388 _ => sp.GetRequiredService<LocalStorageService>()389 });390}391```392393### 多实现注册394395```csharp396// 注册多个实现397services.AddScoped<INotificationService, EmailNotificationService>();398services.AddScoped<INotificationService, SmsNotificationService>();399400// 注入所有实现401public class NotificationManager402{403 private readonly IEnumerable<INotificationService> _notificationServices;404405 public NotificationManager(IEnumerable<INotificationService> notificationServices)406 {407 _notificationServices = notificationServices;408 }409410 public async Task NotifyAllAsync(string message)411 {412 foreach (var service in _notificationServices)413 {414 await service.SendAsync(message);415 }416 }417}418```419420## 命名空间引用421422```csharp423using CodeSpirit.Core.DependencyInjection; // 标记接口424using CodeSpirit.Shared.DependencyInjection; // 扩展方法425```426
Also in xin-lai/CodeSpirit
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| xin-lai/CodeSpirit.cursor/rules/js.mdc · 56 | Cursor rules | apidocs | 46/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/ai-development.mdc · 56 | Cursor rules | api | 46/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/all.mdc · 56 | Cursor rules | testing-strategyapi | 50/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/amis-cards.mdc · 56 | Cursor rules | no sections | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/api-design.mdc · 56 | Cursor rules | no sections | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/controller.mdc · 56 | Cursor rules | api | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/cs.mdc · 56 | Cursor rules | no sections | 25/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/csproj.mdc · 56 | Cursor rules | api | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/css.mdc · 56 | Cursor rules | ui | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/database.mdc · 56 | Cursor rules | no sections | 74/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/dto.mdc · 56 | Cursor rules | no sections | 50/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/enum.mdc · 56 | Cursor rules | no sections | 50/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/i18n.mdc · 56 | Cursor rules | no sections | 50/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/naming-conventions.mdc · 56 | Cursor rules | no sections | 50/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/package-management.mdc · 56 | Cursor rules | no sections | 74/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/performance.mdc · 56 | Cursor rules | no sections | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/project-structure.mdc · 56 | Cursor rules | api | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/security.mdc · 56 | Cursor rules | database | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/service.mdc · 56 | Cursor rules | no sections | 50/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/startup-framework.mdc · 56 | Cursor rules | api | 54/100 | 3 days ago |
Diff against .cursor/rules/js.mdc Diff against .cursor/rules/ai-development.mdc Diff against .cursor/rules/all.mdc Diff against .cursor/rules/amis-cards.mdc Diff against .cursor/rules/api-design.mdc Diff against .cursor/rules/controller.mdc Diff against .cursor/rules/cs.mdc Diff against .cursor/rules/csproj.mdc Diff against .cursor/rules/css.mdc Diff against .cursor/rules/database.mdc Diff against .cursor/rules/dto.mdc Diff against .cursor/rules/enum.mdc Diff against .cursor/rules/i18n.mdc Diff against .cursor/rules/naming-conventions.mdc Diff against .cursor/rules/package-management.mdc Diff against .cursor/rules/performance.mdc Diff against .cursor/rules/project-structure.mdc Diff against .cursor/rules/security.mdc Diff against .cursor/rules/service.mdc Diff against .cursor/rules/startup-framework.mdc
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126 | Cursor rules | setupbuildtestlint-format+6 | 100/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 45 | Cursor rules | testlint-formatstylearch+5 | 100/100 | 3 days ago | |
| markstev/mark-starter.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+6 | 99/100 | 3 days ago | |
| Allymahmoud/case-intake-platform.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| deifos/clipmira-subtitles.cursor/rules/frontend.mdc · 1 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| dodgecfr/combatfilms-webapp.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| langflow-ai/langflow.cursor/rules/docs_development.mdc · 153k | Cursor rules | setupbuildtestlint-format+7 | 97/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 45 | Cursor rules | teststyletesting-strategysecurity+3 | 97/100 | 3 days ago |
