[EntLib]微软企业库5.0 学习之路——第九步、使用PolicyInjection模块进行AOP—PART4——建立自定义Call Handler实现用户操作日志记录

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介:

  在前面的Part3中,我介绍Policy Injection模块中内置的Call Handler的使用方法,今天则继续介绍Call Handler——Custom Call Handler,通过建立Custom Call Handler来实现项目中的用户操作日志的记录,具体的代码可以在项目中EntLib.Helper项目下找到,如下图:

pic67本文将从Custom Call Handler两种方式来介绍:Attribute方式和Configuration方式。

 

一、核心代码

建立Custom Call Handler则需要有以下几个步骤:

1、建立一个类实现接口ICallHandler。

2、根据具体需求建立对应Attribute类或为Custom Call Handler实现特性[ConfigurationElementType(typeof(CustomCallHandlerData))]

首先来介绍下具体的核心代码,由于我是要实现用户的操作日志,则需要对用户的对数据的增删改以及一些特殊的操作进行记录,如:登录,

1、首先需要建立一张表用于存放用户操作记录:

1
2
3
4
5
6
7
8
9
10
CREATE  TABLE  [dbo].[UserLog](
     [ID] [ int ] IDENTITY(1,1) NOT  NULL , --主键
     [StudentId] [ int ] NOT  NULL , --对应学生ID
     [Message] [nvarchar](256) NOT  NULL , --操作消息
     [LogDate] [datetime] NOT  NULL , --记录时间
  CONSTRAINT  [PK_UserLog] PRIMARY  KEY  CLUSTERED
(
     [ID] ASC
) WITH  (PAD_INDEX  = OFF , STATISTICS_NORECOMPUTE  = OFF , IGNORE_DUP_KEY = OFF , ALLOW_ROW_LOCKS  = ON , ALLOW_PAGE_LOCKS  = ON ) ON  [ PRIMARY ]
) ON  [ PRIMARY ]

