.NET 6 반사 등록 서비스 사용

에서 반사 등록 서비스를 사용합니다.그물


본고에서 나는 어떻게 반사와 범주형을 사용하여 서비스 등록을 간소화하는지 설명할 것이다.인터페이스 표시와 사용자 정의 속성 등록 서비스를 어떻게 사용하는지에 대해 우리는 두 가지 다른 방법을 소개할 것이다.
TLDR;다음은 코드의 작업 버전입니다. https://github.com/rogueco/RegisterServicesWithReflection은 방법마다 다른 지점에 있습니다.

왜?


만약 이 글을 읽고 있다면, 모든 서비스를 수동으로 입력할 필요가 없는 더 간단한 방법을 찾았다고 가정합니다.솔직히 말하자면, 당신이 사용하는 해결 방안의 크기는 중요하지 않다. 시간의 추이에 따라 당신과 당신의 팀은 이미 대량의 서비스를 추가했고, 이 모든 것은 수동으로 입력된 것이다.나는 모든 서비스를 등록할 수 있는 더 간단한 방법이 있을 것이라고 의심하는 것을 발견했다.이것이 바로 나로 하여금 반성의 길을 걷게 한 원인이다.나는 너의 책더미 속에서 네가 이렇게 하는 것에 익숙해질 것이라고 조금도 의심하지 않는다.
나는 우리가 보고 있다고 가정한다.NET6는 현재 최신 버전입니다.그물
// Program.cs

// Add Services
builder.Services.AddScoped<ICustomerService, CustomerService>();
builder.Services.AddScoped<IInventoryService, InventoryService>();
builder.Services.AddSingleton<IPaymentService, PaymentService>();
builder.Services.AddTransient<IOrderService, OrderService>();

// Additional Services
서비스의 등록을 정적 클래스로 이동하고 이 방법을 Program.cs 파일에 추가해서 이 문제를 해결할 수 있습니다.
using RegisterServicesWithReflection.Services.Implementations;
using RegisterServicesWithReflection.Services.Interfaces;

namespace RegisterServicesWithReflection.Extensions;

public static class ServiceExtensions
{
    public static void RegisterServices(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddScoped<ICustomerService, CustomerService>();
        services.AddScoped<IInventoryService, InventoryService>();
        services.AddSingleton<IPaymentService, PaymentService>();
        services.AddTransient<IOrderService, OrderService>();
    }
}
//Program.cs

// Add Services
builder.Services.RegisterServices(builder.Configuration);
그러나 이것은 우리가 직면한 문제를 해결할 수 없다. Program.cs을 더욱 깨끗하고 비대해 보이지 않게 하지만, 우리는 여전히 같은 문제가 있기 때문에 모든 서비스를 수동으로 입력해야 한다.
이 단계에서 우리는 일종의 반모드를 도입하여 이 문제를 해결할 수 있다.나는 마이크로소프트가 추천한 방법과 전체적으로 더욱 취할 수 있는 방법을 소개할 것이다.

사용자 정의 속성 방법


우리는 속성을 사용하여 형식 메타데이터에 접근한 다음에 그것들이 추가한 클래스를 등록할 것이다.아직 속성이 무엇인지 모르는 경우 Microsoft는 다음과 같이 속성을 정의합니다.

“...add keyword-like descriptive declarations, called attributes, to annotate programming elements such as types, fields, methods, and properties.”
“.NET uses attributes for a variety of reasons and to address a number of issues. Attributes describe how to serialize data, specify characteristics...”


그렇다면 수중에 있는 문제로 돌아가면 우리는 어떻게 모든 서비스를 등록하고 모든 서비스를 수동으로 입력할 필요가 없습니까?우리는 사용자 정의 속성을 정의한 다음에 이 속성을 사용하여 그들이 정의한 클래스/인터페이스의 메타데이터에 접근할 것입니다.
// Defining a set of attribute
public class ScopedRegistrationAttribute : Attribute { }

public class SingletonRegistrationAttribute : Attribute { }

public class TransientRegistrationAttribute : Attribute { }
정의된 속성은 클래스/인터페이스에 추가되어야 합니다. 반사로 클래스/인터페이스에 접근하기를 희망합니다.
[ScopedRegistration]
public class CustomerService 
{
    // Code...
}

[SingletonRegistration]
public class OrderService 
{
    // Code...
}

[TransientRegistration]
public class PaymentService 
{
    // Code...
}
현재, 우리는 우리가 방문하고자 하는 실현에 속성을 추가했고, 모든 관련 유형을 얻기 위해 반사 방법을 구축해야 한다.
우선, 우리가 포지셔닝하고자 하는 모든 속성을 정의할 것입니다. 이 속성을 필터로 사용해서 이 속성을 가진 모든 유형을 가져옵니다.

