web-dev-qa-db-fra.com

Impossible de résoudre le service limité du fournisseur racine .Net Core 2

Lorsque j'essaie d'exécuter mon application, j'obtiens l'erreur

InvalidOperationException: Cannot resolve 'API.Domain.Data.Repositories.IEmailRepository' from root provider because it requires scoped service 'API.Domain.Data.EmailRouterContext'.

Ce qui est étrange, c’est que EmailRepository et son interface sont configurés de la même manière que tous mes autres référentiels, mais qu’aucune erreur n’a été commise. L'erreur ne survient que si j'essaie d'utiliser la méthode app.UseEmailingExceptionHandling (); ligne. Voici quelques-uns de mes fichiers Startup.cs.

public class Startup
{
    public IConfiguration Configuration { get; protected set; }
    private APIEnvironment _environment { get; set; }

    public Startup(IConfiguration configuration, IHostingEnvironment env)
    {
        Configuration = configuration;

        _environment = APIEnvironment.Development;
        if (env.IsProduction()) _environment = APIEnvironment.Production;
        if (env.IsStaging()) _environment = APIEnvironment.Staging;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        var dataConnect = new DataConnect(_environment);

        services.AddDbContext<GeneralInfoContext>(opt => opt.UseSqlServer(dataConnect.GetConnectString(Database.GeneralInfo)));
        services.AddDbContext<EmailRouterContext>(opt => opt.UseSqlServer(dataConnect.GetConnectString(Database.EmailRouter)));

        services.AddWebEncoders();
        services.AddMvc();

        services.AddScoped<IGenInfoNoteRepository, GenInfoNoteRepository>();
        services.AddScoped<IEventLogRepository, EventLogRepository>();
        services.AddScoped<IStateRepository, StateRepository>();
        services.AddScoped<IEmailRepository, EmailRepository>();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole();

        app.UseAuthentication();

        app.UseStatusCodePages();
        app.UseEmailingExceptionHandling();

        app.UseMvcWithDefaultRoute();
    }
}

Voici le EmailRepository

public interface IEmailRepository
{
    void SendEmail(Email email);
}

public class EmailRepository : IEmailRepository, IDisposable
{
    private bool disposed;
    private readonly EmailRouterContext edc;

    public EmailRepository(EmailRouterContext emailRouterContext)
    {
        edc = emailRouterContext;
    }

    public void SendEmail(Email email)
    {
        edc.EmailMessages.Add(new EmailMessages
        {
            DateAdded = DateTime.Now,
            FromAddress = email.FromAddress,
            MailFormat = email.Format,
            MessageBody = email.Body,
            SubjectLine = email.Subject,
            ToAddress = email.ToAddress
        });
        edc.SaveChanges();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
                edc.Dispose();
            disposed = true;
        }
    }
}

Et enfin le middleware de gestion des exceptions

public class ExceptionHandlingMiddleware
{
    private const string ErrorEmailAddress = "[email protected]";
    private readonly IEmailRepository _emailRepository;

    private readonly RequestDelegate _next;

    public ExceptionHandlingMiddleware(RequestDelegate next, IEmailRepository emailRepository)
    {
        _next = next;
        _emailRepository = emailRepository;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next.Invoke(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex, _emailRepository);
        }
    }

    private static Task HandleExceptionAsync(HttpContext context, Exception exception,
        IEmailRepository emailRepository)
    {
        var code = HttpStatusCode.InternalServerError; // 500 if unexpected

        var email = new Email
        {
            Body = exception.Message,
            FromAddress = ErrorEmailAddress,
            Subject = "API Error",
            ToAddress = ErrorEmailAddress
        };

        emailRepository.SendEmail(email);

        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int) code;
        return context.Response.WriteAsync("An error occured.");
    }
}

