Controller 클래스 이외의 클래스를 사용한 종속성 주입


84

이 시점에서 어떤 경우에는 내 자신의 ResolverServices 클래스를 구축하는 등 컨트롤러에 항목을 쉽게 주입하고 있습니다. 인생은 좋다 .

어떻게해야할지 알 수없는 것은 프레임 워크가 비 컨트롤러 클래스에 자동으로 주입되도록하는 것입니다. 작동하는 것은 프레임 워크 IOptions가 내 프로젝트의 구성 인 내 컨트롤러에 자동으로 주입되도록 하는 것입니다.

public class MessageCenterController : Controller
{
    private readonly MyOptions _options;

    public MessageCenterController(IOptions<MyOptions> options)
    {
        _options = options.Value;
    }
}

제 수업에도 똑같이 할 수 있을지 생각하고 있어요. 다음과 같이 컨트롤러를 모방 할 때 가깝다고 가정합니다.

public class MyHelper
{
    private readonly ProfileOptions _options;

    public MyHelper(IOptions<ProfileOptions> options)
    {
        _options = options.Value;
    }

    public bool CheckIt()
    {
        return _options.SomeBoolValue;
    }
}

내가 실패하는 곳은 다음과 같이 부를 때라고 생각합니다.

public void DoSomething()
{
    var helper = new MyHelper(??????);

    if (helper.CheckIt())
    {
        // Do Something
    }
}

내가 이것을 추적하는 문제는 실제로 DI에 대해 말하는 모든 것이 컨트롤러 수준에서 말하는 것입니다. 나는 그것이 일어나는 곳에서 사냥을 시도했다.Controller 개체 소스 코드 거기에서는 다소 미쳤습니다.

IOptions의 인스턴스를 수동으로 만들고 MyHelper생성자에 전달할 수 있다는 것을 알고 있지만 Controllers.


8
의존성 주입을 사용할 때 new. 해결해야하는 개체에 대해서는 절대 사용 안 함
Tseng

1
MyHelper 인스턴스를 만들려고 할 때 new를 호출하지 않습니까? (1) 너무 쉽게 들리 네요. (2) 구문 오류입니다. :-)
Robert Paulsen

2
예, 이것이 종속성 주입의 전체 지점입니다 (특히이 인스턴스화를 관리하고 수행하는 제어 컨테이너의 반전을 사용하는 경우). 인스턴스화를 서비스 / 클래스 외부에서 ioc 컨테이너가 내부적으로 수행하는 지점까지 푸시합니다. 생성자를 통해 주입 할 수없는 경우 팩토리를 생성하고 팩토리의 인터페이스를 서비스에 전달합니다. 구현은 컨테이너를 사용하여 해결합니다. ASP.NET Core 케이스 IServiceProvider에서 공장에 주입 하고 호출IMyHelper helper = services.RequestService<IMyHelper>()
Tseng

답변:


42

다음은 MVC 컨트롤러를 포함하지 않고 DI를 사용하는 작업 예입니다. 이것이 제가 그 과정을 이해하기 위해해야하는 일입니다. 그래서 아마도 다른 사람에게 도움이 될 것입니다.

ShoppingCart 개체는 DI를 통해 INotifier의 인스턴스 (고객에게 주문을 알립니다)를 가져옵니다.

using Microsoft.Extensions.DependencyInjection;
using System;

namespace DiSample
{
    // STEP 1: Define an interface.
    /// <summary>
    /// Defines how a user is notified. 
    /// </summary>
    public interface INotifier
    {
        void Send(string from, string to, string subject, string body);
    }

    // STEP 2: Implement the interface
    /// <summary>
    /// Implementation of INotifier that notifies users by email.
    /// </summary>
    public class EmailNotifier : INotifier
    {
        public void Send(string from, string to, string subject, string body)
        {
            // TODO: Connect to something that will send an email.
        }
    }

    // STEP 3: Create a class that requires an implementation of the interface.
    public class ShoppingCart
    {
        INotifier _notifier;