using RegisterServicesWithReflection.Services.Base;

namespace RegisterServicesWithReflection.Extensions;

public static class ServiceExtensions
{
    public static void RegisterServices(this IServiceCollection services, IConfiguration configuration)
    {
        // Define types that need matching
        Type scopedRegistration = typeof(ScopedRegistrationAttribute);
        Type singletonRegistration = typeof(SingletonRegistrationAttribute);
        Type transientRegistration = typeof(TransientRegistrationAttribute); 

    }
}
AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes())으로 전화를 걸면 프로젝트에 포함된 모든 유형으로 돌아갑니다.우리는 사용자 정의 속성의 유형만 추가하고 Interface 또는 Class만 확보해야 한다.마지막으로, 우리는 서비스 (인터페이스) 와 실현 (클래스) 을 포함하는 익명 대상을 만들어야 한다.

using RegisterServicesWithReflection.Services.Base;

namespace RegisterServicesWithReflection.Extensions;

public static class ServiceExtensions
{
    public static void RegisterServices(this IServiceCollection services, IConfiguration configuration)
    {
        // Define types that need matching
        Type scopedRegistration = typeof(ScopedRegistrationAttribute);
        Type singletonRegistration = typeof(SingletonRegistrationAttribute);
        Type transientRegistration = typeof(TransientRegistrationAttribute); 

        var types = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(s => s.GetTypes())
            .Where(p =>  p.IsDefined(scopedRegistration, false) || p.IsDefined(transientRegistration, false) || p.IsDefined(singletonRegistration, false) && !p.IsInterface)
            .Select(s => new
            {
                Service = s.GetInterface($"I{s.Name}"),
                Implementation = s 
            })
            .Where(x => x.Service != null);
    }
}
모든 필터 형식을 얻은 후에 나머지는 교체 그룹을 만들고 정의된 속성에 따라 서비스를 등록하는 것입니다.사용자 정의 속성을 정의한 모든 서비스가 응용 프로그램에 등록됩니다.

using RegisterServicesWithReflection.Services.Base;

namespace RegisterServicesWithReflection.Extensions;

public static class ServiceExtensions
{
    public static void RegisterServices(this IServiceCollection services, IConfiguration configuration)
    {
        // Define types that need matching
        Type scopedRegistration = typeof(ScopedRegistrationAttribute);
        Type singletonRegistration = typeof(SingletonRegistrationAttribute);
        Type transientRegistration = typeof(TransientRegistrationAttribute); 

        var types = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(s => s.GetTypes())
            .Where(p =>  p.IsDefined(scopedRegistration, true) || p.IsDefined(transientRegistration, true) || p.IsDefined(singletonRegistration, true) && !p.IsInterface).Select(s => new
            {
                Service = s.GetInterface($"I{s.Name}"),
                Implementation = s 
            }).Where(x => x.Service != null);

        foreach (var type in types)
        {
            if (type.Implementation.IsDefined(scopedRegistration, false))
            {
                services.AddScoped(type.Service, type.Implementation);
            }

            if (type.Implementation.IsDefined(transientRegistration, false))
            {
                services.AddTransient(type.Service, type.Implementation);
            }

            if (type.Implementation.IsDefined(singletonRegistration, false))
            {
                services.AddSingleton(type.Service, type.Implementation);
            }
        }
    }
}
그리고 우리는 Startup.cs 또는 Program.cs 파일을 호출하기만 하면 된다
// Program.cs (.net6)
builder.Services.RegisterServices(builder.Configuration);
다음은 이 방법의 작업 버전 링크: https://github.com/rogueco/RegisterServicesWithReflection
서비스를 어떻게 등록하는지에 있어서 또 다른 방법은 인터페이스 표시를 사용하는 것이다.나는 마이크로소프트가 인터페이스 표시를 사용하는 것을 권장하지 않는다는 것을 다시 한 번 말씀드리고 싶을 뿐입니다. 그러나 대부분의 일과 마찬가지로, 마이크로소프트는 자신의 위치가 있고 매우 유용할 것이라고 믿습니다.주의해야 할 것은 이 두 가지 방법의 실현이 매우 비슷하다는 것이다.

인터페이스 태그


인터페이스 표시는 내가 이전에 일하면서 익숙한 것이다. 나는 관련되지 않은 여러 개의 데이터를 한데 봉합하여 표에 표시해야 한다.이 모델은 내가 이렇게 하는 것을 허락한다. 그것은 반모드로 여겨진다.이것은 아마도 가장 간단한 실현 모델 중의 하나일 것이다.
public interface IMarkerPattern { }

