web-dev-qa-db-fra.com

Comment configurer Web Api 2 pour rechercher des contrôleurs dans un projet distinct? (comme je le faisais dans Web Api)

J'avais l'habitude de placer mes contrôleurs dans un projet de bibliothèque de classes distinct dans Mvc Web Api. J'avais l'habitude d'ajouter la ligne suivante dans global.asax de mon projet d'api web pour rechercher des contrôleurs dans le projet séparé:

ControllerBuilder.Current.DefaultNamespaces.Add("MyClassLibraryProject.Controllers");

Je n'ai jamais eu à faire d'autre configuration, sauf pour ajouter la ligne ci-dessus. Cela a toujours bien fonctionné pour moi.

Cependant, je ne peux pas utiliser la méthode ci-dessus pour faire de même dans WebApi2. Cela ne fonctionne tout simplement pas. Le projet WebApi2 essaie toujours de trouver les contrôleurs dans le dossier des contrôleurs de son propre projet.

- Donner une petite mise à jour récapitulative après 2 mois (au début de la prime):

J'ai créé une solution WebApiOne, elle a 2 projets, le premier est un projet WebApi et le second est une bibliothèque de classes pour les contrôleurs. Si j'ajoute la référence au projet de bibliothèque de classes de contrôleurs dans le projet WebApi, tout fonctionne comme prévu. c'est-à-dire si je vais sur http://mydevdomain.com/api/values je peux voir la sortie correcte.

J'ai maintenant créé un deuxième projet appelé WebApiTwo, il a 2 projets, le premier est le projet WebApi2 et le second est une bibliothèque de classes pour les contrôleurs. Si j'ajoute la référence au projet de bibliothèque de classes de contrôleurs au projet WebApi2, cela ne fonctionne PAS comme prévu. c'est-à-dire si je vais sur http://mydevdomain.com/api/values j'obtiens "Aucun type n'a été trouvé qui correspond au contrôleur nommé 'values'."

pour le premier projet, je ne fais aucun réglage personnalisé, je n'ai PAS:

ControllerBuilder.Current.DefaultNamespaces.Add("MyClassLibraryProject.Controllers");

dans mon global.asax, et je n'ai implémenté aucune solution personnalisée proposée par StrathWeb dans deux de ses articles de blog, car je pense que ce n'est plus applicable; car tout fonctionne simplement en ajoutant la référence du projet de contrôleur au projet WebApi.

Je m'attendrais donc à ce que tout fonctionne de la même manière pour WebApi2 ... mais ce n'est pas le cas. Quelqu'un at-il vraiment essayé de faire cela dans WebAPi2?

49
M. Ali Iftikhar

Je viens de confirmer que cela fonctionne bien. A vérifier:

Références: Votre projet d'API Web principal fait-il référence à la bibliothèque de classes externe?

Routage: Avez-vous mis en place des routes qui pourraient interférer avec les contrôleurs externes?

Niveau de protection: Les contrôleurs de la bibliothèque externe sont-ils public?

Héritage: Les contrôleurs de la bibliothèque externe héritent-ils de ApiController?

Versioning: Votre projet d'API Web et votre bibliothèque de classes utilisent-ils la même version des bibliothèques d'API Web?

Si cela peut aider, je peux emballer ma solution de test et la mettre à votre disposition. De plus, comme point à noter, vous n'avez pas besoin de dire à l'API Web de trouver les contrôleurs avec la ligne que vous avez ajoutée à Global.asax, le système trouve automatiquement les contrôleurs à condition que vous les ayez référencés.

29
DavidG

