asp.net核心DI只注入单例,范围和瞬态不工作

时间:2017-01-25 16:07:40

标签: c# dependency-injection asp.net-core asp.net-core-mvc 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<>正确配置上下文生命周期,但我无法让它工作。然后,我尝试将_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 }));
    }
}

1 个答案:

答案 0 :(得分:8)

中间件只实例化一次,因此它是一个有效的单例。

因此,您注入到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);
}