asp.netコアDIはシングルトンのみを注入し、スコープ付きで一時的ではない

asp.net-core asp.net-core-mvc c# dependency-injection entity-framework-core

質問

私は立ち往生しており、これを理解できないようです。私はインターフェイスを持つ簡単なクラスを持っています。私はこのサービスにEFContextとLoggerを注入しています。何らかの理由で、どのようにサービスを登録しても、常にシングルトンになります。クラスにGuidプロパティを設定して、リクエストごとに変更されているかどうかを確認しますが、同じままです。

AccountServiceクラスとそのインターフェイスは次のとおりです。

public interface IAccountService 
{
    Account GetAccountByEmailAndPassword(string emailAddress, string password);
}

public class AccountService : IAccountService
{
    private readonly IEFContext _context;
    private readonly ILogger<AccountService> _logger;
    private string _guid;

    public AccountService()
    {
        _context = context;
        _logger = logger;
        _guid = Guid.NewGuid().ToString();
    }

    public Account GetAccountByEmailAndPassword(string emailAddress, string password)
    {
        try
        {
            //get the account
            var account = _context.Account.FirstOrDefault(x => x.EmailAddress == emailAddress);

            //make sure we have an account
            if (account == null)
                return null;

            //generate hash from account
            var accountHash = GeneratePasswordSaltHash(account.Password, account.PasswordSalt);

            //generate hash from credentials passed in
            var passedInHash = GeneratePasswordSaltHash(
                Convert.ToBase64String(HashPassword(password)),
                account.PasswordSalt);

            // TODO: number of failed attempts should lock account etc.
            return accountHash == passedInHash ? account : null;
        } catch (Exception ex)
        {
            _logger.LogError("Exception in AccountService: " + ex.ToString());
            throw;
        }
    }
}

私がサービスを登録する方法は次のとおりです。

public void ConfigureServices(IServiceCollection services)
{
    // App Settings
    services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

    // Add DBContext
    var connectionString = Configuration["AppSettings:Data:ConnectionString"];
    services.AddDbContext<EFContext>(options => options.UseSqlServer(connectionString));

    // Add framework services.
    services.AddMvc();

    //  Add DI
    services.AddScoped<IEFContext, EFContext>();
    services.AddScoped<IAccountService, AccountService>();
}

EFContextクラスとそのインタフェースは次のとおりです。

public interface IEFContext
{
    DbSet<Account> Account { get; set; }

    int SaveChanges();
    EntityEntry Update(object entity);
}

public class EFContext : DbContext, IEFContext
{
    public EFContext(DbContextOptions options) : base(options) {}
    public DbSet<Account> Account { get; set; }
}

私はデータベースとそのすべてを文脈で打つことができますが、すべてがシングルトンです。データベースにアクセスしてアカウントの一部のデータを手動で更新し、コードで再度アカウントを要求すると、データが古くなってしまうため、最初にこの問題を警告しました。コンテキストの問題だと思っていましたが、 .AddScoped<>を使用してコンテキストのライフサイクルを正しく設定していると思いますが、動作させることができませんでした。それで、 AccountService_guidプロパティを追加して、リクエストごとにnewdを取得しているかどうかを判断しました。 .AddTransient<>も試し.AddTransient<> 。どんな助けもありがとうございます。本当にありがとう。

EDITこれは私のconfigureメソッドです:

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

        loggerFactory.AddDebug();
        loggerFactory.AddSerilog();

        //Token stuff
        // secretKey contains a secret passphrase only your server knows
        var secretKey = "mysupersecret_secretkey!123";
        var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));

        var tokenValidationParameters = new TokenValidationParameters
        {
            // The signing key must match!
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = signingKey,

            // Validate the JWT Issuer (iss) claim
            ValidateIssuer = true,
            ValidIssuer = "ExampleIssuer",

            // Validate the JWT Audience (aud) claim
            ValidateAudience = true,
            ValidAudience = "ExampleAudience",

            // Validate the token expiry
            ValidateLifetime = true,

            // If you want to allow a certain amount of clock drift, set that here:
            ClockSkew = TimeSpan.Zero
        };

        app.UseJwtBearerAuthentication(new JwtBearerOptions
        {
            AutomaticAuthenticate = true,
            AutomaticChallenge = true,
            TokenValidationParameters = tokenValidationParameters
        });

        // Token generator
        var options = new TokenProviderOptions
        {
            Audience = "ExampleAudience",
            Issuer = "ExampleIssuer",
            SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
        };

        app.UseMiddleware<TokenProviderMiddleware>(Options.Create(options));

        app.UseMvc();
    }

