40 votes

L'injection de dépendances dans ASP.NET Core 2 lève une exception

Je reçois l'exception suivante lorsque j'essaie d'utiliser un DbContext personnalisé dans la base de données de l'entreprise. Configure méthode dans Startup.cs fichier. J'utilise ASP.NET Core dans la version 2.0.0-preview1-005977

Exception non gérée : System.Exception : Could not resolve a service of type 'Communicator.Backend.Data.CommunicatorContext' for the parameter 'dbContext' of method 'Configure' on type 'Communicator.Backend.Startup'. ---> System.InvalidOperationException : Cannot resolve scoped service 'Communicator.Backend.Data.CommunicatorContext' from Root provider.

Cette exception est également levée lorsque j'essaie de recevoir une autre instance.

Exception non gérée : System.Exception : Impossible de résoudre un service de type 'Communicator.Backend.Services.ILdapService'. ...

Voici mes ConfigureServices y Configure méthodes.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<CommunicatorContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    services.AddCookieAuthentication();
    services.Configure<LdapConfig>(Configuration.GetSection("Ldap"));
    services.AddScoped<ILdapService, LdapService>();
    services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, CommunicatorContext dbContext, ILdapService ldapService)
{
    app.UseAuthentication();
    app.UseWebSockets();
    app.Use(async (context, next) =>
    {
        if (context.Request.Path == "/ws")
        {
            if (context.WebSockets.IsWebSocketRequest)
            {
                WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
                await Echo(context, webSocket);
            }
            else
            {
                context.Response.StatusCode = 400;
            }
        }
        else
        {
            await next();
        }
    });
    app.UseMvc();
    DbInitializer.Initialize(dbContext, ldapService);
}

68voto

Nkosi Points 95895

Documentation de citation

Services disponibles au démarrage

L'injection de dépendances d'ASP.NET Core fournit des services d'application pendant au démarrage de l'application. Vous pouvez demander ces services en incluant l'interface appropriée en tant que paramètre de votre Startup c ou l'un de ses Configure o ConfigureServices méthodes.

En examinant chaque méthode dans le Startup dans l'ordre dans lequel ils sont ils sont appelés, les services suivants peuvent être demandés comme paramètres :

  • Dans le constructeur : IHostingEnvironment , ILoggerFactory
  • En el ConfigureServices méthode : IServiceCollection
  • En el Configure méthode : IApplicationBuilder , IHostingEnvironment , ILoggerFactory , IApplicationLifetime

Vous essayez de résoudre des services qui ne sont pas disponibles au démarrage,

