我被困住了,我似乎无法解决这个问题。我有一个带接口的简单类。我正在将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<>
正确配置上下文生命周期,但我无法让它工作。然后,我尝试将_guid
属性添加到AccountService
,以确定是否在每个请求上添加新内容,但似乎不是。我也试过了.AddTransient<>
。任何帮助表示赞赏。非常感谢。
修改 这是我的配置方法:
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 }));
}
}
答案 0 :(得分:8)
中间件只实例化一次,因此它是一个有效的单例。
因此,您注入到Middlewares构造函数中的所有内容都将从单例容器(您可以通过Configure方法中的app.ApplicationServices
访问的容器)中解析出来。
我看到你的IAccountService
被注入中间件,所以这似乎导致了这个问题。您必须使用
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);
}