2012年1月26日 By mikel 分类: C#, 开发笔记

[转载]C#字符串与享元(Flyweight)模式 – winter-cn – 博客园.

写这个文章,主要是因为网上对C#字符串和享元模式的误解比较多。

Flyweight模式

先说这名字,fly呢,就是苍蝇,没错这里面不是飞的意思,是苍蝇的意思,weight大家都知道,就是重量,苍蝇的重量,就是非常非常轻的意思。所以Flyweight模式就是处理非常非常轻量级对象的一个东西。

Flyweight的目标是解决大量细粒度对象的内存消耗问题,当然,巧妇难为无米之炊,任何模式和手法都不能凭空造出内存来,所以享元模式针对的情况是这些细粒度对象的中数据有重复的情况。

Flyweight的做法是,把对象的状态(通常用属性表示),分成两个部分,一部分是内部状态,另一部分是外部状态。内部状态是不易重复的(或者说必要的),外部状态是易重复的。所以,Flyweight把外部状态提取出来共享,这样就一定程度解决了内存占用问题。

C#中的字符串不是Flyweight模式

在网上常常可以看到一个说法,说C#中的字符串使用了Flyweight模式,开门见山地说,这个说法是错误的。

错在哪里呢?按照上文的介绍,错就错在字符串它没有所谓的“内部状态”。

通常讲字符串是享元的原因就是以下代码:

string a = “Hello World”;
Console.WriteLine(Object.ReferenceEquals(a, “Hello World”)); //True

当使用字符串直接量的时候,不论你写了多少个”Hello World”,最终内存里面只有一个字符串对象。

运行时创建的字符串并不在此列,可以使些手段,强制在内存里面产生新的字符串。

string a = “Hello World”;
Console.WriteLine(Object.ReferenceEquals(a, new String(“Hello World”.ToCharArray())));  //False

因为我们强行调用了new,所以这个字符串跟内存中的直接量”Hello World”对应的对象不是同一个。

有趣的是,C#还允许强制把一个字符串加入到(如果已经有了,就只是找出来)字符串池里面。

string a = “Hello World”;
string b = String.Intern(new String(“Hello World”.ToCharArray()));
Console.WriteLine(Object.ReferenceEquals(a,b) );

或者

string a = String.Intern(new String(“Hello World”.ToCharArray()));
string b = String.Intern(new String(“Hello World”.ToCharArray()));
Console.WriteLine(Object.ReferenceEquals(a,b) );

前面提到了,这个行为跟Flyweight使用的内部状态和外部状态不同,是两个对象实实在在就是同一个对象。

C#中的字符串与Flyweight模式

好吧,前面说了不少,C#中的字符串不是Flyweight模式,但是是不是就意味着C#里面字符串跟Flyweight没有关系呢?

当然不是,否则我写这么一篇文章岂不是太蛋疼了……

字符串池和Intern方法简直是实现Flyweight的神器啊!

考虑我们有某一类对象,可能会创建几百万个,对象里面恰巧有这么一个属性叫做颜色,它在对象构造的时候随机产生,颜色用的是rgb色,用rgb24来表示,于是颜色字符串类似#ccc这样子。

代码写起来就像下面的样子:

    class Element
    {
	static Random rnd = new Random();
	static char[] table;
	static Element()
	{
	    table = "0123456789abcdef".ToCharArray();
	}

	public string color;
	public Element()
	{
	    color = "" + table[rnd.Next() % 16] + table[rnd.Next() % 16] + table[rnd.Next() % 16];
	}
    }

接下来我们创建3千万个对象看看如何

	    Element[] eles = new Element[30000000];
	    for (var i = 0; i < 30000000; i++)
	    {
		eles[i] = new Element();
	    }

从任务管理器看到一大块内存被吃掉了

