AOP通过Generic Repository上的Decorator模式实现 [英] AOP implemented with the Decorator pattern over a Generic Repository

查看:183
本文介绍了AOP通过Generic Repository上的Decorator模式实现的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试构建一个将Aspect Oriented Programming应用于使用Decorators的项目的原型。我的项目的一些部分将使用通用存储库(对于简单的CRUD),但是最终我还将结合Command和Query处理程序(这些将执行特定的任务,如ProcessCustomerOrders等)。此外,我想在这里举例说明的交叉关切问题是安全性和日志记录。


此外,我知道我的示例代码不是使用Decorator模式,而只是我已经到位的代码示例对于这个原型来提供一个上下文。


我明白有其他方法来实现AOP(或交叉关注),如Proxy或代码编织模式,但我不熟悉这些模式,因此不了解他们之间的权衡。



我正在使用控制台应用程序我的问题是:



blockquote>

(1)我如何使用简单注射器(在引导类中)连线,并仍然保持相同的顺序?



(2)这是装饰模式的正确使用(因为我没有使用基本的抽象或接口类或装饰器基础)?



(3)是否一个干净的方式来利用多个实现在同一个Repository中不会注入两个不同版本的ILogger服务(例如DatabaseLogger和ConsoleLogger)? (4)实际的日志记录是在Repository方法中实现的,而ILogger服务被注入到Repository类中,但是比硬线更好的方法记录器还是使用通用存储库?



(5)我应该使用代理或代码编织模式,基于我在这个原型中使用Repository吗? / p>

此外,欢迎对此设计的一般批评。



原型代码:

  public class Program 
{
public static void Main ] args)
{
var e = new Entity
{
Id = 1,
Name =Example Entity,
描述=由装饰者,
RowGuild = Guid.NewGuid()
};

控制器控制器=
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;组; }
public string Name {get;组; }
public string描述{get;组; }
public Guid RowGuild {get;组; }
public byte [] RowVersion {get;组; }
}

public class Controller
{
private readonly IGenericRepository< Entity> _repository;
private readonly IUserSecurity _userSecurity;

public Controller(IGenericRepository< Entity> repository,IUserSecurity userSecurity)
{
_repository = repository;
_userSecurity = userSecurity;
}

//显示网页上的所有实体视图
public ActionResult Index(){
IEnumerable< Entity> e = null;
用户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>其中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 +
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 +
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)
{
//数据库记录
}
}

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)
{
//验证MVC用户

返回true;
}
}

更新基于@ Steven的回答: / strong>



装饰器和存储库的简单注入器DI:

  public static class RepositoryBoostrapper 
{
public static void Bootstrap(Container container)
{
container.RegisterOpenGeneric(
typeof(IGenericRepository& ;),
typeof(GenericRepository<));

container.RegisterDecorator(
typeof(IGenericRepository;),
typeof(LoggingRepositoryDe​​corator;));

container.RegisterDecorator(
typeof(IGenericRepository&s)),
typeof(SecurityRepositoryDe​​corator));

}
}

Decorator链的顺序为控制器调用应该是Controller(检查)> Security(如果OK继续,然后允许调用)> Repo(更新持久层然后)> Log(到某些设施)>并返回到Controller。



新的Controller类:

  public class Controller 
{
private readonly IGenericRepository< Entity> securityGenericRepository;

public Controller(
IGenericRepository< Entity> securityGenericRepository)
{
this.securityGenericRepository = securityGenericRepository;
}

//显示网页上的所有实体视图
public bool Index(){
var e = new Entity
{
Id = 1,
Name =Example Entity,
描述=由Decorators使用,
RowGuild = Guid.NewGuid()
};
this.securityGenericRepository.CreateT(e);
返回false;
}

public ActionResult Create(Entity e){
if(ModelState.IsValid)
{
this.securityGenericRepository.CreateT(e);
return RedirectToAction(Index);
}
return View(e);
}
}

有关上述代码摘录的问题:



