该平台于2005年基于DOTNET V1.1实现,版本号V1.0,本文描述一下实现的方案。目前,我正在升级V2.0,该版本Agent将完全兼容OOP,采用面向上下文编程方法实现。这 玩意比较抽象,估计你会晕,如果有晕的地方,就多看几遍,我一开始自己都被自己搞糊涂了。底层应用的技术有 DynamicProxy,ObjectBuilder,Agent技术和面向上下文编程模型。
Object和Agent
什么是Agent?为社么需要Agent?Agent属于人工智能的范畴,它也被称为“代理人”。Agent能够在一定程序上模拟了人类社会的行为和关系,具有一定的智能并能够自主运行。通俗的将,Agent相当于“人”。先用一个场景来看一下为什么需要Agent。
场景为“人物A让人物B关门”,Object模拟:用对象A模拟人物A,对象B模拟人物B,对象B具有关门的方法。在对象A中调用“B.关门()”方法对该场景进行模拟。
该模拟的缺陷如下:
缺陷一:对象是静态的,并不能贴切模拟人的思维等。
缺陷二:“对象A中调用‘B.关门()’方法”模拟该场景时,实际上是对象A的线程执行“B.关门()”方法的代码(与人类活动相左),而现实中是B来执行的。
缺陷三:在该模拟中,对象A调用“B.关门()”方法后,它必须等待该方法执行完毕后才能执行其它操作,这与现实是不一致的(需要异步支持)。
Object用于模拟只能实体,天生就有一些缺陷,相比Agent,它是静态的、被动的和没有思维的。本文针对利用Object模拟现实的缺陷,提出了改进的方案——将Object包装成Agent,使Object具有Agent的特性,方案如下。
缺陷一改进:将消息队列和消息处理器(作用相当于大脑)与Object绑定,使得对象能够模拟人的思维来接收消息并处理。这个消息处理器是一个线程。
缺陷二改进:设计一个调用拦截体系。当一个对象调用另一个对象的方法时,拦截体系能够拦截当前调用,并将该调用封装成一个消息包,发送给目标对象。目标对象的消息处理器从消息队列中获取该消息并处理。
缺陷三改进:拦截体系拦截调用后会根据调用方法的性质(同步或者异步)决定调用线程是否要等待调用结果或直接返回。
本 文把基于拦截体系的OOP平台称为AOP平台。该平台把基于拦截体系改进后的Object称为Agent。在该平台,Agent的本质是基于拦截体系的与 消息队列和消息处理器绑定的对象。上述的Agent能够模拟人与人之间的合作,但是在现实社会,不仅有单个人之间的合作,还存在团队合作。因此,本文提出 了一个Agent组(AgentGroup)概念。利用AgentGroup来模拟一个团队,一个AgentGroup由多个Agent组成。 AgentGroup的本质是与消息队列、消息处理器和消息路由器绑定的Object,并且由一个或几个Agent组成。
Agent和AgentGroup结构

在 本平台,Agent本质上是基于拦截体系的与消息队列和消息处理器绑定的对象。一个Agent至少包含一个消息队列和一个消息处理器。 AgentGroup的本质是与消息队列、消息处理器和消息路由器绑定的Object。一个AgentGroup至少包含一个消息队列、一个消息处理器、 一个消息路由器以及所包含的Agent。AgentGroup的消息队列用来接收、保存和读取与该AgentGroup相关的消息。当系统给一个 Agent发送消息时,如果该Agent属于一个AgentGroup,则这个消息会发送给该Agent所属的组。AgentGroup消息处理器本质也 是一个线程,它的状态图与Agent不同的是它没有“等待调用结果”状态和与该状态相关的转换。AgentGroup消息处理器主要是用来循环的从消息队 列读取消息,然后将该消息发送给消息路由器。消息路由器的作用是将该消息根据某种路由方式路由给该AgentGroup的一个或几个Agent。
拦截体系的实现
该 改进方案的实现依赖于一个拦截体系。拦截体系由3部分组成:(1)目标对象。(2)拦截机对象。(3)目标对象代理。拦截体系的工作机制如下:(1)利 用动态代理技术建立目标对象的代理,该代理暴露了与目标对象相同的接口;(2)调用对象调用目标对象的代理的接口时,拦截体系首先将当前调用封装成调用消 息包,然后建立一个相应拦截机对象,将当前消息作为参数传递给拦截机对象的Invoke方法,由拦截机对象决定如何调用目标对象的方法。

用 户注册一个Agent时,本平台首先创建该Agent的代理对象以及相应的拦截机对象。用户要调用一个Agent方法时,它首先从本平台获取需要调用的 Agent,本平台将会返回该Agent相应的动态代理对象给用户,此时,用户并非获取了Agent,而是Agent的代理对象;当用户调用代理对象的方 法时,拦截体系会从堆栈中取出当前调用信息,并将当前调用转换成一个调用消息包,传递给拦截机对象,拦截机对象会根据当前调用消息包的信息路由给被调用的 Agent或者被调用Agent所在的AgentGroup。因此,要实现一个拦截体系,必须实现:动态代理技术和拦截机。
动态代理技术 可以利用当前流行的OOP平台现成的反射机制和代码生成来完成,它的实现过程如下:(1)利用反射机制获取目标对象的描述信息,这些描述信息包括目标对象 的所有成员和方法的信息;(2)利用代码生成,动态的构建与目标对象的所有成员和方法信息一致的代理对象,并插入相应的拦截机对象。拦截机的作用是在将拦 截体系发送的调用消息包进行处理和路由。拦截机将根据不同的调用方式进行不同的处理。
基于拦截体系的AOP平台,Agent与 Agent、Agent与AgentGroup的调用将不同于Object间的调用。在AOP平台中,因为有了拦截体系,调用者的线程不再进入被调用者的 方法,而是转到拦截机,执行拦截机代码然后等待结果或者返回。该平台允许Agent、AgentGroup间共4种不同的调用方式:Agent与 Agent的同步调用、Agent与Agent的异步调用、Agent与AgentGroup的同步调用和Agent与AgentGroup的异步调用。
[MVC] Oxite2初探
由于Oxite1在ASP.NET MVC1还未发布的时候就早早地发布了,2009年2月15日发布的Oxite1口碑不好,但是即将发布的经过重新开发的Oxite2已经不再羸弱了,有非常多的值得观察与学习的地方。本文讨论的内容是基于Oxite2的,你可以在这里下载到最新的Oxite源码:http://oxite.codeplex.com/SourceControl/ListDownloadableCommits.aspx
欢迎加入Oxite小组一起学习:博客园Oxite小组
既然决定学习Oxite2,并且是有源代码的,那就让我们直接从源码开始吧。
解决方案文件
按照这里的说明,打开Oxite解决方案,VS显示整个解决方案共有七个项目组成:

