web-dev-qa-db-fra.com

Autofac - L'étendue de la durée de vie de la demande ne peut pas être créée car le HttpContext n'est pas disponible - en raison d'un code asynchrone?

Petite question: Identique à ce problème sans réponse

Longue question:

Je viens de transférer du code d'une solution Web Api de MVC 4 + qui utilisait Autofac dans ma nouvelle solution, qui utilise également Autofac mais uniquement avec Web Api 2 (pas de projet MVC 5.1, mais simplement une API Web).

Dans ma solution précédente, j'avais MVC4 et Web Api, donc j'avais 2 fichiers Bootstrapper.cs, un pour chacun. J'ai copié uniquement le programme d'amorçage Web Api pour le nouveau projet.

Maintenant, j'ai 2 autres projets dans la nouvelle solution qui doivent tirer une dépendance. Supposons simplement que je dois utiliser DependencyResolver.Current.GetService<T>(), même s’il s’agit d’un anti-motif.

Au début, cela ne fonctionnait pas tant que je n'ai pas défini le résolveur de dépendances MVC sur le même conteneur:

GlobalConfiguration.Configuration.DependencyResolver = 
     new AutofacWebApiDependencyResolver(container);

//I had to pull in Autofac.Mvc and Mvc 5.1 integration but this line fixed it
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

Ce qui est étrange, c’est de ne le fixer que dans UN de ces projets! Voici la situation:

 Solution.Web project
      Bootstrapper.cs that registers both dependency resolvers for web api and mvc.

 Solution.ClassLib project
      var userRepo = DependencyResolver.Current.GetService<IUserRepo>(); //Good! :)

 Solution.WindowsWorkflow project
      var userRepo = DependencyResolver.Current.GetService<IUserRepo>(); //Throws exception :(

L'exception est la suivante: la portée de la durée de vie de la demande ne peut pas être créée car le HttpContext n'est pas disponible.

