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

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

一、核心代码

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

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

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

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

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

CREATE TABLE [dbo].[UserLog](
[ID] [int] IDENTITY(,) NOT NULL,--主键
[StudentId] [int] NOT NULL,--对应学生ID
[Message] [nvarchar]() 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,具体代码如下(关键处我都写好注释了

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 = ;
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即可,代码如下:

public override string ToString()
{
return this.Id.ToString();
}

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

//操作附加消息,用于获取操作的记录相关标识
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方法,具体代码如下:

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中传递过来的参数:

/// <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))]

然后新增一个构造函数

/// <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步我们就可以通过企业库配置工具进行配置了,见下图:

四、具体使用

在完成了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层进行操作,代码如下:

[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将无法显示出来。

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

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

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

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

  1. [EntLib]微软企业库5.0 学习之路——第一步、基本入门

    话说在大学的时候帮老师做项目的时候就已经接触过企业库了但是当初一直没明白为什么要用这个,只觉得好麻烦啊,竟然有那么多的乱七八糟的配置(原来我不知道有配置工具可以进行配置,请原谅我的小白). 直到去年在 ...

  2. 微软企业库5.0 学习之路——第八步、使用Configuration Setting模块等多种方式分类管理企业库配置信息

    在介绍完企业库几个常用模块后,我今天要对企业库的配置文件进行处理,缘由是我打开web.config想进行一些配置的时候发现web.config已经变的异常的臃肿(大量的企业库配置信息充斥其中),所以决 ...

  3. 微软企业库5.0 学习之路——第七步、Cryptographer加密模块简单分析、自定义加密接口及使用—下篇

    在上一篇文章中, 我介绍了企业库Cryptographer模块的一些重要类,同时介绍了企业库Cryptographer模块为我们提供的扩展接口,今天我就要根据这些 接口来进行扩展开发,实现2个加密解密 ...

  4. 微软企业库5.0 学习之路——第六步、使用Validation模块进行服务器端数据验证

    前端时间花了1个多星期的时间写了使用jQuery.Validate进行客户端验证,但是那仅仅是客户端的验证,在开发项目的过程中,客户端的信息永远是不可信的,所以我们还需要在服务器端进行服务器端的验证已 ...

  5. 微软企业库5.0 学习之路——第四步、使用缓存提高网站的性能(EntLib Caching)

    首先先补习下企业库的Caching Application Block的相关知识: 1.四大缓存方式,在Caching Application Block中,主要提供以下四种保存缓存数据的途径,分别是 ...

  6. 微软企业库5.0 学习之路——第二步、使用VS2010+Data Access模块建立多数据库项目

    现在我就开始进入学习之路的第二步——Data Access模块,这个模块是企业库中被使用频率最高的模块,它很好的封装了数据库操作应用,为我们进行多数据库系统开发提供了便利,只需更改配置文件就 可以很快 ...

  7. 微软企业库5.0 学习之路——第五步、介绍EntLib.Validation模块信息、验证器的实现层级及内置的各种验证器的使用方法——下篇

    一.独立验证器 我上篇中我将AndCompositeValidator和OrCompositeValidator归为独立验证器,这2个验证器主要是为了第一类验证服务,可以进行多种验证组合在一起进行复杂 ...

  8. 微软企业库5.0 学习之路——扩展学习篇、库中的依赖关系注入(重构 Microsoft Enterprise Library)[转]

    这篇文章是我在patterns & practices看到的一篇有关EntLib5.0的文章,主要介绍了EntLib5.0的这次的架构变化由来,觉得很不错,大家可以看一下! 在过去几年中,依赖 ...

  9. 微软企业库5.0 学习之路——第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(3)

    今天继续介绍Unity,在上一篇的文章中,我介绍了使用UnityContainer来注册对象之间的关系.注册已存在的对象之间的关系,同时着重介绍 了Unity内置的各种生命周期管理器的使用方法,今天则 ...

随机推荐

  1. STL源码分析-list

    http://note.youdao.com/noteshare?id=81492dc45602618344edc838ef104581

  2. ubuntu 16.04 镜像下载

    下载地址: http://mirror.pnl.gov/releases/xenial/ Ubuntu 14.04.5 LTS (Trusty Tahr)http://releases.ubuntu. ...

  3. I/O多路复用和异步I/O

    一.I/O模式 对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间.所以说,当一个read操作发生时,它会经历两个阶段: ...

  4. TCP与UDP区别详解

    TCP协议与UDP协议的区别    首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UDP协议的区别,我觉得这是没有从本质上弄清楚网络通信! ...

  5. JS获取URL中参数值(QueryString)的4种方法分享

    方法一:正则法 function getQueryString(name) { var reg = new RegExp('(^|&)' + name + '=([^&]*)(& ...

  6. [DeeplearningAI笔记]卷积神经网络4.1-4.5 人脸识别/one-shot learning/Siamase网络/Triplet损失/将面部识别转化为二分类问题

    4.4特殊应用:人脸识别和神经网络风格转换 觉得有用的话,欢迎一起讨论相互学习~Follow Me 4.1什么是人脸识别 Face verification人脸验证 VS face recogniti ...

  7. 二叉树系列 - 求两节点的最低公共祖先,例 剑指Offer 50

    前言 本篇是对二叉树系列中求最低公共祖先类题目的讨论. 题目 对于给定二叉树,输入两个树节点,求它们的最低公共祖先. 思考:这其实并不单单是一道题目,解题的过程中,要先弄清楚这棵二叉树有没有一些特殊的 ...

  8. gcd的性质+分块 Bzoj 4028

    4028: [HEOI2015]公约数数列 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 865  Solved: 311[Submit][Statu ...

  9. HDU 1211 EXGCD

    EXGCD的模板水题 RSA算法给你两个大素数p,q定义n=pq,F(n)=(p-1)(q-1) 找一个数e 使得(e⊥F(n)) 实际题目会给你e,p,q计算d,$de \mod F(n) = 1$ ...

  10. PowerShell入门

    最近需要写个Windows的脚本,以前一直使用cmd.exe来写批处理脚本,这次接触到了PowerShell,准备把学习过程中学到的知识点整理在这里: 相关文章: 1.https://www.cnbl ...