注:上图移除了Oxite.Database项目
可以看出,解决方案中的七个项目中有五个是被分组放在Modules解决方案虚拟文件夹下进行管理的。剩下两个项目,其中Oxite项目是整个解决方案的最底层和核心架构,其他项目均对Oxite项目有引用关系,而另一个项目是Web应用程序项目。我们先大致看一下OxiteSite Web项目的结构,然后适当的时候转入Oxite项目做重点研究。
OxiteSite Web项目
首先展开OxiteSite Web应用程序项目,如下图:
有以下信息值得留意:
1. 整个Web应用程序项目中除View之外无任何Controller,ActionResult,Model,Filter等。
2. 打开Global.asax文件发现只有一行指令。其中Oxite.OxiteApplication类被放在了Oxite项目中。
<%@ Application Inherits="Oxite.OxiteApplication" Language="C#" %>
……
我们看到Oxite的Web应用程序项目只有一个职责:表现数据。这一点值得借鉴。
Oxite类库项目
因为Global.asax中的OxiteApplication类是在Oxite项目中的,所以很自然的我们打开Oxite类库项目看个究竟。通过该项目的命名、目录信息和其他项目对该项目的引用关系等可以判断Oxite类库项目就是整个解决方案的核心项目,该项目构建了Oxite的基础架构,为各个Module项目所用(Module项目是指放在解决方案文件夹Modules下的所有项目,各Module项目的相关信息通过IOxiteModule接口在Application_start中导入Unity容器。用于借助理解,可以设想将来Modules文件夹下可能还会出现Oxite.BBS、Oxite.Xuefly等)。在Oxite核心项目中定义了大量的基类、接口、基础服务、通用服务、以及必要的扩展方法等。其中IOxiteModule接口,Controller工厂OxiteControllerFactory : DefaultControllerFactory,缓存基础架构(ICacheEntity,IOxiteCacheModule),视图引擎IOxiteViewEngine : IViewEngine,插件机制IPluginEngine等,都是在该项目中定义和实现的。一些通用和底层的Model、Controller、自定义ActionResult等也是在这里定义和实现的。
东西很多,不可能做到在一篇随笔中全部包含。现在就让我们针对OxiteApplication : HttpApplication和IOxiteModule来考察一番吧。
OxiteApplication类
很明显画有下滑线的那些方法都是具有特定前缀名的方法,也是我们已经很熟悉的Application的编程接口。其他方法是用于画有下划线的方法的辅助方法。下面一一列举各个方法的作用:如果对相关方法的执行时机和顺序不太了解的话可以查看MSDN
Application_Start()方法:
双击上面类图中的Application_Start()方法或者选中该方法后按下F7进入代码,如下
/// <summary>
/// Setup and load application
/// 设置和加载应用程序
/// </summary>
protected void Application_Start()
{
Application["container"] = setupContainer();
Application["bootStrappersLoaded"] = false;
load();
}
作用见注释,注意到Unity容器被放在了Application["container"]中。
setupContainer()方法:
光标移动至Application_Start()中的setupContainer()调用上,按下F12进入setupContainer()的定义。
在该方法中创建Oxite所使用的Unity容器(IOC)并注册相关数据。,如果你对Unity不熟悉可以参考TerryLee的这篇文章,或者干脆下载Unity的源码和文档,通过看单元测试代码或者阅读仅仅200页的文档来学习也是一个不错的途径。
load()方法:
/// <summary>
/// Initialize any needed info for Oxite.
/// </summary>
private void load()
{
Load(new HttpContextWrapper(Context));
}
Load(HttpContextBase context)方法:
执行所有导入任务以导入各个模块(即实现了IOxiteModule接口的*Module)中注册的信息。注意到一个有趣的接口IBootStrapperTask(Boot Strapper是导入彪形大汉之意,我记得Visual Studio中的某个用于安装.NET的文件夹就是叫这么个名字,.NET团队把.NET比作彪形大汉,估计是寓意.NET很强大和很能干之意)。在Load(HttpContextBase context)方法中同样使用Unity容器取得注册数据。
因为Load方法中的主要逻辑是执行IBootStrapperTask接口的方法,所以我们看一下Unity容器中注册的是什么类型的彪形大汉(IBootStrapperTask)?Application_Start() è setupContainer()找到如下代码:
parentContainer.RegisterInstance<IBootStrapperTask>("LoadModules", new LoadModules(parentContainer));
可以看到,频繁传递的唯一的IUnityContainer实例中注册的是LoadModules : IBootStrapperTask类型的彪形大汉(IBootStrapperTask)。是在LoadModules 中IBootStrapperTask接口的Execute方法中对各个模块(即实现了IOxiteModule接口的*Module)进行循环注册信息到Unity容器的。如果观察一下Modules解决方案文件夹下的其他“模块项目”的话,就会发现Oxite是把各个“模块项目”相关的信息(Route、Filter、Binder、I**Repository、服务、等)分别放在各自项目的“模块(*Module)”中注册的。可以这样认为:所谓IOxiteModule接口其实就是抽象出来用于统一注册信息用的。我们明确定义所谓“模块”就是指继承自IOxiteModule接口的类,减少交流时的概念不统一障碍。
IOxiteModule接口:
public interface IOxiteModule
{
void Initialize();
void Unload();
void RegisterRoutes(RouteCollection routes);
void RegisterCatchAllRoutes(RouteCollection routes);
void RegisterFilters(IFilterRegistry filterRegistry);
void RegisterModelBinders(ModelBinderDictionary modelBinders);
void RegisterWithContainer();
}
Application_BeginRequest方法:
该方法中有两个逻辑
1, 如果尚未安装站点,即ISiteService的GetSite()方法返回空,即数据库中没有站点存在的话,导向安装页。
2, 核实进来的Uri,判断域名是否跟我们存在数据库中的Uri域名信息一致,否则尝试跳转。
另外该方法最后保存了Application_BeginRequest之前,即上次请求的请求上下文信息。
hasSameHostAsRequest和makeHostMatchRequest这两个私有方法的用途很明显就不说了。
OxiteApplication_AcquireRequestState方法:
该方法是Application的AcquireRequestState事件方法,当 ASP.NET 获取与当前请求关联的当前状态(如会话状态)时执行。该方法对于交由ASP.NET处理的每次请求都会执行,在该方法的逻辑中对包含当前请求上下文信息HttpContext实例中的IHttpHandler属性值的类型进行了判读,如果当前请求是被MvcHandler处 理程序处理的请求的话,接下来会验证用户状态并保存用户和站点的一些相关信息到当前请求上下文中,否则不做处理(由于IIS7将所有请求交由 ASP.NET处理,所以对于静态图片的请求也是会执行该方法的,但因为默认情况下图片并没有被配置到ASP.NET处理程序,所以不用担心OxiteApplication_AcquireRequestState中的代码会被执行)。
OxiteApplication_AcquireRequestState方法
private void OxiteApplication_AcquireRequestState(object sender, EventArgs e)
{
MvcHandler handler = Context.Handler as MvcHandler;
if (handler != null)
{
RequestContext requestContext = handler.RequestContext;
if (requestContext != null)
{
IUnityContainer container = ((IUnityContainer)Application["container"]);
if (container != null)
{
IModulesLoaded modules = container.Resolve<IModulesLoaded>();
if (modules != null)
{
IUser user = new UserAnonymous();
foreach (IOxiteAuthenticationModule module in modules.GetModules<IOxiteAuthenticationModule>().Reverse())
{
user = module.GetUser(requestContext);
if (user.IsAuthenticated)
break;
}
Context.Items[typeof(Site).FullName] = container.Resolve<ISiteService>().GetSite();
Context.Items[typeof(IUser).FullName] = user;
Context.Items[typeof(RequestContext).FullName] = requestContext;
}
}
}
}
}OxiteApplication类介绍完毕。
建立Oxite小组是 为了通过对Oxite2源码的观察和学习最终得到Oxite开发小组对ASP.NET MVC的一些处理和使用经验。对于ASP.NET MVC我们已经有很多人熟悉了,但可能像我一样仅仅只是做过Demo还没有在实际项目中使用过,我们缺少的正是ASP.NET MVC在实际项目中的使用和处理经验,希望大家通过参与Oxite小组发 现信息、总结经验,甚至形成模式。人与人之间的高效交流需要具备一个统一的知识载体,对于我们各自的ASP.NET MVC知识来说现在正是没有这样一个载体,所以才会交流困难,难以协作,存在误解。怎么办?好办,找个项目作为个体知识的共同载体吧!Oxite2挺好 ^_^,我觉得,你觉得呢?
[Flex]flex-htmlfilter swc complete
最近一直在找一个可以比较完美解析HTML的第三方工具包,但是苦无什么进展,就在前几天无意中发现了一个第三方类库,感觉还可以能识别一些常规的HTML,但是在图文混排上面还是差很多。
这个工具包叫做:Html Filter
Html Filter测试地址如下:
http://s.k-zone.cn/htmlfilter1
Html Filter主页地址:
s.k-zone.cn/htmlfilter2
Html Filter Source地址:
http://s.k-zone.cn/htmlfilter3
不过很有意思的是,这位作者,在AS3里面使用了C的宏编码(Make)方式,因此代码下载下来无法直接使用,需要转换。而转换的方式,虽然告诉大家了,但是仍旧有一些麻烦。
Html Filter Make教程地址:
http://s.k-zone.cn/htmlfilter4
所以我索性就把它生产了swc,以便方便大家直接使用。
Html Filter swc 下载地址:
http://s.k-zone.cn/sldown
使用方法跟原来一样,不过,需要注意一点:
原来的代码基本上没有安装编码规约,全部的Class的首字母没有大写。在这个SWC中,我把全部的首字母都改成了大写,即:<html:HtmlText … />
最后,再附上几个解析HTML的工具包:
http://s.k-zone.cn/as3cannonbal
http://s.k-zone.cn/htmlwrapper
[Flex]Flex Ioc 框架概览
Flex IOC 框架概览PDF完整版及配套源码下载地址:http://download.csdn.net/source/1634071
[JQuery]Fckeditor插入内容到当前光标处
有时候在工作的时候需要将某些文字单独的插入到fckeditor编辑器中,但这时如果修改了fckeditor的源文件的话,再想插入其它的东西就还要再修改一次,很不方便 。于是搜了很久,但没有结果,于是柠檬园主给出了解决方案,在此谢谢柠檬园主。
看代码:
function InsertHTML(e,inStr)
{
var oEditor = FCKeditorAPI.GetInstance(e) ;
if ( oEditor.EditMode == FCK_EDITMODE_WYSIWYG )
{
// Insert the desired HTML.
oEditor.InsertHtml( inStr ) ;
}
else
alert( 'You must be on WYSIWYG mode!' ) ;
}
function ExecuteCommand( commandName,e )
{
var oEditor = FCKeditorAPI.GetInstance(e) ;
oEditor.Commands.GetCommand( commandName ).Execute() ;
}
调用代码:
<a href="JavaScript:InsertHTML('obj','>>>>>>>>>& gt;Pagination<<<<<<<<<<');">FCK有API可以用, 可以直接在当前FCK编辑域的光标处执行动作的。</a>
‘下面的那个命令没有用到,应该是某些JS命令,在此没有用到。
对编辑器FCKeditor进行赋值:
var sString = '';
var oEditor = FCKeditorAPI.GetInstance('editor' );
oEditor.SetHTML(sString);
[JQuery]客户端验证插件Validate使用
最近在做一个用户注册登录的页面,资料查寻过程中发现了一个非常不错的客户端验证的极品-JQuery.validate。
它是著名的JavaScript包JQuery的一个插件,其实它还有其它的一些插件应该都爽,有待慢慢来学习
官方地址:http://bassistance.de/jQuery-plugins/jQuery-plugin-validation/
jQuery用户手册:http://jquery.org.cn/visual/cn/index.xml
开发使用起来非常简单明了,
我的代码:
- $(document).ready(function(){
- /* 设置默认属性 */
- $.validator.setDefaults({
- submitHandler: function(form) { form.submit(); }
- });
- // 中文字两个字节
- jQuery.validator.addMethod("byteRangeLength", function(value, element, param) {
- var length = value.length;
- for(var i = 0; i < value.length; i++){
- if(value.charCodeAt(i) > 127){
- length++;
- }
- }
- return this.optional(element) || ( length >= param[0] && length <= param[1] );
- }, "请确保输入的值在3-15个字节之间(一个中文字算2个字节)");
- /* 追加自定义验证方法 */
- // 身份证号码验证
- jQuery.validator.addMethod("isIdCardNo", function(value, element) {
- return this.optional(element) || isIdCardNo(value);
- }, "请正确输入您的身份证号码");
- // 字符验证
- jQuery.validator.addMethod("userName", function(value, element) {
- return this.optional(element) || /^[\u0391-\uFFE5\w]+$/.test(value);
- }, "用户名只能包括中文字、英文字母、数字和下划线");
- // 手机号码验证
- jQuery.validator.addMethod("isMobile", function(value, element) {
- var length = value.length;
- return this.optional(element) || (length == 11 && /^(((13[0–9]{1})|(15[0–9]{1}))+\d{8})$/.test(value));
- }, "请正确填写您的手机号码");
- // 电话号码验证
- jQuery.validator.addMethod("isPhone", function(value, element) {
- var tel = /^(\d{3,4}-?)?\d{7,9}$/g;
- return this.optional(element) || (tel.test(value));
- }, "请正确填写您的电话号码");
- // 邮政编码验证
- jQuery.validator.addMethod("isZipCode", function(value, element) {
- var tel = /^[0–9]{6}$/;
- return this.optional(element) || (tel.test(value));
- }, "请正确填写您的邮政编码");
- $(regFrom).validate({
- /* 设置验证规则 */
- rules: {
- userName: {
- required: true,
- userName: true,
- byteRangeLength: [3,15]
- },
- password: {
- required: true,
- minLength: 5
- },
- repassword: {
- required: true,
- minLength: 5,
- equalTo: "#password"
- },
- question: {
- required: true
- },
- answer: {
- required: true
- },
- realName: {
- required: true
- },
- cardNumber: {
- isIdCardNo: true
- },
- mobilePhone: {
- isMobile: true
- },
- phone: {
- isPhone: true
- },
- email: {
- required: true,
- email: true
- },
- zipCode: {
- isZipCode:true
- }
- },
- /* 设置错误信息 */
- messages: {
- userName: {
- required: "请填写用户名",
- byteRangeLength: "用户名必须在3-15个字符之间(一个中文字算2个字符)"
- },
- password: {
- required: "请填写密码",
- minlength: jQuery.format("输入{0}.")
- },
- repassword: {
- required: "请填写确认密码",
- equalTo: "两次密码输入不相同"
- },
- question: {
- required: "请填写您的密码提示问题"
- },
- answer: {
- required: "请填写您的密码提示答案"
- },
- realName: {
- required: "请填写您的真实姓名"
- },
- email: {
- required: "请输入一个Email地址",
- email: "请输入一个有效的Email地址"
- }
- },
- /* 错误信息的显示位置 */
- errorPlacement: function(error, element) {
- error.appendTo( element.parent() );
- },
- /* 验证通过时的处理 */
- success: function(label) {
- // set as text for IE
- label.html(" ").addClass("checked");
- },
- /* 获得焦点时不验证 */
- focusInvalid: false,
- onkeyup: false
- });
- // 输入框获得焦点时,样式设置
- $('input').focus(function(){
- if($(this).is(":text") || $(this).is(":password"))
- $(this).addClass('focus');
- if ($(this).hasClass('have_tooltip')) {
- $(this).parent().parent().removeClass('field_normal').addClass('field_focus');
- }
- });
- // 输入框失去焦点时,样式设置
- $('input').blur(function() {
- $(this).removeClass('focus');
- if ($(this).hasClass('have_tooltip')) {
- $(this).parent().parent().removeClass('field_focus').addClass('field_normal');
- }
- });
- });
网上的资料有人说,它跟prototype包会有冲突,我还没有同时使用过,这点不是很清楚,但我是发现一个问题:
对于最小/大长度的验证方法,作者可能考虑到大家的命名习惯不同,同时做了minLength和minlength(maxLength和 maxlength)方法,应该哪一个都是可以的,但对于用户Message来说,只能够定义针对minlength(maxlength),才能调用用 户自定义的Message,
否则只是调用包的默认Message,但具体原因还没有查清楚。同时,这个插件提供了本地化的消息,但对于我这里初学者来说,怎么使用它还有待摸索!
[Web]如何让网页更快
谷歌官方博客与代码博客联合发布
发表于:2009年6月23日 下午 15:55:00
我们很高兴能与网络社区讨论谷歌所了解的网络性能。然而,想要让浏览网络的速度与翻杂志的速度一样快,去优化网络应用的速度,我们需要共同努力,来攻克使网络变慢的难题,阻止那些潜在的、导致网络变慢的问题发生:
- 在宽带和丰富的网页交互程序处于初始阶段的时候,一些为网络和网页发展提供动力的协议已经得到了发展。在过去的20年里,网络已经变得越来越快,通过合作来更新网络协议例如HTML和TCP/IP,我们可以为所有用户创造出更好的网络体验。网络社区协作最好的例证就是 HTML5 。现在 HTML5 的一些特征,例如AppCache,可以让开发者用大量复杂的 JavaScript 网络程序编写,并且让它运行起来就像是桌面应用程序一样快捷。
- 在过去的十年里,我们看到JavaScript速度提升了近 100 倍。浏览器的开发者和围绕在他们周围的社区需要持续关改进,以此为浏览者提供平台,让他们能够选择更有特色、更复杂的应用。
- 其实,很多网站只需要很少的努力就能让反应速度变快,多关注用户体验可以让整个网络时间都变得更快。类似于雅虎 YSlow 和谷歌最新发布的 Page Speed 这样的工具,可以帮助网页开发者做出速度更快、更有效的网络插件。作为一个社区,我们需要进一步投资开发新一代工具,来评估、诊断和优化由一次点击带来的工作。
[Flex]FlexPMD开源项目发布
Adobe Technical Service团队今天发布了一个开源的项目,叫做FlexPMD。
FlexPMD主要用来提升Flex/AS3源文件中的代码质量并且检测常见的不好的代码实践,比如无用的代码,效率低的代码片段,过于复杂的代码等等这些都是FlexPMD检测并报告的对象。
作为开发者,现在你也可以完全获得FlexPMD的项目并参与到其中了。
http://opensource.adobe.com/wiki/display/flexpmd/FlexPMD
下载:
http://opensource.adobe.com/wiki/display/flexpmd/Downloads
获取源代码:
http://opensource.adobe.com/wiki/display/flexpmd/Get+Source+Code
[C#]c#扩展方法奇思妙用高级篇五:ToString(string format) 扩展
这篇文章将ToString()进行了扩充,加入了Format参数,一直以来表示层用Json数据格式,起始可以将toString函数进行Json数据格式转换,将实体类转换为Json格式,这样方便了表示层使用
转载自:http://www.cnblogs.com/ldp615/archive/2009/09/02/1559020.html
在.Net中,System.Object.ToString()是用得最多的方法之一,ToString()方法在Object类中被定义为virtual,Object类给了它一个默认实现:
2 {
3 return this.GetType().ToString();
4 }
.Net中原生的class或struct,如int,DateTime等都对它进行重写(override),以让它返回更有价值的值,而不是 类型的名称。合理重写的ToString()方法中编程、调试中给我们很大方便。但终究一个类只有一个ToString()方法,不能满足我们多样化的需 求,很多类都对ToString()进行了重载。如下:
2 string intString = 10.ToString("d4"); //0010
int、DateTime都实现了ToString(string format)方法,极大方便了我们的使用。
对于我们自己定义的类型,我们也应该提供一个合理的ToString()重写,如果能够提供再提供一个ToString(string format),就会令我们后期的工作更加简单。试看以下类型:
2 {
3 private List<People> friends = new List<People>();
4
5 public int Id { get; set; }
6 public string Name { get; set; }
7 public DateTime Brithday { get; set; }
8 public People Son { get; set; }
9 public People[] Friends { get { return friends.ToArray(); } }
10
11 public void AddFriend(People newFriend)
12 {
13 if (friends.Contains(newFriend)) throw new ArgumentNullException("newFriend", "该朋友已添加");
14 else friends.Add(newFriend);
15 }
16 public override string ToString()
17 {
18 return string.Format("Id: {0}, Name: {1}", Id, Name);
19 }
20

21 }
一个简单的类,我们给出一个ToString()重写,返回包含Id和Name两个关键属性的字符串。现在我们需要一个ToString(string format)重写,以满足以下应用:
2 string s0 = p.ToString("Name 生日是 Brithday"); //理想输出:鹤冲天 生日是 1990-9-9
3 string s1 = p.ToString("编号为:Id,姓名:Name"); //理想输出:编号为:1,姓名:鹤冲天
想想怎么实现吧,记住format是可变的,不定使用了什么属性,也不定进行了怎样的组合…
也许一个类好办,要是我们定义很多类,几十、几百个怎么办?一一实现ToString(string format)会把人累死的。好在我们有扩展方法,我们对object作一扩展ToString(string format),.Net中object是所有的基类,对它扩展后所有的类都会自动拥有了。当然已有ToString(string format)实现的不会,因为原生方法的优先级高,不会被扩展方法覆盖掉。
来看如何实现吧(我们会一步一步改进,为区分各个版本,分别扩展为ToString1、ToString2…分别对应版本一、版本二…):
2 {
3 Type type = obj.GetType();
4 PropertyInfo[] properties = type.GetProperties(
5 BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance);
6
7 string[] names = properties.Select(p => p.Name).ToArray();
8 string pattern = string.Join("|", names);
9
10 MatchEvaluator evaluator = match =>
11 {
12 PropertyInfo property = properties.First(p => p.Name == match.Value);
13 object propertyValue = property.GetValue(obj, null);
14 if (propertyValue != null) return propertyValue.ToString();
15 else return "";
16 };
17 return Regex.Replace(format, pattern, evaluator);
18 }
3~5行通过反射获取了公有的、实例的Get属性(如果需要静态的或私有的,修改第5行中即可),7~8行动态生成一个正则表达式来匹配 format,10~16行是匹配成功后的处理。这里用到反射和正则表达式,如果不熟悉不要紧,先调试运行吧,测试一下前面刚提到的应用:
第一个和我们理想的有点差距,就是日期上,我们应该给日期加上"yyyy-MM-dd"的格式,这个我们稍后改进,我们现在有一个更大的问题:
如果我们想输出:“People: Id 1, Name 鹤冲天”,format怎么写呢?写成format="People: Id Id, Name Name",这样没法处理了,format中两个Id、两个Name,哪个是常量,哪个是变量啊?解决这个问题,很多种方法,如使用转义字符,可是属性长 了不好写,如format="\B\r\i\t\h\d\a\y Brithday"。我权衡了一下,最后决定采用类似SQL中对字段名的处理方法,在这里就是给变量加上中括号,如下:
2 string s2 = p1.ToString2("People:Id [Id], Name [Name], Brithday [Brithday]");
版本二的实现代码如下:
2 {
3 Type type = obj.GetType();
4 PropertyInfo[] properties = type.GetProperties(
5 BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance);
6
7 MatchEvaluator evaluator = match =>
8 {
9 string propertyName = match.Groups["Name"].Value;
10 PropertyInfo property = properties.FirstOrDefault(p => p.Name == propertyName);
11 if (property != null)
12 {
13 object propertyValue = property.GetValue(obj, null);
14 if (propertyValue != null) return propertyValue.ToString();
15 else return "";
16 }
17 else return match.Value;
18 };
19 return Regex.Replace(format, @"\[(?<Name>[^\]]+)\]", evaluator, RegexOptions.Compiled);
20 }
调试执行一下:
与版本一类似,不过这里没有动态构建正则表达式,因为有了中括号,很容易区分常量和变量,所以我们通过“属性名”来找“属性”(对应代码中第 10行)。如果某个属性找不到,我们会将这“[Name]”原样返回(对就第17行)。另一种做法是抛出异常,我不建议抛异常,在 ToString(string format)是不合乎“常理”的。
版本二相对版本一效率有很大提高,主要是因为版本二只使用一个简单的正则表达式:@"\[(?<Name>[^\]]+)\]"。而版本一中的如果被扩展类的属性特别多,动态生成的正则表达式会很长,执行起来也会相对慢。
我们现在来解决两个版本中都存在的时间日期格式问题,把时间日期格式"yyyy-MM-dd"也放入中括号中,测试代码如下:
2 string s3 = p3.ToString3("People:Id [Id: d4], Name [Name], Brithday [Brithday: yyyy-MM-dd]");
版本三实现代码:
2 {
3 Type type = obj.GetType();
4 PropertyInfo[] properties = type.GetProperties(
5 BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance);
6
7 MatchEvaluator evaluator = match =>
8 {
9 string propertyName = match.Groups["Name"].Value;
10 string propertyFormat = match.Groups["Format"].Value;
11
12 PropertyInfo propertyInfo = properties.FirstOrDefault(p => p.Name == propertyName);
13 if (propertyInfo != null)
14 {
15 object propertyValue = propertyInfo.GetValue(obj, null);
16 if (string.IsNullOrEmpty(propertyFormat) == false)
17 return string.Format("{0:" + propertyFormat + "}", propertyValue);
18 else return propertyValue.ToString();
19 }
20 else return match.Value;
21 };
22 string pattern = @"\[(?<Name>[^\[\]:]+)(\s*:\s*(?<Format>[^\[\]:]+))?\]";
23 return Regex.Replace(format, pattern, evaluator, RegexOptions.Compiled);
24 }
测试一下,可OK了:
对于简单的值类型属性没问题了,但对于复杂一些类型如,如People的属性Son(Son就是儿子,我一开始写成了Sun),他也是一个People类型,他也有属性的,而且他也可能有Son…
先看下调用代码吧:
2 p4.Son = new People { Id = 2, Name = "鹤小天", Brithday = new DateTime(2015, 9, 9) };
3 p4.Son.Son = new People { Id = 3, Name = "鹤微天", Brithday = new DateTime(2040, 9, 9) };
4 string s4 = p4.ToString4("[Name] 的孙子 [Son.Son.Name] 的生日是:[Son.Son.Brithday: yyyy年MM月dd日]。");
“鹤冲天”也就是我了,有个儿子叫“鹤小天”,“鹤小天”有个儿子,也就是我的孙子“鹤微天”。哈哈,祖孙三代名字都不错吧(过会先把小天、微天这两个名字注册了)!主要看第4行,format是怎么写的。下面是版本四实现代码,由版本三改进而来:
2 {
3 MatchEvaluator evaluator = match =>
4 {
5 string[] propertyNames = match.Groups["Name"].Value.Split('.');
6 string propertyFormat = match.Groups["Format"].Value;
7
8 object propertyValue = obj;
9 try
10 {
11 foreach (string propertyName in propertyNames)
12 propertyValue = propertyValue.GetPropertyValue(propertyName);
13 }
14 catch
15 {
16 return match.Value;
17 }
18
19 if (string.IsNullOrEmpty(format) == false)
20 return string.Format("{0:" + propertyFormat + "}", propertyValue);
21 else return propertyValue.ToString();
22 };
23 string pattern = @"\[(?<Name>[^\[\]:]+)(\s*[:]\s*(?<Format>[^\[\]:]+))?\]";
24 return Regex.Replace(format, pattern, evaluator, RegexOptions.Compiled);
25 }
为了反射获取属性方法,用到了GetPropertyValue扩展如下(版本三的实现用上这个扩展会更简洁)(考虑性能请在此方法加缓存):
2 {
3 Type type = obj.GetType();
4 PropertyInfo info = type.GetProperty(propertyName);
5 return info.GetValue(obj, null);
6 }
先执行,再分析:
执行正确! 版本四,8~17行用来层层获取属性。也不太复杂,不多作解释了。说明一下,版本四是不完善的,没有做太多处理。
我们最后再来看一下更复杂的应用,Peoplee有Friends属性,这是一个集合属性,我们想获取朋友的个数,并列出朋友的名字,如下:
2 p5.AddFriend(new People { Id = 11, Name = "南霸天" });
3 p5.AddFriend(new People { Id = 12, Name = "日中天" });
4 string s5 = p5.ToString5("[Name] 目前有 [Friends: .Count] 个朋友:[Friends: .Name]。");
注意,行4中的Count及Name前都加了个小点,表示是将集合进行操作,这个小点是我看着方便自己定义的。再来看实现代码,到版本五了:
2 {
3 MatchEvaluator evaluator = match =>
4 {
5 string[] propertyNames = match.Groups["Name"].Value.Split('.');
6 string propertyFormat = match.Groups["Format"].Value;
7
8 object propertyValue = obj;
9
10 try
11 {
12 foreach (string propertyName in propertyNames)
13 propertyValue = propertyValue.GetPropertyValue(propertyName);
14 }
15 catch
16 {
17 return match.Value;
18 }
19
20 if (string.IsNullOrEmpty(propertyFormat) == false)
21 {
22 if (propertyFormat.StartsWith("."))
23 {
24 string subPropertyName = propertyFormat.Substring(1);
25 IEnumerable<object> objs = ((IEnumerable)propertyValue).Cast<object>();
26 if (subPropertyName == "Count")
27 return objs.Count().ToString();
28 else
29 {
30 string[] subProperties = objs.Select(
31 o => o.GetPropertyValue(subPropertyName).ToString()).ToArray();
32 return string.Join(", ", subProperties);
33 }
34 }
35 else
36 return string.Format("{0:" + propertyFormat + "}", propertyValue);
37 }
38 else return propertyValue.ToString();
39 };
40 string pattern = @"\[(?<Name>[^\[\]:]+)(\s*[:]\s*(?<Format>[^\[\]:]+))?\]";
41 return Regex.Replace(format, pattern, evaluator, RegexOptions.Compiled);
42 }
执行结果:
比较不可思议吧,下面简单分析一下。行22~行33是对集合进行操作的相关处理,这里只是简单实现了Count,当然也可以实现Min、Max、Sum、Average等等。“.Name”这个表示方法不太好,这里主要是为了展示,大家能明白了就好。
就写到这里吧,版本六、版本七…后面还很多,当然一个比一个离奇,不再写了。给出五个版本,版本一存在问题,主要看后三个版本,给出多个版本 是为满足不同朋友的需要,一般来说版本三足够,对于要求比较高,追求新技术的朋友,我推荐版本四、五。要求更高的,就是没给出的六、七…了。
ToString(string format)扩展带来便利性的同时,也会带来相应的性能损失,两者很难兼得。
最后重申下,本系列文章,侧重想法,所给的代码仅供演示、参考,没有考虑性能、异常处理等,如需实际使用,请自行完善。
本人系列文章《c#扩展方法奇思妙用》,敬请关注!
[Map]Google Map学习(二) 简单的Google Map,Google Map事件
上一节已经学习了Google Map的基础。首先回顾一下上一节的基本内容
地图对象的创建:var map = new GMap2(document.getElementById("map"));
设置map的默认显示形式:map.setMapType(G_HYBRID_MAP);
设置地图的位置(这是必须的):map.setCenter(new GLatLng(22.4977,113.9208),19);
添加一个信息窗口:map.openInfoWindowHtml(map.getCenter(),document.createTextNode("北科创业大厦"));
以上内容都非常的简单,但是仅仅是显示了一张地图,非常单一。
这一节,我们主要学习地图中的事件,并且通过事件做一些简单的交互。
Google Map的命名空间
到现在大家可能已经发现了Google Map API中的类都带有一个大写字母G,G是Google Map API的顶级命名空间,它的原型是google.maps.*,比如GMap2也可以写成google.maps.Map2。
地图事件
JavaScript是”事件驱动的“,也就是说JavaScript通过事件来响应交互。比如用户希望在某个DOM元素移动鼠标时,发生一 些什么,那么在这个DOM元素上移动鼠标就是一个事件,我们要做的就是监听这个事件,为这个事件注册监听器,也就是写一个监听函数,用户所希望的东西,我 们都可以在函数中进行表达。
Google 地图 API 通过为地图 API 对象定义自定义事件而添加到此事件模型中。虽然不同浏览器中的DOM事件不同,但是Google Map API已经帮包含跨浏览器的机制,我们无需再因为用户使用不同浏览器而苦恼了。
注册事件监听器
通过使用GEvent命名空间中的实用工具函数注册事件监听器,来处理Google Map API中的一些事件。每个对象都包含很多已有的事件,例如GMap2对象的click、dbclick事件。
注册事件的方法很简单:GEvent.addListener(object,event,function),三个参数分别对象,待监听事件,事件调用函数。例如:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript" src="http://ditu.google.cn/maps?file=api&v=2">
</script>
<script type="text/javascript">
function load() {
if (GBrowserIsCompatible()) {
var px = 30.6562;
var py = 104.0639;
var cx = px;
var cy = py;
var zoom = 12;
var map = new GMap2(document.getElementById("map"));
var point = new GLatLng(px, py);
map.setCenter(point,zoom);
}
/*
获得当前点击的地理坐标
*/
GEvent.addListener(map, "click", function(marker, point) {
if (point) {
px = point.lat();
py = point.lng();
zoom = map.getZoom();
document.getElementById("lat").value = px;
document.getElementById("lng").value = py;
}
});
/*
zoomend地图到达新的缩放级别时会触发此事件。事件处理程序接收先前的缩放级别和新缩放级别作为参数。
*/
GEvent.addListener(map, "zoomend", function() {
zoom = map.getZoom();
//document.getElementById("zoom").value = zoom;
});
/*
moveend结束地图视图的更改时会触发此事件。拖动或放大/缩小都会触发此事件
*/
GEvent.addListener(map, "moveend", function() {
var center = map.getCenter();
cx = center.lat();
cy = center.lng();
document.getElementById("lat").value = cx;
document.getElementById("lng").value = cy;
document.getElementById("centerLat").value = cx;
document.getElementById("centerLng").value = cy;
document.getElementById("zoom").value = zoom;
});
document.getElementById("lat").value = px;
document.getElementById("lng").value = py;
document.getElementById("centerLat").value = cx;
document.getElementById("centerLng").value = cy;
document.getElementById("zoom").value = zoom;
}
window.onload = load;
</script>
</head>
<body>
<div>
当前经度:<input type="text" id="lat"/><br />
当前纬度:<input type="text" id="lng"/><br />
中心经度:<input type="text" id="centerLat"/><br />
中心纬度:<input type="text" id="centerLng"/><br />
当前倍率:<input type="text" id="zoom"/>
</div>
<div style="width:500px; height:500px;" id="map"></div>
</body>
</html>
(如果在IE下出现GMap2未定义的错误,请刷新一遍,火狐下是正常的)
将事件绑定到对象方法
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript" src="http://ditu.google.cn/maps?file=api&v=2">
</script>
<script type="text/javascript">
function MyApplication() {
this.counter = 0;
this.map = new GMap2(document.getElementById("map"));
this.map.setCenter(new GLatLng(39.9493, 116.3975), 13);
GEvent.bind(this.map, "click", this, this.onMapClick);
}
MyApplication.prototype.onMapClick = function() {
this.counter++;
alert("这是您第" + this.counter + " " + "次点击");
}
window.onload = function(){
var application = new MyApplication();
}
</script>
</head>
<body>
<div id="map" style="width:500px; height:500px;"></div>
</body>
</html>(如果在IE下出现GMap2未定义的错误,请刷新一遍,火狐下是正常的)
监听DOM事件
Google地图API事件模型创建并管理自己的自定义事件。但是,DOM也会根据当前使用的特定浏览器事件模型创建和调度自己的事件。如果你希望捕获这 些事件,Google地图API提供的独立于浏览器的包装器可以监听和绑定DOM事件而不需要自定义代码。
GEvent.addDomListener()方法为DOM节点上的DOM注册事件处理程序。同样,GEvent.bindDom()方法允许你给类实例上的DOM事件注册事件处理程序。
删除事件监听器
不再需要事件监听器时,应将其删除。甚至在事件只需触发一次的情况下,也可能需要删除。删除闭包内的匿名函数所定义的事件监听器可能很困难。但是,addListener()、addDomListener()、bind() 和 bindDom() 函数会返回 GEventListener 句柄,可用来最终取消注册处理程序。
下面的示例通过在地图上放置标记来响应点击。任何后续点击都可删除事件监听器。请注意,这会导致不再执行 removeOverlay() 代码。另请注意,您甚至可以从事件监听器自身内部删除事件监听器。
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript" src="http://ditu.google.cn/maps?file=api&v=2">
</script>
<script type="text/javascript">
function MyApplication(){
this.counter = 0;
this.map = new GMap2(document.getElementById("map"));
this.map.setCenter(new GLatLng(39.9493, 116.3975), 13);
var myEventListener = GEvent.bind(this.map, "click", this, function(overlay, latlng) {
if (this.counter == 0) {
if (latlng) {
this.map.addOverlay(new GMarker(latlng))
this.counter++;
}
else if (overlay instanceof GMarker) {
this.removeOverlay(marker);
}
}
else {
GEvent.removeListener(myEventListener);
}
});
}
window.onload = function(){
var application = new MyApplication();
}
</script>
</head>
<body onunload = "GUnload()">
<div id="map" style="width:500px; height:500px;">
</div>
</body>
</html>(如果在IE下出现GMap2未定义的错误,请刷新一遍,火狐下是正常的)
Mikel