...CommunicatorContext dbContext, ILdapService ldapService) {

qui vous donnera les erreurs que vous obtenez. Si vous avez besoin d'accéder aux implémentations, vous devez faire l'une des choses suivantes :

  1. Modifiez le ConfigureServices et y accéder à partir de la collection de services, à savoir

    public IServiceProvider ConfigureServices(IServiceCollection services) {
        services.AddDbContext<CommunicatorContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        services.AddCookieAuthentication();
        services.Configure<LdapConfig>(Configuration.GetSection("Ldap"));
        services.AddScoped<ILdapService, LdapService>();
        services.AddMvc();
    
        // Build the intermediate service provider
        var serviceProvider = services.BuildServiceProvider();
    
        //resolve implementations
        var dbContext = serviceProvider.GetService<CommunicatorContext>();
        var ldapService = serviceProvider.GetService<ILdapService>();
        DbInitializer.Initialize(dbContext, ldapService);
    
        //return the provider
        return serviceProvider();
    }
  2. Modifiez le ConfigureServices pour renvoyer IServiceProvider, Configure pour prendre un IServiceProvider puis résolvez vos dépendances à cet endroit, c'est-à-dire

    public IServiceProvider ConfigureServices(IServiceCollection services) {
        services.AddDbContext<CommunicatorContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        services.AddCookieAuthentication();
        services.Configure<LdapConfig>(Configuration.GetSection("Ldap"));
        services.AddScoped<ILdapService, LdapService>();
        services.AddMvc();
    
        // Build the intermediate service provider then return it
        return services.BuildServiceProvider();
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
                          ILoggerFactory loggerFactory, IServiceProvider serviceProvider) {
    
        //...Other code removed for brevity
    
        app.UseMvc();
    
        //resolve dependencies
        var dbContext = serviceProvider.GetService<CommunicatorContext>();
        var ldapService = serviceProvider.GetService<ILdapService>();
        DbInitializer.Initialize(dbContext, ldapService);
    }

0 votes

Merci, cela résout mon problème. Si je peux ajouter quelque chose, après avoir testé les deux options, j'ai constaté que je dois encore modifier ConfigureServices de revenir void (par défaut) pour retourner IServiceProvider pour que la deuxième option fonctionne.

0 votes

J'ai eu un problème similaire. Fabrication de ConfigureServices() retourner le IServiceProvider a permis à lui seul de résoudre mes dépendances via IApplicationBuilder.ApplicationServices.GetService<T>() . Je n'ai pas eu besoin d'injecter le IServiceProvider dans le Configure() méthode.

32voto

Krzysztof Branicki Points 2827

La solution de NKosi fonctionne car en invoquant services.BuildServiceProvider() vous-même sans paramètres, vous ne transmettez pas l validateScopes . Comme cette validation est désactivée, l'exception n'est pas levée. Cela ne signifie pas que le problème n'existe pas.

EF Core DbContext est enregistré avec un mode de vie scopé. Dans ASP native DI, la portée du conteneur est liée à l'instance de IServiceProvider . Normalement, lorsque vous utilisez votre DbContext à partir d'un contrôleur, il n'y a pas de problème car l'ASP crée une nouvelle portée (nouvelle instance de IServiceProvider ) pour chaque demande et l'utilise ensuite pour tout résoudre dans cette demande. Cependant, lors du démarrage de l'application, vous ne disposez pas de la portée de la requête. Vous avez une instance de IServiceProvider qui n'est pas scopé (ou en d'autres termes dans la portée de Root). Cela signifie que vous devez créer vous-même une portée. Vous pouvez le faire comme suit :

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
    using (var scope = scopeFactory.CreateScope())
    {
        var db = scope.ServiceProvider.GetRequiredService<CommunicatorContext>();
        var ldapService = scope.ServiceProvider.GetRequiredService<ILdapService>();
        // rest of your code
    }
    // rest of Configure setup
}

En ConfigureServices peut rester inchangée.

EDIT

Votre solution fonctionnera dans la version 2.0.0 RTM sans aucun changement puisque dans la version RTM, un fournisseur de service scopé sera créé pour la méthode Configure. https://github.com/aspnet/Hosting/pull/1106 .

24voto

Nate Barbettini Points 26922

Dans ASP.NET Core 2.0 et les versions plus récentes, vous pouvez simplement injecter le service scopé dont vous avez besoin dans la balise Configure constructeur, comme vous avez essayé de le faire initialement :

public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    ILoggerFactory loggerFactory,
    CommunicatorContext dbContext,
    ILdapService ldapService)
{
  // ...
}

C'est beaucoup plus facile, grâce aux améliorations apportées à l'application #1106 .

0 votes

C'est la façon naturelle de résoudre le problème de la question. Merci.

0 votes

La meilleure façon de le faire avec la 2.0 RTM

17voto

BriM Points 171
.UseDefaultServiceProvider(options => 
            options.ValidateScopes = false)

ajouter ceci dans Program.cs après .UseStartup<Startup>()

Cela fonctionne pour moi

Documentation ici

1 votes

Merci. Cela fonctionne aussi dans l'application Api d'Asp.Net Core 2.1. Que fait ce ValidateScopes en fait ?

5voto

Matthew Abbott Points 32861

Alternativement, vous pouvez créer une étendue de service au sein de votre Configure méthode :

var scopeFactory = ApplicationServices.GetService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
    var dbContext = scope.ServiceProvider.GetService<CommunicatorDbContext>();
    DbInitializer.Initializer(dbContext, ldapService);
}

Bien que, comme mentionné sur Slack, ne faites pas cela ;-)

3 votes

Quel est le problème ? Pourquoi suggérez-vous de ne pas le faire ? J'ai maintenant ceci : using (var scope = app.ApplicationServices.CreateScope()) scope.ServiceProvider.GetService<IInitDatabase>().InitAsync(‌​).Wait(); devrais-je le faire d'une autre manière ?

1 votes

Mentionné quoi sur Slack ? Pourquoi cela ne devrait-il pas être fait ? La réponse de @krzysztof-branicki a des upvotes qui le suggèrent et a été postée des mois plus tôt, pourquoi n'en avez-vous pas tenu compte dans votre réponse ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X