使用装饰器模式在通用存储库上实现的

使用装饰器模式在通用存储库上实现的

本文介绍了使用装饰器模式在通用存储库上实现的 AOP的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 29岁程序员,3月因学历无情被辞! 我正在尝试构建一个原型,该原型使用装饰器将面向方面的编程应用于我的项目.我的项目的某些部分将使用通用存储库(用于简单的 CRUD),但最终我还将合并命令和查询处理程序(这些将执行特定任务,如 ProcessCustomerOrders 等).此外,我想在这里举例的跨领域关注点是安全性和日志记录.另外,我知道我的示例代码不是使用装饰器模式,而只是我为这个原型提供上下文的代码示例.我知道还有其他方法可以实现 AOP(或横切关注点),例如代理或代码编织模式,但我不熟悉这些模式,因此不知道它们之间的权衡.>我在这里使用一个控制台应用程序只是为了展示如果我以链接的方式更新"它们会是什么样子.我的问题是:(1) 如何使用 Simple Injector(在引导程序类中)将其连接起来并保持相同的顺序?(2) 这是装饰器模式的正确使用吗(因为我没有使用基础抽象或接口类或装饰器基础)?(3) 是否有一种干净的方法可以在不注入两个不同版本的情况下在同一个 Repository 中使用多个 ILogger 服务(例如 DatabaseLogger 和 ConsoleLogger)实现?(4) 实际的日志记录是在 Repository 方法中实现的,并且 ILogger 服务被注入到 Repository 类中,但是有没有比硬连接记录器并仍然使用通用存储库更好的方法来做到这一点?(5) 根据我在此原型中使用存储库的方式,我应该使用代理模式还是代码编织模式?此外,欢迎对此设计提出一般性批评.原型代码:公共类程序{public static void Main(string[] args){var e = 新实体{编号 = 1,Name = "示例实体",Description = "被装饰者使用",RowGuild = Guid.NewGuid()};控制器控制器 =新控制器(新的 GenericRepository(新的 ClientManagementContext(),新的 ConsoleLogger()),新的网络用户());控制器.创建(e);}}公共静态类 RepositoryBoostrapper{public static void Bootstrap(容器容器){container.RegisterOpenGeneric(typeof(IGenericRepository), typeof(GenericRepository));}}公共类实体{公共 int Id { 获取;放;}公共字符串名称{获取;放;}公共字符串 描述 { get;放;}公共 Guid RowGuild { 获取;放;}公共字节[] RowVersion { 获取;放;}}公共类控制器{私有只读 IGenericRepository_repository;私有只读 IUserSecurity _userSecurity;公共控制器(IGenericRepository 存储库,IUserSecurity userSecurity){_repository = 存储库;_userSecurity = 用户安全;}//在网页视图中显示所有实体公共动作结果索引(){IEnumerablee = 空;用户用户 = User.Identity.Name;if (_userSecurity.ValidateUser(user)){e = _repository.ReadTs();}返回视图(e);}公共 ActionResult 创建(实体 e){用户用户 = User.Identity.Name;if (_userSecurity.ValidateUser(user)){如果(模型状态.IsValid){_repository.CreateT(e);return RedirectToAction("索引");}}返回视图(e);}}公共接口 IGenericRepository{T ReadTById(对象ID);IEnumerableReadTs();void UpdateT(T entity);void CreateT(T entity);void DeleteT(T entity);}公共类 GenericRepository:IGenericRepository其中 T : 班级{私有只读 ClientManagementContext _context;私有只读 ILogger _logger;公共 GenericRepository(ClientManagementContext 上下文,ILogger 记录器){_context = 上下文;_logger = 记录器;}公共 T ReadTById(对象 ID){返回 _context.Set().Find(id);}公共 IEnumerable读Ts(){返回 _context.Set().AsNoTracking().AsEnumerable();}public void UpdateT(T entity) {var watch = Stopwatch.StartNew();_context.Entry(entity).State = EntityState.Modified;_context.SaveChanges();_logger.Log(typeof(T).Name +执行于" +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 +执行于" +watch.ElapsedMilliseconds + " ms.");}public void DeleteT(T entity) {_context.Entry(entity).State = EntityState.Deleted;_context.SaveChanges();}}公共类记录器{私有只读 ILogger _logger;公共记录器(ILogger 记录器){_logger = 记录器;}公共无效日志(字符串消息){_logger.Log(message);}}公共接口 ILogger{无效日志(字符串消息);}公共类 ConsoleLogger : ILogger{公共无效日志(字符串消息){Console.WriteLine(message);}}公共类 DatabaseLogger : ILogger{公共无效日志(字符串消息){//数据库日志}}公共接口 IUserSecurity{bool ValidateUser(用户用户);}公共类 UserSecurity{私有只读 IUserSecurity _userSecurity;公共用户安全(IUserSecurity userSecurity){_userSecurity = 用户安全;}public bool ValidateUser(用户用户){返回 _userSecurity.ValidateUser(user);}}公共类 WebUser : IUserSecurity{public bool ValidateUser(用户用户){//验证MVC用户返回真;}}更新基于@Steven 的回答:装饰器和存储库的简单注入器 DI:公共静态类 RepositoryBoostrapper{public static void Bootstrap(容器容器){container.RegisterOpenGeneric(typeof(IGenericRepository),typeof(GenericRepository<>));容器.注册装饰器(typeof(IGenericRepository),typeof(LoggingRepositoryDe​​corator));容器.注册装饰器(typeof(IGenericRepository),typeof(SecurityRepositoryDe​​corator));}}控制器调用的装饰器链的顺序应该是控制器(检查)> 安全(如果可以继续,则允许调用)> 回购(然后更新持久层)> 日志(到某个设施)>并返回控制器.新的控制器类:公共类控制器{私有只读 IGenericRepositorysecurityGenericRepository;公共控制器(IGenericRepositorysecurityGenericRepository){this.securityGenericRepository = securityGenericRepository;}//在网页视图中显示所有实体公共布尔索引(){var e = 新实体{编号 = 1,Name = "示例实体",Description = "被装饰者使用",RowGuild = Guid.NewGuid()};this.securityGenericRepository.CreateT(e);返回假;}公共 ActionResult 创建(实体 e){如果(模型状态.IsValid){this.securityGenericRepository.CreateT(e);return RedirectToAction("索引");}返回视图(e);}}关于上面代码摘录的问题:如果我想根据返回值在控制器中采取一些操作(例如从安全装饰器返回一个布尔值),我是否必须修改 IGenericRepository 接口(以及 GenericRepository 类)?这在某种程度上意味着,由于 Repo 和 Security Decorator 类都实现了相同的接口,如果我想更改 Security 方法的返回值或参数,我还需要更改 Repository 方法吗?另外,我现在才将 IGenericRepository 的安全实现传递给控制器​​吗?此外,记录器已更改为如下所示:公共类 LoggingRepositoryDe​​corator:IGenericRepository{私有只读 IGenericRepository;装饰者;私有只读 ILogger 记录器;public LoggingRepositoryDe​​corator(IGenericRepositorydecoratee, ILogger logger){this.decoratee = 装饰者;this.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 的功能.最后是安全装饰器:公共类 SecurityRepositoryDe​​corator:IGenericRepository{私有只读 IGenericRepository;装饰者;私有只读 IUserSecurity userSecurity;私人用户用户;public SecurityRepositoryDe​​corator(IGenericRepository<T>装饰者,IUserSecurity userSecurity){this.decoratee = 装饰者;this.userSecurity = userSecurity;this.user = User.Identity.Name;}//...public void CreateT(T entity){如果 (userSecurity.ValidateUser(user))this.decoratee.CreateT(entity);}//...}上面我不明白的是,记录器在何处/何时被调用?更新 2:现在看起来像装饰者模式一样工作;感谢史蒂文的所有精彩答案.原型主函数:public static void Main(string[] args){var 容器 = 新容器();PrototypeBoostrapper.Bootstrap(容器);IRepository存储库 =新的 ValidateUserDecorator(新的 LoggingDecorator(新存储库(新原型上下文()),新的 ConsoleLogger()),new ClaimsPrincipal());var controller = new Controller(repository);var e = 新实体{编号 = 1,Name = "示例实体",Description = "被装饰者使用",RowGuild = Guid.NewGuid()};控制器.创建(e);}验证(安全)装饰器:公共类ValidateUserDecorator:IRepository<T>{私有只读 IRepository<T>装饰者;//私有只读 IUserSecurity userSecurity;私人 IPrincipal 用户 { get;放;}公共验证用户装饰器(IRepository<T>装饰者,IPrincipal 校长){this.decoratee = 装饰者;用户 = 委托人;}//..public void CreateT(T entity){if (!User.IsInRole("ValidRoleToExecute"))抛出新的验证异常();this.decoratee.CreateT(entity);}//..日志装饰器:公共类 LoggingDecorator:IRepository<T>{私有只读 IRepository<T>装饰者;私有只读 ILogger 记录器;public LoggingDecorator(IRepositorydecoratee, ILogger logger){this.decoratee = 装饰者;this.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;:IRepository<T>其中 T : 班级{私有只读 PrototypeContext _context;公共存储库(PrototypeContext 上下文){_context = 上下文;}//..public void CreateT(T entity) {_context.Entry(entity).State = EntityState.Added;_context.SaveChanges();}//..控制器:公共类控制器{私有只读 IRepository存储库;公共控制器(IRepository存储库){this.repository = 存储库;}//..公共布尔创建(实体 e){this.repository.CreateT(e);返回真;}//.. 解决方案 Simple Injector 包含一个 RegisterDecorator 方法,可用于注册装饰器.注册的装饰器(保证)按照它们注册的顺序应用.示例:container.RegisterOpenGeneric(typeof(IGenericRepository),typeof(GenericRepository<>));容器.注册装饰器(typeof(IGenericRepository),typeof(LoggingRepositoryDe​​corator));容器.注册装饰器(typeof(IGenericRepository),typeof(SecurityRepositoryDe​​corator));此配置确保每次请求 IGenericRepository 时,都会返回一个 GenericRepository,并用 LoggingRepository 包装起来. 由 SecurityRepository 包装.最后注册的装饰器将是最外层的装饰器.(2) 这是装饰者模式的正确使用吗(因为我不是使用基础抽象或接口类或装饰器基础)我不确定您目前的工作情况;我在您的代码中没有看到任何装饰器.但有一件事是错误的.您的 GenericRepository 使用 ILogger,但日志记录是一个横切关注点.它应该放在装饰器中.该装饰器可能如下所示:public LoggingRepositoryDe​​corator:IGenericRepository{私有 IGenericRepository<T>装饰者;私人 ILogger _logger;public LoggingRepositoryDe​​corator(IGenericRepositorydecoratee,ILogger 记录器) {this.decoratee = 装饰者;this._logger = 记录器;}public T ReadTById(object id) { return this.decoratee.ReadTById(id);}公共 IEnumerableReadTs() { 返回 this.decoratee.ReadTs();}public void UpdateT(T entity) {var watch = Stopwatch.StartNew();this.decoratee.UpdateT(entity);_logger.Log(typeof(T).Name + " 执行于 " +watch.ElapsedMilliseconds + " ms.");}public void CreateT(T entity) {var watch = Stopwatch.StartNew();this.decoratee.CreateT(entity);_logger.Log(typeof(T).Name + " 执行于 " +watch.ElapsedMilliseconds + " ms.");}public void DeleteT(T entity) { this.decoratee.DeleteT(entity);}}(3) 是否有一种干净的方法来使用多个实现中的 ILogger 服务(例如 DatabaseLogger 和 ConsoleLogger)没有注入两个不同版本的同一个 Repository?这取决于您的需求,但是复合模式或代理模式 在这里可能会有所帮助.Composite 模式允许您将一组事物"隐藏在该事物的接口后面.例如:public class CompositeLogger : ILogger {私有只读 IEnumerable记录员;公共 CompositeLogger(IEnumerable 记录器){this.loggers = 记录器;}公共无效日志(字符串消息){foreach(this.loggers 中的 var 记录器){logger.Log(message);}}}您可以按如下方式注册://注册一个IEnumerablecontainer.RegisterCollection(new[] {类型(数据库记录器),typeof(ConsoleLogger)});//注册一个依赖于 IEnumerable 的 ILogger(CompositeLogger)container.Register<ILogger, CompositeLogger>(Lifestyle.Singleton);另一方面,使用代理模式,您可以隐藏一些关于如何在代理内部根植消息的决定.示例:public class LoggerSelector : ILogger {私有只读 ILogger 离开;私有只读 ILogger 权限;公共记录器选择器(ILogger 左,ILogger 右){this.left = 左;this.right = 正确;}公共无效日志(字符串消息){var logger = this.SelectLogger(message);logger.Log(message);}私人 ILogger SelectLogger(字符串消息){返回 message.Contains("fatal") ?this.left : this.right;}}您可以按如下方式注册:container.Register();container.Register();container.Register(() => new LoggerSelector(左:container.GetInstance(),右:container.GetInstance());(4) 实际的日志记录在 Repository 方法和ILogger 服务注入到 Repository 类中,但是有没有比硬接线记录器并仍然使用更好的方法来做到这一点通用存储库?绝对:不要将记录器注入存储库,因为这是一个跨领域的问题.您可能会比更改通用存储库代码的其余部分更快地更改日志记录逻辑.所以你应该写一个装饰器.令人高兴的是,由于您为存储库创建了通用接口,因此您只需编写一个通用装饰器即可将日志记录行为添加到存储库.不幸的是,由于存储库接口有 5 个成员,您的装饰器将需要实现所有成员.但是你不能为此责怪装饰者;存储库模式本身违反了接口隔离原则.更新:私有只读 IGenericRepository securityGenericRepository;您不应该这样命名您的存储库.安全和日志记录是交叉关注的问题,消费者不应该知道它们的存在.如果您决定需要一个额外的跨领域关注点,该关注点应该在安全性消失之前触发怎么办?您是否要将所有 securityGenericRepository 依赖项重命名为 fooGenericRepository?这将违背使用装饰器的全部目的:它们允许您动态插入新的横切关注点,而无需更改应用程序中的任何一行代码.如果我想根据返回值在控制器中采取一些行动如果这真的是您所需要的,请仔细想想.特别是为了安全.在那个级别,您通常只想检查并抛出异常.你不想在你的控制器中捕获这样的异常,更不用说你想返回一个值了.这样的安全装饰器通常意味着安全机制,以防止不法分子对您的系统做坏事.抛出 SecurityException 是正确的做法.此类异常将被记录并由您的团队或支持人员处理.您可能想要做的是在用户单击其当前角色不允许的按钮时向用户显示一条友好消息,但您应该阻止向用户显示此按钮.并且您仍然可以通过实现 Application_Error 事件并检查是否抛出 SecurityException 并将用户重定向到解释他们不幸的是试图访问系统不允许访问的页面.但是 IMO,如果用户看到那个页面,他们要么在入侵"系统,要么你犯了一个编程错误.请记住,装饰器实现与包装相同的抽象.这意味着您不能使用装饰器更改抽象(并且不能返回不同的东西).如果这是您所需要的,您的消费者将不得不依赖于不同的东西.但请注意,这不是一个很常见的场景,所以如果这真的是你所需要的,你必须认真考虑.在我现在正在使用的系统中,我的 Windows 窗体类依赖于 IPromptableCommandHandler 而不是 ICommandHandler.那是因为我们想向用户显示一个对话框,解释他们输入的数据无效(某些数据只能由服务器验证),除了命令之外,我们还传递了一个委托,允许提示命令处理程序"在命令处理成功的情况下回调.可提示的命令处理程序实现本身依赖于 ICommandHandler 并委托工作并捕获从 WCF 服务返回的任何 ValidationException.这可以防止每个表单都有一个丑陋的 try-catch 块.解决方案仍然不是很好,当我得到更好的解决方案时我会改变.但是,即使有了这样的解决方案,您可能仍然希望创建一个装饰器来实现安全性并拥有一个包含 catch 语句的代理(在我的例子中是可提示的命令处理程序).不要试图返回与装饰器不同的东西.上面我不明白的是,记录器在何处/何时被调用?注册两个装饰器确保当请求 IGenericRepositotory 时,会构建以下对象图:IGenericRepository存储库 =新的 SecurityRepositoryDe​​corator(新的 LoggingRepositoryDe​​corator(新的 GenericRepository(新的 ClientManagementContext()),数据库记录器(),新的 AspNetUserSecurity());当控制器调用存储库Create方法时,将执行以下调用链:Begin SecurityRepositoryDe​​corator.Create(调用`userSecurity.ValidateUser`)开始 LoggingRepositoryDe​​corator.Create(调用 `Stopwatch.StartNew()`)开始 GenericRepository.Create结束 GenericRepository.Create结束 LoggingRepositoryDe​​corator.Create(调用 `this.logger.Log`)结束 SecurityRepositoryDe​​corator.Create所以,安全装饰器调用日志装饰器,因为安全装饰器包装了日志装饰器(并且日志装饰器包装了GenericRepository).ps.您为存储库命名的方法真的很难看.以下是一些提示:调用接口 IRepository 而不是 IGenericRepository(因为 T 暗示它实际上是通用的).从方法中删除所有 T 后缀;当您定义封闭的存储库时,它们没有意义.例如,IRepository.CreateT 是做什么的?IRepository 上下文中的T"是什么?更好的名称是 CreateCustomer,但这是不可能的,因为 IRepository.CreateCustomer 没有任何意义.通过将其命名为 IRepository.Create,所有这些问题都迎刃而解.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. 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: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 viewpublic 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; } // .. 解决方案 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.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); }}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>());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: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.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.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>.CreateSo, 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的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云!
05-29 01:02