Simple Injector无法在Web API控制器中注入依赖项。

47
我希望能够使用Simple Injector进行基本的构造函数依赖注入,但似乎无法解决Web API控制器的依赖关系。
  • 我有一个位于“API”文件夹中的API控制器,它在“Controllers”文件夹外。
  • 我还尝试将其放置在“Controllers”文件夹中,但似乎没有什么区别。我收到的堆栈跟踪与此问题中提出的相似。
  • 我正在使用“Simple Injector MVC Integration Quick Start”NuGet包(v. 2.1.0)的最新安装版。
  • 我有从文档中得到的基本SimpleInjectorWebApiDependencyResolver,它也与此处找到的相同。
  • 我正在使用Entity Framework,并查看了关于更改以正确加载上下文的讨论线程

这似乎不是问题,但我仍然收到以下错误:

Type 'MyProject.API.ArticleController' does not have a default constructor

System.ArgumentException 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)

如果有人能为我提供一些建议,告诉我是否应修改任何内容/调用顺序,那将不胜感激。

ArticleController(基本结构):

public class ArticleController : ApiController
{
    private readonly IArticleRepository articleRepository;
    private readonly IUserRepository userRepository;
    private readonly IReleaseRepository releaseRepository;

    public ArticleController(IArticleRepository articleRepository, IUserRepository userRepository, IReleaseRepository releaseRepository)
    {
        this.articleRepository = articleRepository;
        this.userRepository = userRepository;
        this.releaseRepository = releaseRepository;
    }

