Dependency Injection với các lớp khác với lớp Controller


84

Tại thời điểm này, tôi đang đưa mọi thứ vào Bộ điều khiển của mình một cách dễ dàng, trong một số trường hợp, xây dựng lớp ResolverServices của riêng tôi. Cuộc sống là tốt .

Những gì tôi không thể tìm ra cách làm là làm cho khung tự động đưa vào các lớp không phải bộ điều khiển. Những gì hoạt động là có khuôn khổ tự động đưa vào bộ điều khiển của tôi IOptions, đó là cấu hình hiệu quả cho dự án của tôi:

public class MessageCenterController : Controller
{
    private readonly MyOptions _options;

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

Tôi đang nghĩ liệu tôi có thể làm điều tương tự đối với các lớp học của riêng tôi. Tôi cho rằng tôi đang ở gần khi bắt chước bộ điều khiển, như thế này:

public class MyHelper
{
    private readonly ProfileOptions _options;

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

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

Tôi nghĩ tôi thất bại là do đâu khi tôi gọi nó như thế này:

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

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

Vấn đề mà tôi đang theo dõi điều này là thực tế mọi thứ nói về DI đều đang nói về nó ở cấp bộ điều khiển. Tôi đã thử tìm kiếm nơi nó xảy ra trong Controllermã nguồn đối tượng, nhưng nó trở nên điên rồ ở đó.

Tôi biết mình có thể tạo một thể hiện IOptions theo cách thủ công và chuyển nó cho phương thức MyHelperkhởi tạo, nhưng có vẻ như tôi có thể làm cho khung làm việc đó vì nó hoạt động cho Controllers.


8
Khi bạn sử dụng tiêm phụ thuộc, bạn không gọi new. Không bao giờ cho các đối tượng cần được giải quyết
Tseng

1
Khi tôi đang cố gắng tạo một phiên bản của MyHelper, tôi không gọi là new? (1) Nghe có vẻ quá dễ dàng, (2) Đó là một lỗi cú pháp. :-)
Robert Paulsen

2
Vâng, đó là toàn bộ điểm của việc tiêm phụ thuộc (đặc biệt nếu sử dụng đảo ngược vùng chứa điều khiển quản lý và thực hiện việc khởi tạo này). Để đẩy quá trình khởi tạo bên ngoài các dịch vụ / lớp của bạn lên đến điểm mà vùng chứa ioc thực hiện điều đó bên trong. Trong trường hợp bạn không thể đưa nó qua phương thức khởi tạo, bạn hãy tạo một nhà máy và chuyển giao diện của nhà máy cho dịch vụ của bạn. việc triển khai nó sử dụng vùng chứa để giải quyết nó, trong trường hợp ASP.NET Core tiêm IServiceProvidervào nhà máy của bạn và gọiIMyHelper helper = services.RequestService<IMyHelper>()
Tseng

Câu trả lời:


42

Dưới đây là một ví dụ hoạt động của việc sử dụng DI mà không có bất kỳ thứ gì liên quan đến Bộ điều khiển MVC. Đây là những gì tôi cần làm để hiểu quy trình, vì vậy có thể nó sẽ giúp ích cho người khác.

Đối tượng ShoppingCart, thông qua DI, một phiên bản của INotifier (thông báo cho khách hàng về đơn đặt hàng của họ.)

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
Điều gì sẽ xảy ra nếu tôi muốn khởi tạo ShoppingCarttrong một lớp hoặc phương thức khác mà chúng tôi không truy cập serviceProviderđối tượng?
Bagherani

1
gặp vấn đề tương tự ở đây
Casey

4
Cảm ơn bạn, tôi đã phải tìm kiếm quá rộng để đến được ActivatorUtilities.CreateInstance.
H. Tugkan Kibar

1
nếu tôi không có serviceProvider thì sao ?? !!
HelloWorld,

1
Cảm ơn bạn! Tôi đang sử dụng nó với Microsoft.AspNetCore.TestHost, tạo TestServer và gọi ActivatorUtilities.CreateInstance <MyCustomerController> (_server.Host.Services);
Tolga

35

Giả MyHelpersử được sử dụng bởi MyServicenó được sử dụng bởi bộ điều khiển của bạn.

Cách giải quyết tình trạng này là:

  • Đăng ký cả hai MyServiceMyHelpertrong Startup.ConfigureServices.

    services.AddTransient<MyService>();
    services.AddTransient<MyHelper>();
    
  • Bộ điều khiển nhận được một thể hiện MyServicetrong phương thức khởi tạo của nó.