如果要根据返回值在Controller中执行某些操作(例如从Security Decorator返回bool),那么我需要修改IGenericRepository接口(因此GenericRepository类)?在某种意义上,由于Repo和安全装饰器类都实现相同的接口,如果要更改Security方法的返回值或参数,我还需要更改Repository方法? / p>

另外,我现在只将IGenericRepository的Security实现传递给Controller吗?



另外,记录器已被更改为如下所示:

  public class LoggingRepositoryDe​​corator< T> :IGenericRepository< T> 
{
private readonly IGenericRepository< T> decoratee;
私人只读ILogger记录器;

public LoggingRepositoryDe​​corator(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 +执行在+
watch.ElapsedMilliseconds +ms。);
}
// ...
}

我只是打电话给Decoratee,并在上面添加了Decorator的功能。



最后安全装饰器:

  public class SecurityRepositoryDe​​corator< T> :IGenericRepository< T> 
{
private readonly IGenericRepository< T> decoratee;
私有只读IUserSecurity userSecurity;
私人用户用户;

public SecurityRepositoryDe​​corator(
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);
}
// ...
}

不明白以上是在哪里/什么时候记录器被调用?



更新2:



看起来像现在的装饰图案一样工作;感谢史蒂芬所有的伟大的答案。



原型主要功能:

  public static void Main(string [] args)
{
var container = new Container();
PrototypeBoostrapper.Bootstrap(container);

IRepository< Entity>存储库=
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 =示例实体,
描述=由Decorators使用,
RowGuild = Guid.NewGuid()
};

controller.Create(e);
}

验证(安全)装饰:

  public class ValidateUserDecorator< T> :IRepository< T> 
{
private readonly IRepository< T> decoratee;
// private readonly IUserSecurity userSecurity;
private IPrincipal User {get;组;

public ValidateUserDecorator(
IRepository< T> decoratee,
IPrincipal principal)
{
this.decoratee = decoratee;
User = principal;
}

// ..
public void CreateT(T entity)
{
if(!User.IsInRole(ValidRoleToExecute))
抛出新的ValidationException();
this.decoratee.CreateT(entity);
}
// ..

记录装饰器:

  public class LoggingDecorator< T> :IRepository< T> 
{
private readonly IRepository< T> decoratee;
私人只读ILogger记录器;

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 +执行在+
watch.ElapsedMilliseconds +ms。);
}
// ..

通用存储库:

  public class Repository< T> :IRepository< T>其中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();
}
// ..

控制器:

  public class Controller 
{
private readonly IRepository< Entity>库;

public Controller(
IRepository< Entity> repository){
this.repository = repository;
}
// ..
public bool Create(Entity e){
this.repository.CreateT(e);
返回true;
}
// ..


解决方案

blockquote>

(1)如何使用简单的注入器(在引导
类)中连线,并仍然保持相同的顺序,




简单注射器包含一个可用于注册装饰器的 RegisterDecorator 方法。 (保证是)按照注册的顺序申请注册装潢师。示例:

  container.RegisterOpenGeneric(
typeof(IGenericRepository& b $ b typeof(GenericRepository<));

container.RegisterDecorator(
typeof(IGenericRepository;),
typeof(LoggingRepositoryDe​​corator;));

container.RegisterDecorator(
typeof(IGenericRepository&s)),
typeof(SecurityRepositoryDe​​corator));

此配置确保每次 IGenericRepository< T> 被请求,返回一个 GenericRepository< T> ,它被一个 LoggingRepository< T> SecurityRepository< T> 包装。最后一个注册的装饰器将是最外层的装饰器。


(2)这是装饰模式的正确使用(因为我' m不是
使用基本的抽象或接口类或装饰器基础)


我不知道你目前是做事;我的代码中没有看到任何装饰器。但有一件事是错误的。您的 GenericRepository< T> 使用 ILogger ,但日志记录是一个横切关注的问题。它应该放在装饰器中。该装饰器可能如下所示:

