220 votes

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

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

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

Ce qui est bizarre, c'est que ce EmailRepository et l'interface est configuré exactement la même chose que ce que j'en dis que tous mes autres référentiels encore aucune erreur n'est levée pour eux. L'erreur se produit uniquement si j'essaie d'utiliser l'application.UseEmailingExceptionHandling(); ligne. Voici une partie de mon Démarrage.cs fichier.

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 la 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, la gestion des exceptions middleware

public class ExceptionHandlingMiddleware
{
    private const string ErrorEmailAddress = "errors@ourdomain.com";
    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 conduit à changer mon Programme.cs du fichier BuildWebHost méthode à partir de ce

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

pour ce

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 il semble fonctionner maintenant.

398voto

user1336 Points 1518

Vous avez enregistré le IEmailRepository tant que service étendu, dans la classe Startup . Cela signifie que vous ne pouvez pas l'injecter en tant que paramètre de constructeur en Middleware car seuls Singleton services peuvent être résolus par injection de constructeur en 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);
    }
}
 

213voto

Riddik Points 407

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

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

    //do your stuff....
}

Découvrez la Résolution de Services dans un Corps de Méthode dans asp.net de base de l'injection de dépendances meilleures pratiques conseils astuces pour plus de détails.

44voto

Joe Audette Points 15293

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 emailRepository 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)
{

    ....
}
 

10voto

Votre middleware et de la service a être compatibles les uns avec les autres afin d'injecter de l' service via l' constructor de votre middleware. Ici, votre middleware a été créé en tant que convention-based middleware ce qui signifie qu'il agit comme un 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 de l' singleton-service , parce qu'il oblige l' scoped-service d'agir en tant que singleton on. Cependant, voici vos options.

  1. Injecter votre service en tant que paramètre à l' InvokeAsync méthode.
  2. Faire à votre service un singleton si possible.
  3. Transformer votre middleware d'un factory-based on.

Un Factory-based middleware est en mesure d'agir en tant que scoped-service. Ainsi, vous pouvez injecter scoped-service via le constructeur de ce middleware. Ci-dessous, je vous ai montré comment créer un factory-based middleware.

C'est uniquement 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>();
    }
}

L' TestMiddleware:

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

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

L' TestService:

public class TestService
{
}

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