2、建立一个名为UserLogCallHandler的类来实现接口ICallHandler,实现其中的方法Invoke(具体的拦截操作方法)和属性Order,具体代码如下(关键处我都写好注释了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
using  System;
using  System.Collections.Generic;
using  System.Collections.Specialized;
using  System.Data;
using  System.Data.Common;
using  System.Linq;
using  System.Text;
 
using  Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using  Microsoft.Practices.EnterpriseLibrary.Data;
using  Microsoft.Practices.Unity.InterceptionExtension;
 
namespace  EntLibStudy.Helper.EntLibExtension.PolicyInjectionExtension
{
     [ConfigurationElementType( typeof (CustomCallHandlerData))]
     public  class  UserLogCallHandler : ICallHandler
     {
         /// <summary>
         /// 构造函数,此处不可省略,否则会导致异常
         /// </summary>
         /// <param name="attributes">配置文件中所配置的参数</param>
         public  UserLogCallHandler(NameValueCollection attributes)
         {
             //从配置文件中获取key,如不存在则指定默认key
             this .Message = String.IsNullOrEmpty(attributes[ "Message" ]) ? ""  : attributes[ "Message" ];
             this .ParameterName = String.IsNullOrEmpty(attributes[ "ParameterName" ]) ? ""  : attributes[ "ParameterName" ];
         }
 
         /// <summary>
         /// 构造函数,此构造函数是用于Attribute调用
         /// </summary>
         /// <param name="message">消息</param>
         /// <param name="parameterName">参数名</param>
         public  UserLogCallHandler( string  message, string  parameterName)
         {
             this .Message = message;
             this .ParameterName = parameterName;
         }
 
         /// <summary>
         /// 实现ICallHandler.Invoke方法,用于对具体拦截方法做相应的处理
         /// </summary>
         /// <param name="input"></param>
         /// <param name="getNext"></param>
         /// <returns></returns>
         public  IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
         {
             //检查参数是否存在
             if  (input == null ) throw  new  ArgumentNullException( "input" );
             if  (getNext == null ) throw  new  ArgumentNullException( "getNext" );
 
             //开始拦截,此处可以根据需求编写具体业务逻辑代码
 
             //调用具体方法
             var  result = getNext()(input, getNext);
             //判断所拦截的方法返回值是否是bool类型,
             //如果是bool则判断返回值是否为false,false:表示调用不成功,则直接返回方法不记录日志
             if  (result.ReturnValue.GetType() == typeof ( bool ))
             {
                 if  (Convert.ToBoolean(result.ReturnValue) == false )
                 {
                     return  result;
                 }
             }
             //如果调用方法没有出现异常则记录操作日志
             if  (result.Exception == null )
             {
                 //获取当前登录的用户名,从cookies中获取,如果采用的session记录,则更改为从session中获取
                 var  uid = Utils.GetCookies( "sid" );
                 //如果未登录则抛出异常
                 if  (String.IsNullOrEmpty(uid)) throw  new  Exception( "用户未登录!" );
                
                 //操作附加消息,用于获取操作的记录相关标识
                 var  actionMessage = "" ;
                 object  para = null ;
                 //判断调用方法的主要参数名是否为空,不为空则从拦截的方法中获取参数对象
                 if  (String.IsNullOrEmpty( this .ParameterName) == false )
                 {
                     para = input.Inputs[ this .ParameterName];
                 }
                 //判断参数对象是否为null,不为null时则获取参数标识
                 //此处对应着具体参数的ToString方法,我已经在具体类中override了ToString方法
                 if  (para != null )
                 {
                     actionMessage = " 编号:["  + para.ToString() + "]" ;
                 }
 
                 //插入操作日志
                 Database db = DBHelper.CreateDataBase();
                 StringBuilder sb = new  StringBuilder();
                 sb.Append( "insert into UserLog(StudentId,Message,LogDate) values(@StudentId,@Message,@LogDate);" );
                 DbCommand cmd = db.GetSqlStringCommand(sb.ToString());
                 db.AddInParameter(cmd, "@StudentId" , DbType.Int32, uid);
                 db.AddInParameter(cmd, "@Message" , DbType.String, this .Message + actionMessage);
                 db.AddInParameter(cmd, "@LogDate" , DbType.DateTime, DateTime.Now);
                 db.ExecuteNonQuery(cmd);
             }
             //返回方法,拦截结束
             return  result;
         }
 
         public  string  Message { get ; set ; }
 
         public  string  ParameterName { get ; set ; }
 
         private  int  _order = 0;
         public  int  Order
         {
             get
             {
                 return  _order;
             }
             set
             {
                 _order = value;
             }
         }
     }
}

这段代码主要部分就是具体的Invoke方法实现,这个方法有2个参数:

input,这个参数中封装了已拦截的方法、方法的参数等有用的信息

getNext,一个委托,用于调用拦截的方法,通过这个委托我们可以很好的控制我们需要在拦截了具体方法后如何进行具体的业务逻辑操作。

通过getNext()(input, getNext); 这段代码即可完成对方法的调用,这样可以根据具体需求决定在调用方法前还是方法后进行具体操作。

由于我这边是要实现一个用户操作记录,那么我要知道一些具体的信息:是谁在什么时候对什么数据做了操作,这边我需要获取3个参数:具体的操作人、操作的数据及具体描述。

首先来看下第一个参数:

具体的操作人,由于这个项目采用的是cookies来记录当前的登录用户,所以我可以直接从cookies中获取当前登录的人,具体可以查看代码69-71行。

操作的数据,这边我在这个Call Handler中建立了一个ParameterName属性用来指定记录所拦截的方法中存放所操作数据的参数名,具体可以查看代码74-86行。

由于指定了具体的参数名,我们则需要根据参数获取具体数据值,我们来看下增删改的方法签名:

int Add(Student student); 
bool Update(Student student); 
bool Delete(int id);

可以看到,我们都可以从这3个方法获取到用户具体操作的数据标识,如Student.Id和id,这样我们只需变通一下,在具体的类中,如Student中,重写ToString方法,返回具体的ID即可,代码如下:

1
2
3
4
public  override  string  ToString()
{
     return  this .Id.ToString();
}

这样,我们在Call Handler中我们就可以根据参数名获取到具体操作的数据了(如果需要详细描述具体的数据的话则需要更复杂的设计了,这边就不深入了),代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//操作附加消息,用于获取操作的记录相关标识
var  actionMessage = "" ;
object  para = null ;
//判断调用方法的主要参数名是否为空,不为空则从拦截的方法中获取参数对象
if  (String.IsNullOrEmpty( this .ParameterName) == false )
{
     para = input.Inputs[ this .ParameterName];
}
//判断参数对象是否为null,不为null时则获取参数标识
//此处对应着具体参数的ToString方法,我已经在具体类中override了ToString方法
if  (para != null )
{
     actionMessage = " 编号:["  + para.ToString() + "]" ;
}

具体描述,这个我也是建立一个Message数据,用于存放操作的具体描述。

特殊情况,当然操作日志也不可能就仅仅增删改3种情况,就比如登录,注销,这种情况则只需指定具体的消息即可,参数名无需指定,如果还有更加特殊的情况则需要根据具体需求来更改这边的设计,我这边只是给出个最基本的。

 

二、Attribute实现

在完成了核心代码后,我们则可以根据需求建立Attribute拦截还是Configuration拦截了。

实现Attribute拦截,需要建立一个类,实现HandlerAttribute类,实现其中的CreateHandler方法,用于调用具体的Call Handler方法,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using  System;
using  System.Collections.Generic;
using  System.Linq;
using  System.Text;
using  Microsoft.Practices.Unity;
using  Microsoft.Practices.Unity.InterceptionExtension;
 
namespace  EntLibStudy.Helper.EntLibExtension.PolicyInjectionExtension
{
     [AttributeUsage(AttributeTargets.Method)]
     public  class  UserLogCallHandlerAttribute : HandlerAttribute
     {
         public  UserLogCallHandlerAttribute( string  message, string  ParameterName)
         {
             this .Message = message;
             this .ParameterName = ParameterName;
         }
 
         public  string  Message { get ; set ; }
 
         public  string  ParameterName { get ; set ; }
 
         public  override  ICallHandler CreateHandler(IUnityContainer container)
         {
             //创建具体Call Handler,并调用
             UserLogCallHandler handler = new  UserLogCallHandler( this .Message, this .ParameterName);
 
             return  handler;
         }
     }
}

这个特性类就比较简单了,不过还需要在Call Handler中进行处理,增加一个构造函数,接收从Attribute中传递过来的参数:

1
2
3
4
5
6
7
8
9
10
/// <summary>
/// 构造函数,此构造函数是用于Attribute调用
/// </summary>
/// <param name="message">消息</param>
/// <param name="parameterName">参数名</param>
public  UserLogCallHandler( string  message, string  parameterName)
{
     this .Message = message;
     this .ParameterName = parameterName;
}

 

三、Configuration方式

如果要实现可以通过企业库配置工具进行配置Custom Call Handler的话,则需要对Call Handler增加一个特性:

[ConfigurationElementType(typeof(CustomCallHandlerData))]

然后新增一个构造函数

1
2
3
4
5
6
7
8
9
10
/// <summary>
/// 构造函数,此处不可省略,否则会导致异常
/// </summary>
/// <param name="attributes">配置文件中所配置的参数</param>
public  UserLogCallHandler(NameValueCollection attributes)
{
     //从配置文件中获取key,如不存在则指定默认key
     this .Message = String.IsNullOrEmpty(attributes[ "Message" ]) ? ""  : attributes[ "Message" ];
     this .ParameterName = String.IsNullOrEmpty(attributes[ "ParameterName" ]) ? ""  : attributes[ "ParameterName" ];
}

完成以上2步我们就可以通过企业库配置工具进行配置了,见下图:

pic68

 

四、具体使用

在完成了Call Handler的代码编写和登录拦截配置后,我们就可以进行使用了,我这边更改了项目的结构,建立了一个IBLL的接口层,现有的BLL层的类则实现IBLL层中接口,而且由于Policy Injection模块要实现AOP,则具体类必须继承自MarshalByRefObject或实现一个接口(如果不清楚可以查看Part1),所以为了项目的各模块解耦、方便Policy Injection对具体类的创建和未来Unity介绍做铺垫则创建了IBLL层。(具体可以参看项目代码)

由于建立了IBLL层,则表示层的代码则需要发生变化,所有BLL层创建都需要通过PolicyInjection.Create方法来创建,具体代码如下:

1
IStudentManage studentBll = PolicyInjection.Create<StudentManage, IStudentManage>();

这样,当我们运行代码后,进入数据库查看就可以看,操作日志已经被记录下来了。

上面说的是通过Configuration方式来进行操作日志记录,如果我们想通过Attribute方式来记录日志消息,则需要到具体的BLL层进行操作,代码如下:

1
2
3
4
5
[UserLogCallHandler( "更新学生信息" , "student" )]
public  bool  Update(Student student)
{
     return  studentService.Update(student);
}

注意:这边需要为项目引用Microsoft.Practices.Unity.Interception,因为Call Handler的Attribute是继承自HandlerAttribute,这个HandlerAttribute就是存放于Microsoft.Practices.Unity.Interception,否则自定义的Call Handler Attribute将无法显示出来。

这样,更新下学生信息后,我们可以就可以看到具体的操作日志了,见下图:

pic69

 

以上就是本文的所有内容,主要介绍了如何通过Custom Call Handler实现用户操作日志记录,如果有什么不对,欢迎大家指出,谢谢:)

