所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

不用手动注册!.NET基于类名约定的自动依赖注入最佳实践

不用手动注册!.NET基于类名约定的自动依赖注入最佳实践 一

文章目录CloseOpen

其实不用这么折腾!用“类名约定”就能彻底告别手动注册:只要提前定好接口和实现类的命名规则(比如IUserService对应UserServiceImpl,或IService后缀对应Service实现),框架会自动扫描项目中的类、匹配规则、完成注册,根本不用手写AddScoped/AddTransient!

这篇文章就把.NET基于类名约定的自动依赖注入最佳实践讲透:从命名规则设计(怎么定规则最合理?要不要绑定生命周期?),到具体实现步骤(用反射自己写还是用Autofac等库?),再到避坑要点(同名类怎么处理?生命周期配置错了怎么办?),甚至大型项目优化(如何过滤不需要自动注册的类?),全流程覆盖。不管你是刚入门的新手,还是想优化老项目的老手,都能直接抄作业,把“手动注册”的麻烦彻底干掉,省出时间做更有价值的事!

你有没有过这种崩溃时刻?做.NET项目时,每加一个新服务都要翻Program.cs或Startup.cs,找到依赖注入的注册代码,复制粘贴改类名——比如从services.AddScoped()改成services.AddScoped()。项目小的时候还好,一旦上了规模,上百个服务堆在那,漏写一个、写错类名都是常事,排查起来得翻半天代码,完全是“重复劳动”的浪费。我去年帮一个做电商的朋友改项目时,他就吐槽:“写业务代码已经够累了,还要跟注册代码较劲,真不如把时间花在优化用户体验上。”

其实解决这问题特简单:用类名约定让框架自动帮你注册依赖,根本不用手动写一行AddScoped/AddSingleton!

为什么类名约定能解决手动注册的痛点?

手动注册的麻烦,本质是“人要记住所有服务的接口和实现对应关系”——但人会忘、会错,框架不会。类名约定的核心逻辑,就是用统一的命名规则告诉框架:“这个实现类对应那个接口”,比如接口叫IUserService,实现类就叫UserServiceImpl;接口叫IOrderRepository,实现类就叫OrderRepositoryImpl。框架扫描项目时,会自动根据规则匹配接口和实现,帮你完成注册。

我朋友一开始担心“规则太死,灵活度不够”,试了之后才发现:规则反而能帮团队统一规范。比如他们项目里有多个版本的支付服务,旧版叫IPaymentService(实现类PaymentServiceImpl),新版叫IPaymentServiceV2(实现类PaymentServiceV2Impl),只要规则统一(接口加版本号,实现类后缀保持Impl),框架照样能识别。甚至遇到“一个接口多个实现”的场景,也能通过规则区分——比如IUserService对应UserServiceByDbImpl(数据库实现)和UserServiceByCacheImpl(缓存实现),只要在注入时指定名字(比如[FromKeyedServices("Db")]),框架也能精准匹配。

