代码改变世界

如何使用动态代理实现权限验证

2009-02-07 10:06  宝宝合凤凰  阅读(425)  评论(0)    收藏  举报

如何使用动态代理实现权限验证

        AOP(即面向方面编程)的一个最重要的职责就是把那些与业务无关的方面剥离出来,开发人员在开发业务模块的时候不用去考虑什么权限管理,日志记录等,其实这些都是很公用的部分,应该有单独的模块去做这样的事情。而动态代理是实现AOP的一个关键技术,其通过动态为目标类生成代理的方式,动态织入相关的代码,扩充被代理类的功能。听起来似乎挺神奇的,马上我将给大家展示动态代理的奇妙之处。(我前面翻译过一篇从CodeProject上获取的关于介绍动态代理的文章,如果对动态代理还不熟悉的可以去看看。)

        好,现在我们就开始吧!

        我先简单描述一下要完成的功能(如图): 
       
 

        有一个窗体,上面有三个按钮,现在我们需要对其进行权限控制,控制哪些按钮可以操作,哪些不可以操作,而且当权限控制发生改变时不会去修改代码,只需要简单修改配置即可。

       首先我们建一个工程,名字随个人喜好啦,(由于本人使用的机器是装的VS2005英文版,所以Demo也是用它完成的,其中可能有些文件的组织方式是按照VS2005来的,不过也没关系,只要把partial类的内容合并了就可以放在2003下面用了)
配置文件内容如下:

 1<configuration>
 2  <configSections>
 3    <!--用来配置权限的配置节-->
 4    <section name="PopedomControlList"  type="System.Configuration.DictionarySectionHandler"/>    
 5  </configSections>
 6
 7  <!--控制信息,这里控制了按钮的访问权限-->
 8  <PopedomControlList>
 9    <add key="btnOne"   value="true"/>
10    <add key="btnTwo"   value="true"/>
11    <add key="btnThree" value="false"/>
12  </PopedomControlList>
13  
14</configuration>

配置文件中配置了我对3个按钮的访问权限,true为可以访问,false不能访问。

OK,我们再在工程中添加对Castle.DynamicProxy.dll的引用,然后就可以开始写代码了。

设计好窗体(这里就不再详细说明了),然后创建一个拦截器:

 1/******************************
 2 * 作者:米小波
 3 * 日期:2005-12-05
 4 * ****************************/

 5 
 6
 7using System;
 8using System.Collections.Generic;
 9using System.Text;
10using System.Windows.Forms;
11
12using Castle.DynamicProxy;
13
14namespace PopedomDemo
15{
16    /// <summary>
17    /// 实现方法调用拦截处理
18    /// </summary>

19    public class MyInterceptor : StandardInterceptor
20    {
21
22        IInterceptor Members
47        /// <summary>
48        /// 检查按钮权限
49        /// </summary>
50        /// <param name="btnName"></param>
51        /// <returns></returns>

52        private bool Check(string btnName)
53        {
54            if (Program.PopedomList[btnName] != null)
55            {
56                if (Program.PopedomList[btnName].ToString().ToLower() == "true")
57                {
58                    return true;
59                }

60            }

61            return false;
62        }

63    }

64}

65

上面代码中,我们重载了StandardInterceptor的Intercept方法,这个是拦截器必须实现的IInterceptor中的方法,StandardInterceptor实现了IInterceptor的该方法,并且在其中使用了模板方法模式,

 1namespace Castle.DynamicProxy
 2{
 3    using System;
 4
 5    [Serializable]
 6    public class StandardInterceptor : IInterceptor
 7    {
 8        public StandardInterceptor() { }
 9        protected virtual void PreProceed(IInvocation invocation, params object[] args) { }
10        protected virtual void PostProceed(IInvocation invocation, ref object returnValue, params object[] args) { }
11
12        public virtual object Intercept(IInvocation invocation, params object[] args)
13        {
14            PreProceed(invocation, args);
15            object retValue = invocation.Proceed(args);
16            PostProceed(invocation, ref retValue, args);
17            return retValue;
18        }

19    }

20}

如果只是简单的拦截处理,重载其
void PostProceed(IInvocation invocation, ref object returnValue, params object[] args) 
void PreProceed(IInvocation invocation, params object[] args)

 两个方法就可以了,但是我们这里需要直接重载Intercept方法。
 在Intercept方法判断了调用方法button对象,通过其名字判断了是否具有执行方法的权限,有则执行,没有就忽略。
做好了拦截器,可以来产生窗体的代理了

 1/******************************
 2 * 作者:米小波
 3 * 日期:2005-12-05
 4 * ****************************/

 5using System;
 6using System.Collections;
 7using System.Windows.Forms;
 8using System.Configuration;
 9