    public HomeController(MyService service) { ... }
    
  • MyServiceđến lượt nó, hàm tạo sẽ nhận được một thể hiện của MyHelper.

    public MyService(MyHelper helper) { ... }
    

Khung DI sẽ có thể giải quyết toàn bộ đồ thị đối tượng mà không có vấn đề gì. Nếu bạn lo lắng về các phiên bản mới được tạo mỗi khi một đối tượng được giải quyết, bạn có thể đọc về các tùy chọn đăng ký và thời gian tồn tại khác nhau như singleton hoặc yêu cầu thời gian tồn tại.

Bạn sẽ thực sự nghi ngờ khi nghĩ rằng bạn phải tạo một bản sao của một số dịch vụ theo cách thủ công, vì bạn có thể rơi vào kiểu chống lại bộ định vị dịch vụ . Tốt hơn hãy để tạo các đối tượng cho DI Container. Nếu bạn thực sự thấy mình trong tình huống đó (giả sử bạn tạo một nhà máy trừu tượng), thì bạn có thể sử dụng IServiceProvidertrực tiếp (Yêu cầu một phương thức IServiceProviderkhởi tạo của bạn hoặc sử dụng phương thức hiển thị trong httpContext ).

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

Tôi khuyên bạn nên đọc tài liệu cụ thể về khung ASP.Net 5 DI và về tiêm phụ thuộc nói chung.


Vấn đề tôi gặp phải với điều này là tôi sử dụng các dịch vụ nền tương tác với cơ sở dữ liệu, vì vậy thời gian tồn tại trong phạm vi với dbcontext không hoạt động, phải không? Làm thế nào để bạn sử dụng DI đúng cách với các dịch vụ cốt lõi và nền tảng của EF?

6

Thật không may là không có cách trực tiếp. Cách duy nhất tôi quản lý để làm cho nó hoạt động là tạo một lớp tĩnh và sử dụng lớp đó ở mọi nơi khác như bên dưới:

public static class SiteUtils
{

 public static string AppName { get; set; }

    public static string strConnection { get; set; }

}

Sau đó, trong lớp khởi động của bạn, hãy điền vào như dưới đây:

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");
}

Mặc dù đây là mô hình xấu, vì điều này sẽ tồn tại trong toàn bộ vòng đời của ứng dụng và tôi không thể tìm ra cách tốt hơn để sử dụng nó bên ngoài bộ điều khiển.


4

Đây là một ví dụ đầy đủ hơn để trả lời trực tiếp câu hỏi của OP, dựa trên tài liệu .NET Core 2.2 DI hiện tại ở đây . Thêm câu trả lời này vì nó có thể giúp ích cho những người mới sử dụng .NET Core DI và vì câu hỏi này là kết quả tìm kiếm hàng đầu của Google.

Đầu tiên, thêm giao diện cho MyHelper:

public interface IMyHelper
{
    bool CheckIt();
}

Thứ hai, cập nhật lớp MyHelper để triển khai giao diện (trong Visual Studio, nhấn ctrl-. Để triển khai giao diện):

public class MyHelper : IMyHelper
{
    private readonly ProfileOptions _options;

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

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

Thứ ba, đăng ký giao diện như một dịch vụ do khuôn khổ cung cấp trong vùng chứa dịch vụ DI. Thực hiện việc này bằng cách đăng ký dịch vụ IMyHelper với loại cụ thể là MyHelper trong phương thức ConfigureServices trong Startup.cs.

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

Thứ tư, tạo một biến private để tham chiếu đến một phiên bản của dịch vụ. Truyền dịch vụ như một đối số trong phương thức khởi tạo (thông qua phương thức tiêm hàm tạo) sau đó khởi tạo biến với thể hiện dịch vụ. Tham chiếu bất kỳ thuộc tính hoặc phương thức gọi nào trên phiên bản này của lớp tùy chỉnh thông qua biến private.

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

Bạn có thể sử dụng Activator.CreateInstance (). Đây là một hàm wrapper cho nó. Cách bạn sử dụng như sau.

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");

Bây giờ bạn có một phiên bản của obj được khởi tạo từ loại được xác định theo chương trình. Đối tượng này có thể được đưa vào các lớp không phải bộ điều khiển.

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);

}

Tái bút: Bạn có thể lặp qua System.AppDomain.CurrentDomain.GetAssemblies () để xác định tên của assembly chứa lớp của bạn. Tên này được sử dụng trong tham số thứ 3 của hàm wrapper.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.