I'm trying to build a prototype that applies Aspect Oriented Programming to my project using Decorators. Some portion of my project will use a generic Repository (for simple CRUD), but eventually I'll also incorporate Command and Query handlers (these will perform specific tasks like ProcessCustomerOrders, etc.). Also, the cross-cutting concerns I’d like to example here are Security and Logging.
Also, I know that my example code is not the using the Decorator pattern, but is just an example of the code I have in place for this prototype to provide a context.
I understand there are other ways to implement AOP (or cross-cutting concerns), like Proxy or Code Weaving patterns, but I'm not familiar with these patterns and therefore don't know trade-offs between them.
I'm using a console app here just to show how things will look if I "new" them up in a chained fashion.
My questions are:
(1) How do I wire this up using Simple Injector (in the bootstrap class) and still keep the ordering the same?
(2) Is this the proper use of the Decorator Pattern (since I'm not using the base abstract or interface class or decorator base)?
(3) Is there a clean way to make use of more than one implementation of a ILogger service (for example DatabaseLogger and ConsoleLogger) in the same Repository without injecting two different versions?
(4) The actual logging is implemented in the Repository method and the ILogger service is injected into the Repository class, but is there a better way to do this than hard wire up the logger and still use Generic Repositories?
(5) Should I be using Proxy or Code Weaving patterns based on how I'm using the Repository in this prototype?
Also, general critiques on this design are welcomed.
Prototype code:
public class Program
{
public static void Main(string[] args)
{
var e = new Entity
{
Id = 1,
Name = "Example Entity",
Description = "Used by Decorators",
RowGuild = Guid.NewGuid()
};
Controller controller =
new Controller(
new GenericRepository<Entity>(
new ClientManagementContext(),
new ConsoleLogger()
),
new WebUser()
);
controller.Create(e);
}
}
public static class RepositoryBoostrapper
{
public static void Bootstrap(Container container)
{
container.RegisterOpenGeneric(typeof(IGenericRepository<>), typeof(GenericRepository<>));
}
}
public class Entity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Guid RowGuild { get; set; }
public byte[] RowVersion { get; set; }
}
public class Controller
{
private readonly IGenericRepository<Entity> _repository;
private readonly IUserSecurity _userSecurity;
public Controller(IGenericRepository<Entity> repository, IUserSecurity userSecurity)
{
_repository = repository;
_userSecurity = userSecurity;
}
// Displays all Entities on a web page view
public ActionResult Index() {
IEnumerable<Entity> e = null;
User user = User.Identity.Name;
if (_userSecurity.ValidateUser(user))
{
e = _repository.ReadTs();
}
return View(e);
}
public ActionResult Create(Entity e) {
User user = User.Identity.Name;
if (_userSecurity.ValidateUser(user))
{
if (ModelState.IsValid)
{
_repository.CreateT(e);
return RedirectToAction("Index");
}
}
return View(e);
}
}
public interface IGenericRepository<T>
{
T ReadTById(object id);
IEnumerable<T> ReadTs();
void UpdateT(T entity);
void CreateT(T entity);
void DeleteT(T entity);
}
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
private readonly ClientManagementContext _context;
private readonly ILogger _logger;
public GenericRepository(ClientManagementContext context, ILogger logger)
{
_context = context;
_logger = logger;
}
public T ReadTById(object id) {
return _context.Set<T>().Find(id);
}
public IEnumerable<T> ReadTs() {
return _context.Set<T>().AsNoTracking().AsEnumerable();
}
public void UpdateT(T entity) {
var watch = Stopwatch.StartNew();
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
_logger.Log(typeof(T).Name +
" executed in " +
watch.ElapsedMilliseconds + " ms.");
}
public void CreateT(T entity) {
var watch = Stopwatch.StartNew();
_context.Entry(entity).State = EntityState.Added;
_context.SaveChanges();
_logger.Log(typeof(T).Name +
" executed in " +
watch.ElapsedMilliseconds + " ms.");
}
public void DeleteT(T entity) {
_context.Entry(entity).State = EntityState.Deleted;
_context.SaveChanges();
}
}
public class Logger
{
private readonly ILogger _logger;
public Logger(ILogger logger)
{
_logger = logger;
}
public void Log(string message)
{
_logger.Log(message);
}
}
public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
public class DatabaseLogger : ILogger
{
public void Log(string message)
{
// database logging
}
}
public interface IUserSecurity
{
bool ValidateUser(User user);
}
public class UserSecurity
{
private readonly IUserSecurity _userSecurity;
public UserSecurity(IUserSecurity userSecurity)
{
_userSecurity = userSecurity;
}
public bool ValidateUser(User user)
{
return _userSecurity.ValidateUser(user);
}
}
public class WebUser : IUserSecurity
{
public bool ValidateUser(User user)
{
// validate MVC user
return true;
}
}
UPDATE Based on @Steven's answer:
Simple Injector DI of Decorators and Repository:
public static class RepositoryBoostrapper
{
public static void Bootstrap(Container container)
{
container.RegisterOpenGeneric(
typeof(IGenericRepository<>),
typeof(GenericRepository<>));
container.RegisterDecorator(
typeof(IGenericRepository<>),
typeof(LoggingRepositoryDecorator<>));
container.RegisterDecorator(
typeof(IGenericRepository<>),
typeof(SecurityRepositoryDecorator<>));
}
}
The order of the Decorator chain as called by the Controller should be Controller (checks) > Security (if OK to proceed then allow call to) > Repo (update the persistence layer and then) > Log (to some facility) > and return back to Controller.
New Controller class:
public class Controller
{
private readonly IGenericRepository<Entity> securityGenericRepository;
public Controller(
IGenericRepository<Entity> securityGenericRepository)
{
this.securityGenericRepository = securityGenericRepository;
}
// Displays all Entities on a web page view
public bool Index() {
var e = new Entity
{
Id = 1,
Name = "Example Entity",
Description = "Used by Decorators",
RowGuild = Guid.NewGuid()
};
this.securityGenericRepository.CreateT(e);
return false;
}
public ActionResult Create(Entity e) {
if (ModelState.IsValid)
{
this.securityGenericRepository.CreateT(e);
return RedirectToAction("Index");
}
return View(e);
}
}
Question about the above code excerpt:
If I want to take some action in the Controller based on a return value (for example returning a bool from the Security Decorator), do I then have to modify the IGenericRepository interface (and therefore the GenericRepository class)? In a way this means, since the Repo and the Security Decorator classes both implement the same interface, if I want to make a change to the return value or parameters of the Security methods, I'll also need to change the Repository methods?
Also, do I only now pass in the Security implementation of the IGenericRepository to the Controller?
Also, the logger has been changed to look like the following:
public class LoggingRepositoryDecorator<T> : IGenericRepository<T>
{
private readonly IGenericRepository<T> decoratee;
private readonly ILogger logger;
public LoggingRepositoryDecorator(IGenericRepository<T> decoratee, ILogger logger)
{
this.decoratee = decoratee;
this.logger = logger;
}
// ...
public void CreateT(T entity)
{
var watch = Stopwatch.StartNew();
this.decoratee.CreateT(entity);
this.logger.Log(typeof(T).Name + " executed in " +
watch.ElapsedMilliseconds + " ms.");
}
// ...
}
Above, I just call to the Decoratee and add the Decorator's functionality on top.
And finally the Security Decorator:
public class SecurityRepositoryDecorator<T> : IGenericRepository<T>
{
private readonly IGenericRepository<T> decoratee;
private readonly IUserSecurity userSecurity;
private User user;
public SecurityRepositoryDecorator(
IGenericRepository<T> decoratee,
IUserSecurity userSecurity)
{
this.decoratee = decoratee;
this.userSecurity = userSecurity;
this.user = User.Identity.Name;
}
// ...
public void CreateT(T entity)
{
if (userSecurity.ValidateUser(user))
this.decoratee.CreateT(entity);
}
// ...
}
What I don't understand above is, where/when does the logger get called?
UPDATE 2:
Seems to work as the Decorator pattern should now; thanks to Steven for all the great answers.
Prototype Main function:
public static void Main(string[] args)
{
var container = new Container();
PrototypeBoostrapper.Bootstrap(container);
IRepository<Entity> repository =
new ValidateUserDecorator<Entity>(
new LoggingDecorator<Entity>(
new Repository<Entity>(
new PrototypeContext()),
new ConsoleLogger()),
new ClaimsPrincipal());
var controller = new Controller(repository);
var e = new Entity
{
Id = 1,
Name = "Example Entity",
Description = "Used by Decorators",
RowGuild = Guid.NewGuid()
};
controller.Create(e);
}
Validation (Security) Decorator:
public class ValidateUserDecorator<T> : IRepository<T>
{
private readonly IRepository<T> decoratee;
//private readonly IUserSecurity userSecurity;
private IPrincipal User { get; set; }
public ValidateUserDecorator(
IRepository<T> decoratee,
IPrincipal principal)
{
this.decoratee = decoratee;
User = principal;
}
//..
public void CreateT(T entity)
{
if (!User.IsInRole("ValidRoleToExecute"))
throw new ValidationException();
this.decoratee.CreateT(entity);
}
//..
Logging Decorator:
public class LoggingDecorator<T> : IRepository<T>
{
private readonly IRepository<T> decoratee;
private readonly ILogger logger;
public LoggingDecorator(IRepository<T> decoratee, ILogger logger)
{
this.decoratee = decoratee;
this.logger = logger;
}
// ..
public void CreateT(T entity)
{
var watch = Stopwatch.StartNew();
this.decoratee.CreateT(entity);
this.logger.Log(typeof(T).Name + " executed in " +
watch.ElapsedMilliseconds + " ms.");
}
// ..
Generic Repository:
public class Repository<T> : IRepository<T> where T : class
{
private readonly PrototypeContext _context;
public Repository(PrototypeContext context)
{
_context = context;
}
//..
public void CreateT(T entity) {
_context.Entry(entity).State = EntityState.Added;
_context.SaveChanges();
}
//..
The Controller:
public class Controller
{
private readonly IRepository<Entity> repository;
public Controller(
IRepository<Entity> repository) {
this.repository = repository;
}
// ..
public bool Create(Entity e) {
this.repository.CreateT(e);
return true;
}
// ..