I am developing a new WebApi using .NetCore2.2, Autofac4, Dapper. There are few very basic questions because this is my first WebApi project. As part of this project I have to write both unit-test and integration-test.
My questions are as follows (Sample Code is give below):
What is recommended return type between "Task< IActionResult >" and "Task< IEnumerable >"?
Recommended object Scope of the dependencies in startup class for my project?
Do I really need UnitOfWork for this given project structure?
What are the flaws if I follow this design?
Is there any better way to design this API?
As TDD do I need write test cases for API layer(Controller) and Infrastructure layer only or Doman Layer (it doesn't have any logic) as well?
What are the scenario I must include in my controller unit test?
Domain Layer:
[Table("Movie")]
public class Movie
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID {get;set;}
public string Title {get;set;}
}
public interface ICommandRepository<T> where T : class
{
Task CreateAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
}
public interface IQueryRepository<T> where T : class
{
Task<IEnumerable<T>> GetAllMoviesAsync();
Task<IEnumerable<T>> GetMoviesByTitleAsync(string title);
Task<T> GetMovieByIDAsync(int id);
}
Infrastructure Layer:
public class MovieCommandContext : DbContext
{
public MovieCommandContext(DbContextOptions<MovieCommandContext> options)
: base(options)
{}
public DbSet<Movie> Movies { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
public class MovieQueryContext : IDisposable
{
private readonly IDbConnection connection;
public MovieQueryContext(string connectionString)
{
connection = new SqlConnection(connectionString);
}
public async Task<IEnumerable<Movie>> GetAllMovies()
{
// Use Dapper->QueryAsync
throw new NotImplementedException();
}
...
public void Dispose()
{
if (connection?.State == ConnectionState.Open)
connection.Close();
}
}
public class MovieCommandRepository : ICommandRepository<Movie>
{
private readonly MovieCommandContext context;
public MovieCommandRepository(MovieCommandContext dbContext)
{
context = dbContext;
}
public async Task CreateAsync(Movie movie)
{
await context.AddAsync<Movie>(movie);
await context.SaveChangesAsync();
}
public async Task UpdateAsync(Movie movie)
{
var entity = context.Attach<Movie>(movie);
context.Entry<Movie>(movie).State = EntityState.Modified;
await context.SaveChangesAsync();
}
public async Task DeleteAsync(Movie movie)
{
context.Remove<Movie>(movie);
await context.SaveChangesAsync();
}
}
public class MovieQueryRepository : IQueryRepository<Movie>
{
private readonly MovieQueryContext context;
public MovieQueryRepository(MovieQueryContext dbContext)
{
context = dbContext;
}
public async Task<IEnumerable<Movie>> GetAllMoviesAsync()
{
return await context.GetAllMovies();
}
public async Task<IEnumerable<Movie>> GetMoviesByTitleAsync(string title)
{
return await context.GetMovieByName(title);
}
public async Task<Movie> GetMovieByIDAsync(int id)
{
return await context.GetMovieByID(id);
}
}
API Layer:
[Route("api/sample")]
[ApiController]
public class SampleController : ControllerBase
{
private readonly ICommandRepository<Movie> movieCommand;
private readonly IQueryRepository<Movie> movieQuery;
public SampleController(ICommandRepository<Movie> command, IQueryRepository<Movie> query)
{
movieCommand = command;
movieQuery = query;
}
[HttpGet]
public async Task<IActionResult> GetMoviesAsync()
{
try
{
var movies = await movieQuery.GetAllMoviesAsync();
return Ok(movies);
}
catch
{
// TODO: Logging
return BadRequest();
}
}
[Route("{name:alpha}")]
[HttpGet]
public async Task<IActionResult> GetMoviesByTitle(string movieTitle)
{
try
{
var movies = await movieQuery.GetMoviesByTitleAsync(movieTitle);
return Ok(movies);
}
catch
{
// TODO: Logging
return BadRequest();
}
}
[Route("{movieID:int:min(1)}")]
[HttpGet]
public async Task<IActionResult> GetMovieByID(int movieID)
{
try
{
var movie = await movieQuery.GetMovieByIDAsync(movieID);
return Ok(movie);
}
catch
{
// TODO: Logging
return BadRequest();
}
}
[Route("")]
[HttpDelete("{id:int:min(1)}")]
public async Task<IActionResult> Delete(int id)
{
try
{
var movie = await movieQuery.GetMovieByIDAsync(id);
if (movie == null)
return BadRequest();
await movieCommand.DeleteAsync(movie);
return Ok();
}
catch
{
// TODO: Logging
return BadRequest();
}
}
}
Startup.cs:
private void ConfigureContainer(ContainerBuilder builder)
{
var contextOptions = new DbContextOptionsBuilder<MovieCommandContext>()
.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
.Options;
builder.RegisterType<MovieCommandContext>()
.WithParameter("options", contextOptions);
builder.RegisterType<MovieQueryContext>()
.AsSelf()
.WithParameter("connectionString",Configuration.GetConnectionString("DefaultConnection"));
builder.RegisterType<MovieCommandRepository>().As<ICommandRepository<Movie>>();
builder.RegisterType<MovieQueryRepository>().As<IQueryRepository<Movie>>();
}