Claudio Tejada
Claudio Tejada

Reputation: 239

I'm new to .NET Core 2.1 MVC and I'm having trouble understanding how a few things work

I'm currently following a .Net Core Angular 8 tutorial in Udemy. I'm able do get/post requests in Postman and I can also see what I've posted in a .db file using sqlite as my database and viewing the data through Db Browser. Everything seems to be working great but is all for nothing if I can't comprehend what's going on in some areas of the application. I would really appreciate it if someone could help me answer a few questions.

My entire project is in GitHub: https://github.com/cjtejada/ASP.NetCoreAngular8/tree/master/DatingApp.API

Problem 1: I have the following the following controller:

    [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);
        }
    }

I know that when the client makes a request to register, the register() method will be called and the Username that gets passed in will set the Username from DTO userForRegisterDto. After this then we call method UserExists() to check if the user exists in our database.

Question 1: How is _repo aware of the logic in method UserExists() when it is only using the interface IAuthRepository? I know that IAuthRepository and class AuthRepository are somehow linked but I don't see anywhere in the app where Constructor DI is happening. My suspicion is that it has something to do with this line in startup.cs under the ConfigureServices method :

    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
            };
        });
    }

After these two are "linked up", then the UserExists() method can be accessed through the AuthRepository class:

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;
    }
}

I've been reading about the AddScoped method and what it does but this is not clear to me that this is the case. Any clarification as to how this works would be great.

Problem 2: This one is more or less the same. If we keep following the path of the request we will hit the register() method in the AuthRepository class.

Question 2: How does this class have access to the properties of DataContext _context when I also can't spot any instances of constructor DI anywhere?

Here are the rest of my project files if needed:

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; }
}

Any clarifications and suggestions are greatly appreciated. Thanks, all.

Upvotes: 2

Views: 153

Answers (2)

pkantorowicz
pkantorowicz

Reputation: 41

Question 1 - You've right this line in Startup.cs provide creating a new object AuthRepository. For this example you must to know that DI container creates an AuthRepository object for you based on the interface and his own implementation and you only need to pass an interface in properly constructor. AddScope() is related with lifetime of created objects. When you register object by method AddScope() then the object will be created for a single request and after the request, the object will be disposed.

Question 2 - Your dbContext is registered in DI container. AddDbContext() is a specific extension method provided to registration of entity framework dbContextes. This line of code registers your dbContext with connection strings got from the appSetting.json file.

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

This DbContext is injected into the constructor of the AuthRepository class and when you use this class DI container created DbContext instance for you.

private readonly DataContext _context;

public AuthRepository(DataContext context)
{
    _context = context;
}

Upvotes: 1

  1. You are correct. The line services.AddScoped<IAuthRepository, AuthRepository>(); simply instructs the ASP.NET Core service container to substitute an instance of concrete class AuthRepository wherever it sees a reference to IAuthRepository at runtime.

    The various Add* methods all do the same thing under the hood regarding registering the mapping of interfaces => classes, the key difference is the scope of the created class, i.e. how long it persists for:

    • AddScoped classes will be created at the beginning of every request to the server, and destroyed at the end of every request. In other words, every request results in a new instance of that class being created.
    • AddSingleton classes are created when your ASP.NET Core application starts up, and are destroyed when it shuts down. In other words, only a single instance of that class exists within your application.
    • AddTransient classes are recreated whenever they are requested. In other words, if a page on your site used the same service transient twice, there would be two instances created. (Contrast this with a scoped service, where only a single instance would be created, as each page is a single request.)


    A fuller explanation, including examples: https://stackoverflow.com/a/38139500/70345

  2. In order to fulfill (1) by creating an instance of your class AuthRepository, the service container needs to call that class's constructor. The container inspects your class to find the first public constructor and retrieves any arguments to that constructor, in this case an instance of the DataContext class. The container then searches its internal class mappings for that class and, because you have registered that mapping via services.AddDbContext<DataContext>(...), is able to construct and return the class instance. Thus it's able to pass that instance to AuthRepository, so AuthRepository is constructed successfully.

    The AddDbContext method is simply a wrapper around AddScoped, that performs some additional scaffolding to allow Entity Framework DbContexts to work correctly.

For the official explanation, refer to Microsoft's official page on DI and IoC.

Upvotes: 3

Related Questions