Provides service registration description to simplify service registration and description

Source: Internet
Author: User

Provides service registration description to simplify service registration and description

Asp.net core provides support for dependency injection. You can register the service ing relationships required by the system in the Startup. ConfigureServices method, as shown in figureServices. TryAddScoped <TInterface, IBD> (),In this way, you can complete a service registration, and obtain the instance of the IBD in the Code by injecting it. If many services are required in the system, it is obviously not a very good method to register one by one. How can we simplify the registration process? We can develop a rule, such as adding feature description information to the service definition to identify the current service to be registered, and then obtain the information through reflection resolution, and complete service registration. You can also write the service registration information to a file to resolve the file to complete the same process.

First, we define a class that can describe service registration information. The definition is as follows:

Public class ServiceRegisteDescriptor {public Type ServiceType {get; set;} // service Type, which can be an interface or public ServiceLifetime LifeTime {get; set ;} // The Service Life Cycle Type public bool AllowMultipleImp {get; set;} // whether multiple public Type Imp {get; set;} can be implemented on the server ;} // specify the specific public Type [] GenericParameterTypes {get; set;} // if the service is a generic Type, you can specify the specific Type of the registered generic parameter}

The description information class is defined. Next we need to provide a method to obtain the ServiceRegisteDescriptor set. Here we first define an IServiceRegisteDescriptorCollectionProvider interface, which is defined as follows:

public interface IServiceRegisteDescriptorCollectionProvider{      ServiceRegisteDescriptorCollection ServiceRegisteDescriptors { get; }}

This interface contains a ServiceRegisteDescriptors attribute, which is a ServiceRegisteDescriptorCollection type. From this name, we can see that ServiceRegisteDescriptorCollection represents the ServiceRegisteDescriptor set, which is defined as follows:

public class ServiceRegisteDescriptorCollection{        public ServiceRegisteDescriptorCollection(IReadOnlyList<ServiceRegisteDescriptor> items)        {            Items = items ?? throw new ArgumentNullException(nameof(items));        }        public IReadOnlyList<ServiceRegisteDescriptor> Items { get; private set; }}

After the interface is defined, how can we implement it? As we mentioned above, the service registration information source can be Assembly reflection information, file content, or even data in the database. In order to meet the diversified support of the information source and to facilitate the expansion, we Abstract An IServiceRegisteDescriptorProvider interface, which is defined as follows:

Public interface IServiceRegisteDescriptorProvider {// <summary> // the order number. We recommend that you set the order value to 1, the default Provider provided by the system is 0 /// </summary> int Order {get ;}/// <summary> /// obtain the Service Registration description from a specific target, put it in ServiceRegisteDescriptorProviderContext /// </summary> /// <param name = "context"> </param> void OnProvidersExecuting (ServiceRegisteDescriptorProviderContext context ); /// <summary> /// in this method, you can modify the collected ServiceRegisteDescriptor set, such as deleting, replace and so on /// </summary> /// <param name = "context"> </param> void OnProvidersExecuted (ServiceRegisteDescriptorProviderContext context );}

The function of this interface is to obtain information from a specific target and convert it to ServiceRegisteDescritor information, put it in a ServiceRegisteDescriptorProviderContext, and modify the result in OnProvidersExecuted. We will first implement a collection of information from the program. You can use reflection to retrieve information from a set of programs. we can define a feature to obtain a list of Type Definitions containing this feature during reflection, and then perform type analysis based on the data provided by the feature, finally, a service ing list is obtained.

After the implementation method is completed, the following is the specific implementation. Then, define a feature as follows:

  [AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface,AllowMultiple =true,Inherited =false)]    public class ServiceRegisteDescriptorAttribute:Attribute    {        public ServiceRegisteDescriptorAttribute(ServiceLifetime lifetime)        {            LifeTime = lifetime;        }        public ServiceLifetime LifeTime { get; }        public bool AllowMultipleImp { get; set; }        public Type Imp { get; set; }        public Type GenericType { get; set; }    }

The definition of this feature is similar to that of ServiceRegisteDescriptor. The only property that does not contain the ServiceType attribute has the same meaning as that of ServiceRegisteDescriptor. This feature can be applied to classes and interfaces. With the feature definition, we can add this feature to the type definition to be registered, for example:

  [ServiceRegisteDescriptor(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton)]    public interface IServiceTest    {        void TestM();    }

This indicates that IServiceTest is a service to be registered. We obtain a class that implements this interface from the set of programs included in the current program. We recommend a service ing relationship. The following is the specific implementation of IServiceRegisteDescriptorProvider. Define a class defaservicserviceregisterdescriptorprovider to implement the IServiceRegisteDescriptorProvider interface, as follows:

public class DefaultServiceRegisterDescriptorProvider : IServiceRegisteDescriptorProvider{}

The core is the OnProvidersExecuting method. In this method, we implement the reflection parsing process we just mentioned. The specific implementation code is as follows:

Public void OnProvidersExecuting (ServiceRegisteDescriptorProviderContext context) {// gets all the Assembly sets of the current running program. Let's talk about AssemblyDisconvery to implement Assembly [] assemblys = AssemblyDiscovery. discovery (); // obtain the IEnumerable <Type> types = assemblys. selectiterator (m => m. getTypes (). where (t => t. getTypeInfo (). getCustomAttributes (). any (a =>. getType () = typeof (ServiceRegisteDescriptorA Ttribute )))). toList (); foreach (var type in types) {TypeInfo typeInfo = type. getTypeInfo (); // obtain the ServiceRegisteDescriptorAttribute attribute object of the current type. getTypeInfo (). getCustomAttributes (). firstOrDefault (m => m. getType () = typeof (ServiceRegisteDescriptorAttribute) as ServiceRegisteDescriptorAttribute; // if the current type is a generic type, GenericType if (typeInfo. isGenericTypeDefi Nition & attr. genericType = null) {throw new NotSupportedException (nameof (attr);} // The following procedure is to obtain the Type [] impTypes = null; if (typeInfo. isInterface | typeInfo. isAbstract) & attr. imp = null) {// obtain from the Assembly all if (typeInfo. isGenericTypeDefinition & typeInfo. isInterface) {impTypes = assemblys. selectiterator (m => m. getTypes (). where (t => t. getTypeInfo (). isClass &&! T. getTypeInfo (). isAbstract & t. getTypeInfo (). getInterfaces (). any (I => I. getTypeInfo (). isGenericType & I. getGenericTypeDefinition () = type ))). toArray ();} else {if (typeInfo. isInterface) {impTypes = assemblys. selectiterator (m => m. getTypes (). where (t => t. getTypeInfo (). isClass &&! T. getTypeInfo (). isAbstract & t. getTypeInfo (). getInterfaces (). any (I => I = type ))). toArray ();} else {impTypes = assemblys. selectiterator (m => m. getTypes (). where (t => t. getTypeInfo (). isClass &&! T. GetTypeInfo (). IsAbstract & t. GetTypeInfo (). IsSubclassOf (type). ToArray () ;}} else if (attr. Imp! = Null) {impTypes = new Type [1] {attr. imp };} else {impTypes = new Type [1] {type };}// create a Foreing relationship foreach (var imp in impTypes) {ServiceRegisteDescriptor d = new ServiceRegisteDescriptor {AllowMultipleImp = attr. allowMultipleImp, Imp = imp, ServiceType = type, LifeTime = attr. lifeTime}; // if it is a generic type, obtain all types as attr. A set of GenericType types, including all child types if (typeInfo. isGenericType) {d. genericParameterTypes = assembl Ys. selecttypes (m => m. GetTypes (). Where (t =>! T. getTypeInfo (). isAbstract & (t. getTypeInfo (). isSubclassOf (attr. genericType) | t = attr. genericType ))). toArray ();} context. results. add (d );}}}

  

  

Through the above method, we can obtain all the service definition information from the program set. An AssemblyDisconery is used to obtain the Assembly set. Its implementation is as follows:

  public class AssemblyDiscovery    {        public static Assembly[] Discovery()        {                       return DependencyContext.Default.RuntimeLibraries.SelectMany(l => l.GetDefaultAssemblyNames(DependencyContext.Default)).Select(Assembly.Load).ToArray();        }    }

AssemblyDiscovery uses DependencyContext to obtain the Assembly set.

The above shows how to obtain the Service Registration description from the Assembly. What if the source is a file? We can save the ServiceRegisteDescriptor configuration set in xml or json format, then read the content from the file and parse it in a specific format. Based on the above analysis process, we can finally get the ServiceRegisteDescriptorCollection we need, if the source database is the same, these Implementation codes will no longer be provided.

With IServiceRegisteDescriptorProvider, we can call the methods of all IServiceRegisteDescriptorProvider objects to complete information collection. The following describes how to implement IServiceRegisteDescriptorCollectionProvider. The Code is as follows:

Public class providers: providers {private readonly providers [] _ serviceRegisteDescriptorProviders; private ServiceRegisteDescriptorCollection _ collection; public libraries (IEnumerable <strong> attributes) {_ optional = serviceRegisteDescriptorProviders. orderBy (p => p. order ). toArray ();} private void UpdateCollection () {var context = new ServiceRegisteDescriptorProviderContext (); // loop all providers to complete parsing for (var I = 0; I <_ serviceRegisteDescriptorProviders. length; I ++) {_ serviceRegisteDescriptorProviders [I]. onProvidersExecuting (context);} // modify the ServiceRegisteDescriptor set for (var I = _ serviceRegisteDescriptorProviders. length-1; I> = 0; I --) {_ serviceRegisteDescriptorProviders [I]. onProvidersExecuted (context);} // generate collection _ collection = new ServiceRegisteDescriptorCollection (new ReadOnlyCollection <ServiceRegisteDescriptor> (context. results);} public ServiceRegisteDescriptorCollection ServiceRegisteDescriptors {get {if (_ collection = null) {UpdateCollection ();} return _ collection ;}}

After collecting ServiceRegisteDescriptorCollection, the following code is registered to ServiceCollection. We directly extend the IServiceCollection object. The Code is as follows:

Public static class ServiceScanServiceCollectionExtensions {public static IServiceCollection AddScanServices (this IServiceCollection services) {return AddScanServices (services, null);} public static IServiceCollection AddScanServices (this IServiceCollection services, Action <ServiceScanOptions> options) {// provides the configuration entry for customizing the IServiceRegisteDescriptorProvider extension ServiceScanOptions option = new ServiceScanOpt Ions (); option. DescriptorProviderTypes. Add (new DefaultServiceRegisterDescriptorProvider (); options ?. Invoke (option); // obtain the IServiceRegisteDescriptorCollectionProvider provider = new ServiceRegisteDescriptorCollectionProvider (option. descriptorProviderTypes); ServiceRegisteDescriptorCollection = provider. serviceRegisteDescriptors; foreach (var item in collection. items) {// register ServiceRegister. registe (services, item);} return services ;}

  

One problem is described first by ServiceScanOptions. This is to customize the class provided by the IServiceRegisteDescriptorProvider extension configuration. The Code is as follows:

  public class ServiceScanOptions    {        public IList<IServiceRegisteDescriptorProvider> DescriptorProviderTypes { get; } = new List<IServiceRegisteDescriptorProvider>();    }

The code above shows that we call IServiceCollectoin. when AddScanServices is used, you can use the Action <ServiceScanOptions> delegate to add custom extensions to ServiceScanOptions. in DescriptorProviderTypes, defaservicserviceregisterdescriptorprovider is provided by default.

The ServiceRegister. Registe method registers the service to IServiceCollectoin according to ServiceRegisteDescriptor. The implementation is as follows:

Public class ServiceRegister {private static void RegisteItem (IServiceCollection services, ServiceLifetime lifeTime, Type serviceType, Type impType, bool allowMultipleImp) {ServiceDescriptor serviceDescriptor = null; switch (lifeTime) {case later. singleton: serviceDescriptor = ServiceDescriptor. singleton (serviceType, impType); break; case ServiceLifetime. scoped: serviceDescriptor = Service Descriptor. scoped (serviceType, impType); break; case ServiceLifetime. transient: serviceDescriptor = ServiceDescriptor. transient (serviceType, impType); break;} if (allowMultipleImp) {services. tryAddEnumerable (serviceDescriptor);} else {services. tryAdd (serviceDescriptor) ;}} public static void Registe (IServiceCollection services, ServiceRegisteDescriptor descriptor) {// determine whether it is a generic interface if (descriptor. ServiceType. GetTypeInfo (). IsGenericType) {if (descriptor. GenericParameterTypes = null) {throw new NullReferenceException (nameof (descriptor. GenericParameterTypes);} if (! Descriptor. imp. getTypeInfo (). isGenericType) {throw new NotSupportedException (nameof (descriptor. imp);} // register the service foreach (var item in descriptor. genericParameterTypes) {RegisteItem (services, descriptor. lifeTime, descriptor. serviceType. makeGenericType (item), descriptor. imp. makeGenericType (item), descriptor. allowMultipleImp) ;}} else {RegisteItem (services, descriptor. lifeTime, descriptor. serviceType, descriptor. imp, descriptor. allowMultipleImp );}}}

Here we will focus on generic rules. For example, we have defined a generic interface as follows:

  [ServiceRegisteDescriptor(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Scoped,AllowMultipleImp =true,GenericType =typeof(EntityTest))]    public interface IGenericTest<T>    {            }

ServiceRegisteDescriptor indicates that the current service needs to be registered, and the generic parameter type is EntityTest, which means that an IGnericTest service must be registered for all EntityTest types (including all descendant types, the mappings are as follows:

IGenericTest <EntityTest> --> GenericTest <EntityTest>

IGenericTest <EntityTest1> --> GenericTest <EntityTest1> EntityTest1 is derived from EntityTest

  The following describes how to use it:

1. Introduce the DepencencyInjectionScan Library: Install-Package Microsoft. Extensions. DependencyInjection. Scan
2. Use IServiceCollection. AddScanServices () to complete service registration
3. Custom IServiceRegisteDescriptorProvider registration method: IServiceCollection. AddScanServices (options >{options. DescriptorProviderTypes. Add (object );});

 

The complete implementation code is also available on github at: https://github.com/dxp909/dependencyinjectionscan.git. you can download it.

  

 

  

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.