10using Castle.DynamicProxy;
11
12namespace PopedomDemo
13{
14    static class Program
15    {
16        /// <summary>
17        /// 全局权限信息
18        /// </summary>

19        public static Hashtable PopedomList = new Hashtable();
20
21        /// <summary>
22        /// The main entry point for the application.
23        /// </summary>

24        [STAThread]
25        static void Main()
26        {
27            
28            PopedomList.Clear();
29           
30            //从配置文件读取权限信息
31            IDictionary dir = (IDictionary)System.Configuration.ConfigurationSettings.GetConfig("PopedomControlList");
32            string[] keys = new string[dir.Keys.Count];
33            string[] values = new string[dir.Keys.Count];
34            dir.Keys.CopyTo(keys, 0);
35            dir.Values.CopyTo(values, 0);
36            for (int i = 0; i < keys.Length; i++)
37            {
38                PopedomList.Add(keys[i], values[i]);
39            }

40            try
41            {
42                ProxyGenerator gen = new ProxyGenerator();
43                frmDemoMain frm = (frmDemoMain)gen.CreateClassProxy(typeof(frmDemoMain), new MyInterceptor());
44
45                //Application.EnableVisualStyles();
46                //Application.SetCompatibleTextRenderingDefault(false);
47                Application.Run(frm);
48            }

49            catch (Exception ex)
50            {
51                string msg = ex.Message;
52            }

53
54
55        }

56    }

57}

代码中前半部分是读取权限信息,后面则会为该窗体生成一个动态的代理
ProxyGenerator gen = new ProxyGenerator();
 frmDemoMain frm = (frmDemoMain)gen.CreateClassProxy(typeof(frmDemoMain), new MyInterceptor());
记住有一点,需要被拦截处理的方法需要是虚方法,可被重载的,其实动态代理的生成原理就是继承了该类,并重载了其虚方法。

所以3个按钮的事件方法可能是这样的:

 1  private void frmDemoMain_Load(object sender, EventArgs e)
 2        {
 3        }

 4
 5        public virtual void btnOne_Click(object sender, EventArgs e)
 6        {
 7            MessageBox.Show("BtnOne Process-1");
 8        }

 9
10        public virtual void btnTwo_Click(object sender, EventArgs e)
11        {
12            MessageBox.Show("BtnTwo Process-2");
13        }

14
15        public virtual void btnThree_Click(object sender, EventArgs e)
16        {
17            MessageBox.Show("BtnTwo Process-3");
18        }

19

写完了,回头看看代码,对于窗体里面的逻辑几乎不会增加任何与权限有关的代码,很干净。而且动态代理使用的委托技术,并不会对性能造成影响,所以利用它来处理与业务无关的方面是再好不过啦 

本示例完整的程序代码可以到https://files.cnblogs.com/mixiaobo/PopedomDemo.rar下载

posted @ 2005-12-05 17:53 米小波 阅读(1759) 评论(8)  编辑 收藏 网摘 所属分类: AOP

  回复  引用    
#1楼 2006-01-03 14:04 | MYNet [未注册用户]
本人最近也在研究,关注中
  回复  引用    
#2楼 2006-08-30 00:20 | iicc [未注册用户]
我在VS2003下运行时,在行
return base.Intercept(invocation, args);
处出现异常:
未处理的“System.ExecutionEngineException”类型的异常出现在 system.windows.forms.dll 中。

如何解决呢?
  回复  引用  查看    
#3楼 2006-09-14 17:05 | 月亮lover      
在2003中调通了,但还是不太明白, invocation.Proceed(args)做了些什么?base.Intercept(invocation, args)又做了些什么呢?忘楼主赐教哦

  回复  引用  查看    
#4楼 2006-09-14 17:13 | 月亮lover      
老兄,使用反射机制能实现相同功能不?
  回复  引用  查看    
#5楼 [楼主]2006-09-15 12:48 | 米小波      
1、invocation.Proceed(args) 是原始的调用,比如点击button1事件,实现代码是: public virtual void btnOne_Click(object sender, EventArgs e)
{
MessageBox.Show("BtnOne Process-1");
}
那么invocation.Proceed(args) 与其等同,我们的织入就是在这个原始调用的基础上进行各种包装
2、 public override object Intercept(IInvocation invocation, params object[] args)
就是一个拦截方法,里面开发人员可以对原始调用进行各种加工,标准的拦截器是在原始调用的前后织入代码,如:
public virtual object Intercept(IInvocation invocation, params object[] args)
13 {
14 PreProceed(invocation, args);
15 object retValue = invocation.Proceed(args);
16 PostProceed(invocation, ref retValue, args);
17 return retValue;
18 }
本文章的示例是通过拦截,决定原始方法时候被执行,只有权限检查通过的才会被执行 ,如:
if (args.Length > 0)
27 {
28 Button btn = args[0] as Button;
29 if (btn != null)
30 {
31 string btnName = btn.Name;
32 if (Program.PopedomList.Contains(btnName))
33 {
34 if (Check(btnName))
35 {
36 return invocation.Proceed(args);
37 }
38 return null;
39 }
40 }
41 }
42 return base.Intercept(invocation, args);



  回复  引用  查看    
#6楼 [楼主]2006-09-15 12:58 | 米小波      
动态代理方面如果要了解比较底层的实现可以看看:
System.Reflection.Emit 命名空间
System.Reflection.Emit 命名空间包含允许编译器或工具发出元数据和 Microsoft 中间语言 (MSIL) 并可选择在磁盘上生成 PE 文件的类。这些类的主要客户端是脚本引擎和编译器。