    // GET api/Article
    public IEnumerable<Article> GetArticles(){ // code }

    // GET api/Article/5
    public Article GetArticle(int id){ // code }

    // PUT api/Article/5
    public HttpResponseMessage PutArticle(int id, Article article){ // code }

    // POST api/Article
    public HttpResponseMessage PostArticle(ArticleModel article){ // code }

    // DELETE api/Article/5
    public HttpResponseMessage DeleteArticle(int id){ // code }
}

SimpleInjectorInitializer:

public static class SimpleInjectorInitializer
{
    public static void Initialize()
    {
        var container = new Container();
        InitializeContainer(container);
        container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
        container.RegisterMvcAttributeFilterProvider();
        container.Verify();

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

    private static void InitializeContainer(Container container)
    {
        container.Register<IArticleRepository, ArticleRepository>();
        container.Register<IUserRepository, UserRepository>();
        container.Register<IReleaseRepository, ReleaseRepository>();
    }
}

Global.asax.cs:

public class WebApiApplication : System.Web.HttpApplication
{
    private void ConfigureApi()
    {
        // Create the container as usual.
        var container = new Container();

        // Verify the container configuration
        // container.Verify();

        // Register the dependency resolver.
        GlobalConfiguration.Configuration.DependencyResolver =
                new SimpleInjectorWebApiDependencyResolver(container);
    }

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        ConfigureApi();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

你确定 ConfigureApi 方法真的被执行了吗?你设置了断点吗? - Steven
你为什么要为你的Web API配置创建一个单独的“容器”实例? - Steven
是的,我确定它已经运行了。我在设置DependencyResolver的那一行上设置了断点。如果你指的是Global.asax.cs文件内部,那么我是按照这个指南(http://www.asp.net/web-api/overview/extensibility/using-the-web-api-dependency-resolver)来操作的,看看是否能解决我的问题。然而,似乎并没有起作用。 - user1417835
2个回答

49
TLTR: 问题是由Web API隐式处理控制器类型解析引起的;显式注册Web API控制器,您将看到问题所在。
以下是发生的步骤:
  1. System.Web.Http.DefaultHttpControllerActivator 调用 SimpleInjectorWebApiDependencyResolver 并请求创建一个API控制器。
  2. SimpleInjectorWebApiDependencyResolver 将该调用转发到 SimpleInjector.Container 实例。
  3. 然而,该 Container 实例没有为该API控制器进行任何显式注册(因为您向解析器提供了一个空容器)。
  4. 由于没有显式注册,容器会尝试对该类型进行最后一分钟的注册。
  5. 但是,该控制器类型依赖于无法解析的接口,因为它们未在容器中注册(请记住,您的容器为空)。
  6. 尽管容器通常会抛出异常,但在这种情况下返回null,因为该类型是通过 IServiceProvider.GetService 方法请求的,并且该类型未被显式注册。
  7. SimpleInjectorWebApiDependencyResolverGetService 方法也将返回 null,因为根据定义它应该返回null;当不存在注册时,它应该返回null(目前就是这种情况)。
  8. 由于 DependencyResolver 返回了null,所以 DefaultHttpControllerActivator 将回退到其默认行为,这意味着它将自己创建该类型,但这需要控制器具有默认构造函数。
长话短说,问题是由Web API隐式处理控制器类型解析方式引起的。
因此,解决方案如下:
  1. 在您的Web应用程序中只有一个单一的Container。这可以防止所有种类的麻烦和配置的复杂性。
  2. 在容器中明确注册所有Web API控制器。明确注册控制器将确保Simple Injector在无法解析控制器时抛出异常。此外,这允许您调用container.Verify(),它将使应用程序在启动期间失败,当配置无效时(可验证的配置很重要)。这还允许您诊断配置,这使您对配置的正确性更加有信心。
我的建议是将MVC和Web API放在自己的项目中。这会让事情变得更加容易。
以下代码可以完成注册所有Web API控制器:
container.RegisterWebApiControllers(GlobalConfiguration.Configuration);

更新:

由于这个错误很常见,新版本的SimpleInjectorWebApiDependencyResolver类将永远不会返回null当请求控制器类型时。相反,它会抛出一个描述性错误。因此,只要使用官方的SimpleInjectorWebApiDependencyResolver,你就不应该再看到这个错误了。


这是一个扩展方法。您可以将其放置在任何静态类中,以便您可以从组合根内执行 container.RegisterApiControllers() - Steven
SimpleInitializer中设置GlobalConfiguration DependencyResolver是否合适?还是应该保留在Global.asax.cs中?文档(http://simpleinjector.codeplex.com/wikipage?title=Web%20API%20Integration)建议在Global.asax.cs的`Application_Start()`中完成容器创建和服务/资源注册,但`InitializeContainer()`中的注释似乎也表明注册应该在那里完成。 - user1417835
1
把这个放在 SimpleInjectorInitializer 中不会有任何问题。 - Steven
使用GlobalConfiguration.Configuration.Services.GetHttpControllerTypeResolver().GetControllerTypes()是否与您使用的LINQ代码搜索程序集相同? - diegohb
@diegohb:很好的发现。使用HttpControllerTypeResolve会更好。我更新了我的答案。 - Steven
显示剩余2条评论

1

以下设置适用于我:

1) 从https://www.nuget.org/packages/Unity.WebAPI/中包括Unity.WebAPI 2) 在UnityConfig中进行设置

public static class UnityConfig
    {
        public static void RegisterComponents()
        {
            var container = new UnityContainer();
            // **** Important note -----
            // register all your components with the container here
            // e.g. container.RegisterType<ITestService, TestService>();


            DependencyResolver.SetResolver(new Unity.Mvc5.UnityDependencyResolver(container));

            GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
        }
    }

3) 在 Global.asax 文件中

 public class MvcApplication : System.Web.HttpApplication
        {
            protected void Application_Start()
            {
                AreaRegistration.RegisterAllAreas();
                UnityConfig.RegisterComponents();
                GlobalConfiguration.Configure(WebApiConfig.Register);
                FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
                RouteConfig.RegisterRoutes(RouteTable.Routes);
                BundleConfig.RegisterBundles(BundleTable.Bundles);
            }
        }

开始使用Unity.WebAPI

要开始使用,只需在Global.asax.cs文件的Application_Start方法中添加对UnityConfig.RegisterComponents()的调用, 然后Web API框架将使用Unity.WebAPI DependencyResolver来解决您的组件。

例如:

public class WebApiApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
    AreaRegistration.RegisterAllAreas();
    UnityConfig.RegisterComponents();                           // <----- Add this line
    GlobalConfiguration.Configure(WebApiConfig.Register);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
  }           
}  

在UnityConfig类的RegisterComponents方法中添加您的Unity注册。所有实现IDisposable接口的组件都应该使用HierarchicalLifetimeManager进行注册,以确保它们在请求结束时被正确处理。

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