但是,现实情况可能不完全是这样的。如果你创建了子范围作用域并在子作用域范围内解析RequestItemsService,你会得到一个全新的RequestItemsService,而这并非我们所期望的那样。所有Scoped服务并非一个Web请求时共享一个服务实例。
你可能会认为你不会犯这样明显的错误(在子作用域内解析服务依赖)。但是这不是错误(一个常规用法而已)并且情况并没有那么简单。假设在你的服务中有庞大的服务依赖关系,你可能不知道是否有人会这么做(在子作用域内解析服务依赖)。
良好实践
Scoped服务可以视作一种优化手段(在一个web请求中不想注入太多服务)。这样在同一个Web请求中所有的服务使用同一个实例。
Scoped服务不需要设计线程安全。因为Scoped服务通常在一个线程或Web请求中使用,但是,这种场景下,不应该在不同线程之间共享Scoped服务。
如果要设计一个作用域服务来在web请求中的其他服务之间共享数据,小心上述问题。你可以使用HttpContext(通过IHttpContextAccessor来访问它)来存储每一个Web请求需要存储的数据,这是安全的处理方式。HttpContext生命周期并不是Scoped。实际上并没有注入到依赖注入的容器内(这是为什么使用IHttpContextAccessor访问它而不是注入到容器内的原因)。++在一个Web请求中,HttpContextAccessor使用AsyncLocal来共享相同的HttpContext++。
结论依赖注入在最初使用的时候好像是挺简单的。如果不遵循严格的使用原则,依然会有潜在的多线程和内存泄漏问题。我在开发ASP.NET Boilerplate框架过程中,基于我的实践体会分享了这些实践原则。
总结在使用ASP.NET Core 依赖注入时需要注意几项:
在构造函数中显示的注入依赖关系。
在依赖关系众多时,职责单一原则,考虑拆分职责
更有利于单元测试。
属性注入,适用于可选依赖项,不影响服务正常运行,考虑空实现模式。
通常我们在设计框架/基类时,可以适当引入属性注入,这样可以使得继承类代码更简洁。
必要时,属性提供懒加载方式,提高服务启动速度。
选择合适的服务生命周期。顺序依次Transient > Singleton > Scoped,不确定时使用Transient ,明确使用场景的时候考虑Singleton和Scoped。同需要需要考虑服务的构建成本。
Transient服务的生命周期短,可以有效的规避多线程和内存泄漏问题,同时也引起应用程序的内存使用量上升,带了部分性能问题。
在Singleton服务中,禁止依赖Transient/Scoped服务,一方面,Transient/Scoped服务也会变成单例服务。另一方面,Transient/Scoped服务没有考虑多线程问题。
在使用Singleton服务时,多注意潜在的线程安全和内存泄漏问题。
在非Web应用场景和子作用服务场景,Scoped服务,并不能正确处理一个线程内共享实例。