        public ShoppingCart(INotifier notifier)
        {
            _notifier = notifier;
        }

        public void PlaceOrder(string customerEmail, string orderInfo)
        {
            _notifier.Send("admin@store.com", customerEmail, $"Order Placed", $"Thank you for your order of {orderInfo}");
        }

    }

    public class Program
    {
        // STEP 4: Create console app to setup DI
        static void Main(string[] args)
        {
            // create service collection
            var serviceCollection = new ServiceCollection();

            // ConfigureServices(serviceCollection)
            serviceCollection.AddTransient<INotifier, EmailNotifier>();

            // create service provider
            var serviceProvider = serviceCollection.BuildServiceProvider();

            // This is where DI magic happens:
            var myCart = ActivatorUtilities.CreateInstance<ShoppingCart>(serviceProvider);

            myCart.PlaceOrder("customer@home.com", "2 Widgets");

            System.Console.Write("Press any key to end.");
            System.Console.ReadLine();
        }
    }
}

17
객체에 ShoppingCart액세스하지 않는 다른 클래스 나 메서드에서 인스턴스화하려면 어떻게해야 serviceProvider합니까?
Bagherani

1
여기에 같은 문제가 발생
케이시

4
감사합니다. ActivatorUtilities.CreateInstance로 이동하려면 너무 광범위하게 검색해야했습니다.
H. Tugkan Kibar

1
serviceProvider가 없으면 어떻게합니까 ?? !!
HelloWorld

1
감사합니다! Microsoft.AspNetCore.TestHost, TestServer 생성 및 ActivatorUtilities.CreateInstance <MyCustomerController> (_ server.Host.Services);
Tolga

35

컨트롤러에서 차례로 사용되는를 MyHelper사용 한다고 가정 해 보겠습니다 MyService.

이 상황을 해결하는 방법은 다음과 같습니다.

  • MyServiceMyHelper에 모두 등록하십시오Startup.ConfigureServices .

    services.AddTransient<MyService>();
    services.AddTransient<MyHelper>();
    
  • 컨트롤러는 MyService생성자에서 의 인스턴스를받습니다 .

    public HomeController(MyService service) { ... }
    
  • MyService생성자는 차례로의 인스턴스를받습니다 MyHelper.

    public MyService(MyHelper helper) { ... }
    

DI 프레임 워크는 문제없이 전체 개체 그래프를 해결할 수 있습니다. 객체가 해결 될 때마다 생성되는 새 인스턴스가 걱정된다면 싱글 톤 또는 요청 수명과 같은 다양한 수명 및 등록 옵션 에 대해 읽을 수 있습니다 .

서비스 로케이터 anti-pattern 에서 끝날 수 있으므로 일부 서비스의 인스턴스를 수동으로 만들어야한다고 생각할 때 정말 의심 스러워야 합니다. DI 컨테이너에 객체를 생성하는 것이 좋습니다. 실제로 그러한 상황에 처해 있다면 (추상 팩토리를 생성한다고 가정 해 보겠습니다) IServiceProvider직접 사용할 수 있습니다 ( IServiceProvider생성자에서를 요청 하거나 httpContext에 노출 된 것을 사용하십시오). ).

var foo = serviceProvider.GetRequiredService<MyHelper>();

ASP.Net 5 DI 프레임 워크와 일반적인 종속성 주입에 대한 특정 문서를 읽는 것이 좋습니다 .


내가 가진 문제는 데이터베이스와 상호 작용하는 백그라운드 서비스를 사용하므로 dbcontext로 범위가 지정된 수명이 작동하지 않는다는 것입니다. EF 코어 및 백그라운드 서비스에서 DI를 어떻게 적절하게 사용합니까?

6

불행히도 직접적인 방법은 없습니다. 내가 작동하도록 관리하는 유일한 방법은 정적 클래스를 만들고 아래의 다른 모든 곳에서 사용하는 것입니다.

