Design Patterns for Webdev
Dependency Injection
- Overcome coupled classes

- highly coupled
- difficult to maintain
- not testable

NEW == GLUE
Dependency Injection
- Loosly coupled

Dependency Injection
- Loosely coupled
- Objects are created by DI container
- Configured in a central place
- Interface instead of concrete classes
- implementation can be changed
- testability
- DI in three ways
- constructor injection
- method injection
- property injection
Dependency Injection
- ASP.NET Core Dependency Injection
public class MyDependency : IMyDependency
{
private readonly ILogger<MyDependency> _logger;
public MyDependency(ILogger<MyDependency> logger)
{
_logger = logger;
}
public Task WriteMessage(string message)
{
_logger.LogInformation(
"MyDependency.WriteMessage called. Message: {MESSAGE}",
message);
return Task.FromResult(0);
}
}//startup.cs
public void ConfigureServices(IServiceCollection services)
{
//Demo (show with an example)
...
}Dependency Injection
-
Register additional services (groups of dependencies) with extension methods (Add...)
public void ConfigureServices(IServiceCollection services)
{
services.Add(new ServiceDescriptor(typeof(IX), typeof(X), ServiceLifetime.Scoped));
services.AddScoped<IX, X>();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
...
}Dependency Injection
//https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.0
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IAuthorRepository, AuthorRepository>();
services.AddScoped<IMessageRepository, MessageRepository>();
services.AddScoped<IBlackListRepository, BlackListRepository>();
services.AddTransient<IUnitOfWork, UnitOfWork>();
...
}- Lifetime of Dependencies
- Per (HTTP) Request
- Scoped
- Transient (tijdelijk)
- Created every time when requested
- Singelton (shared by all users & all request)
- Created once, shared everywhere
- Per (HTTP) Request
Trick to Check if object are the same
- In most debuggers, it's possible to "Make Object ID"
- Check if life-time is correct and object reference variable use the same object

Object Marker
N - Layer architecture

- Different Models for different "layers"
- ViewModel
- DataTransferModel (DTO)
- Conversion between models, with Mapper (Assambler Pattern)
Repository Pattern

Repository Pattern
- Separate business code from data retrieval and update logic
- Shielded from Low-level Details
- Any Data Source can be used (combination)
- SQL Code (ADO.NET), ORM Frameworks, File, Webservice, NoSQL database, etc
- Testability
- Mock with stubs
- Use a simple (in memory) database for testing
- Data access is not the responsibility of Business Logic
- SoC = Separation of Concerns
- ORM/Data-access details changes over time, new features other/frameworks
Repository Pattern