  public LoggingRepositoryDe​​corator&T :IGenericRepository< T> {
private IGenericRepository< T> decoratee;
私人ILogger _logger;

public LoggingRepositoryDe​​corator(IGenericRepository< T> decoratee,
ILogger logger){
this.decoratee = decoratee;
this._logger = logger;
}

public T ReadTById(object id){return this.decoratee.ReadTById(id); }
public IEnumerable< T> ReadTs(){return this.decoratee.ReadTs(); }

public void UpdateT(T entity){
var watch = Stopwatch.StartNew();

this.decoratee.UpdateT(entity);

_logger.Log(typeof(T).Name +in+
watch.ElapsedMilliseconds +ms。);
}

public void CreateT(T entity){
var watch = Stopwatch.StartNew();

this.decoratee.CreateT(entity);

_logger.Log(typeof(T).Name +in+
watch.ElapsedMilliseconds +ms。);
}

public void DeleteT(T entity){this.decoratee.DeleteT(entity);
}




(3)是否有干净的方式使用不同注册两个版本的不同版本的

中的ILogger服务(例如DatabaseLogger和ConsoleLogger)的多个实现


这取决于您的需求,但复合模式代理模式可能在此处有所帮助。复合模式允许您在该事物的界面后面隐藏东西的集合。例如:

  public class CompositeLogger:ILogger {
private readonly IEnumerable< ILogger>记录仪;

public CompositeLogger(IEnumerable< ILogger> loggers){
this.loggers = loggers;
}

public void Log(string message){
foreach(var.logger in this.loggers){
logger.Log(message);
}
}
}

您可以注册如下:

  //注册IEnumerable< ILogger> 
container.RegisterCollection< ILogger>(new [] {
typeof(DatabaseLogger),
typeof(ConsoleLogger)
});

//注册依赖于IEnumerable< ILogger>的ILogger(CompositeLogger)
container.Register< ILogger,CompositeLogger>(Lifestyle.Singleton);

另一方面,代理模式可以隐藏一些关于如何根据消息内部的消息代理。示例:

  public class LoggerSelector:ILogger {
private readonly ILogger left;
私人只读ILogger权利;

public LoggerSelector(ILogger left,ILogger right){
this.left = left;
this.right = right;
}

public void Log(string message){
var logger = this.SelectLogger(message);
logger.Log(message);
}

private ILogger SelectLogger(string message){
return message.Contains(fatal)? this.left:this.right;
}
}

您可以注册如下:

  container.Register< ConsoleLogger>(); 
container.Register< DatabaseLogger>();

container.Register< ILogger>(()=> new LoggerSelector(
left:container.GetInstance< ConsoleLogger>(),
right:container.GetInstance< DatabaseLogger> ());




(4)实际的日志记录是在Repository方法和
ILogger服务注入到Repository类中,但是有一个
更好的方式来做这个比硬记录器还要使用
通用存储库?


绝对是:不要将记录器注入到存储库中,因为这是一个交叉的问题,您可能会更快地更改日志记录比你更改通用存储库代码的其余部分,所以你应该编写一个装饰器。



很高兴,因为你创建了一个通用的存储库接口,必须编写一个通用装饰器来将日志记录行为添加到存储库nce存储库界面有5个成员,您的装饰器将需要实现所有这些。但是你不能怪这个装饰师;这是违反存储库模式。 org / wiki / Interface_segregation_principlerel =nofollow>界面隔离原则。



更新:


私有只读IGenericRepository securityGenericRepository;


这样的仓库。安全和伐木是交叉的关切,消费者不必了解其存在。如果您决定在安全性发生之前需要触发额外的交叉关切,该怎么办?您是否要将所有您的 securityGenericRepository 依赖关系重命名为 fooGenericRepository ?这将破坏装饰器的全部目的:它们允许您动态地插入新的交叉问题,而无需更改应用程序中的单行代码。


如果我想根据返回值在控制器中采取一些操作


真的是你所需要的特别是为了安全。在这个级别你通常只需要检查并抛出异常。您不想在控制器中捕获此类异常,更不用说要返回值。



这样的安全装饰器通常意味着可以防止邪恶的人从你的系统做坏事。投掷 SecurityException 是正确的事情。这种异常将被记录,并由您的团队或支持人员接管。您可能想要做的是向用户显示一个友好的消息,当他们点击当前角色不允许的按钮,而不应该阻止向用户显示此按钮。



您还可以通过实现 Application_Error 事件来显示用户友好的消息,并检查 SecurityException

请记住,装饰器实现相同的抽象当它包装。这意味着您无法使用装饰器更改抽象(并且不能返回不同的东西)。如果这是您需要的,您的消费者将不得不依靠不同的东西。但请注意,这不是一个非常常见的情况,所以你必须非常努力,如果这真的是你需要的。



在我正在处理的系统中现在,我的Windows窗体类依赖于一个 IPromptableCommandHandler< TCommand> 而不是 ICommandHandler< TCommand> 。这是因为我们想向用户展示一个对话框,解释他们输入的数据是无效的(一些数据只能由服务器验证),除了命令之外,我们还传递一个允许promptable命令处理程序的委托调用命令成功处理。提示的命令处理程序实现本身取决于一个 ICommandHandler< TCommand> ,并委托工作并捕获任何返回的 ValidationException 来自 WCF 服务。这样可以防止每个窗体都有一个丑的try-catch块。仍然解决方案不是很好,当我得到一个更好的解决方案时,我会改变。



但是,即使有这样的解决方案,你可能还是想创建一个具有安全性的装饰器,并具有包含catch语句的代理(在我的例子中是可提示的命令处理程序)。不要尝试返回与装饰器不同的东西。


上面我不明白的是,在哪里/什么时候记录器得到调用?


与两个装饰器的注册确保当 IGenericRepositotory< Customer> 被请求,构造了以下对象图:

  IGenericRepository< Customer> (
new GenericRepository< Customer>(
new ClientManagementContext()),
DatabaseLogger(),(
new ClientReader
new AspNetUserSecurity());

当控制器调用存储库创建方法,将执行以下调用链:

  Begin SecurityRepositoryDe​​corator< Customer> .Create (调用`userSecurity.ValidateUser`)
开始LoggingRepositoryDe​​corator.Create(调用`Stopwatch.StartNew()`)
开始GenericRepository< Customer> .Create
结束GenericRepository&客户> .Create
End LoggingRepositoryDe​​corator.Create(调用`this.logger.Log`)
End SecurityRepositoryDe​​corator< Customer> .Create

所以,安全装饰器调用日志装饰器,因为安全装饰器包装日志装饰器(并且日志装饰器包裹 GenericRepository< T> )。



。您的方法命名的存储库真的很丑陋。以下是一些提示:




  • 调用界面 IRepository< T> code> IGenericRepository< T> (因为 T 意味着它实际上是通用的)。

  • 从方法中删除所有的 T postfixes;当您定义关闭的存储库时,它们没有意义。例如, IRepository< Customer> .CreateT 做什么?在 IRepository< Customer> 的上下文中为'T'?一个更好的名字将是 CreateCustomer ,但这是不可能的,因为 IRepository< Order> .CreateCustomer 不会任何意义通过命名它 IRepository< T>。创建所有这些问题都消失了。


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

解决方案

(1) How do I wire this up using Simple Injector (in the bootstrap class) and still keep the ordering the same,

Simple Injector contains a RegisterDecorator method that can be used to register decorators. Registered decorators are (guaranteed to be) applied in the order in which they are registered. Example:

container.RegisterOpenGeneric(
    typeof(IGenericRepository<>), 
    typeof(GenericRepository<>));

container.RegisterDecorator(
    typeof(IGenericRepository<>), 
    typeof(LoggingRepositoryDecorator<>));

container.RegisterDecorator(
    typeof(IGenericRepository<>), 
    typeof(SecurityRepositoryDecorator<>));

This configuration ensures that every time an IGenericRepository<T> is requested, an GenericRepository<T> is returned which is wrapped with an LoggingRepository<T> which is wrapped by an SecurityRepository<T>. The last registered decorator will be the outer-most decorator.

(2) Is this the proper use of the Decorator Pattern (since I'm not using the base abstract or interface class or decorator base)

I'm not sure how you're currently doing things; I don't see any decorators in your code. But one thing is wrong. Your GenericRepository<T> uses the ILogger, but logging is a cross-cutting concern. It should be placed in a decorator. That decorator might look like this:

public LoggingRepositoryDecorator<T> : IGenericRepository<T> {
    private IGenericRepository<T> decoratee;
    private ILogger _logger;

    public LoggingRepositoryDecorator(IGenericRepository<T> decoratee,
        ILogger logger) {
        this.decoratee = decoratee;
        this._logger = logger;
    }

    public T ReadTById(object id) { return this.decoratee.ReadTById(id); }
    public IEnumerable<T> ReadTs() { return this.decoratee.ReadTs(); }

    public void UpdateT(T entity) {
        var watch = Stopwatch.StartNew();

        this.decoratee.UpdateT(entity);

        _logger.Log(typeof(T).Name + " executed in " + 
            watch.ElapsedMilliseconds + " ms.");    
    }

    public void CreateT(T entity)  {
        var watch = Stopwatch.StartNew();

        this.decoratee.CreateT(entity); 

        _logger.Log(typeof(T).Name + " executed in " + 
            watch.ElapsedMilliseconds + " ms.");    
    }

    public void DeleteT(T entity) { this.decoratee.DeleteT(entity); }
}

(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?

It depends on your needs, but either the Composite Pattern or the Proxy pattern might be of help here. The Composite pattern allows you to hide a collection of 'things' behind an interface of that thing. For instance:

public class CompositeLogger : ILogger {
    private readonly IEnumerable<ILogger> loggers;

    public CompositeLogger(IEnumerable<ILogger> loggers) {
        this.loggers = loggers;
    }

    public void Log(string message) {
        foreach (var logger in this.loggers) {
            logger.Log(message);
        }        
    }    
}

You can register this as follows:

// Register an IEnumerable<ILogger>
container.RegisterCollection<ILogger>(new[] {
    typeof(DatabaseLogger), 
    typeof(ConsoleLogger)
});

// Register an ILogger (the CompositeLogger) that depends on IEnumerable<ILogger>
container.Register<ILogger, CompositeLogger>(Lifestyle.Singleton);

With the proxy pattern on the other hand you could hide some decision about how to root the message inside the proxy. Example:

public class LoggerSelector : ILogger {
    private readonly ILogger left;
    private readonly ILogger right;

    public LoggerSelector(ILogger left, ILogger right) {
        this.left = left;
        this.right = right;
    }

    public void Log(string message) {
        var logger = this.SelectLogger(message);
        logger.Log(message);
    }

    private ILogger SelectLogger(string message) {
        return message.Contains("fatal") ? this.left : this.right;
    }
}

You can register this as follows:

container.Register<ConsoleLogger>();
container.Register<DatabaseLogger>();

container.Register<ILogger>(() => new LoggerSelector(
    left: container.GetInstance<ConsoleLogger>(),
    right: container.GetInstance<DatabaseLogger>());

(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?

Absolutely: don't inject the logger into the repository, since this is a cross-cutting concern. You will probably be changing the logging logic much sooner than you will change the rest of the generic repository code. So you should write a decorator instead.

Happily, since you created a generic interface for your repositories, you will only have to write one generic decorator for adding logging behavior to repositories. Unfortunately, since the repository interface has 5 members, your decorators will need to implement all of them. But you can't blame decorators for this; it's the Repository pattern itself that violates the Interface Segregation Principle.

UPDATE:

private readonly IGenericRepository securityGenericRepository;

You shouldn't name your repository like this. Security and logging are cross-cutting concerns and the consumer should not have to know about their existence. What if you decide you need an extra cross-cutting concern that should be triggered before the security goes off? Are you going to rename all your securityGenericRepository dependencies to fooGenericRepository? That would defeat the whole purpose of having decorators: they allow you to plug in new cross-cutting concerns dynamically, without having to change a single line of code in your application.

If I want to take some action in the Controller based on a return value

Think hard if that really is what you need. Especially for security. At that level you should usually only want to check and throw an exception. You don't want to catch such exception in your controllers, let alone that you want to return a value.

Such a security decorator is usually meant as safety mechanism to prevent evil doers from doing bad things with your system. Throwing SecurityException is the right thing to do. Such exception will be logged and will be picked up by your team or by support. What you are probably trying to do is to show users a friendly message when they clicked a button that their current role doesn't allow, but instead you should prevent showing this button to the user.

And you might still show the user a friendly message by implementing the Application_Error event and checking whether a SecurityException was thrown and redirecting the user to a page that explains that they unfortunately tried to accessed a page that the system didn't allow access to. But IMO, if the user sees that page, they either are 'hacking' the system, or you made a programming mistake.

Please remember that a decorator implements the same abstraction as it wraps. This means that you can't change the abstraction (and can't return something different) with an decorator. If this is what you need, your consumer will have to depend on something different. But please note that this is not a very common scenario, so you have to think really hard if this really is what you need.

In a system I'm working on right now, my Windows forms classes depend on an IPromptableCommandHandler<TCommand> instead of ICommandHandler<TCommand>. That's because we wanted to show a dialog to the user that explained that the data they entered was invalid (some data can only be validated by the server) and besides the command, we pass in a delegate that allows the 'promptable command handler' to call back in case the command was handled successfully. The promptable command handler implementation itself depends on an ICommandHandler<TCommand> and delegates the work and catches any ValidationException that are returned from the WCF service. This prevents each form from having an ugly try-catch block. Still the solution isn't really nice, and I will change when I got a better solution.

But still, even with such solution, you probably still want to create a decorator that does security and have a proxy (the promptable command handler in my case) that contains the catch statement. Don't try to return something different from a decorator.

What I don't understand above is, where/when does the logger get called?

The registration with the two decorators ensures that when a IGenericRepositotory<Customer> is requested, the following object graph is constructed:

IGenericRepository<Customer> repository =
    new SecurityRepositoryDecorator<Customer>(
        new LoggingRepositoryDecorator<Customer>(
            new GenericRepository<Customer>(
                new ClientManagementContext()),
            DatabaseLogger(),
        new AspNetUserSecurity());

When a controller calls the repository Create method, the following call chain will be executed:

Begin SecurityRepositoryDecorator<Customer>.Create (calls `userSecurity.ValidateUser`)
    Begin LoggingRepositoryDecorator.Create (calls `Stopwatch.StartNew()`)
        Begin GenericRepository<Customer>.Create
        End GenericRepository<Customer>.Create
    End LoggingRepositoryDecorator.Create (calls ` this.logger.Log`)
End SecurityRepositoryDecorator<Customer>.Create

So, the security decorator calls the logging decorator, because security decorator wraps the logging decorator (and the logging decorator wraps the GenericRepository<T>).

ps. Your method naming for the repository is really ugly. Here are some tips:

  • Call the interface IRepository<T> instead of IGenericRepository<T> (because T implies that it is in fact generic).
  • Remove all the T postfixes from the methods; they have no meaning when you define closed repositories. For instance, what does IRepository<Customer>.CreateT do? What is 'T' in the context of an IRepository<Customer>? A better name would be CreateCustomer, but that is not possible, because IRepository<Order>.CreateCustomer wouldn't make any sense. By naming it IRepository<T>.Create all those problems go away.

这篇关于AOP通过Generic Repository上的Decorator模式实现的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