public static class SiteUtils
{

 public static string AppName { get; set; }

    public static string strConnection { get; set; }

}

그런 다음 시작 클래스에서 다음과 같이 작성하십시오.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    //normal as detauls , removed for space 
    // set my variables all over the site

    SiteUtils.strConnection = Configuration.GetConnectionString("DefaultConnection");
    SiteUtils.AppName = Configuration.GetValue<string>("AppName");
}

이것은 나쁜 패턴이지만 응용 프로그램의 전체 수명주기 동안 유지되며 컨트롤러 외부에서 사용하는 더 좋은 방법을 찾을 수 없습니다.


4

여기에있는 현재 .NET Core 2.2 DI 문서를 기반으로 OP의 질문에 직접 답할 수있는 더 완전한 예제가 있습니다 . 이 답변을 추가하면 .NET Core DI를 처음 사용하는 사람에게 도움이 될 수 있으며이 질문이 Google의 상위 검색 결과이기 때문입니다.

먼저 MyHelper에 대한 인터페이스를 추가하십시오.

public interface IMyHelper
{
    bool CheckIt();
}

둘째, MyHelper 클래스를 업데이트하여 인터페이스를 구현합니다 (Visual Studio에서 ctrl-.을 눌러 인터페이스를 구현).

public class MyHelper : IMyHelper
{
    private readonly ProfileOptions _options;

    public MyHelper(IOptions<ProfileOptions> options)
    {
        _options = options.Value;
    {

    public bool CheckIt()
    {
        return _options.SomeBoolValue;
    }
}

셋째, 인터페이스를 DI 서비스 컨테이너에 프레임 워크 제공 서비스로 등록합니다. Startup.cs의 ConfigureServices 메서드에서 구체적인 유형 MyHelper로 IMyHelper 서비스를 등록하면됩니다.

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<IMyHelper, MyHelper>();
    ...
}

넷째, 서비스 인스턴스를 참조하는 개인 변수를 만듭니다. 생성자 주입을 통해 생성자에 서비스를 인수로 전달한 다음 서비스 인스턴스로 변수를 초기화합니다. 개인 변수를 통해 사용자 정의 클래스의이 인스턴스에 대한 속성 또는 호출 메서드를 참조하십시오.

public class MessageCenterController : Controller
{
    private readonly MyOptions _options;
    private readonly IMyHelper _myHelper;

    public MessageCenterController(
        IOptions<MyOptions> options,
        IMyHelper myHelper
    )
    {
        _options = options.value;
        _myHelper = myHelper;
    }

    public void DoSomething()
    {
        if (_myHelper.CheckIt())
        {
            // Do Something
        }
    }
}

0

Activator.CreateInstance ()를 사용할 수 있습니다. 여기에 래퍼 함수가 있습니다. 이것을 사용하는 방법은 다음과 같습니다.

var determinedProgrammatically = "My.NameSpace.DemoClass1"; // implements IDemo interface
var obj = CreateInstance<My.NameSpace.IDemo, string>(determinedProgrammatically, "This goes into the parameter of the constructor.", "Omit this parameter if your class lives in the current assembly");

이제 프로그래밍 방식으로 결정된 유형에서 인스턴스화되는 obj 인스턴스가 있습니다. 이 obj는 비 컨트롤러 클래스에 주입 될 수 있습니다.

public TInterface CreateInstance<TInterface, TParameter>(string typeName, TParameter constructorParam, string dllName = null)
{
    var type = dllName == null ? System.Type.GetType(typeName) :
            System.AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName.StartsWith(dllName, System.StringComparison.OrdinalIgnoreCase)).GetType(typeName);
    return (TInterface)System.Activator.CreateInstance(type, constructorParam);

}

추신 : System.AppDomain.CurrentDomain.GetAssemblies ()를 반복하여 클래스를 수용하는 어셈블리의 이름을 확인할 수 있습니다. 이 이름은 랩퍼 함수의 세 번째 매개 변수에 사용됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.