web-dev-qa-db-fra.com

Injecteur simple impossible d'injecter des dépendances dans les contrôleurs d'API Web

J'essaie de faire un constructeur de base DI avec Simple Injector, et il semble qu'il ne soit pas en mesure de résoudre les dépendances des contrôleurs d'API Web.

  • J'ai un contrôleur API dans un dossier "API", qui est en dehors du dossier "Contrôleurs".
  • J'ai également essayé de le placer dans le dossier "Contrôleurs", mais cela ne semble pas faire beaucoup de différence. La trace de pile que je reçois est similaire à celle présentée dans cette question .
  • J'utilise une nouvelle installation du package NuGet "Simple Injector MVC Integration Quick Start" (v. 2.1.0).
  • J'ai la base SimpleInjectorWebApiDependencyResolver de la documentation, qui est aussi la même que celle trouvée ici .
  • J'utilise Entity Framework et j'ai examiné le fil de discussion sur les modifications pour charger correctement le contexte.

Cela ne semble pas être un problème, mais je reçois toujours l'erreur suivante:

Le type 'MyProject.API.ArticleController' n'a pas de constructeur par défaut

System.ArgumentException à

System.Linq.Expressions.Expression.New (Type type) sur System.Web.Http.Internal.TypeActivator.Create [TBase] (Type instanceType) sur System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator (HttpRequestMessage , Func`1 & activator) sur System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create (demande HttpRequestMessage, HttpControllerDescriptor controllerDescriptor, Type controllerType)

Il serait apprécié que quelqu'un puisse m'offrir quelques suggestions, si quelque chose devrait être modifié par rapport à son état actuel/ordre d'appel.

ArticleController (structure de base):

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);
    }
}
39
user1417835

TLTR: le problème est dû à la façon implicite dont l'API Web gère la résolution des types de contrôleurs; enregistrez vos contrôleurs API Web de manière explicite et vous verrez où est le problème.

Voici pas à pas ce qui se passe sous les couvertures:

  1. Le System.Web.Http.DefaultHttpControllerActivator Appelle le SimpleInjectorWebApiDependencyResolver et demande la création d'un contrôleur API.
  2. SimpleInjectorWebApiDependencyResolver transfère cet appel à l'instance SimpleInjector.Container.
  3. Cependant, cette instance Container n'a pas d'enregistrement explicite pour ce contrôleur API (puisque vous avez fourni un conteneur vide au résolveur).
  4. Puisqu'il n'y a pas d'enregistrement explicite, le conteneur essaie de faire un enregistrement de dernière minute pour ce type.
  5. Ce type de contrôleur dépend cependant d'interfaces qui ne peuvent pas être résolues car elles ne sont pas enregistrées dans le conteneur (rappelez-vous, votre conteneur est vide).
  6. Bien que le conteneur lève normalement une exception, null est renvoyé dans ce cas, car le type est demandé via la méthode IServiceProvider.GetService Et le type n'a pas été enregistré explicitement.
  7. La méthode SimpleInjectorWebApiDependencyResolver de GetService renverra également null, car c'est par définition qu'elle doit retourner null; Il devrait retourner null quand aucun enregistrement n'existe (ce qui est actuellement le cas).
  8. Puisque DependencyResolver a retourné null, DefaultHttpControllerActivator reprendra son comportement par défaut, ce qui signifie créer ce type lui-même, mais cela nécessite que le contrôleur ait un constructeur par défaut.

Pour faire court, le problème est dû à la façon implicite dont l'API Web gère la résolution des types de contrôleurs.

La solution est donc de:

  1. N'ayez qu'un seul Container dans votre application Web. Cela évite toutes sortes de problèmes et de complications de votre configuration.
  2. Enregistrez tous les contrôleurs d'API Web explicitement dans le conteneur. L'enregistrement explicite des contrôleurs garantit que Simple Injector lèvera une exception lorsqu'un contrôleur ne peut pas être résolu. De plus, cela vous permet d'appeler container.Verify() ce qui fera échouer l'application au démarrage lorsque la configuration n'est pas valide (a la configuration vérifiable est importante ). Et cela vous permet également de diagnostiquer la configuration ce qui vous donne encore plus de confiance quant à l'exactitude de votre configuration.

Mon le conseil est de placer MVC et l'API Web dans leur propre projet . Cela rendra les choses beaucoup plus faciles.

L'enregistrement de tous les contrôleurs d'API Web peut être effectué avec le code suivant:

container.RegisterWebApiControllers(GlobalConfiguration.Configuration);

MISE À JOUR:

Étant donné que cette erreur est si courante, les versions plus récentes de la classe SimpleInjectorWebApiDependencyResolver ne renvoient simplement jamais null lorsqu'un type de contrôleur Est demandé. Au lieu de cela, il générera une erreur descriptive. Pour cette raison, vous ne devriez plus voir d'erreur, tant que vous utilisez le SimpleInjectorWebApiDependencyResolver officiel.

43
Steven