netCore3从入门到实战-读书笔记-1
.netcore核心组件
.netcore提供了一组内置的组件来帮助开发者来实现一个应用程序的基本功能,如依赖注入 、配置、选项、日志等,这些组件以Microsoft.Extensions为前缀;
依赖注入
依赖注入就是一个类型使用或引用到另一个类型,从严格意义上来讲,如果A类型的定义或实现中出现了B类型, 就可以理解为A类型依赖了B类型, 大体上有下列几种情形或者这些情形的组合:
- A类型的构造函数入参包含了B类型
- A类型的属性、字段包含了B类型
- A类型的方法输入或输出参数包含了B类型
- A类型的方法的局部变量包含了B类型
**什么是依赖注入?**按照上例中B类型的实例注入给A类型就是。讲到依赖注入,就会提到控制反转(Inversion of Control), 它们本质上讲同一个事物的两面,都是处理对象之间的依赖关系;
从容器的角度来看,依赖注入由容器负责对象的构造,对象所依赖的其他对象由容器负责管理并注入给对象。控制反转是从对象的视角来讲的,指对象把构造自己及其依赖对象的控制权交给容器来管理;
为什么需要依赖注入?在oop思想中有个很重要的原则叫单一职责原则,指一个类型应该仅负责单一的职责,当一个类型负责过多的职责的时候,它被改变的因素就会被增多,使得类型变得更加不稳定;
像前面讲的那样,当A类型依赖B类型时,A类型并不关心B类型的实例是怎么产生的,A类型只在需要的地方使用B的属性或者方法;(类比Driver类型并不关心Car类型是怎么造出来的,他只关心这个车是否可被正常驾驶?(使用Car类型的Run方法)),也不关心给Driver的是电动车还是汽油车;
还有一个对应的设计原则叫关注点分离原则,指在设计系统时应该在宏观上为不同的关注点分别设计,然后组合起来。这里的“关注点”可以理解为是不同的能力,比如以MVC框架为例,Model负责数据的承载,View负责UI的渲染,Cotroller负责View, Model之间的数据传递以及通过仓储层将数据存储在数据库。这里Model的关注点就是数据本身,View的关注点是UI渲染,Cotroller的关注点是传递和组合。
反过来想一想,假设我们没有为对象的创建进行关注点设计,那就意味着系统代码到处充斥着new关键字。对于简单类型,这还可以容忍;但是对于复杂类型,尤其是那些负责将不同组件组装在一起的类型,构造过程就显得非常繁琐,因为它依赖了多个其他组件。而那些被依赖的组件很有可能也依赖了其他的组件,最终我们会发现负责创建对象的代码让系统难以维护,任何类型的变更都可能影响到那些依赖它的代码,这也就是为什么会有工厂模式/抽象工厂模式等负责创建对象的设计模式,本质上都是为了将创建对象这个关注点分离出来。
如果说工厂模式、抽象设计模式是特定类型的创建过程的分离,那么依赖注入就是将任意类型的创建的能力分离出来,实际上在我们使用依赖注入框架时会惊奇地发现它实际是支持工厂模式的;
本质上,依赖注入是控制反转思想的一种实现方式。在.NET Core生态中,是由依赖注入组件来实现依赖注入设计模式的,它的设计思路是通过定义类型的生命周期、构造方式等规则配置到容器中,由容器负责管理对象的创建和资源释放。
依赖注入组件
要用到依赖注入框架, 就需要使用以下两个包:
1
2
|
Microsoft.Extensions.DependencyInjection.Abstractions;
Microsoft.Extensions.DependencyInjection;
|
其中 Microsoft.Extensions.DependencyInjection.Abstractions
包含了依赖注入框架核心接口的定义,Microsoft.Extensions.DependencyInjection
包含了框架的具体实现,这种将接口定义和具体实现放在不同包的模式叫做接口实现分离模式, 其好处是组件的使用者可以只依赖抽象的定义包,可以在运行时决定使用哪种具体的实现,这与依赖注入的核心思想是一致的。后面的配置组件,选项组件和日志组件,也都是在组件的设定初期就设定用户会使用依赖注入来管理对象
依赖注入框架类型的主要命名空间为Microsoft.Extensions.DependencyInjection
, 其中核心的类型是ServiceDescriptor, ServiceCollection, ServiceProvider. 下图也揭示了这三个类之间的关系和DI框架的使用过程;
服务描述类ServiceDescriptor
ServiceDescriptor类包含服务的类型, 生命周期和构造方式等信息. ServiceCollection是ServiceDescriptor的集合, 通过BuildServiceProvider方法可以获得服务容器ServiceProvider, 通过ServiceProvider的GetService方法可以获得注册服务的实例;
1
2
3
4
5
6
7
8
|
public class ServiceDescriptor
{
public Type ImplementationType { get; }
public object ImplementationInstance { get; }
public Func<IServiceProvider, object> ImplementationFactory { get; }
public ServiceLifetime Lifetime { get; }
public Type ServiceType { get; }
}
|
实际上, 这个只是列出了ServiceDescriptor的主要定义, 其中ServiceType是要注册的类型, 也就是将来要获取的类型; 既可以是接口, 也可以是抽象类, 也可以是普通的类型; 属性ImplementationType表明实际要创建的类型, 属性ImplementationInstance表明注册时候已经获取了类型实例并将它注册进来, 将来要获取的时候将ImplementationInstance返回给调用者即可; 属性ImplementationFactory表示注册了一个用于创建ServiceType指定的类型的工厂, 当需要从容器获取时, 由这个工厂负责创建类型实例;
总结起来就是, 可以有3种方式获得目标类型(ServiceType)的实例:
- 从已有的实例来创建, 即ImplementationInstance;
- 从指定的类型来动态创建, 即ImplementationType;
- 从指定的工厂方法来来动态创建, 即ImplementationFactory;
而且, 对于一个ServiceDescriptor实例, 只能注册为这三种方式之一, 它们时互斥的, 不可重复定义; 为了方便的创建ServiceDescriptor实例, 提供了一系列的静态方法可供调用(瞬时服务注册, 范围服务注册, 单例服务注册, 指定生命周期注册);
IServiceCollection与服务注册
看一下IServiceCollection的定义, 没有包含任何特别的方法, 仅仅是一个ServiceDescriptor的集合, 在组件的实现包Microsoft.Extensions.DependencyInjection中, 类ServiceCollection实现了IServiceCollection接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public interface IServiceCollection : ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable, IList<ServiceDescriptor>
{
}
public class ServiceCollection : IServiceCollection, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable, IList<ServiceDescriptor>
{
public ServiceCollection();
public ServiceDescriptor this[int index] { get; set; }
public int Count { get; }
public bool IsReadOnly { get; }
public void Clear();
public bool Contains(ServiceDescriptor item);
public void CopyTo(ServiceDescriptor[] array, int arrayIndex);
public IEnumerator<ServiceDescriptor> GetEnumerator();
public int IndexOf(ServiceDescriptor item);
public void Insert(int index, ServiceDescriptor item);
public bool Remove(ServiceDescriptor item);
public void RemoveAt(int index);
}
|
最直接的方式就是 创建ServiceDescriptor实例, 并通过Add方法添加到IServiceCollection中,
1
2
|
var serviceDescriptor = ServiceDescriptor.Singleton<IMyService, MyService>();
services.Add(serviceDescriptor);
|
另外提供了一组扩展方法来快速注册服务, 并给出备注说明:
- AddSingleton, 将服务注册为单例的;
- AddScoped, 将服务注册为范围的;
- AddTransisent, 将服务注册为瞬时的;
这些不同生命周期的服务注册方法都提供了多种重载, 支持用户按类型, 按实例, 按工厂方法的方式来定义服务的构造方式;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
IServiceCollection services = new ServiceCollection();
// 指定实例
services.AddSingleton<IMyService>(new MyService());
// 指定类型
services.AddSingleton<IMyService, MyService>();
// 工厂模式
services.AddScoped<IMyService>(provider => new MyService());
// 工厂模式, 从容器中获取
services.AddTransient<IMyService>(provider => new MyService());
services.AddTransient<IMyService>(provider => provider.GetService<MyService>());
|
需要注意的是, 指定已有实例的方式仅支持单例模式;
工厂模式使用了委托Func<IServiceProvider, object>
, 其入参是一个IServiceProvider, 实际上就是当前容器实例;
所以可以使用上面的代码从容器中取出需要的服务实例用于构造服务;
在实际的应用场景中, 我们通常会遇到一个服务被多个组件依赖的情形, 仅当服务未被注册时才需要注册, 可以使用TryAddXXX系列的扩展方法, 这些方法会被判断指定的服务是否已经注册过; 如果已经注册过, 则忽略本次注册操作; 如果未注册过, 则注册该服务;
还有一种比较特殊的场景, 就是为服务注册不同的实现. 要避免同一具体实现被重复注册, 可以使用TryAddEnumerable
方法, 该方法有两个重载, 一个是注册单个ServiceDescriptor
, 另一个是批量注册, 传入IEnumerable<ServiceDescriptor>
.
在ASP.NET Core应用中, 可以在Startup类的ConfigServices方法中为IServiceCollection注册服务, 而IServiceCollection本身的创建由框架负责, 不需要特别地处理;
通过IServiceProvider获取服务实例
接口IServiceProvider位于System命名空间下, 是一个拥有很长历史的接口(在.NETFramework1.1版本就存在了), 并且在很多针对特定场景的实现中, 它的定义只有一个GetService方法, 入参是服务的类型, 返回值是服务的实例;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public interface IServiceProvider
{
//
// Summary:
// Gets the service object of the specified type.
//
// Parameters:
// serviceType:
// An object that specifies the type of service object to get.
//
// Returns:
// A service object of type serviceType. -or- null if there is no service object
// of type serviceType.
object GetService(Type serviceType);
}
|
在依赖注入组件中, 由类ServiceProvider实现了IServiceProvider, 它位于Microsoft.Extensions.DependencyInjection包中, 同时类ServiceProviderServiceExtensions
提供了一组扩展方法, 让开发者可以更方便编写获取实例的代码, 尤其是泛型方法, 它可以直接获得特定类型的返回值, 而无需进行类型转换;
1
2
3
4
5
6
7
8
9
10
11
12
13
|
假设已经注册好的服务也获得了IServiceProvider实例:
IServiceCollection services = new ServiceCollection();
// 忽略服务注册的代码
IServiceProvider serviceProvider = services.BuildServiceProvider();
获取服务实例的代码如下:
// 指定类型获取服务实例
object myService = serviceProvider.GetService(typeof(IMyService));
// 泛型方法指定类型获取服务实例
IMyService myService = serviceProvider.GetService<IMyService>();
|
上面的代码中, 如果要获取的服务类型没有注册在容器中,, 则返回null; 如果我们期望在服务未被注册的情形下抛出异常而不是返回null,
则可以调用GetRequiredService方法. 如果服务未被注册, 则会抛出异常InvalidOperationException并显示服务未被注册. GetRequiredService方法在服务不可为空的场景下非常有用, 有了该方法我们就无需重复地编写验证空服务的代码:
1
2
3
4
5
|
// 指定类型获取服务实例
object myService = serviceProvider.GetRequiredService(typeof(IMyService));
// 泛型方法指定类型获取服务实例
IMyService myService = serviceProvider.GetRequiredService<IMyService>();
|
在实际的应用程序中, 当我们为同一类型重复注册, GetService方法会以最后注册的生命周期和构造方法为准. 我们也可以通过复数方法GetServices来获得一个实例集合, 它对应了为同一类型的多个注册:
1
2
3
4
5
|
// 指定类型, 获取object实例集合
IEnumerable<object> myServiceList = serviceProvider.GetService(typeof(IMyService));
// 泛型方法, 获取指定类型的实例集合
IEnumerable<T> myServiceList = serviceProvider.GetService<T>();
|
在ASP.NET Core应用中, 我们可以看到Startup类的Configure有一个IApplicationBuilder类型的入参:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class Startup
{
// some code
public void ConfigureServices(IServiceCollection services)
{
// 服务注册
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 根容器
var rootServiceProvider = app.ApplicationServices;
// some code
}
}
|
查看一下IApplicationBuilder接口的代码, 它有一个IServiceProvider类型的属性ApplicationServices, 这个属性就是应用程序的根容器实例. 当我们类访问根容器时, 可以通过接口IApplicationBuilder来获取根容器的实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
public interface IApplicationBuilder
{
//
// Summary:
// Gets or sets the System.IServiceProvider that provides access to the application's
// service container.
IServiceProvider ApplicationServices { get; set; }
//
// Summary:
// Gets a key/value collection that can be used to share data between middleware.
IDictionary<string, object> Properties { get; }
//
// Summary:
// Gets the set of HTTP features the application's server provides.
IFeatureCollection ServerFeatures { get; }
//
// Summary:
// Builds the delegate used by this application to process HTTP requests.
//
// Returns:
// The request handling delegate.
RequestDelegate Build();
//
// Summary:
// Creates a new Microsoft.AspNetCore.Builder.IApplicationBuilder that shares the
// Microsoft.AspNetCore.Builder.IApplicationBuilder.Properties of this Microsoft.AspNetCore.Builder.IApplicationBuilder.
//
// Returns:
// The new Microsoft.AspNetCore.Builder.IApplicationBuilder.
IApplicationBuilder New();
//
// Summary:
// Adds a middleware delegate to the application's request pipeline.
//
// Parameters:
// middleware:
// The middleware delegate.
//
// Returns:
// The Microsoft.AspNetCore.Builder.IApplicationBuilder.
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
}
|
作用域与生命周期
要理解生命周期, 先要去了解概念-Scope(范围的意思), 也就是作用域; Scope本身具有一个生命周期, 就是从它被"创建"到被"回收释放"这个周期. 在Scope生存期间, 将一些对象与其关联, 当Scope回收释放时, 同时也释放与其关联的对象, 就能达到相应的效果, 这些对象的生命周期和Scope的相同, 或者可以理解为这些对象的生命周期由Scope决定;
在依赖注入组件中, 我们将由ServiceCollection通过BuildServiceProvider构造的IServiceProvider实例称为"根容器", 对应的依赖注入组件提供了一个接口IServiceScope来表示"子作用域", 我们可以通过扩展方法CreateScope来创建IServiceScope实例.
1
2
3
4
5
6
7
|
public interface IServiceScope : IDisposable
{
//
// Summary:
// The System.IServiceProvider used to resolve dependencies from the scope.
IServiceProvider ServiceProvider { get; }
}
|
下面的代码定义了创建IServiceScope的方法:
1
2
3
4
5
6
7
|
IServiceCollection services = new ServiceCollection();
// ignore service register code here
IServiceProvider serviceProvider = services.BuildServiceProvider();
// 创建了子容器
IServiceScope scope = serviceProvider.CreateScope();
|
实际上IServiceScope的实例是由接口IServiceScopeFactory来负责创建的, 上面示例代码中的扩展方法CreateScope的内部实现逻辑是从IServiceScopeFactory, 然后调用IServiceScopeFactory的CreateScope, 当我们构造好一个IServiceProvider容器时, 其内部实际上已经注入了IServiceScopeFactory, 这一切在依赖注入的内部实现了;
来看一下IServiceScopeFactory的定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public interface IServiceScopeFactory
{
//
// Summary:
// Create an Microsoft.Extensions.DependencyInjection.IServiceScope which contains
// an System.IServiceProvider used to resolve dependencies from a newly created
// scope.
//
// Returns:
// An Microsoft.Extensions.DependencyInjection.IServiceScope controlling the lifetime
// of the scope. Once this is disposed, any scoped services that have been resolved
// from the Microsoft.Extensions.DependencyInjection.IServiceScope.ServiceProvider
// will also be disposed.
IServiceScope CreateScope();
}
|
前面展示的ServiceDescriptor的定义中, 其生命周期由枚举ServiceLifetime来定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public enum ServiceLifetime
{
//
// Summary:
// Specifies that a single instance of the service will be created.
Singleton = 0,
//
// Summary:
// Specifies that a new instance of the service will be created for each scope.
//
// Remarks:
// In ASP.NET Core applications a scope is created around each server request.
Scoped = 1,
//
// Summary:
// Specifies that a new instance of the service will be created every time it is
// requested.
Transient = 2
}
|
这里说到的生命周期决定了我们调用GetService来获取服务实例时的行为:
- Singleton表示单例, 容器只会给生命周期为Singleton的类型创建一次实例, 后续获取实例时都会返回这个唯一的实例. Singleton的服务只会由根容器创建, 从子容器获取服务实例时会从根容器中查找;
- Scoped 表示范围, 本质上是在作用域内的单例模式, 在IServiceScope作用域内只给生命周期为Scoped的类型创建一次实例, 后续在该作用域内都会获得同一实例. 基于这个原则, 根容器中的Scoped服务和Singleton服务是对等的;
- Transient表示瞬时, 是指每次通过容器获取对象都会创建一个新的实例, 不论是在根容器还是在子容器中.
下图展示了根容器, 子容器和不同生命周期服务实例间的关系:
对于从根容器获得Scoped服务的情形, 在BuildServiceProvider中可以使用validateScopes(验证Scope)来决定行为:
- false, 允许从根容器获取Scoped服务, 行为与Singleton服务一致;
- true, 不允许从根容器获取服务, 如果尝试这样做, 则会抛异常;
在.netcore中, 框架会为每个HTTP请求创建一个Scope, 这样可以为不同的请求创建其需要的服务, 避免请求之间的相互影响, 例如将数据库连接对象注册为Scoped, 类型HttpContext表示一个Http请求的上下文, 可以通过它的RequiredServices属性来访问请求子容器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
public abstract class HttpContext
{
protected HttpContext();
//
// Summary:
// Gets information about the underlying connection for this request.
public abstract ConnectionInfo Connection { get; }
//
// Summary:
// Gets the collection of HTTP features provided by the server and middleware available
// on this request.
public abstract IFeatureCollection Features { get; }
//
// Summary:
// Gets or sets a key/value collection that can be used to share data within the
// scope of this request.
public abstract IDictionary<object, object> Items { get; set; }
//
// Summary:
// Gets the Microsoft.AspNetCore.Http.HttpRequest object for this request.
public abstract HttpRequest Request { get; }
//
// Summary:
// Notifies when the connection underlying this request is aborted and thus request
// operations should be cancelled.
public abstract CancellationToken RequestAborted { get; set; }
//
// Summary:
// Gets or sets the System.IServiceProvider that provides access to the request's
// service container.
public abstract IServiceProvider RequestServices { get; set; }
//
// Summary:
// Gets the Microsoft.AspNetCore.Http.HttpResponse object for this request.
public abstract HttpResponse Response { get; }
//
// Summary:
// Gets or sets the object used to manage user session data for this request.
public abstract ISession Session { get; set; }
//
// Summary:
// Gets or sets a unique identifier to represent this request in trace logs.
public abstract string TraceIdentifier { get; set; }
//
// Summary:
// Gets or sets the user for this request.
public abstract ClaimsPrincipal User { get; set; }
//
// Summary:
// Gets an object that manages the establishment of WebSocket connections for this
// request.
public abstract WebSocketManager WebSockets { get; }
//
// Summary:
// Aborts the connection underlying this request.
public abstract void Abort();
}
|
IDisposable与生命周期
C#是类型安全的语言, 通常不需关心对象的回收, 由垃圾回收机制(GC)来负责回收那些不再使用的对象实例, 但是垃圾回收机制回收对象并不是实时的, 因此当对象使用了网络链接, 线程, 文件等资源时, 就需要在使用完成后立即释放这些资源, 以避免资源耗尽的情况; 一般约定类型实现IDispoable接口来表示其使用了系统资源, 需要在使用完成之后调用Dispose方法来主动释放资源;
DI框架负责对象的创建, 容器的生命周期和其创造的对象的生命周期是一致的, 因此依赖注入组件"恰好"具备了对其创建的对象进行"释放"操作的时机, 即容器或子容器释放之时;
在具体的实现上, 依赖注入组件负责对实现了IDispoable接口类的实例的资源释放, 其原则就是容器(子容器)仅负责由其构造且实现了IDispoable实例的Dispose方法的调用, 调用的时机是容器(子容器)本身释放之时;
下表展示容器释放与实现了IDisposable的服务的生命周期的关系:
|
释放根容器 |
释放子容器 |
Singleton |
释放 |
不释放 |
Scoped |
释放 |
释放 |
Transient |
释放 |
释放 |
在.netcore框架中, 每个请求作为一个Scope来处理, 当请求结束时, 请求子容器会被释放, 因此由其创建的IDisposable, 请求子容器会被释放, 因此由其创建的IDisposable服务也会被释放, 注册的Scoped服务(例如数据库连接)也会被释放, 从而达到以下效果: 以HTTP请求为生命周期来管理请求内使用的资源.
需要注意的是, 根容器的生命周期与应用进程是相同的, 也就是当应用程序退出时回收根容器, 那么当服务同时满足下面的情形时会存在内存等资源持续占用的情形:
-
服务实现了IDisposable接口;
-
服务的生命周期为Transient;
-
服务的实例从根容器获取;
这是因为每次从根容器获取服务时都会给生命周期为Transient的服务创建新实例, 并且实现了IDisposable. 所以容器会负责管理这些实例的释放, 从而造成持续不断的新实例的积累. 由于根容器在程序退出时才回收, 因此这些实例会一直存在到程序退出, 实际时候应避免这种情形产生;
扩展接口
IServiceProviderFactory<TContainerBuilder>
依赖注入组件提供了对象的创建和基于Scope的生命周期管理的能力, 能够满足大部分场景, 但是需要额外的扩展能力时, 如面向切面编程(AOP), 嵌套子容器, 属性注入, 基于名称的注入, 基于约定的注入, 就需要对依赖注入组件进行扩展; 官方提供的策略就是 提供扩展接口IServiceProviderFactory<TContainerBuilder>
让我们自定义来实现.
其接口定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public interface IServiceProviderFactory<TContainerBuilder>
{
//
// Summary:
// Creates a container builder from an Microsoft.Extensions.DependencyInjection.IServiceCollection.
//
// Parameters:
// services:
// The collection of services
//
// Returns:
// A container builder that can be used to create an System.IServiceProvider.
TContainerBuilder CreateBuilder(IServiceCollection services);
//
// Summary:
// Creates an System.IServiceProvider from the container builder.
//
// Parameters:
// containerBuilder:
// The container builder
//
// Returns:
// An System.IServiceProvider
IServiceProvider CreateServiceProvider(TContainerBuilder containerBuilder);
}
|
在上面的定义中, 泛型类型参数TContainerBuilder表示自定义的容器构造器类型, CreateBuilder方法实现了从IServiceCollection构造TContainerBuilder的能力,
CreateServiceProvider提供了从TContainerBuilder构造IServiceProvider的能力, 最终应用程序通过IServiceProvider来获取服务, 而无需进行修改; 工作过程如图所示:
实际上, 在开源社区已经由很多成熟的第三方依赖注入框架可供选择, 如Autofac, Unity, AspectCore(国人开发的优秀项目之一), 它们分别提供了上面接口的实现并提供了对应的扩展包:
- Autofac.Extensions.DependencyInjection
- Unity.Microsoft.DependencyInjection
- AspectCore.Extensions.DependencyInjection
这些开源组件提供了详细的使用文档和示例代码, 可以通过阅读他们的源码来了解其设计原理;
值得注意的是, 在ConfigureServices方法中注册服务仍然是有效的, 因此通常情况下可以通过ConfigureServices来注册服务, 仅对需要高级功能的服务调用ConfigureContainers方法来注册; 另外, 对于其他第三方组件需要修改ConfigureContainers方法的入参类型为组件提供的服务注册上下文类型;
在Controller中获取服务
在netcore中, Controller是定义Web API的核心方法, 在Controller中获取服务有以下几种途径:
- 使用构造函数参数
- 使用HttpContext.RequestServices属性
- 使用FromServicesAttribute标注Action的入参
在实际的场景中, 建议的做法是:
- 当Controller大部分Action都需要使用某个服务时, 使用构造函数注入该服务;
- 仅有个别的Action需要使用某个服务时, 使用FromServicesAttribute为具体的Action注入服务;
- 应尽量避免使用HttpContext.RequestServices来获取服务, 这样会使类难以编写测试;
默认情况下, Controller实例的构造是由.netcore框架来负责的, 而不是由容器负责的, 因此当使用第三方组件来扩展其他能力(如AOP, 属性注入等)时, 对于Controller本身并不会生效. 如果要使用容器来负责Controller的构造, 需要在Startup类的ConfigureServices方法中加入AddControllersAsServices方法, 具体代码如下:
1
2
3
4
|
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddControllersAsServices();
}
|