我是 .NET Core 2.1 MVC 的新手,我无法理解一些事情是如何工作的

Posted

技术标签:

【中文标题】我是 .NET Core 2.1 MVC 的新手,我无法理解一些事情是如何工作的【英文标题】:I'm new to .NET Core 2.1 MVC and I'm having trouble understanding how a few things work 【发布时间】:2019-11-22 21:42:10 【问题描述】:

我目前正在学习 Udemy 的 .Net Core Angular 8 教程。我可以在 Postman 中获取/发布请求,还可以使用 sqlite 作为我的数据库并通过 Db 浏览器查看我在 .db 文件中发布的内容。一切似乎都很好,但如果我无法理解应用程序的某些区域发生了什么,一切都是徒劳的。如果有人能帮我回答几个问题,我将不胜感激。

我的整个项目都在 GitHub 上:https://github.com/cjtejada/ASP.NetCoreAngular8/tree/master/DatingApp.API

问题1:我有以下控制器:

    [Route("api/[controller]")]
    [ApiController]
    public class AuthController : ControllerBase
    

        private readonly IAuthRepository _repo;
        private readonly IConfiguration _config;

        public AuthController(IAuthRepository repo, IConfiguration config)
        
            _repo = repo;
            _config = config;
        

        [HttpPost("register")]
        public async Task<IActionResult> Register(UserForRegisterDto userForRegisterDto)
        
            // validate request

            userForRegisterDto.Username = userForRegisterDto.Username.ToLower();

            if (await _repo.UserExists(userForRegisterDto.Username))
                return BadRequest("User already exists");

            var userToCreate = new User
            
                Username = userForRegisterDto.Username
            ;

            var createdUser = await _repo.Register(userToCreate, userForRegisterDto.Password);

            return StatusCode(201);
        
    

我知道当客户端发出注册请求时,将调用 register() 方法,传入的用户名将从 DTO userForRegisterDto 设置用户名。在此之后,我们调用方法 UserExists() 来检查用户是否存在于我们的数据库中。

问题 1: 当 _repo 仅使用接口 IAuthRepository 时,它如何知道方法 UserExists() 中的逻辑?我知道 IAuthRepository 和类 AuthRepository 以某种方式链接,但我没有看到应用程序中发生 Constructor DI 的任何地方。我的怀疑是它与 ConfigureServices 方法下 startup.cs 中的这一行有关:

    public void ConfigureServices(IServiceCollection services)
    
        services.AddDbContext<DataContext>(x => x.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        services.AddCors();
        services.AddScoped<IAuthRepository, AuthRepository>(); //<---- This Line
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => 
            options.TokenValidationParameters = new TokenValidationParameters
            
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
                ValidateIssuer = false,
                ValidateAudience = false
            ;
        );
    

这两个“联通”后,就可以通过AuthRepository类访问UserExists()方法了:

public class AuthRepository : IAuthRepository

    private readonly DataContext _context;
    public AuthRepository(DataContext context)
    
        _context = context;
    

    public async Task<User> Login(string username, string password)
    

    

    private bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
    

    

    public async Task<User> Register(User user, string password)
    
        byte[] passwordHash, passwordSalt;
        CreatePasswordHash(password, out passwordHash, out passwordSalt);

        user.PasswordHash = passwordHash;
        user.PasswordSalt = passwordSalt;

        await _context.Users.AddAsync(user);
        await _context.SaveChangesAsync();

        return user;
    

    private void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
    


    

    public async Task<bool> UserExists(string username)
    
        if (await _context.Users.AnyAsync(x => x.Username == username))
            return true;

        return false;
    

我一直在阅读有关 AddScoped 方法及其作用的信息,但我不清楚情况是否如此。任何有关其工作原理的说明都会很棒。

问题 2: 这一个或多或少是一样的。如果我们继续跟踪请求的路径,我们将点击 AuthRepository 类中的 register() 方法。

问题 2: 当我也无法在任何地方发现构造函数 DI 的任何实例时,此类如何访问 DataContext _context 的属性?

如果需要,这是我的其余项目文件:

Startup.cs