Maintenant, avant de commencer à blâmer le flux de travail, sachez que cette configuration exacte fonctionnait parfaitement dans une autre solution: le flux de travail pouvait parfaitement utiliser DependencyResolver. Donc, je suppose que cela est dû à l'utilisation d'une version plus récente d'Autofac et au fait que le flux de travail s'exécute de manière asynchrone (tout comme la question à laquelle j'ai lié en ce qui concerne le code async)

J'ai essayé de changer tout le code d'enregistrement pour utiliser InstancePerLifetimeScope() au lieu de InstancePerHttpRequest() et essayer de créer une étendue:

using (var c= AutofacDependencyResolver.Current
                     .ApplicationContainer.BeginLifetimeScope("AutofacWebRequest"))
{
   var userRepo = DependencyResolver.Current.GetServices<IUserRepo>();
}

Mais cela n'a pas changé l'exception. Décomposant le code encore plus loin voici le coupable exact:

var adr = AutofacDependencyResolver.Current; //Throws that exception 

Vraiment besoin de passer outre cela a passé trop de temps bloqué. Récompensera la réponse existante avec une prime en 2 jours

14
parliament

UPDATE 20 nov. 2014: Dans les versions de Autofac.Mvc5 depuis la publication de cette question, l'implémentation de AutofacDependencyResolver.Current a été mise à jour pour supprimer le besoin de HttpContext. Si vous rencontrez ce problème et avez trouvé cette réponse, vous pouvez potentiellement résoudre facilement les problèmes en mettant à jour une version plus récente de Autofac.Mvc5. Cependant, je laisserai la réponse originale intacte pour permettre aux gens de comprendre pourquoi le demandeur de la question initiale posait problème.

La réponse originale suit:


AutofacDependencyResolver.Current nécessite une HttpContext.

En parcourant le code, AutofacDependencyResolver.Current ressemble à ceci:

public static AutofacDependencyResolver Current
{
  get
  {
    return DependencyResolver.Current.GetService<AutofacDependencyResolver>();
  }
}

Et, bien sûr, si le résolveur de dépendance actuel est une AutofacDependencyResolver, alors il va essayer de faire une résolution ...

public object GetService(Type serviceType)
{
  return RequestLifetimeScope.ResolveOptional(serviceType);
}

Ce qui obtient la portée de la durée de vie d'une RequestLifetimeScopeProvider...

public ILifetimeScope GetLifetimeScope(Action<ContainerBuilder> configurationAction)
{
  if (HttpContext.Current == null)
  {
    throw new InvalidOperationException("...");
  }

  // ...and your code is probably dying right there so I won't
  // include the rest of the source.
}

Cela doit fonctionner comme ça pour supporter des outils tels que Glimpse qui encapsule/substitue dynamiquement le résolveur de dépendances afin de l’instrumenter. C'est pourquoi vous ne pouvez pas simplement lancer DependencyResolver.Current as AutofacDependencyResolver.

Pratiquement tout ce qui utilise le Autofac.Integration.Mvc.AutofacDependencyResolver nécessite HttpContext.

C'est pourquoi vous continuez à avoir cette erreur. Peu importe que vous n'ayez aucune dépendance enregistrée InstancePerHttpRequest - AutofacDependencyResolver nécessitera quand même un contexte Web.

J'imagine que l'autre application de flux de travail que vous aviez ne posant pas problème était une application MVC ou quelque chose dans laquelle il y avait toujours un contexte Web.

Voici ce que je recommanderais:

  • Si vous devez utiliser des composants en dehors d'un contexte Web et que vous êtes dans WebApi, utilisez le Autofac.Integration.WebApi.AutofacWebApiDependencyResolver.
  • Si vous êtes dans WCF, utilisez le AutofacHostFactory.Container standard et cette implémentation d'usine Host pour résoudre les dépendances. (WCF est un peu bizarre avec son potentiel d’hôte singleton, etc., donc "à la demande" n’est pas aussi simple.)
  • Si vous avez besoin de quelque chose "agnostique" de technologie, considérez l'implémentation CommonServiceLocator pour Autofac. Cela ne crée pas de durée de vie de demande, mais cela peut résoudre certains problèmes.

Si vous maintenez ces choses au clair et n'essayez pas d'utiliser les divers résolveurs en dehors de leur habitat natal, pour ainsi dire, vous ne devriez pas vous heurter à des problèmes.

Vous pouvez utiliser _InstancePerApiRequest et InstancePerHttpRequest de manière relativement sécurisée de manière interchangeable dans les enregistrements de service. Ces deux extensions utilisent la même balise de portée de vie, de sorte que la notion de requête Web MVC et de requête d'API Web puisse être traitée de la même manière, même si la portée de vie sous-jacente d'un cas est basée sur HttpContext et l'autre sur IDependencyScope . Vous pouvez donc hypothétiquement partager un module d’enregistrement sur plusieurs types d’applications et il devrait faire ce qui est bien.

Si vous avez besoin du conteneur Autofac d'origine, enregistrez votre propre référence. Plutôt que de penser qu'Autofac retournera ce conteneur d'une manière ou d'une autre, vous devrez peut-être stocker une référence dans votre conteneur d'application si vous devez l'obtenir plus tard, pour une raison quelconque.

public static class ApplicationContainer
{
  public static IContainer Container { get; set; }
}

// And then when you build your resolvers...
var container = builder.Build();
GlobalConfiguration.Configuration.DependencyResolver =
  new AutofacWebApiDependencyResolver(container);
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
ApplicationContainer.Container = container;

Cela vous évitera beaucoup de problèmes par la suite.

23
Travis Illig

Mes hypothèses: 

  1. Vous exécutez un projet de flux de travail dans un projet Thread/AppDomain from MVC distinct. 
  2. IUserRepo dépend de HttpContext

Si mon hypothèse est correcte, le projet de workflow n’aurait aucune idée de HttpContext.Current.

Le projet WindowsWorkflow s'exécute tout le temps (si je comprends bien - ne fonctionnait pas réellement avec cette technologie). Where as MVC est basé sur des requêtes HTTP. HttpContext.Current est rempli uniquement lorsqu'une demande arrive. Si aucune demande, cette variable est null. Que se passe-t-il s'il n'y a pas de demande, mais que l'instance de flux de travail tente d'accéder à HttpContext? Correct - exception de référence nulle. Ou dans votre exception de résolution de dépendance de cas.

Qu'as tu besoin de faire: 

  1. Séparez les inscriptions de conteneurs en modules - module de domaine pour toutes vos classes de domaine. Ensuite, module MVC: pour toutes vos spécificités MVC, telles que User.Current ou HttpContext.Current. Et module de flux de travaux (si nécessaire) avec toutes les implémentations spécifiques aux flux de travaux.
  2. Lors de l'initialisation du workflow, créez un conteneur autofac avec des modules de domaine et de workflow, excluez les dépendances MVC. Pour le conteneur MVC - créez-le sans module de flux de travail.
  3. Pour IUserRepo, créez une implémentation indépendante de HttpContext. Ce sera probablement le plus problématique à faire.

J'ai effectué quelque chose de similaire pour l'exécution de Quartz.Net dans Azure. Voir mon article de blog à ce sujet: http://tech.trailmax.info/2013/07/quartz-net-in-Azure-with-autofac-smoothness/ . Cet article ne vous aidera pas directement, mais explique mon raisonnement pour la scission des modules autofac.

Mise à jour selon le commentaire: WebApi clarifie beaucoup de choses ici. Les demandes WebApi ne passent pas par le même pipeline que vos demandes MVC. Et les contrôleurs WebApi n'ont pas accès à HttpContext. Voir cette réponse

Maintenant, en fonction de ce que vous faites dans votre contrôleur wepApi, vous voudrez peut-être modifier l'implémentation IUserRepo pour pouvoir utiliser MVC et WebApi.

3
trailmax

Nous sommes actuellement dans une situation où nous avons des tests qui souffrent du problème de «manque de httpcontext» mais ne peuvent pas encore utiliser les excellentes recommandations ci-dessus en raison de contraintes de version.

Nous avons résolu le problème en créant un contexte "simulé" dans notre configuration de test: Voir: Mock HttpContext.Current in Test Init, méthode

0
SteveT