public static class AppErrorHandlingExtensions
{
    public static IApplicationBuilder UseEmailingExceptionHandling(this IApplicationBuilder app)
    {
        if (app == null)
            throw new ArgumentNullException(nameof(app));
        return app.UseMiddleware<ExceptionHandlingMiddleware>();
    }
}

Mise à jour: j'ai trouvé ce lien https://github.com/aspnet/DependencyInjection/issues/578 ce qui m'a amené à modifier la méthode BuildWebHost de mon fichier Program.cs à partir de cette page

public static IWebHost BuildWebHost(string[] args)
{
    return WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .Build();
}

pour ça

public static IWebHost BuildWebHost(string[] args)
{
    return WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseDefaultServiceProvider(options =>
            options.ValidateScopes = false)
        .Build();
}

Je ne sais pas ce qui se passe exactement, mais cela semble fonctionner maintenant.

43
geoff swartz

Vous avez enregistré le IEmailRepository en tant que service limité, dans la classe Startup. Cela signifie que vous ne pouvez pas l'injecter en tant que paramètre de constructeur dans Middleware, car seuls les services Singleton peuvent être résolus par injection de constructeur dans Middleware. Vous devriez déplacer la dépendance vers la méthode Invoke comme ceci:

public ExceptionHandlingMiddleware(RequestDelegate next)
{
    _next = next;
}

public async Task Invoke(HttpContext context, IEmailRepository emailRepository)
{
    try
    {
        await _next.Invoke(context);
    }
    catch (Exception ex)
    {
        await HandleExceptionAsync(context, ex, emailRepository);
    }
}
91
user1336

Une autre façon d'obtenir l'instance de dépendance étendue consiste à injecter le fournisseur de services (IServiceProvider) dans le constructeur du middleware, à créer la méthode scope dans Invoke, puis à obtenir le service requis à partir de la portée:

using (var scope = _serviceProvider.CreateScope()) {
    var _emailRepository = scope.ServiceProvider.GetRequiredService<IEmailRepository>);

    //do your stuff....
}

Consultez la résolution des services dans un corps de méthode dans astuces relatives aux meilleures pratiques d'injection de dépendance fondamentale pour plus de détails.

29
Riddik

Le middleware est toujours un singleton, vous ne pouvez donc pas définir de dépendances en tant que dépendances de constructeur dans le constructeur de votre middleware.

Le middleware prend en charge l’injection de méthode sur la méthode Invoke. Vous pouvez donc ajouter e-mail à IEmailRepository en tant que paramètre de cette méthode. Elle y sera injectée et sa portée sera étendue.

public async Task Invoke(HttpContext context, IEmailRepository emailRepository)
{

    ....
}
21
Joe Audette

Votre middleware et le service doivent être compatibles entre eux pour pouvoir injecter le service via le constructor de votre middleware. Ici, votre middleware a été créé en tant que convention-based middleware, ce qui signifie qu'il agit en tant que singleton service et vous avez créé votre service en tant que scoped-service. Donc, vous ne pouvez pas injecter un scoped-service dans le constructeur d'un singleton-service car il oblige le scoped-service à agir comme un singleton. Cependant, voici vos options.

  1. Injectez votre service en tant que paramètre de la méthode InvokeAsync.
  2. Faites votre service un singleton, si possible.
  3. Transformez votre middleware en un factory-based.

Un Factory-based middleware peut agir comme un scoped-service. Vous pouvez donc injecter un autre scoped-service via le constructeur de ce middleware. Ci-dessous, je vous ai montré comment créer un middleware factory-based.

Ceci est seulement pour la démonstration. Donc, j'ai supprimé tous les autres codes.

public class Startup
{
    public Startup()
    {
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<TestMiddleware>();
        services.AddScoped<TestService>();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMiddleware<TestMiddleware>();
    }
}

Le TestMiddleware:

public class TestMiddleware : IMiddleware
{
    public TestMiddleware(TestService testService)
    {
    }

    public Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        return next.Invoke(context);
    }
}

Le TestService:

public class TestService
{
}
1