public class Startup

    public Startup(IConfiguration configuration)
    
        Configuration = configuration;
    

    public IConfiguration Configuration  get; 

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    
        services.AddDbContext<DataContext>(x => x.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        services.AddCors();
        services.AddScoped<IAuthRepository, AuthRepository>();
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => 
            options.TokenValidationParameters = new TokenValidationParameters
            
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
                ValidateIssuer = false,
                ValidateAudience = false
            ;
        );
    

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    
        if (env.IsDevelopment())
        
            app.UseDeveloperExceptionPage();
        
        else
        
            //app.UseHsts();
        

        //app.UseHttpsRedirection();
        app.UseCors(x => x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
        app.UseAuthentication();
        app.UseMvc();
    

DataContext.cs

public class DataContext : DbContext

    public DataContext(DbContextOptions<DataContext> options) : base (options)
    public DbSet<Value> Values  get; set; 
    public DbSet<User> Users  get; set; 

非常感谢任何澄清和建议。谢谢大家。

【问题讨论】:

听起来您已经了解它是如何工作的。在您的代码中,您只需声明您的类需要哪些依赖项,假设它们会神奇地提供给您。在启动逻辑中,您指定您的依赖关系,有效地声明“谁需要 IDataContext,就给他们一个 DataContext 实例”。在运行时,依赖管理器将解析和实例化这些类,并以看似神奇的方式将它们提供给您的其他类。当然没有魔法,这只是“控制反转”。 【参考方案1】:

    你是对的。 services.AddScoped&lt;IAuthRepository, AuthRepository&gt;(); 行只是指示 ASP.NET Core 服务容器替换具体类 AuthRepository 的实例,只要它在运行时看到对 IAuthRepository 的引用。

    各种Add* 方法在注册接口=> 类的映射方面都做同样的事情,关键区别在于创建的类的scope,即它持续多长时间:

    AddScoped 类将在对服务器的每个请求开始时创建,并在每个请求结束时销毁。换句话说,每个请求都会导致创建该类的一个新实例。 AddSingleton 类在您的 ASP.NET Core 应用程序启动时创建,并在它关闭时被销毁。换句话说,您的应用程序中只存在该类的一个实例。 AddTransient 类在被请求时重新创建。换句话说,如果您站点上的一个页面两次使用相同的服务瞬态,就会创建两个实例。 (这与只创建一个实例的范围服务相比,因为每个页面都是一个请求。)

    更完整的解释,包括示例:https://***.com/a/38139500/70345

    为了通过创建您的类AuthRepository 的实例来实现 (1),服务容器需要调用该类的构造函数。容器检查您的类以找到第一个公共构造函数并检索该构造函数的任何参数,在本例中是DataContext 类的实例。然后容器在其内部类映射中搜索该类,并且由于您已通过services.AddDbContext&lt;DataContext&gt;(...) 注册了该映射,因此能够构造并返回该类实例。因此它能够将该实例传递给AuthRepository,因此AuthRepository 构造成功。

    AddDbContext 方法只是对AddScoped 的包装,它执行一些额外的脚手架以允许实体框架DbContexts 正常工作。

官方解释,refer to Microsoft's official page on DI and IoC。

【讨论】:

【参考方案2】:

问题 1 - 您在 Startup.cs 中的这一行提供了创建新对象 AuthRepository 的权利。对于这个例子,你必须知道DI容器根据接口和他自己的实现为你创建了一个AuthRepository对象,你只需要在正确的构造函数中传递一个接口。 AddScope() 与创建对象的生命周期有关。当您通过方法 AddScope() 注册对象时,将为单个请求创建对象,并在请求之后释放该对象。

问题 2 - 您的 dbContext 已在 DI 容器中注册。 AddDbContext() 是提供给实体框架dbContextes 注册的特定扩展方法。这行代码使用从 appSetting.json 文件中获取的连接字符串注册您的 dbContext。

services.AddDbContext<DataContext>(x => 
   x.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));

这个 DbContext 被注入到 AuthRepository 类的构造函数中,当你使用这个类时,DI 容器会为你创建 DbContext 实例。

private readonly DataContext _context;

public AuthRepository(DataContext context)

    _context = context;

【讨论】:

以上是关于我是 .NET Core 2.1 MVC 的新手,我无法理解一些事情是如何工作的的主要内容,如果未能解决你的问题,请参考以下文章

.Net Core MVC:尝试上传图像时控制器中出现 NullReferenceException

Asp.net core MVC post 参数始终为空

如何使用 EF Core 代码优先迁移为 ASP.NET Core MVC 配置 N 层架构

ASP.NET Core 2.1 MVC - 无法设置从 Web api 响应接收到的 cookie

从 ASP.NET MVC 迁移到 ASP.NET Core MVC

在 2 个控制器 ASP.NET Core 3.1 MVC 之间传递 ID