至此,Policy Injection模块的介绍也结束了,下面将开始介绍企业库中使用最广泛的IOC容器——Unity,敬请期待!

 

微软企业库5.0 学习之路系列文章索引:

第一步、基本入门

第二步、使用VS2010+Data Access模块建立多数据库项目

第三步、为项目加上异常处理(采用自定义扩展方式记录到数据库中)

第四步、使用缓存提高网站的性能(EntLib Caching)

第五步、介绍EntLib.Validation模块信息、验证器的实现层级及内置的各种验证器的使用方法——上篇

第五步、介绍EntLib.Validation模块信息、验证器的实现层级及内置的各种验证器的使用方法——中篇 

第五步、介绍EntLib.Validation模块信息、验证器的实现层级及内置的各种验证器的使用方法——下篇

第六步、使用Validation模块进行服务器端数据验证

第七步、Cryptographer加密模块简单分析、自定义加密接口及使用—上篇

第七步、Cryptographer加密模块简单分析、自定义加密接口及使用—下篇

第八步、使用Configuration Setting模块等多种方式分类管理企业库配置信息

第九步、使用PolicyInjection模块进行AOP—PART1——基本使用介绍

第九步、使用PolicyInjection模块进行AOP—PART2——自定义Matching Rule

第九步、使用PolicyInjection模块进行AOP—PART3——内置Call Handler介绍

