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

时间:2014-02-20 18:05:08

标签: c# asp.net-mvc dependency-injection inversion-of-control unity-container

我的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的错误。 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的方法签名,但在我开始编写任何涉及的内容之前,我想知道是否有人解决了这个问题以前的问题?我发现网上有一些解决方案,但它们是我不想使用的服务定位器等解决方法。

谢谢!

3 个答案:

答案 0 :(得分:29)

您必须要了解的第一件事是您没有实例化基本控制器。您正在实例化子控制器,它继承了基本控制器的接口和功能。这是一个重要的区别。当你说“ChildControllers没有使用其他依赖项”时,你就完全错了。因为ChildController IS 也是BaseController。没有创建两个不同的类。只有一个实现这两种功能的类。

因此,由于ChildController 是一个 BaseController,在调用基类构造函数的子控制器构造函数中传递参数没有任何错误或奇怪。这是它应该做的方式。

如果更改基类,则可能必须更改子类。无法使用构造函数注入来注入未包含在子类中的基类依赖项。

我不建议使用属性注入,因为这意味着您可以在没有正确初始化的情况下创建对象,并且必须记住正确配置它们。

顺便说一下,正确的术语是Subclass和Superclass。 “child”是子类,父类是“超类”。

答案 1 :(得分:12)

使用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)
{}
}

<强>更新

Microsoft.Extensions.DependencyInjection中还有一个扩展方法,可以缩短

SomeMember = serviceProvider.GetRequiredService<ISomeMember>();

答案 2 :(得分:0)

我采取了一些不同的方法(但对我而言,相当明显且可能很常见)。它适用于我,但可能存在我不知道的陷阱。

我在我的BaseController类中创建了大多数控制器使用的公共服务属性。它们在需要/引用时被实例化。

如果某个特定控制器需要的服务使用较少,我会照常将其注入该控制器的构造函数中。

$DHCP = (Get-Content -Path "\\termserv\d$\SERVER1\SERVER2\Scripts\morescripts\DHCPServers.txt")

foreach ($Server in $DHCP) {
    Get-Service -ComputerName $Server -DisplayName "DHCP Server" |
        ConvertTo-Html -Title "PScomputername" -Body "<H3> SERVER2 Uptime Report </H3> " -Property PSComputername >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
    Get-Service -ComputerName $Server -DisplayName "DHCP Server" |
        ConvertTo-Html -Property MachineName,Status,ServiceName | 
        foreach {
            if ($_ -like "*<td>Running</td>*") {
                $_ -replace "<tr>", "<tr bgcolor=green>"
            } elseif ($_ -like "*<td>Stopped</td>*") {
                $_ -replace "<tr>", "<tr bgcolor=red>"
            } else {
                $_
            }
        } >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
    Get-WmiObject win32_operatingsystem -ComputerName $Server | 
        Select PSComputername, @{n='BootTime';e={$_.ConvertToDateTime($_.LastBootupTime)}} |
        ConvertTo-Html -Property PSComputerName,BootTime >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
    ConvertTo-Html -Property PSComputerName,Installedon,Description,caption >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
}


$Print = (Get-Content -Path "\\termserv\d$\SERVER1\SERVER2\Scripts\morescripts\PrintServers.txt")

foreach ($Server in $Print) {
    Get-Service -ComputerName $Server -DisplayName "Print Spooler" |
        ConvertTo-Html -Property MachineName,Status,ServiceName | 
        foreach {
            if ($_ -like "*<td>Running</td>*") {
                $_ -replace "<tr>", "<tr bgcolor=green>"
            } elseif ($_ -like "*<td>Stopped</td>*") {
                $_ -replace "<tr>", "<tr bgcolor=red>"
            } else {
                $_
            }
        } >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
    Get-WmiObject win32_operatingsystem -ComputerName $Server | 
        Select PSComputername, @{n='BootTime';e={$_.ConvertToDateTime($_.LastBootupTime)}} | 
        ConvertTo-Html -Property PSComputerName,BootTime >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
    ConvertTo-Html -Property PSComputerName,Installedon,Description,caption >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
}

在我的控制器中,我只是从这个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);
        }
    }
}

通过这种方式,可以在需要时动态生成常用服务,并且可以在没有大量样板的情况下使用我的控制器。