QFOMR9}(NR%(T3`V3Q35MSY

接下来我们使用String.Intern来实现Flyweight:

    class Element
    {
	static Random rnd = new Random();
	static char[] table;
	static Element()
	{
	    table = "0123456789abcdef".ToCharArray();
	}

	public string color;
	public Element()
	{
	    color = String.Intern("" + table[rnd.Next() % 16] + table[rnd.Next() % 16] + table[rnd.Next() % 16]);
	}
    }

同样来看看运行结果:
@XMI6L75IKU}S%NGI6L31@K

可以看到内存占用量的明显变化。

因为字符串对象的不可更改性质,使用了String.Intern之后,我们完全看不出前后color的区别,也就是说,修改前后的Element类是完全等效的,但是Flyweight为我们节约了大量的内存。

更多思考

这个典型的使用flyweight场景为我们揭示了享元外部状态的特征:像字符串一样不可更改的对象。GoF原书的例子中的字型对象Glyph也是如此。

String.Intern这种对象池的方式实现flyweight也值得借鉴,我们可以考虑自己设计flyweight的外部状态对象时使用类似的方式。

收藏与分享
2012年1月26日 By mikel 分类: C#, 开发笔记, 架构设计

[转载]WCF RESTful服务的Google Protocol Buffers超媒体类型 – 张善友 – 博客园.

Protocol Buffers 是在一个很理想的结构化数据的语言中立的序列化格式。你可以考虑一下XML或JSON,但更轻,更小的协议缓冲区。 这种格式的广应用于谷歌不同的系统之间交换数据。

由于其结构化数据的最佳表现,protocol buffers 是一个代表RESTful服务处理的数据很好的选择。要遵循REST的原则, protocol buffers 应作为一个新的超媒体类型的代表。 在当前版本(.NET 4) 的Windows通讯基础(WCF),包含一个新的媒体类型,需要相当数量的努力。 幸运的是,新版本的WCF HTTP堆栈,使媒体类型的WCF编程模型的一等公民,大家可以Glenn Block’s 博客去了解更详细的内容。推荐大家假期可以看下这本书《REST实战》http://book.douban.com/subject/6854551/

下面我们来介绍如何使用Google Protocol Buffers,只定义一个超媒体类型 ProtoBufferFormatter:

自 定义超媒体类型是通过创建自定义的MediaTypeFormatter,实现OnWritetoStream() 和 OnReadFromStream() 方法进行序列化和反序列化处理。人们经常认为媒体类型只是在服务端使用,但是它用来在客户端控制序列化和反序列化的要求,下图显示了一个HTTP 请求/响应和媒体类型格式化扮演的角色:

MediaTypeFormatterProcess

这个例子我们使用入门:构建简单的Web API 的代码和WCF Web API Preview 6。使用的媒体类型是application/x-protobuf ,REST服务的核心原则就是服务器和客户端之间的松耦合性,客户端需要知道书签的URI,但不应该知道任何其他的URI的知识,但是客户端必须知道链接关系。

image

下面的代码是自定义的ProtoBufferFormatter,构造函数里指明了支持的媒体类型 application/x-protobuf。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Net.Http.Formatting;
using System.IO;
using ProtoBuf;
using ProtoBuf.Meta;

namespace WcfWebFormat.Formatters
{
public class ProtoBufferFormatter : MediaTypeFormatter
{
public ProtoBufferFormatter()
{
this.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue(“application/x-protobuf”));
}

protected override void OnWriteToStream(Type type, object value, Stream stream, System.Net.Http.Headers.HttpContentHeaders contentHeaders, System.Net.TransportContext context)
{
Serializer.Serialize(stream, value);
}

protected override object OnReadFromStream(Type type, Stream stream, System.Net.Http.Headers.HttpContentHeaders contentHeaders)
{
object obj = (RuntimeTypeModel.Default).Deserialize(stream, null, type);
return obj;
}

}
}

如上所示,我们在OnWriteToStream方法中将.NET对象序列化为ProtoBuf格式,在OnReadFromStream方法中将ProtoBuf格式饭序列化为.NET对象。

现在需要给我们的.NET对象加入ProtoBuf 序列化的标签:

using System.Collections.Generic;
using System.Xml.Serialization;
using ProtoBuf;

namespace ContactManager.Resources
{
[ProtoContract]
public class Contact
{
[ProtoMember(1)]
public int ContactId { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
}
}

把ProtoBufferFormatter 加入到WCF运行时的超媒体类型集合里。

using Microsoft.ApplicationServer.Http;
using WcfWebFormat.Formatters;

namespace ContactManager
{
public class ContactManagerConfiguration : HttpConfiguration
{
public ContactManagerConfiguration()
{
this.Formatters.Add(new ProtoBufferFormatter());
}
}
}

修改服务配置,使用ContactManagerConfiguration:

var config = new ContactManagerConfiguration() { EnableTestClient = true };
routes.Add(new ServiceRoute(“api/contacts”, new HttpServiceHostFactory() { Configuration = config }, typeof(ContactsApi)));

在客户端调用的代码如下:

var serviceUri = new Uri(“http://localhost:9000/api/contacts/”);
var httpClient = new HttpClient();
httpClient.BaseAddress = serviceUri;
httpClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue(“application/x-protobuf”));

var response = httpClient.GetAsync(“1″).Result;
Contact obj = (RuntimeTypeModel.Default).Deserialize(response.Content.ReadAsStreamAsync().Result, null, typeof(Contact)) as Contact;

var formatters = new MediaTypeFormatterCollection() { new ProtoBufferFormatter() };
var content = new ObjectContent<Contact>(obj, “application/x-protobuf”,formatters);
content.Headers.ContentType = new MediaTypeHeaderValue(“application/x-protobuf”);

httpClient.PostAsync(serviceUri,content);

即使目前来说Google Protocol Buffers没有XML/JSON那样普及,RESTful服务使用中ProtoBuf无疑是一个非常有效的超媒体类型。祝大家龙年新春愉快,吉祥如意!

相关文章:

收藏与分享
2012年1月25日 By mikel 分类: Android, 开发笔记

[转载]Android读写文件基于Java的文件输入输出流 – 小文字 – 博客园.

一、向本地默认地址存储文件

1 public void save(String filename, String content) throws Exception
2 {
3       FileOutputStream outStream = context.openFileOutput(filename, Context.MODE_PRIVATE);
4       outStream.write(content.getBytes());
5       outStream.close();
6 }

首先创建一个文件输出流对象,它的值等于上下文context调用openFileOutput()方法的返回值,这个openFileOutput()方法将为本应用打开指定的私有文件以供写操作,如果当前文件不存在将创建一个。

方法的两个参数分别指定了文件的名称和读写模式,其中文件名称不能包含路径,文件的位置在:data/data/<package name>/files里面(这个目录可以用getFilesDir()方法得到,类似的getCacheDir()方法可以得到和files同级的cache目录)。读写模式分为4种基本模式,分别是:

MODE_PRIVATE或0 即私有模式:默认模式,文件只能被当前应用操作,而对其它应用透明。如果文件已经存在,新数据会把原有数据覆盖。

MODE_APPEND或32768 即附加模式:文件只能被当前应用操作,而对其它应用透明。如果文件已经存在,新数据会添加在旧数据之后。

MODE_WORLD_READABLE或1 即读模式:允许其他应用对本模式下创建的文件进行读操作。

MODE_WORLD_WRITEABLE或2 即写模式:允许其他应用对本模式下创建的文件进行写操作。

如果允许其他应用对该文件读和写两种操作,参数可以为Context.MODE_WORLD_READABLE+ Context.MODE_WORLD_WRITEABLE或者3,也就是读,写的组合。

然后调用输出流对象的write()写方法,其参数是字节类型,所以要把字符串类型转换为字节,调用getBytes()方法。

最后记得关闭输出流。

二、向SD卡存储文件

1 public void saveToSDCard(String filename, String content) throws Exception
2 {
3      File file = new File(Environment.getExternalStorageDirectory(), filename);
4      FileOutputStream outStream = new FileOutputStream(file);
5      outStream.write(content.getBytes());
6      outStream.close();
7 }

首先在清单文件里配置操作SD卡的权限

1 <!-- 在SDCard中创建与删除文件权限 -->
2 <uses-permission Android:name="Android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
3 <!-- 往SDCard写入数据权限 -->
4 <uses-permission Android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

要想将文件存放到SD卡上面,必须指定目录,这时我们不再像之前一样调用openFileOutput()方法,因为它存放的文件是位于手机本身“内存” 里面的,利用File的构造函数可以传入文件地址,有三种常用的,参数分别是目录与文件名,绝对路径,目录路径与文件名

1 public File(File dir, String name)
2 public File(String path)
3 public File(String dirPath, String name)

案例中通过Environment.getExternalStorageDirectory()方法获得Android手机中扩展卡的根目录,然后把file作为参数传给文件输出流的构造器,返回一个输出流,后面的操作和前面的相同。

收藏与分享
2012年1月18日 By mikel 分类: 开发笔记

[转载][推荐]番茄工作法——专治拖延症、精神涣散、再要五分钟综合症 – 1-2-3 – 博客园.

首先向仍然在工作岗位上奋战以及回到家还不忘刷博客园的兄弟们致敬。
最近读了一点《The Clean Coder》,一个意外的收获是,知道了原来还有个“番茄工作法”。尝试了几天,觉得很有效,推荐给你。
XX工作法,一听这名字你就能猜到,是时间管理,而时间管理又往往等同于成功学,所以你看了标题之后习惯性地无视本文的话,我也不怪你。成功学没有效果,是因为每个人的特质、境遇都不相同,成功往往无法复制。相反的,想办法改掉自己的一些坏习惯会更有效果。
我同时患有严重的拖延症、精神涣散和再要五分钟综合症,用了番茄工作法之后,好转了很多。
先说说我的病情。
拖延症——一直在心里想着“那件事必须得做了”,可就是迟迟不能开始。越是困难的、重要的事情,越容易这样。
再要五分钟综合症——无论娱乐还是工作,都容易烂尾。想好了娱乐到9点就工作,结果拖拖拉拉到10点还没开始干正经事;下定决心要在11点之前睡觉,但是天天都会磨蹭到11点半,12点还没上床。
精神涣散——我们都知道打断的危害。但是仔细想想,被别人打断的次数远没有被自己打断自己的次数多。譬如编译程序的时候,感觉好慢呐,赶快趁机刷下微博或者豆瓣吧,然后看到网友上传了美女图片,呦,韩寒写了新博客啦,完了又想起周报还没写呢……
这三种毛病综合发作的结果就是,整天愁眉苦脸、心急火燎,好像忙忙叨叨累的不行,到了该睡觉的时候却发现其实没干多少正经事,越发舍不得睡觉。脸色苍白,目光空洞,神疲气短,恶性循环。

番茄工作法

番茄工作法用一句话来概括就是——工作25分钟休息5分钟。详细条款如下:
0. 准备工作:买一个厨房计时器,或者使用软件计时器也行pcmaciphoneAndroid)。
1. 把计时器设定为25分钟,开始工作,直到响铃,算工作了一个番茄时间。然后在你的本子的工作项后面画一个叉叉表示此项工作已经耗费了一个番茄时间(如果使用软件会自动记录,更为方便)。
在一个番茄时间内,不可以被打断。这意味着等待编译的时候也不可以刷微博,也不要去泡咖啡或者吃东西。如果想到周报还没写,在本子上记上“需要写周 报”然后马上回到工作中。如果有同事打扰你或者接到客户的电话,礼貌地请求他等待20分钟,在本子上记上“需要给王科长回电话”然后马上回到工作中。当然 有时人家也会大喊一声“我等不了了!”,需要你立即紧急处理,那么这个番茄时间就相当于白费了,回到工作中时要重新开始这个番茄时间,就当这个番茄时间从 来没有开始过。响铃之后,你就可以看一下你的本子,处理刚刚记下的事情。如果这些零碎的小事很多,可以开启一个番茄时间集中处理它们。
在 一个番茄时间内,只做一件工作。如果工作了20分钟就完成了,也不要开始下一项工作或者去娱乐,可以回顾一下,检查/测试,总结经验,做一些优化或美化。 如果工作了5分钟就完成了,而且你觉得它本可以在上一个番茄时间里就完成的,并且再去复查一遍也没什么必要,可以将这个番茄时间作废。
2. 休息5分钟。
响铃之后必须立即停止手中的工作,想象你在考试,时间到,马上停笔。即使你觉得在休息的几分钟里就能完成它,也不能继续工作。而且,脑子里也不要再去想任何有关你工作的事情。这其实挺难做到的,我的建议是——离开你的座位——去做运动,或者跟同事聊聊天。
3. 每4个番茄之后休息15分钟。
如果感到疲劳,也可3个番茄就长休一次。或者把休息时间延长,但是不应超过25分钟。

更为详细的内容可以参考网站,电子书英文版中文版(仅32页,值得一读)。番茄工作法由意大利人Francesco Cirillo(弗朗切斯科•齐立罗)发明于1992年。因发明人最开始使用的是番茄形状的计时器而得名。

番茄工作法的好处

健康。 这个世界上有太多好玩的事情让人目不暇接。今晚有NFL季后赛野马vs爱国者耶,另外巴萨的比赛也不能错过,还有NBA快船vs湖人也想看一看。《The Clean Coder》看了一半不能扔下,还想再研究下ios编程……一件接着一件,一直不休息,直到累得实在难受才去床上趴一会儿,要么睡不着要么一睡不起。这种 被动的休息方式对健康极为不利。最好的休息方式是在感到累之前就做短暂的休息。
保持大脑的活力。由于特定 的生化原理,大脑内每个神经元都只能连续工作几分钟,之后就必须休息。连续的娱乐和工作会让大脑处于一种麻木、呆滞的状态。番茄团队经过大量研究发现,为 保持大脑的活力和效率最大化,最佳番茄时间为25~35分钟。我们大多数人连续工作的时间都是1小时或2小时,所以直觉上会怀疑25分钟的工作时间是不是 短了点?会不会刚刚进入状态就被休息打断了?其实,5分钟的休息并不会中断你的思路,反而可以让大脑里的清洁工有时间打扫战场,给脑细胞做做马杀鸡,之后 你会文思泉涌也说不定。
成就感。每个番茄代表一个纯的25分钟的工作时间。每天数一下番茄数就知道自己做了多少有用功,对每项工作耗费了多少时间也会有更具体的感觉。你可以知道自己娱乐了几个番茄,看了几个番茄的书,做了几个番茄的练习,对时间更有掌控感。
安全感。你可以安全地先娱乐一个番茄,然后再看一个番茄的书,不必担心一娱乐起来就停不下。
专心。 娱乐的时候想着工作,工作的时候不忘娱乐,这是我们大多数人的工作方式,也是低效和焦虑的根源。稀里糊涂地就到了下午,到了晚上,计划难以完成,好像做了 很多额外的事情,却又想不起来都干了些什么了。要想摆脱精神涣散的阴影,必须明确地强迫自己“一次只做一件事情,并且必须连续做25分钟”。
张弛有度。根据我的经验,对于一个严重的“再要五分钟综合症”患者,只是自己在心里想着“到九点就休息”是不够的,到时候不是差一点没完成,就是有了新的兴趣点。借助于计时器和“铃声响起立即停止”条款,才能干净利落地根治此症。
不再拖延。拖延症患者大多喜欢追求完美。越是想要做得好,就越容易否定自己,觉得没思路,不够好,信心受挫之下更容易精神涣散。使用番茄法,按下计时器,叮咚,强迫自己没思路也不许干别的,就算干想也要想满25分钟。

娱乐也番茄

书里说空闲的时间就不要用番茄法了。但是我的问题是,因为我觉得刚刚吃饱就工作不太合适,总想先娱乐一会儿。可是一旦娱乐起来就停不下来,所以我想娱乐 也可以使用番茄法。比如“看一个番茄的电影”,或者“看一个番茄的比赛”,之后休息五分钟。这样既有利于健康,也有机会决定之后是继续娱乐一个番茄,还是 工作一个番茄。

番茄你的计划

把番茄法与你的工作计划结合起来也很简单,就是使用番茄数作为工作项的时间单位。首先,你可以通过经验知道你每天平均能做几个番茄的工作。然后,估算每 个工作项需要的番茄数,这样你就能知道明天可以完成哪几个工作项了。每天结束的时候,再回顾一下每个工作项实际使用的番茄数。简单实用,是吧?
小贴士 一个工作项最多5到7个番茄,如果多于这个数,就认为这个任务太过复杂,最好把它分解为几个小任务。如果一个工作项的估值小于一个番茄,把它与其它的小任务组合成一个大任务。
小贴士 你可以在做计划的时候用空心方框“[]”表示一个番茄时间,3个番茄时间就是“[][][]”,把它标记在工作项的后面。实际工作时,每耗费一个番茄时间 就在空心方框里打个叉叉,耗费2个番茄时间是就是“[x][x][]”,耗费4个番茄时间时是“[x][x][x]x”)。

收藏与分享
2012年1月18日 By mikel 分类: ASP.NET MVC, 开发笔记

[转载]Asp.net MVC 基于规则的权限设计 – 胡以谦 – 博客园.

上面一篇文章我们简单介绍了一个一级菜单的应用。

在实际的设计中菜单的的信息基本存储在sitemap的xml文件中,菜单还涉及到权限问题。

本章将介绍并举例说明如何设计基于规则的MVC应用程序的安全性。

基于角色的授权

在计算机系统的安全,基于角色的访问控制(RBAC)是一个系统访问限制授权用户的方法。在一个组织内,角色创建的各项工作职能。来执行某些操作的权限分配给特定的角色。
业务上我们必须定义一套针对不同的业务功能的角色体系,例如管理员,数据管理员,普通用户的角色… …

基于规则的访问控制

以规则为基础的授权框架,一般利用XML文档存储简单的规则设置,来控制系统访问权限。(也可以存储在数据库中,读者可以扩展Enterprise Library)

请参见下面的例子。

<rules>
    <add expression="R:Administrator" name="IsAdministrator" />
     <add expression="R:Administrator OR R:DataSteward" name="IsDataSteward" />
     <add expression="R:User OR R:DataSteward OR R:Administrator"  name="IsUser" />
 </rules>

规则“IsAdministrator”会检查当前用户是否有Administrator的角色。 “IsUser”将对角色User, DataSteward或者Administrator都有效。

SecurityHelper

SecurityHelper类利用了Enterprise Library 的默认的AuthorizationRuleProvider,它是我们整个系统的权限核心。主要代码如下。

using System.Collections.Generic;
using Microsoft.Practices.EnterpriseLibrary.Security;
using Microsoft.Practices.Unity;
using Volvo.CustomerMaster.Infrastructure.Common.Utilities.Entities;
using Volvo.CustomerMaster.Infrastructure.Common.Utilities.Unity;
using Volvo.POS.UserDomain.ServiceLayer;
using Volvo.POS.UserDomain.DomainLayer;
using Volvo.CustomerMaster.Web.Common.Session;
namespace Volvo.CustomerMaster.Web.Common.Security
{
public class SecurityHelper
{
[Dependency]
public static IUserService UserService { get; set; }
/// <summary>
/// Authenticate the user to verify that the user is a logged in user and that the user is approved
/// by the external authorization system.
/// </summary>
/// <returns></returns>
public static bool Authenticate()
{
// Inject implementation of the UserService through DI
if (UserService == null)
{
UserService = Container.Resolve<UserService>();
}
string userName = GetWindowsVcnUserName();
// Get user from external authorization system
GenericUser user = UserService.GetUser(userName);
if (user == null)
{
return false;
}
// Set session
SessionWrapper.CurrentUser = user;
return true;
}
/// <summary>
/// Returns true if the user contain roles that is valid for selected rule
/// </summary>
/// <param name="rule"></param>
/// <returns></returns>
public static bool Authorized(string rule)
{
try
{
IList<string> rules = new List<string> { rule };
return Authorized(rules);
}
catch
{
return false;
}
}
/// <summary>
/// Returns true if the user contain roles that is valid for selected rules
/// </summary>
/// <param name="rules"></param>
/// <returns></returns>
public static bool Authorized(IList<string> rules)
{
// If user is not defined, try to authenticate it
if (SessionWrapper.CurrentUser == null)
{
if (!Authenticate())
{
return false;
}
}
// Get authorization provider from Entlib
IAuthorizationProvider auth = AuthorizationFactory.GetAuthorizationProvider("RulesProvider");
if (rules.Count > 0 && SessionWrapper.CurrentUser.Principal != null)
{
foreach (string rule in rules)
{
// Authorize user (with its roles) agains the rule
if (!auth.Authorize(SessionWrapper.CurrentUser.Principal, rule))
{
return false;
}
}
}
else
{
return false;
}
return true;
}
private static string GetWindowsVcnUserName()
{
// Get windows user
System.Security.Principal.WindowsIdentity loggedInUser =
System.Security.Principal.WindowsIdentity.GetCurrent();
if (loggedInUser != null)
{
string username = loggedInUser.Name;
username = username.Substring(username.IndexOf('\\') + 1);
username = username.ToUpper();
return username;
}
return null;
}
}
}

其中对当前用户检查某个规则的有效性代码如下。

IAuthorizationProvider auth = AuthorizationFactory.GetAuthorizationProvider("RulesProvider");
if (rules.Count > 0 && SessionWrapper.CurrentUser.Principal != null)
{
foreach (string rule in rules)
{
// Authorize user (with its roles) agains the rule
if (!auth.Authorize(SessionWrapper.CurrentUser.Principal, rule))
{
return false;
}
}
}
else
{
return false;
}
return true;

菜单的访问控制

在Web.sitemap文件中我们对每个节点增加一个属性,AuthorizationRule这样菜单和用户角色就关联起来了。

<?xml version="1.0" encoding="utf-8" ?>
<siteMap enableLocalization="true">
  <siteMapNode title="Menu">
    <siteMapNode controller="Home" title="Home"    action="Index" resourceKey="Tab_Home" AuthorizationRule="IsUser"/>
    <siteMapNode controller="Customer" title="Manage Customers"    action="Index" resourceKey="Tab_ManageCustomers" AuthorizationRule="IsDataSteward"/>
    <siteMapNode title="Switching Brands" resourceKey="Tab_SwitchingBrands" AuthorizationRule="IsUser">
      <siteMapNode title="Violin" controller="Home" action="SetTheme/Violin"  AuthorizationRule="IsUser"/>
      <siteMapNode title="Mack" controller="Home" action="SetTheme/Mack" AuthorizationRule="IsUser"/>
      <siteMapNode title="Mack Dual" controller="Home" action="SetTheme/MackDual" AuthorizationRule="IsUser"/>
      <siteMapNode title="Renault" controller="Home" action="SetTheme/Renault"  AuthorizationRule="IsUser"/>
      <siteMapNode title="Volvo BA" controller="Home" action="SetTheme/VolvoBA" AuthorizationRule="IsUser"/>
      <siteMapNode title="Volvo Group" controller="Home" action="SetTheme/VolvoGroup" AuthorizationRule="IsUser"/>
    </siteMapNode>
  </siteMapNode>
</siteMap>

菜单的规则如何、什么时候被加载呢?在渲染菜单的SiteMapBinding.cshtml文件中,我们的代码如下。(示例利用了Telerik for ASP.NET MVC控件)

@using Volvo.CustomerMaster.Web.Common.Security
@using Volvo.CustomerMaster.Web

 @{ Html.Telerik().Menu()
        .Name("Menu")
        .BindTo("Web",(item, node) =>{
             if (node.Attributes["resourceKey"] !=null)
                item.Text = UI_Resources.ResourceManager.GetString(node.Attributes["resourceKey"] as string) ?? item.Text;
            if(node.Attributes["imageurl"] != null)
            item.ImageUrl = node.Attributes["imageurl"].ToString();
            item.Visible = SecurityHelper.Authorized(node.Attributes["AuthorizationRule"].ToString());
        })
         .Effects(fx =>
          fx.Toggle()
          .OpenDuration(200)
          .CloseDuration(200))
       .Render();
 }

其中item.Visible=SecurityHelper.Authorized(node.Attributes["AuthorizationRule"].ToString());这行代码就决定了菜单的可见性由我们定义的规则控制。

UI元素访问控制

利用同样原理,按钮的enable/disable也可以基于规则来控制。我们首先构造一个类 (HtmlHelper)用于在页面上显示按钮。

ButtonHelper

以下核心代码将权限规则和按钮的显示关联。

Button

在页面中,我们如何利用ButtonHelper呢?下面的例子利用Telerik来显示一个Grid,在Grid的头上我么将显示edit, add, delete 按钮。

按钮的生成就利用了我么的ButtonHelper类。它提供了一些扩展方法。

@(Html.Telerik().Grid<Customer>()
         .Name("CustomerGrid")
         .EnableCustomBinding(true)
         .DataBinding(bind => bind.Ajax().Select("ListCustomerAjax", "Customer"))
         .ToolBar(toolBar => toolBar.Template
          (
             @Html.Button("toolbarEditRow", UI_Resources.ListCustomer_EditCustomerButton,
             ButtonHelper.SetButtonDisability("toolbarEditRow", "IsAdministrator"),
             new { title = UI_Resources.ListCustomer_EditCustomerButtonTooltip, @class = "icon edit" })
             +"<span >&nbsp;</span>"+
             @Html.Button("toolbarAddRow", UI_Resources.ListCustomer_AddNewCustomerButton, ButtonHelper.SetButtonDisability("toolbarAddRow", "IsAdministrator"), new { title = UI_Resources.ListCustomer_AddNewCustomerButtonTooltip, @class = "icon add" })
             +"<span >&nbsp;</span>"+
             @Html.Button("toolbarDeleteRow", UI_Resources.ListCustomer_DeleteCustomerButton, ButtonHelper.SetButtonDisability("toolbarDeleteRow", "IsAdministrator"), new { title = UI_Resources.ListCustomer_DeleteCustomerButtonTooltip, @class = "icon delete" })
          ))
         .Columns(columns =>
                    {
                        columns.Bound(o => o.Number).Title("Number").Width(40);
                        columns.Bound(o => o.Name).Title("Name").Width(100);
                        columns.Bound(o => o.Address).Title("Address").Width(100);
                    }
                 )
        .ClientEvents(x => x.OnLoad("CustomerGrid_OnLoad"))
        .Selectable()
        .Pageable(paging => paging.PageSize(10).Total(Model.CustomerCount))
        .Sortable())

显示按钮的时候,我们调用了ButtonHelper.SetButtonDisability来控制按钮的enable/disable状态,我们也可以通过它来控制显示、不显示按钮。

MVC Controller类的访问控制

有些用户可能会直接在浏览器中输入URL来绕过菜单的权限控制,我们必须在MVC的Controller级别加上我们的基于规则的权限管理。

我们增加一个新的类RuleAuthorizeAttribute,它继承于System.Web.Mvc.AuthorizeAttribute

View Code

代码很简单,它也利用了我们前面提到的SecurityHelper类的功能。

我们把这个属性设置的示例程序中的CustomerController类中。

    [HandleError]
    [RuleAuthorize(Allow="IsDataSteward")]
    public class CustomerController : BaseController
    {
        public ICustomerService CustomerService { get; set; }

        public CustomerController(ICustomerService customerService)
        {
            CustomerService = customerService;  

        }

假设我们登录的用户没有DataSteward或Administrator角色,但是他尝试直接在浏览器里面输入URL:http://localhost:2967/Customer。

新增的Filter控制了直接URL的权限管理。

按钮显示的控制

———————————————————————-
示例代码.

http://files.cnblogs.com/huyq2002/Sample.zip

运行程序您需要生成数据库CustomerMaster,运行CustomerMaster.SQL,同时修改NHibernate.config中的connection.connection_string

系统适用于域认证,也很容易扩展到其他认证方式,如form认证等

通过改变代码中的UserService的GetUser方法可以模拟不同的角色来使用系统

// 2. Get roles defined for the user
            if (userName.Equals("v0cn174", StringComparison.CurrentCultureIgnoreCase))
            {
                //user.AddRole(new UserRole(UserRoleConstants.Administrator));
                user.AddRole(new UserRole(UserRoleConstants.DataSteward));
                //user.AddRole(new UserRole(UserRoleConstants.User));
            }
            else
            {
                // All users are superusers in this mock
                //user.AddRole(new UserRole(UserRoleConstants.Administrator));
                //user.AddRole(new UserRole(UserRoleConstants.DataSteward));
                user.AddRole(new UserRole(UserRoleConstants.User));
            }
            return user;
收藏与分享
备案信息:冀ICP备10007948号