MVC - IoC Unity - 确保控制器具有无参数的公共构造函数

5

问题

我使用Unity作为IoC,一开始很好用,但是随着添加更多的功能,想要确定错误变得越来越困难,因为Unity提供的错误只是错误的症状,而不是实际错误:

"Message":"An error has occurred.","ExceptionMessage":"An error occurred
when trying to create a controller of type 'MyController'. Make sure that the
controller has a parameterless public
constructor.","ExceptionType":"System.InvalidOperationException"

背景

我有一个MVC Web Api控制器,它依赖于一个Manager实例(来自domain):

public class MyController : ApiController
{
    private IMyManager MyManager { get; set; }

    public MyController(IMyManager myManager)
    {
        this.MyManager = myManager;
    }

    ...
}

上述错误发生的原因是IMyManager的IoC映射失败。由于它失败了,我没有参数,这意味着MyController使用无参数构造函数调用,但由于它不存在(也不应该存在),我得到了上述错误。
我尝试过的方法: 所以,我得到的错误不是“真正”的错误。显而易见的是,确保每个新实现在IoC下注册,我检查过了,它们是注册的。我手动执行此操作以保持可管理性(多么讽刺!)。 我这样做:
container.RegisterType<IMyManager, MyManager>();

但这不是问题所在。

我做的一个修复是解决了循环依赖。我改变了所有涉及的构造函数和方法,使用属性值而不是实例。我检查了所有涉及的类,不再存在循环依赖。

然而,问题仍然存在。

问题

我该怎么办才能找到实际的问题?手动检查依赖关系太过繁琐,因为结构是一个复杂的深层依赖网。随着应用程序的成熟,情况会变得更加严重。

或者,如果Unity始终模糊这些消息(无法修复),是否有其他替代品提供有价值的错误信息?

更新

根据请求,以下是完整的错误消息(尽管我认为堆栈跟踪并不是很有用):

