Registration and provision of services
In applications where dependency injection is used, we are always directly using DI containers to get the required service instance directly, in other words, the DI container serves as a service provider that can provide a usable service object based on the service description information we provide. The Di container in the ASP. NET core is represented as an object that implements the IServiceProvider interface.
first, serviceprovider and Servicedescriptor
I have always felt that good design should first be simple design, at least it looks simple design, this is what we call the Boulevard to Jane. As a provider of a service, the DI container in ASP. NET core is finally represented as a IServiceProvider interface, and we collectively refer to all of the types that implement the interface and their instances as serviceprovider. As the following code snippet shows, the interface is simply extremely simple, providing only a single GetService method that provides you with a corresponding service instance based on the type of service provided.
1:public Interface IServiceProvider
2: {
3:object GetService (Type servicetype);
4:}
An internal type that implements the IServiceProvider interface (the name of the type "serviceprovider") is actually used internally by ASP. We cannot create the object directly. It can only be obtained indirectly by calling the extension method of the Iservicecollection interface Buildserviceprovider. The Iservicecollection interface is defined under the "Microsoft.Extensions.DependencyInjection" namespace, and if not specifically stated, this series of articles deals with the ASP. This namespace is used by core dependency injection-related types. As shown in the following code fragment, the Iservicecollection interface actually represents an element as a collection of Servicedescriptor objects, which directly inherits from the other interface Ilist<servicedescriptor> The Servicecollection class implements the interface.
1:public Static Class Servicecollectionextensions
2: {
3:public static IServiceProvider Buildserviceprovider (this iservicecollection services);
4:}
6:public Interface iservicecollection:ilist<servicedescriptor>
7: {}
9:public class Servicecollection:iservicecollection
10: {
11://Omit member
12:}
The serviceprovider that manifests as Di container is able to provide an out-of-the-box service instance based on our given service type (typically an interface type) because we have pre-registered the corresponding service description information, These guidance serviceprovider service descriptions that correctly implement service delivery operations are reflected in the following servicedescriptor types.
1:public class Servicedescriptor
2: {
3:public Servicedescriptor (Type servicetype, object instance);
4:public Servicedescriptor (Type servicetype, Func<iserviceprovider, object> factory, Servicelifetime Lifetime);
5:public servicedescriptor (Type servicetype, type Implementationtype, servicelifetime lifetime);
7:public Type servicetype {get;}
8:public servicelifetime Lifetime {get;}
10:public Type implementationtype {get;}
11:public object implementationinstance {get;}
13:}
The servicetype attribute of the Servicedescriptor represents the life type of the service provided, and as a standard service is generally defined as an interface, in most cases it is represented as an interface type. A property of type Servicelifetime lifetime embodies serviceprovider control over the life cycle of the service instance. As shown in the following code snippet, Servicelifetime is an American drama type that defines three options (Singleton, scoped, and transient) that embody three forms of control over the life cycle of the service object, which we will cover in a later section of this section.
1:public enum Servicelifetime
2: {
3:singleton,
4:scoped,
5:transient
6:}
For the other three properties of Servicedescriptor, they are actually auxiliary serviceprovider to complete a specific service instance to provide the operator. The Implementationtype property represents the True type of the provided service instance, and the attribute implementationinstance directly represents the provided service instance. Implementationfactory provides a delegate object to create a service instance. Several core types associated with the dependency injection of ASP. NET core have a relationship of 10.
Because the serviceprovider in ASP. NET core is created from a Iservicecollection object representing the Servicedescriptor collection, when we call its GetService method, It will find the corresponding Servicedecriptor object based on the type of service we provide. If the Implementationinstance property of the Servicedecriptor object returns a specific object, the object will be used directly as the provided service instance. If the implementationfactory of the Servicedecriptor object returns a specific delegate, the delegate object is used directly as the factory that created the service instance.
If both properties are Null,serviceprovider, the corresponding constructor is called to create the provided service instance based on the type returned by the Implementationtype property. As for the three dependency injection methods we mentioned in the previous section, ServiceProvider only supports constructor injection, and support for attribute injection and method injection is not provided.
second, the registration and provision of services
ASP. NET core programming for Dependency injection is primarily in two ways: one is to create a Servicecollection object and add the service registration information as a Servicedescriptor object; Create a corresponding serviceprovider for the Servicecollection object and use it to provide the service instance we need.
At the time of service registration, we can directly invoke the corresponding constructor to create the Servicedescriptor object and add it to the Servicecollection object. In addition, the Iservicecollection interface has the following three sets of extension methods to combine the two steps. The code snippet given below is not difficult to see the three sets of extension methods for the above mentioned three kinds of life cycle control methods for the service instance, the generic parameter tservice represents the service declaration type, that is, Servicedescriptor's ServiceType property, As for the other properties of Servicedescriptor, it is provided by the corresponding parameters of the method.
1:public Static Class Servicecollectionextensions
2: {
3:public static Iservicecollection addscoped<tservice> (this iservicecollection services) where Tservice:class;
4://Other addscoped<tservice> reload
6:public static Iservicecollection addsingleton<tservice> (this iservicecollection services) where TSERVICE:CLA ss
7://Other addsingleton<tservice> reload
9:public static Iservicecollection addtransient<tservice> (this iservicecollection services) where TSERVICE:CLA ss
10://Other addtransient<tservice> reload
11:}
For a ServiceProvider object used as a Di container, we can call its GetService method directly to get the service instance that you want to use based on the specified service type. In addition, the provision of the service can also be done through the corresponding constructor of the IServiceProvider interface. As shown in the following code fragment, the extension method getservice<t> specifies the claim type of the service in the form of a generic parameter. As for the other two extension methods Getrequiredservice and getrequiredservice<t> if serviceprovider cannot provide a specific service instance, A InvalidOperationException exception is thrown out and the corresponding service registration information is insufficient.
1:public Static Class Serviceproviderextensions
3:public static T getservice<t> (this iserviceprovider provider);
4:public Static Object Getrequiredservice (this IServiceProvider provider, Type servicetype);
5:public static T getrequiredservice<t> (this iserviceprovider provider);
6:}
Example Demo: Using ServiceProvider to provide services
The following example shows how to use Servicecollection for service registration and how to use servicecollection to create a corresponding serviceprovider to provide the service instance we need. We define four service interfaces (IFoo, IBar, Ibaz, and Igux) in one console application and four service classes (foo, Bar, Baz, and Gux) that implement them respectively, as shown in the following code snippet, Igux has three read-only properties (foo, Bar and Baz) are interface types and are initialized in constructors.
1:public interface IFoo {}
2:public interface IBar {}
3:public interface Ibaz {}
4:public Interface Igux
5: {
6:ifoo Foo {get;}
7:ibar Bar {get;}
8:ibaz Baz {get;}
9:}
11:public class Foo:ifoo {}
12:public class Bar:ibar {}
13:public class Baz:ibaz {}
14:public class Gux:igux
15: {
16:public IFoo Foo {get; private set;}
17:public IBar Bar {get; private set;}
18:public Ibaz Baz {get; private set;}
20:public Gux (IFoo foo, IBar Bar, Ibaz Baz)
21: {
22:this. foo = foo;
23:this. bar = bar;
24:this. Baz = Baz;
25:}
Now we have created a Servicecollection object in the main method as the entrance to the program, and have completed the registration for four service interfaces in different ways. Specifically, for the servicedescriptor of the Service interface IFoo and Igux, we have specified a Implementationtype attribute representing the true type of the service. For Servicedescriptor for service Interfaces Ibar and Ibaz, we initialize the Implementationinstance implementationfactory properties that represent the service instance and the Service factory respectively. Since we are calling the Addsingleton method, the Lifetime property of the four Servicedescriptor is singleton.
1:class Program
2: {
3:static void Main (string[] args)
4: {
5:iservicecollection Services = new Servicecollection ()
6:. Addsingleton<ifoo, Foo> ()
7:. Addsingleton<ibar> (New Bar ())
8:. Addsingleton<ibaz> (_ = = new Baz ())
9:. Addsingleton<igux, gux> ();
11:iserviceprovider serviceprovider = Services. Buildserviceprovider ();
12:console.writeline ("serviceprovider.getservice<ifoo> (): {0}",serviceprovider.getservice<ifoo> ());
13:console.writeline ("serviceprovider.getservice<ibar> (): {0}", serviceprovider.getservice<ibar> ());
14:console.writeline ("serviceprovider.getservice<ibaz> (): {0}", serviceprovider.getservice<ibaz> ());
15:console.writeline ("serviceprovider.getservice<igux> (): {0}", serviceprovider.getservice<igux> ());
16:}
17:}
Next we call the extension method of the Servicecollection object Buildserviceprovider get the corresponding ServiceProvider object, and then call its extension method getservice<t> Obtain a service instance object for four interfaces and output the type name to the console, respectively. After running the program, we will get the following output on the console, which confirms that ServiceProvider provides us with the service instance we expect.
1:serviceprovider.getservice<ifoo> (): Foo
2:serviceprovider.getservice<ibar> (): Bar
3:serviceprovider.getservice<ibaz> (): Baz
4:serviceprovider.getservice<igux> (): Gux
provides a collection of service instances
If we specify the service type as IENUMERABLE<T> when calling the GetService method, the returned result will be a collection object. In addition, we can directly call IServiceProvider the following two extension methods getserveces achieve the same purpose. In this case, ServiceProvider will take advantage of all servicedescriptor that match the specified service type to provide a specific service instance, which will act as an element of the returned collection object. If all servicedescriptor do not match the specified service type, then the final return is an empty collection object.
1:public Static Class Serviceproviderextensions
2: {
3:public static ienumerable<t> getservices<t> (this iserviceprovider provider);
4:public static ienumerable<object> GetServices (this IServiceProvider provider, Type servicetype);
5:}
It is worth mentioning that if ServiceProvider's servicecollection contains multiple servicedescriptor with the same service type (corresponding to the servicetype attribute), When we call the GetService method to get a single service instance, only the last Servicedescriptor is valid, and for the other servicedescriptor, they only make sense in the scenario where the collection of services is obtained.
We demonstrate how to use ServiceProvider to get a collection that contains multiple service instances through a simple example. We have defined the following service interface Ifoobar in a console application, with two service types Foo and bar implementing this interface. In the main method as the entrance to the program, we add the pin to the created Servicecollection object for the two servicedescriptor of the service type Foo and bar. These two Servicedescriptor objects have a servicetype property of Ifoobar.
1:class Program
2: {
3:static void Main (string[] args)
4: {
5:iservicecollection servicecollection = new Servicecollection ()
6:. Addsingleton<ifoobar, Foo> ()
7:. Addsingleton<ifoobar, bar> ();
9:iserviceprovider serviceprovider = Servicecollection.buildserviceprovider ();
10:console.writeline ("serviceprovider.getservice<ifoobar> (): {0}", SERVICEPROVIDER.GETSERVICE<IFOOBAR&G t; ());
12:ienumerable<ifoobar> services = serviceprovider.getservices<ifoobar> ();
13:int index = 1;
14:console.writeline ("serviceprovider.getservices<ifoobar> ():");
15:foreach (Ifoobar foobar in Services)
16: {
17:console.writeline ("{0}: {1}", index++, Foobar);
18:}
19:}
20:}
22:public interface Ifoobar {}
23:public class Foo:ifoobar {}
24:public class Bar:ifoobar {}
After calling the extension method of the Servicecollection object Buildserviceprovider get the corresponding ServiceProvider object, we call its getservice<t> method to determine whether the true type of service instance received for the service interface Ifoobar is foo or bar. Next we call the ServiceProvider extension method getservices<t> get a set of service instances for the service interface Ifoobar and print their true types on the console. After the program is run, the following output will be generated on the console.
1:serviceprovider.getservice<ifoobar> (): Bar
2:serviceprovider.getservices<ifoobar> ():
3:1: Foo
4:2: Bar
get serviceprovider Self object
For ServiceProvider's service delivery mechanism, there is a small detail that deserves our attention, That is, when we call the GetService or Getrequiredservice method, if the service type is set to IServiceProvider, then the resulting object is actually the object of serviceprovider itself. Similarly, calling the GetServices method returns a collection that contains itself. The code snippet shown below embodies this feature of serviceprovider.
1:class Program
2: {
3:static void Main (string[] args)
4: {
5:iserviceprovider serviceprovider = new Servicecollection (). Buildserviceprovider ();
6:debug.assert (object. ReferenceEquals (ServiceProvider, serviceprovider.getservice<iserviceprovider> ()));
7:debug.assert (object. ReferenceEquals (ServiceProvider, serviceprovider.getservices<iserviceprovider> (). Single ()));
8:}
9:}
Dependency injection in ASP. (1): Control inversion (IoC)
Dependency Injection (2): Dependency Injection (DI) in ASP.
Dependency injection in ASP. NET Core (3): Service Registration and extraction
Dependency injection in ASP. 4: Selection of constructors and life cycle management
Dependency injection in ASP. (5): Serviceprvider realizes "overall design"
Dependency injection in ASP. (5): Serviceprvider realization of "interpreting Servicecallsite"
Dependency injection in ASP. Serviceprvider (5): The implementation of the secret "to fill out the missing details"
Registration and provision of services