Cela devrait fonctionner tel quel. Liste de contrôle

  • Hériter ApiController
  • Terminez le nom du contrôleur avec Controller . Par exemple. ValuesController
  • Assurez-vous que le projet WebApi et le projet de bibliothèque de classes référencent les mêmes assemblys WebApi
  • Essayez de forcer les itinéraires à l'aide du routage d'attributs
  • Clean la solution, supprimez manuellement les dossiers bin et reconstruisez
  • Supprimer Temporary ASP.NET Files Dossiers. Résultat de la recherche de contrôleur de cache WebApi et MVC
  • Appelez `config.MapHttpAttributeRoutes (); pour s'assurer que le framework prend en compte les routes d'attributs
  • Assurez-vous que la méthode que vous appelez est conçue pour gérer le bon verbe HTTP (s'il s'agit d'une méthode Web GET, vous pouvez appeler via l'URL du navigateur, si elle est POST vous devez sinon créer un demande Web)

Ce contrôleur:

[RoutePrefix("MyValues")]
public class AbcController : ApiController
{
    [HttpGet]
    [Route("Get")]
    public string Get()
    {
        return "Ok!";
    }
}

correspond à cette URL:

http://localhost/MyValues/Get (notez qu'il n'y a pas de /api/ en route car il n'était pas spécifié dans RoutePrefix.


Mise en cache de la recherche de contrôleur: Il s'agit du résolveur de contrôleur par défaut . Vous verrez dans le code source qu'il met en cache le résultat de la recherche.

/// <summary>
/// Returns a list of controllers available for the application.
/// </summary>
/// <returns>An <see cref="ICollection{Type}" /> of controllers.</returns>
public override ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver)
{
    HttpControllerTypeCacheSerializer serializer = new HttpControllerTypeCacheSerializer();

    // First, try reading from the cache on disk
    List<Type> matchingTypes = ReadTypesFromCache(TypeCacheName, IsControllerTypePredicate, serializer);
    if (matchingTypes != null)
    {
        return matchingTypes;
    }
...
}
16

Courait dans le même scénario et @justmara m'a mis sur la bonne voie. Voici comment accomplir le chargement de force des assemblages dépendants à partir de la réponse de @ justmara:

1) Remplacez la classe DefaultAssembliesResolver

public class MyNewAssembliesResolver : DefaultAssembliesResolver
{
    public override ICollection<Assembly> GetAssemblies()
    {

        ICollection<Assembly> baseAssemblies = base.GetAssemblies();
        List<Assembly> assemblies = new List<Assembly>(baseAssemblies);
        var controllersAssembly = Assembly.LoadFrom(@"Path_to_Controller_DLL");
        baseAssemblies.Add(controllersAssembly);
        return baseAssemblies;

    }
}

2) Dans la section de configuration, remplacez la valeur par défaut par la nouvelle implémentation

config.Services.Replace(typeof(IAssembliesResolver), new MyNewAssembliesResolver());

J'ai bricolé cette syntaxe à l'aide de pointeurs de ce blog:

http://www.strathweb.com/2013/08/customizing-controller-discovery-in-asp-net-web-api/

Comme d'autres l'ont dit, vous savez si vous rencontrez ce problème si vous forcez le contrôleur à se charger en le référençant directement. Une autre façon consiste à illustrer les résultats de CurrentDomain.GetAssemblies() et à voir si votre assembly est dans la liste.

Aussi: Si vous êtes un auto-hébergeur utilisant des composants OWIN, vous allez le rencontrer. Lors des tests, gardez à l'esprit que DefaultAssembliesResolver ne démarrera PAS tant que la première demande WebAPI n'aura pas été soumise (il m'a fallu un certain temps pour le réaliser).

9
user1821052

Êtes-vous sûr que votre assembly référencé a été chargé AVANT le service IAssembliesResolver appelé? Essayez d'insérer du code factice dans votre application, quelque chose comme

var a = new MyClassLibraryProject.Controllers.MyClass();

dans la méthode de configuration (mais n'oubliez pas que le compilateur peut "optimiser" ce code et le supprimer totalement, si "a" n'est jamais utilisé). J'ai rencontré un problème similaire avec l'ordre de chargement de l'assembly. A fini avec des assemblages dépendants à chargement forcé au démarrage.

7
justmara

Vous devez indiquer à webapi/mvc de charger votre assembly référencé. Vous faites cela avec la section compilation/assemblages dans votre web.config.

<compilation debug="true" targetFramework="4.5.2">
  <assemblies>
    <add Assembly="XYZ.SomeAssembly" />
  </assemblies>
</compilation>

Aussi simple que cela. Vous pouvez le faire avec du code comme le suggère @ user1821052, mais cette version web.config aura le même effet.

5
Ryan Mann

Outre ce qui a déjà été dit:

Assurez-vous que vous n'avez pas deux contrôleurs du même nom dans des espaces de noms différents.

Juste eu le cas où un contrôleur (foo.UserApiController) devrait être partiellement migré vers un nouvel espace de noms (bar.UserApiController) et URI. L'ancien contrôleur a été mappé par convention vers/userapi, le nouveau a été routé par attribut via RoutePrefix["api/users"]. Le nouveau contrôleur n'a pas fonctionné tant que je ne l'ai pas renommé bar.UserFooApiController.

2
Igor Lankin

Lorsque vous utilisez AttributeRouting, il est facile d'oublier de décorer vos méthodes avec l'attribut Route, en particulier lorsque vous utilisez l'attribut RoutePrefix sur votre classe de contrôleur. Il semble que l'assemblage de votre contrôleur n'ait pas été capté par le pipeline d'API Web à l'époque.

1
ngu

Si votre bibliothèque de classes est construite avec EF, assurez-vous que la chaîne de connexion est spécifiée dans le App.config pour le projet de bibliothèque de classes, [~ # ~] et [~ # ~] dans le Web.config pour votre projet API Web MVC.

0
CodeCabbie