私のトークンミドルウェアでは、私はそれがAccountServiceを使用している、ここではトークンミドルウェアです:

public class TokenProviderMiddleware
{
    private readonly RequestDelegate _next;
    private readonly TokenProviderOptions _options;
    private readonly IAccountService _accountService;

    public TokenProviderMiddleware(RequestDelegate next, IOptions<TokenProviderOptions> options, IAccountService accountService)
    {
        _next = next;
        _options = options.Value;
        _accountService = accountService;
    }

    public Task Invoke(HttpContext context)
    {
        // If the request path doesn't match, skip
        if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal))
        {
            return _next(context);
        }

        if (!context.Request.Method.Equals("POST")
           || !context.Request.ContentType.Contains("application/json"))
        {
            context.Response.StatusCode = 400;
            return context.Response.WriteAsync("Bad request.");
        }

        return GenerateToken(context);
    }

    private async Task GenerateToken(HttpContext context)
    {
        var rawAccount = await new StreamReader(context.Request.Body).ReadToEndAsync();
        var authAccount = JsonConvert.DeserializeObject<AuthAccount>(rawAccount);

        var account = _accountService.GetAccountByEmailAndPassword(authAccount.EmailAddress, authAccount.Password);
        if (account == null)
        {
            context.Response.StatusCode = 400;
            await context.Response.WriteAsync("Invalid email address or password.");
            return;
        }

        var now = DateTime.UtcNow;

        // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
        // You can add other claims here, if you want:
        var claims = new Claim[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, account.EmailAddress),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Iat, ((DateTimeOffset)now).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
            new Claim(ClaimTypes.Role, account.RoleId.ToString()),
            new Claim(ClaimTypes.Name, account.EmailAddress)
        };

        // Create the JWT and write it to a string
        var jwt = new JwtSecurityToken(
            issuer: _options.Issuer,
            audience: _options.Audience,
            claims: claims,
            notBefore: now,
            expires: now.Add(_options.Expiration),
            signingCredentials: _options.SigningCredentials);
        var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

        var response = new ApiResponse<AuthAccount>
        {
            StatusCode = (int)HttpStatusCode.OK,
            Message = "Access granted",
            Data = new AuthAccount
            {
                Access_Token = encodedJwt,
                Expires_In = (int)_options.Expiration.TotalSeconds
            }
        };

        // Serialize and return the response
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync(JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented }));
    }
}

受け入れられた回答

ミドルウェアは一度だけインスタンス化されるため、効果的にシングルトンになります。

したがって、あなたがMiddlewaresコンストラクタに注入するものはすべて、シングルトンコンテナ(Configureメソッド内でapp.ApplicationServicesを介してアクセスできるもの)から解決されます。

あなたのIAccountServiceがミドルウェアに注入されていることがわかります。そのため、問題が発生しているようです。 Invokeメソッドで、コンテキスト単位で解決する必要があります。

public Task Invoke(HttpContext context, IAccountService accountService)
{
    // If the request path doesn't match, skip
    if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal))
    {
        return _next(context);
    }

    if (!context.Request.Method.Equals("POST")
       || !context.Request.ContentType.Contains("application/json"))
    {
        context.Response.StatusCode = 400;
        return context.Response.WriteAsync("Bad request.");
    }

    return GenerateToken(context, accountService);
}

または

public Task Invoke(HttpContext context)
{
    var accountService = context.RequestServices.GetRequiredService<IAccountService>();

    // If the request path doesn't match, skip
    if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal))
    {
        return _next(context);
    }

    if (!context.Request.Method.Equals("POST")
       || !context.Request.ContentType.Contains("application/json"))
    {
        context.Response.StatusCode = 400;
        return context.Response.WriteAsync("Bad request.");
    }

    return GenerateToken(context, accountService);
}


Related

ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