Repository behaves like a collection!
Repository Generic Interface
public interface IRepository<T> where T : class
{
T GetById(int id);
IEnumerable<T> Get();
IEnumerable<T> Get(Expression<Func<T, bool>> predicate);
void Insert(T entity);
void Delete(T entity);
void Update(T entity);
}Repository Generic Abstract base class
public abstract class Repository<T> : IRepository<T> where T : class
{
protected readonly DbContext _dbContext;
//Normally Repository works only with UnitOfWork!
//this is only for demonstration purpose, such that this repository
//also works without a UnitOfWork.
public Repository(DbContext dbContext)
{
_dbContext = dbContext;
}
//exposes IQueryable interface to outside world (with a public access modifier)
//is not a good idea!
protected IQueryable<T> AsQueryable()
{
return _dbContext.Set<T>().AsNoTracking();
}
public virtual T GetById(int id)
{
return _dbContext.Set<T>().Find(id);
}
public virtual IEnumerable<T> Get()
{
return _dbContext.Set<T>().AsEnumerable();
}
public virtual IEnumerable<T> Get(Expression<Func<T, bool>> predicate)
{
return _dbContext.Set<T>()
.Where(predicate)
.AsEnumerable();
}
public void Insert(T entity)
{
_dbContext.Set<T>().Add(entity);
}
public void Update(T entity)
{
_dbContext.Entry(entity).State = EntityState.Modified;
}
public void Delete(T entity)
{
_dbContext.Set<T>().Remove(entity);
}
}Specific Repository Implementation
public interface IAuthorRepository : IRepository<Author>
{
Author GetByIdWithOwner(int id);
}
public class AuthorRepository : Repository<Author>, IAuthorRepository
{
public AuthorRepository(BlogContext dbContext) : base(dbContext)
{
}
public Author GetByIdWithOwner(int id)
{
return AsQueryable().Include(x => x.Owner)
.FirstOrDefault(x => x.Id == id);
}
}Usage of Repository in Controller
[HttpGet(Name = nameof(GetAuthor))]
public ActionResult<IEnumerable<AuthorVM>> GetAuthor()
{
// var authorDtos = _db.Authors
// .Include(x => x.Owner)
// .Select(x => AuthorVM.From(x)).ToList();
var authors = _authorRepository.Get();
var authorDtos = _mapper.Map<IEnumerable<AuthorVM>>(authors);
// .Select(x => AuthorDto.From(x));
return Ok(authorDtos);
}
[HttpGet("{id}")]
public ActionResult<AuthorVM> GetAuthors(int id)
{
// var author = _db.Authors
// .Include(x => x.Owner)
// .SingleOrDefault(x => x.Id == id);
var author = _authorRepository.GetByIdWithOwner(id);
if (author == null)
{
return NotFound();
}
return Ok(AuthorVM.From(author));
}Mocking Repository to test controller
[Fact]
public void Get_ReturnsAuthorVm_VMModelHasCorrectAuthorValues()
{
// Arrange
var mockRepo = new Mock<IAuthorRepository>();
var author = new Author()
{
Id = 1,
BirthDate = new DateTime(2000, 1, 1),
Email = "test@test.com",
FirstName = "test1 firstname",
LastName = "test1 lastname",
Owner = new IdentityUser("AdminTest")
};
mockRepo.Setup(repo => repo.GetByIdWithOwner(1))
.Returns(author);
var controller = new SimpleControllerForTest(mockRepo.Object);
// Act
var result = controller.Get(1);
var okObjectResult = Assert.IsAssignableFrom<OkObjectResult>(result.Result);
var authorVm = Assert.IsAssignableFrom<AuthorVM>(okObjectResult.Value);
// Assert
Assert.Equal(author.Id, authorVm.Id);
Assert.Equal(author.FirstName + " " +author.LastName, authorVm.Name);
Assert.Equal(author.Owner.UserName, authorVm.OwnerName);
}Repository Result
- Complexity reduces because of abstractions
- more classes, interfaces
- Benefits
- keeps business logic free from data access code
- central class for data access
- maintainability, flexibility
- not tied to a data access implementation
- testability (mocks)
- Related Design Patterns
- Unit of Work (atomic work over multiple Repo's)
- Decorating a Repository (caching, logging, etc)
- Mappers (to and from View Models Models)
- The repository can be used in services (complex business logic)
Unit of Work
- Unit of Work = track changes to be made
- efficiency
- transaction management (atomic operation)
- commit or rollback
- Complex interactions with all or nothing behavior with multiple repositories
- Complex interaction = update, delete, insert
- all or nothing = transaction (concurrency issues)
- For example
- Subtract the amount x from account 1
- Add the amount x to account 2
- For example
- Saves database roundtrips
- when doing multiple C
RUD operations- updates/inserts/delete
- when doing multiple C
Unit of Work

Unit of Work
- An example without Unit of Work
- A lot of database interaction, for each Delete(...)
- What happens when code crashes?
- Author not Blacklisted, some message "f**k" deleted others not
[HttpGet("{id}")]
public ActionResult<bool> BlackListAuthorWithoutUnitOfWork(int id)
{
var author = _authorRepository.GetById(id);
bool blackList = false;
foreach (var message in _messageRepository.GetMessageForAuthor(author))
{
if (message.Description.Contains("f**k"))
{
_messageRepository.Delete(message);
//suppose something goes wrong with deleting or program crashes (Exception)!
//some messages are delete and others not
blackList = true;
}
}
if (blackList)
{
_blackListRepository.AddAuthorToBlacklist(author);
}
return Ok(blackList);
}Unit of Work
- An example with Unit of Work
- all database calls are sent once
[HttpGet("{id}")]
public ActionResult<bool> BlackListAuthorWithUnitOfWork(int id)
{
bool blackList = false;
var author = _authorRepository.GetById(id);
foreach (var message in _messageRepository.GetMessageForAuthor(author))
{
if (message.Description.Contains("f**k"))
{
_messageRepository.Delete(message);
//suppose something goes wrong with deleting or program crashes (Exception)!
//some message are delete and others not
blackList = true;
}
}
if (blackList)
{
_blackListRepository.AddAuthorToBlacklist(author);
_db.SaveChanges(); //Commit() the Unit Of Work
}
return Ok(blackList);
}Unit of Work
- Implementation
- In Entity Framework Core
- The DbContext is a Unit Of Work!!!
- Unit Of Work for free by Dependency Injection container
- Why are there so many crappy implementations on the internet?
- In Entity Framework Core
Unit of Work
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BlogContext>(options =>
options.UseSqlite("Data Source=Blog.db"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<BlogContext>();
services.AddScoped<IAuthorRepository, AuthorRepository>();
services.AddScoped<IMessageRepository, MessageRepository>();
services.AddTransient<IBlacklistService, BlacklistService>();- Unit Of Work == BlogContext == DbContext
- AddDbContext registers BlogContext as Scoped
- Lifetype is Scoped!
- The same BlogContext instance is injected into the Repo's by dependency injection
Unit of Work
- For each HTTP Request
- For each HTTP Request Create a Unit of Work
- AddDbContext<T> (lifetime scoped)
- For each HTTP Request Create a Unit of Work
-
Atomicity & Saves database calls!
- SaveChanges()
- Logical transactions
- Tracks changes
- Related to Repository Design Pattern
- they work together
Design Patterns for Webdev
By Joris Lops
Design Patterns for Webdev
- 567