微软在《.NET依赖注入最佳实践》文档里早就提到:“减少手动注册是提升维护性的关键”(链接:https://learn.microsoft.com/zh-cn/dotnet/core/extensions/dependency-injection#best-practicesnofollow)。类名约定刚好踩中了这个点——把“人要做的重复活”交给框架,人只需要定好规则,剩下的让程序自己搞定。

手把手教你实现.NET类名约定自动注入

说了这么多,直接上实操步骤。我自己常用Scrutor(.NET生态里轻量且流行的依赖注入扩展库),因为它文档全、配置简单,你也可以试试Autofac,逻辑差不多,选顺手的就行。

第一步:定好你的命名规则

规则是核心,一定要团队统一,不然框架会“找不着北”。我常用的规则是:

  • 接口:I + 业务名称 + 功能(比如IUserServiceIOrderRepository);
  • 实现类:业务名称 + 功能 + Impl(比如UserServiceImplOrderRepositoryImpl);
  • 生命周期:根据功能定(比如服务类用Scoped,仓储类用Singleton,工具类用Transient)。
  • 你可以根据项目调整,比如把Impl改成ServiceRepository,甚至用V1/V2区分版本——只要全团队统一,怎么改都行。比如我另一个做ERP的客户,他们把仓储类的实现后缀定为Repository(接口IOrderRepository对应OrderRepository),服务类后缀定为Service(接口IOrderService对应OrderService),照样能自动注册。

    第二步:用Scrutor实现自动扫描

    Scrutor的作用是帮你“扫描项目里的类,按规则绑定接口和实现”。具体操作就3步:

  • 安装Scrutor包:打开NuGet包管理器,搜索“Scrutor”,安装最新版(目前是4.2.2);
  • 写扫描代码:在Program.cs(.NET 6+)或Startup.cs(.NET Core)的ConfigureServices方法里加这段:
  •  services.Scan(scan => scan
    

    //

  • 扫描当前项目的程序集(如果服务在类库,改成FromAssemblyOf(),DomainMarker是类库的空标记类)
  • .FromAssemblyOf()

    //

  • 筛选符合规则的类:以Impl 且不是抽象类
  • .AddClasses(classes => classes

    .Where(type => type.Name.EndsWith("Impl"))

    .Where(type => !type.IsAbstract))

    //

  • 绑定到实现的接口
  • .AsImplementedInterfaces()

    //

  • 设置默认生命周期(这里用Scoped,可根据需要改Singleton/Transient)
  • .WithScopedLifetime()

    );

  • 验证规则:比如你有个接口
  • IUserService,实现类UserServiceImpl,不用写任何注册代码,直接在控制器里通过构造函数注入IUserService——运行项目,看能不能正常使用。

    我第一次用Scrutor时踩过坑:没加

    !type.IsAbstract筛选,导致框架试图注册抽象类BaseServiceImpl,直接报错“无法实例化抽象类”。后来加了这个条件,问题就解决了——一定要排除抽象类和接口,不然框架会“瞎找”

    第三步:灵活调整生命周期和特殊场景

    如果有些类的生命周期和默认规则不一样(比如仓储类要Singleton),可以单独加筛选条件。比如:

    csharp

    services.Scan(scan => scan

    .FromAssemblyOf()

    // 服务类:Impl Scoped生命周期

    .AddClasses(classes => classes.Where(type => type.Name.EndsWith(“ServiceImpl”)))

    .AsImplementedInterfaces()

    .WithScopedLifetime()

    // 仓储类:RepositoryImpl Singleton生命周期

    .AddClasses(classes => classes.Where(type => type.Name.EndsWith(“RepositoryImpl”)))

    .AsImplementedInterfaces()

    .WithSingletonLifetime()

    );

    这样就能区分不同类型的类,给不同的生命周期——比手动注册灵活多了!

    为了让你更清楚,我整理了常用命名规则示例表,你可以直接抄作业:

    接口命名 实现类命名 生命周期 适用场景
    IUserService UserServiceImpl Scoped 业务服务类(每次请求新建实例)
    IOrderRepository OrderRepositoryImpl Singleton 仓储类(全局唯一实例,减少数据库连接)
    IPaymentGateway PaymentGatewayImpl Transient 工具类(每次使用新建实例,避免状态污染)

    最后想说:先试小模块,再推广全项目

    我 你先在小模块(比如用户模块、订单模块)试这个方法——定好规则,加个新服务(比如

    IUserProfileServiceUserProfileServiceImpl),按照上面的步骤配置,验证能不能自动注入。如果成了,再慢慢推广到整个项目,这样风险小,也容易调整规则。

    我朋友的项目用了这个方法后,注册代码从100多行降到了10行以内,他说:“现在加新服务只要写接口和实现类,再也不用跟注册代码较劲了,省下来的时间能多测几个边界case。”

    如果你按这些步骤试了,欢迎回来告诉我有没有解决你的“注册焦虑”!或者有什么问题——比如不知道怎么选库、规则定不好——咱们一起聊聊,帮你出出主意。 解决重复劳动的快感,谁试谁知道~


    类名约定的命名规则必须用Impl后缀吗?

    不是必须的,Impl只是常用的一种约定,核心是团队要统一规则。比如你可以把接口叫IUserService,实现类直接叫UserService(去掉接口的I前缀);或者接口叫IOrderRepository,实现类叫OrderRepository;甚至用版本号区分,比如IPaymentServiceV2对应PaymentServiceV2Impl。只要全团队都认可同一个“接口-实现”的命名对应关系,框架就能准确匹配。

    我之前帮一个客户做项目时,他们习惯把仓储类的实现后缀定为Repository,服务类后缀定为Service,比如IUserRepository对应UserRepository,IUserService对应UserService,照样能通过Scrutor的AddClasses筛选(比如Where(type => type.Name.EndsWith(“Repository”)))完成自动注册。

    实现自动注入一定要用Scrutor吗?

    不是,Scrutor是.NET生态里轻量且流行的选择,但你也可以用Autofac、DryIoc甚至自己写反射逻辑。比如Autofac的RegisterAssemblyTypes方法,同样能扫描程序集、按类名规则绑定接口和实现,逻辑和Scrutor差不多。

    我自己选Scrutor主要是因为它文档全、配置简单,不需要额外学复杂的语法;如果你的项目已经在用Autofac做依赖注入,直接用Autofac的规则配置就行,不用特意换库——重点是“按类名约定自动匹配”这个逻辑,工具只是载体。

    一个接口有多个实现类,类名约定能处理吗?

    完全可以。比如你有IUserService接口,对应两个实现类:UserServiceByDbImpl(数据库实现)和UserServiceByCacheImpl(缓存实现),只要在类名里加区分标识(比如ByDb、ByCache),框架就能识别不同的实现。

    注入的时候,你可以用.NET 8+的Keyed Services(比如在构造函数里加[FromKeyedServices(“Db”)])或者Autofac的Named注册,告诉框架你要哪个实现。比如Scrutor里可以用AddClasses筛选出UserServiceByDbImpl,然后用AsKeyed(“Db”)绑定,这样注入时指定Key就能拿到对应的实现类。

    自动注入会不会把不需要的类也注册进去?

    只要加好筛选条件,就不会。比如抽象类、基类或者测试用的类,你可以在AddClasses方法里加Where条件排除。比如用Scrutor时,你可以写classes.Where(type => !type.IsAbstract)(排除抽象类),或者Where(type => type.Namespace.Contains(“YourProject.Services”))(只扫描服务命名空间下的类),甚至Where(type => !type.Name.Contains(“Test”))(排除测试类)。

    我第一次用的时候没加!type.IsAbstract这个条件,结果框架试图注册抽象基类BaseServiceImpl,直接报错“无法实例化抽象类”,后来加上这个筛选条件就解决了——一定要根据项目实际情况加过滤,避免框架“误注册”。

    生命周期能通过类名约定区分吗?

    可以的,你可以按类名的后缀或规则来绑定不同的生命周期。比如服务类用ServiceImpl后缀,生命周期设为Scoped;仓储类用RepositoryImpl后缀,生命周期设为Singleton;工具类用ToolImpl后缀,生命周期设为Transient。

    具体操作就是在Scrutor的Scan里写多个AddClasses块:比如第一个块筛选EndsWith(“ServiceImpl”)的类,用WithScopedLifetime;第二个块筛选EndsWith(“RepositoryImpl”)的类,用WithSingletonLifetime;第三个块筛选EndsWith(“ToolImpl”)的类,用WithTransientLifetime。这样不同类型的类就会自动应用对应的生命周期,不用手动一个个设置。

    原文链接:https://www.mayiym.com/50837.html,转载请注明出处。
    0
    显示验证码
    没有账号?注册  忘记密码?

    社交账号快速登录

    微信扫一扫关注
    如已关注,请回复“登录”二字获取验证码