第九步、使用PolicyInjection模块进行AOP—PART4——建立自定义Call Handler实现用户操作日志记录 

第十步、使用Unity解耦你的系统—PART1——为什么要使用Unity?

第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(1)

第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(2)

第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(3)

第十步、使用Unity解耦你的系统—PART3——依赖注入

第十步、使用Unity解耦你的系统—PART4——Unity&PIAB

扩展学习:

扩展学习篇、库中的依赖关系注入(重构 Microsoft Enterprise Library)[转]

 

源代码下载:点我下载

 

注意:

1、MSSQL数据库在DataBase目录下(需要自行附加数据库),SQLite数据库在Web目录的App_Data下,由于考虑到项目的大小,所以每个项目的BIN目录都已经删除,如出现无法生成项目请自行添加相关企业库的DLL。

2、由于微软企业库5.0 学习之路这个系列我是准备以一个小型项目的形式介绍企业库的各模块,所以源代码会根据系列文章的更新而更新,所以源代码不能保证与文章中所贴代码相同。

3、项目开发环境为:VS2010+SQL2005。

4、管理员帐户:admin

              密码:admin



本文转自kyo-yo博客园博客,原文链接:http://www.cnblogs.com/kyo-yo/archive/2010/10/13/Learning-EntLib-Ninth-Use-PolicyInjection-Module-AOP-PART4-Call-Handler-To-Build-User-Logging.html,如需转载请自行联系原作者


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
打赏
0
0
0
0
51
分享
相关文章
智能运维,由你定义:SAE自定义日志与监控解决方案
通过引入 Sidecar 容器的技术,SAE 为用户提供了更强大的自定义日志与监控解决方案,帮助用户轻松实现日志采集、监控指标收集等功能。未来,SAE 将会支持 istio 多租场景,帮助用户更高效地部署和管理服务网格。
257 51
智能运维,由你定义:SAE自定义日志与监控解决方案
SAE(Serverless应用引擎)是阿里云推出的全托管PaaS平台,致力于简化微服务应用开发与管理。为满足用户对可观测性和运维能力的更高需求,SAE引入Sidecar容器技术,实现日志采集、监控指标收集等功能扩展,且无需修改主应用代码。通过共享资源模式和独立资源模式,SAE平衡了资源灵活性与隔离性。同时,提供全链路运维能力,确保应用稳定性。未来,SAE将持续优化,支持更多场景,助力用户高效用云。
131 2
|
1月前
|
基于责任链与策略模式的轻量级PHP日志库设计
项目日志乱成一团,bug 时好时坏,服务器问题难以复现?我写了个 PHP 日志系统,第一时间发现问题,避免跑路。实现了责任链模式+策略模式,让日志存储更灵活,支持多种输出方式。
在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
这篇文章介绍了如何在IDEA和Spring Boot中使用AOP技术实现日志信息的记录到数据库的详细步骤和代码示例。
在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
164 8
|
4月前
|
.net 自定义日志类
在.NET中,创建自定义日志类有助于更好地管理日志信息。示例展示了如何创建、配置和使用日志记录功能,包括写入日志文件、设置日志级别、格式化消息等。注意事项涵盖时间戳、日志级别、JSON序列化、线程安全、日志格式、文件处理及示例使用。请根据需求调整代码。
77 13
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
105 1
领导被我的花式console.log吸引了!直接写入公司公共库!
【8月更文挑战第23天】领导被我的花式console.log吸引了!直接写入公司公共库!
73 2
领导被我的花式console.log吸引了!直接写入公司公共库!
超级好用的C++实用库之日志类
超级好用的C++实用库之日志类
96 0
salt自定义模块内使用日志例子
salt自定义模块内使用日志例子

热门文章

最新文章

下一篇
oss创建bucket
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等