私は立ち往生しており、これを理解できないようです。私はインターフェイスを持つ簡単なクラスを持っています。私はこのサービスに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);
}