public class Inventory : IMarkerPatten 
{
    public int Id {get; set;}
    public string Title {get; set;}
    public string Description {get; set;}
    public decimal Price {get; set;}

    // .... 
}
그렇습니다. 이것이 바로 당신이 모델을 실현하는 방식입니다. 만약 당신이 이렇게 말할 수 있다면?빈 IMarkerPattern 인터페이스의 용도는 위에서 보듯이 반사와 범형을 사용하여 메타데이터에 접근하려는 시도가 그 모델의 강점이다.
우선, 역할 영역, 순간, 단일 예제를 한정해야 하는지 확인하기 위해 빈 인터페이스를 만들어야 한다.
public interface IScopedService { }
public interface ITransientService { }
public interface ISingletonService { }
만약 우리가 모든 생명 주기에 등록할 것이 있다면, 우리는 저장소 모드를 사용할 것이다.
// interfaces
public interface ICustomerService : IServiceScope { }

public interface IProductService : IServiceTransient { }

public interface IOrderService : IServiceSingleton { }
// classes
public class Customer : ICustomerService { }

public class Product : IProductService { }

public class Order : IOrderService { }
RegisterServices 방법에서 우리는 현재 반사를 이용하여 메타데이터를 얻어야 한다.간단하게 보기 위해서, 나는 이 방법의 각 부분을 상세하게 소개할 것이다.
// RegisterServices
    {
        // Define types that need matching
        Type scopedService = typeof(IScopedService);
        Type singletonService = typeof(ISingletonService);
        Type transientService = typeof(ITransientService); 

    // Rest of method
    }

현재 프로그램에 등록된 모든 종류를 가져와서, 위에서 정의한 모든 종류가 이 종류에 분배될 수 있도록 선별해야 합니다.이것은 우리에게 필요한 모든 인터페이스와 종류를 제공할 것이다.그리고 서비스 (인터페이스) 와 실현 (클래스) 을 포함하는 새로운 익명 대상을 만듭니다.
// Code excluded
        var types = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(s => s.GetTypes())
                .Where(p => scopedService.IsAssignableFrom(p) || transientService.IsAssignableFrom(p) || singletonService.IsAssignableFrom(p) && !p.IsInterface).Select(s => new
                {
                    Service = s.GetInterface($"I{s.Name}"),
                    Implementation = s 
                }).Where(x => x.Service != null);

// Rest of method
우리는 모든 유형을 교체하고 분배 가능한 유형에 따라 서비스를 등록해야 한다. 완성하는 방법은 다음과 같다.
public static class ServiceExtensions
{
    public static void RegisterServices(this IServiceCollection services, IConfiguration configuration)
    {
        // Define types that need matching
        Type scopedService = typeof(IScopedService);
        Type singletonService = typeof(ISingletonService);
        Type transientService = typeof(ITransientService); 

        var types = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(s => s.GetTypes())
                .Where(p => scopedService.IsAssignableFrom(p) || transientService.IsAssignableFrom(p) || singletonService.IsAssignableFrom(p) && !p.IsInterface).Select(s => new
                {
                    Service = s.GetInterface($"I{s.Name}"),
                    Implementation = s 
                }).Where(x => x.Service != null);

        foreach (var type in types)
        {
            if (scopedService.IsAssignableFrom(type.Service))
            {
                services.AddScoped(type.Service, type.Implementation);
      }

            if (transientService.IsAssignableFrom(type.Service))
            {
                services.AddTransient(type.Service, type.Implementation);
      }

            if (singletonService.IsAssignableFrom(type.Service))
            {
                services.AddSingleton(type.Service, type.Implementation);
      }
        }
    }
}
RegisterServices()에서 program.cs을 호출하면 I{Type}Service에 추가된 모든 서비스가 응용 프로그램에 등록됩니다.응용 프로그램은 모든 서비스를 수동으로 등록할 필요가 없이 이전처럼 작동할 것입니다.
다음은 이 버전 코드의 링크: https://github.com/rogueco/RegisterServicesWithReflection/tree/MarkerInterface
수동으로 입력할 필요 없이 몇 가지 다른 방식으로 당신의 서비스를 등록할 수 있습니다.
앞에서 설명한 바와 같이 Microsoft에서는 빈 인터페이스(https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/interface?redirectedfrom=MSDN)를 사용하지 말고 사용자 정의 속성을 사용하는 것을 권장합니다.
참조 자료:
Accessing Attributes by Using Reflection (C#)
Dependency injection in ASP.NET Core
Interface Design - Framework Design Guidelines
Extending Metadata Using Attributes

좋은 웹페이지 즐겨찾기