{"Message":"An error has occurred.","ExceptionMessage":"An error occurred when trying to create a controller of type 'MyController'. Make sure that the controller has a parameterless public constructor.","ExceptionType":"System.InvalidOperationException","StackTrace":"   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)
at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()","InnerException":{"Message":"An error has occurred.","ExceptionMessage":"Type 'MyProject.Web.Api.Controllers.MyController' does not have a default constructor","ExceptionType":"System.ArgumentException","StackTrace":"   at System.Linq.Expressions.Expression.New(Type type)
at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"}}

更新2

我深入挖掘并检查了所有依赖项(它们非常庞大),但是一切都已正确注册和加载。也没有任何循环依赖关系,因此据我所知,一切“应该”正常工作。既然它没有,我得出结论是发生了一些错误,但没有被抛出。


大多数情况下,Unity会在异常链中放置详细的异常,其中您有一个带有内部异常和内部异常的异常,最后一个是实际问题。在您的情况下它不是这样吗? - Wiktor Zychla
请看这个相关的问答:https://dev59.com/iGUo5IYBdhLWcg3wmQdN#15910758。只需将“Web API”替换为“MVC”,将“Simple Injector”替换为“Unity”,您就可以了解这里正在发生什么。简而言之,明确注册您的控制器。 - Steven
Unity不会隐藏任何错误,从我所看到的来看,你遇到的错误都是由Unity产生的错误。听起来你还没有将WebAPI连接到依赖注入中。如果你还没有配置它使用相同的容器,那么你必须进行配置。你的WebAPI控制器中是否有任何依赖注入工作,或者这是第一个? - Luke
请注意,您应该发布完整的异常信息+堆栈跟踪,因为这通常包含关键信息,告诉我们更多关于正在发生什么的情况。如果没有这些信息,我们只能猜测。 - Steven
4个回答

9
如果错误提示说您需要一个无参构造函数,那么我认为Unity没有为WebAPI注册,即使它已经为您的ASP.NET MVC应用程序注册过了。如果您的IoC工作正常,它不应该需要一个无参构造函数,因为它应该能够解析控制器构造函数中存在的依赖项。
WebAPI有自己的管道与ASP.NET MVC进行交互,例如,WebAPI可以通过OWIN托管,因此即使它们存在于同一项目中,也需要单独连接。
您应该将WebAPI连接到您的UnityConfig中的依赖关系解析器,这里有一个示例:http://www.devtrends.co.uk/blog/using-unity.mvc5-and-unity.webapi-together-in-a-project
using Microsoft.Practices.Unity;
using System.Web.Http;
using System.Web.Mvc;

namespace WebApplication1
{
    public static class UnityConfig
    {
        public static void RegisterComponents()
        {
            var container = new UnityContainer();

            // register all your components with the container here
            // it is NOT necessary to register your controllers

            // e.g. container.RegisterType<ITestService, TestService>();

            // Configures container for ASP.NET MVC
            DependencyResolver.SetResolver(new Unity.Mvc5.UnityDependencyResolver(container));

            // Configures container for WebAPI
            GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
        }
    }
}

在上面的示例中,有两行配置,但它们共享同一个容器,因此也共享相同的绑定 - 您不必定义两次。如果您有一天要将WebAPI项目与ASP.NET MVC分开,则必须拥有单独的容器和绑定。
就问题查找或Unity是否对您隐藏错误而言,在我的经验中,并非如此。您遇到的错误直接来自ASP.NET MVC。在没有IoC的正常使用中,需要一个无参数构造函数... 使用IoC控制器的情况下,它控制控制器的创建并因此解决依赖关系。

@downvoter,如果您能留下意见,那我将不胜感激,这样我就可以改进答案了 - 谢谢。 - Luke
我不是那个点踩者(我还没有这个特权),但这不是一个好答案,因为我说在添加新功能之前IoC确实运行良好。在新开发中出了些问题,而不是现有代码的问题。 - Spikee
@Spikee 请回答以下问题:你的项目中有多少个WebAPI控制器?其中有多少正在使用依赖注入?请详细说明如何将你的WebAPI控制器连接到Unity容器。然后我们才能判断这是否是一个好答案... - Luke
所有控制器都使用注入参数工作,实例被抛出并具有进一步的依赖关系,形成了一个太复杂以至于无法在此概括的结构。 - Spikee
1
@Luke 抱歉,我应该更清楚地表明我是在提到参考 ASP.NET MVC 代码。Web API 配置仍然需要。 - Neo
显示剩余3条评论

2

我找到了问题所在,原因是我正在重新制作现有代码,而这些代码在同一个解决方案中,意味着会有重复的文件名。

在这种情况下,我有一个接口与先前存在的接口完全相同。在代码的某个深处,我使用了错误的命名空间,导致IoC映射不正确。

因此,就像这样:

我有:

old.managers.IMyManager

并且

new.managers.IMyManager

我在映射过程中所做的是:

container.RegisterType<IMyManager, MyManager>();

在这种情况下,IMyManager是new.managers.IMyManager。其中一个依赖项的使用者期望old.managers命名空间中的IMyManager。将使用者更新为使用新的命名空间即可解决问题。

这没有意义,你在绑定中使用的接口的命名空间如何修复错误“确保控制器具有无参数的公共构造函数。”?你一定改变了配置。 - Luke
已经有一段时间了,但是:如果我的构造函数期望一个类型为 new.managers.IMyManager 的参数,而我的绑定是针对 old.managers.IMyManager 进行的,那么它永远不会到达构造函数,因为定义上存在显著差异。这很难在表面上注意到。未识别的参数意味着构造函数的签名没有匹配。这意味着它寻找默认的无参构造函数。这个构造函数是故意不存在的。 - Spikee
通常情况下,如果出现这种情况,IoC容器将会识别到无法绑定接口,并相应地生成特定的错误消息。当MVC框架无法创建控制器实例时,参数为空的构造函数错误消息是由框架本身产生的。 - Luke
拜托,这个问题自从十月份以来就没有人碰过了,昨天我回答后差不多同时被踩了。但是无论如何。 - Spikee

0

在你的 web.config 中不要忘记这个

<system.web>
    <compilation targetFramework="4.8" />
    <httpRuntime targetFramework="4.8" />
    <customErrors mode="Off" />
</system.web>

缺少 httpRuntime targetFramework="4.8" 部分将导致参数为空的公共构造函数错误


-5

我认为你应该为你的控制器添加另一个构造函数。

public MyController() {}

如果仍然出现错误,您应该检查在Global.asax中注册Application_Start的组件。在UnityConfig.cs中:
public static void RegisterComponents()
    {
        var container = new UnityContainer();

        container.RegisterType<IMyManager, MyManager>();

        MvcUnityContainer.Container = container;

        DependencyResolver.SetResolver(new UnityDependencyResolver(container));
    }

将控制器设置为默认值。

public class UnityControllerFactory : DefaultControllerFactory
{
    public override IController CreateController(RequestContext requestContext, string controllerName)
    {
        Type controllerType = null;
        if (TypeHelper.LooksLikeTypeName(controllerName))
        {
            controllerType = TypeHelper.GetType(controllerName);
        }

        if (controllerType == null)
        {
            controllerType = this.GetControllerType(requestContext, controllerName);
        }

        return controllerType != null ? this.GetControllerInstance(requestContext, controllerType) : null;
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                throw new ArgumentNullException("controllerType");

            if (!typeof(IController).IsAssignableFrom(controllerType))
                throw new ArgumentException(string.Format(
                    "Type requested is not a controller: {0}",
                    controllerType.Name),
                    "controllerType");

            return MvcUnityContainer.Container.Resolve(controllerType) as IController;
        }
        catch
        {
            return null;
        }
    }
}

public static class MvcUnityContainer
{
    public static IUnityContainer Container { get; set; }
}

在 Application_Start() 方法中(Global.asax.cs 和 Global.asax 文件)
UnityConfig.RegisterComponents();

3
这只是应对症状,而非解决问题。这样会导致空引用错误,因为你的依赖项未被注入。 - Spikee
在我的项目中,我有两个构造函数。 public MyController(IMyManager myManager) { this.MyManager = myManager; } 和 public MyController() {}。它工作得很好。 - Ken Nguyen
1
如果使用默认构造函数,您如何实例化依赖项? - Spikee
您的建议使IOC变得无用。 - birwin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接