簡體   English   中英

使用Unity的ASP.NET MVC中的基本控制器構造函數注入

[英]Base controller constructor injection in ASP.NET MVC with Unity

我的MVC 5項目中有一個基本控制器,它實現了一些共享功能。 此功能需要一些依賴項。 我正在使用Unity 3將這些實現注入到我的控制器中,這種模式運行良好,直到我將控制器切換為從該基本控制器繼承。 現在我遇到了以下問題:

public class BaseController : Controller
{
    private readonly IService _service;

    public BaseController(IService service)
    {
        _service = service;
    }
}

public class ChildController : BaseController
{
    private readonly IDifferentService _differentService;

    public ChildController(IDifferentService differentService)
    {
        _differentService = differentService;
    }
}

可以理解的是,拋出'BaseController' does not contain a constructor that takes 0 arguments的錯誤'BaseController' does not contain a constructor that takes 0 arguments Unity沒有解析BaseController的構造,因此它無法將依賴項注入其中。 我看到了解決這個問題的兩種明顯方法:

1.)顯式調用BaseController ctor並讓每個ChildController ctor注入BaseController的依賴項

public class ChildController : BaseController
{
    private readonly IDifferentService _differentService;

    public ChildController(IDifferentService differentService,
                           IService baseService)
        : base(baseService)
    {
        _differentService = differentService;
    }
}

我不喜歡這種方法有幾個原因:一,因為ChildControllers沒有使用額外的依賴項(因此它會導致子控制器中的構造函數膨脹),更重要的是,如果我更改了構造函數簽名基本控制器,我必須更改每個子控制器的構造函數簽名。

2.)通過屬性注入實現BaseController的依賴項

public class BaseController : Controller
{
    [Dependency]
    public IService Service { get; set; }

    public BaseController() { }
}

我更喜歡這種方法 - 我沒有使用BaseController的構造函數代碼中的任何依賴項 - 但它使代碼的依賴注入策略不一致,這也不理想。

可能有一種更好的方法,它涉及某種類型的BaseController依賴解析函數,該函數調用Unity容器來整理ctor的方法簽名,但在我開始編寫任何涉及的內容之前,我想知道是否有人之前已經解決了這個問題? 我發現網上有一些解決方案,但它們是我不想使用的服務定位器等解決方法。

謝謝!

您必須要了解的第一件事是您沒有實例化基本控制器。 您正在實例化子控制器,它繼承了基本控制器的接口和功能。 這是一個重要的區別。 當你說“ChildControllers沒有使用其他依賴項”時,你就完全錯了。 由於ChildController IS的BaseController為好。 沒有創建兩個不同的類。 只有一個實現這兩種功能的類。

因此,由於ChildController 是一個 BaseController,因此在調用基類構造函數的子控制器構造函數中傳遞參數沒有任何錯誤或奇怪。 這是它應該做的方式。

如果更改基類,則可能必須更改子類。 無法使用構造函數注入來注入未包含在子類中的基類依賴項。

我不建議使用屬性注入,因為這意味着可以在沒有正確初始化的情況下創建對象,並且必須記住正確配置它們。

順便說一句,正確的術語是Subclass和Superclass。 “child”是子類,父類是“超類”。

使用ASP.Net 5,它內置DI

public class BaseController : Controller
{
    protected ISomeType SomeMember { get; set; }

    public BaseController(IServiceProvider serviceProvider)
    {
        //Init all properties you need
        SomeMember = (SomeMember)serviceProvider.GetService(typeof(ISomeMember));
    }
}

public class MyController : BaseController  
{
public MyController(/*Any other injections goes here*/, 
                      IServiceProvider serviceProvider) : 
 base(serviceProvider)
{}
}

UPDATE

Microsoft.Extensions.DependencyInjection中還有一個擴展方法,可以縮短它

SomeMember = serviceProvider.GetRequiredService<ISomeMember>();

我采取了一些不同的方法(但對我來說,相當明顯,可能很常見)。 它適用於我,但可能存在我不知道的陷阱。

我在我的BaseController類中創建了大多數控制器使用的公共服務屬性。 它們在需要/引用時被實例化。

如果某個特定控制器需要的服務使用較少,我會照常將其注入該控制器的構造函數中。

using JIS.Context;
using JIS.Managers;
using JIS.Models;
using JIS.Services;
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;

namespace JIS.Controllers
{
    public class BaseController : Controller
    {
        public BaseController()
        {
            ViewBag.User = UserManager?.User;
        }

        private IUserManager userManager;
        public IUserManager UserManager
        {
            get
            {
                if (userManager == null)
                {
                    userManager = DependencyResolver.Current.GetService<IUserManager>();
                }
                return userManager;
            }
            set
            {
                userManager = value;
            }
        }

        private ILoggingService loggingService;
        public ILoggingService LoggingService
        {
            get
            {
                if (loggingService == null)
                {
                    loggingService = DependencyResolver.Current.GetService<ILoggingService>();
                }
                return loggingService;
            }
            set { loggingService = value; }
        }

        private IUserDirectory userDirectory;
        public IUserDirectory UserDirectory
        {
            get
            {
                if (userDirectory == null)
                {
                    userDirectory = DependencyResolver.Current.GetService<IUserDirectory>();
                }
                return userDirectory;
            }
            set { userDirectory = value; }
        }

        private ApplicationDbContext appDb;
        public ApplicationDbContext AppDb
        {
            get
            {
                if (appDb == null)
                {
                    appDb = new ApplicationDbContext();
                }
                return appDb;
            }
            set
            {
                appDb = value;
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing && appDb != null)
            {
                appDb.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}

在我的控制器中,我只是從這個BaseController繼承:

public class UsersController : BaseController
{
    // and any other services I need are here:
    private readonly IAnotherService svc;
    public UsersController(IAnotherService svc)
    {
        this.svc = svc;
    }
...
}

通過這種方式,可以在需要時動態生成常用服務,並且可以在沒有大量樣板的情況下使用我的控制器。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM