[转载]详解主流浏览器多个外部JavaScript请求和执行机制

mikel阅读(1115)

[转载]详解主流浏览器多个外部JS请求和执行机制 – Simon4545的IT生活 – 博客园.

在IE8,Firefox3.6之前页面加载外部的JavaScript文件(IE6,7会连同图片,样式资源和页面渲染一同阻塞)是阻塞式的,而 在之后的版本中,浏览器都使用了瀑布式加载,这样页面的打开及渲染速度都会变快,请注意,我提到的瀑布式加载,仅仅指的是加载,而非JS的执行,在主流浏 览器中JS的执行总是阻塞的。用简单一点的语言描述,就是同一时间,页面只会加载一个js文件。在第一个js文件加载并执行完之前,第二个要引入的js不 会下载和执行。而页面中js的引入顺序以请求的顺序为定。
我们来看一个IE6下,页面加载多个js文件的例子:
inline script block:为页面内嵌js代码块,而external script则是需要从外部加载进来的js文件。

image我 们设定inline script执行用时3秒,external script1加载用时2秒,执行用时3秒,external script2加载用时2秒,执行用0秒(实际上会稍大于0,下同),external script3加载用2秒,执行用时0秒。
由上图,可知页面需要15641毫秒才能将js加载并执行完。我们可以得到一个公式:

image

而同样的页面,在IE8和Firefox3.6下,我们会得到:
IE8: (只需要9秒,速度快了近一倍)

image

Firefox:

image

由以上的现象,可以证实上面的观点。

那么是否有一个方案可以让IE6/7或是Chrome展示我们的页面更快一点,可以同时加载多个文件,并且不影响页面中DOM元素的渲染?答案是肯定的。

在之前的项目中,我曾经写过类似的代码,来处理请求多个外部JavaScript文件。

function loadScript(url, fn, doc, charset){
doc
= doc || document;
var script = doc.createElement(script);
script.language
= javascript;
script.charset
= charset?charset:’utf8’;
script.type
= text/javascript;

script.onload = script.onreadystatechange = function(){
if (!script.readyState || loaded === script.readyState || complete === script.readyState) {
fn
&& fn();
script.onload
= script.onreadystatechange = null;
script.parentNode.removeChild(script);
};
};
script.src
= url;
$(
head)[0].appendChild(script);
}

那个当网页中需要动态调用多个js文件时,我们可以这样写:

loadScript('../jquery-1.4.2.js',function(){console.log('jquery-1.4.2.js loaded')}); loadScript('$.wbx.js',function(){console.log('example/$.wbx.js loaded')}); loadScript('gadgets.js',function(){console.log('example/gadgets.js loaded')}); loadScript('jui-all.js',function(){console.log('example/jui-all.js loaded')});
 上段代码处理这样的功能:可以动态加载多个js文件,当文件下载完成后可以执行回调函数。当然它的好外不止于此,我们还可以按需获取,减少流量和浏览器内存占用!对于高并发请求的网站,有着天大的好处!我以前在的一家公司做过统计首页减少50k的流量,一年就可以节省十几万的成本呀。。。但上面的代码也不是完美的,在一些应用下会出现新的问题。请看下面的截图
image

image

不难发现,加载的顺序变了!这时,如果a.js依赖于b.js,而b.js偏偏又迟于a.js加载完成时,就会出现“变量未定义”的js错误,无法执行下去。

那我们的下一个目标就是如何让这些外部文件有序的加载进网页。这样无论智商高的还是低的,都会想到 队列 ,不错,就是队列。在JS里实现队列并不难办,是的,数组就行,我们可以用数组的shift方法,弹出数组的第一个JS文件(这也是我们最先加入数组的那 个JS文件),模拟了队列的实现方法。不过,在这里我们还要多考虑一个问题:必须要上一个js文件加载完成后,下一个JS的请求才能开始。我们怎么判断上 一个JS文件已经加载完成了呢?上代码:

var testNode = doc.createElement(script), fn, node;
fn
= testNode.readyState ? function(node, callback){
node.onreadystatechange
= function(){
var rs = node.readyState;
if (rs === loaded || rs === complete) {
// handle memory leak in IE
node.onreadystatechange = null;
callback.call(
this);
}
};
}:
function(node, callback){
node.onload
= callback;
};

在非IE情况下我们可以很方便的用dom元素的onload和onerror来判断元素是否已经加载完成,而IE不吃这套,它并不支持script 节点的onload判断,所以我们只好寻求其它的解决方案。还好,IE对onreadystatechange和readyState的支持足以让我们完 成这个任务。

readyState 的值  可能为 以下几个 :

“uninitialized” – 原始状态
“loading” – 下载数据中..
“loaded” – 下载完成
“interactive” – 还未执行完毕.
“complete” – 脚本执行完毕.

在IE6/7/8下,虽然script节点加载完成,但结果并不总是loaded或是complete,并且先设置src再append到节点树和 先append到节点树再设定src,IE7/8处理加载src文件的时间是不同的。为了减少不必要的麻烦,我们这里就两个状态都判断了。

接下来还有一个问题,如果其中某个js文件,需要去处理dom节点,而我们在加载js时并不能确定这个节点是否已经渲染完成,既我们的Js加载需要在所有的dom节点渲染完成后才开始加载执行,这时我们就要用到经典的domReady判断。

代码

function domReady(){
if (readyBound) {
return;
}

readyBound = true;

if (document.readyState === complete) {
return dequeue();
}

if (document.addEventListener) {
document.addEventListener(
DOMContentLoaded, dequeue, false);
window.addEventListener(
load, dequeue, false);
}
else
if (document.attachEvent) {
document.attachEvent(
onreadystatechange, dequeue);

window.attachEvent(onload, dequeue);
var toplevel = false;
try {
toplevel
= window.frameElement == null;
}
catch (e) {
}

if (document.documentElement.doScroll && toplevel) {
try {
// If IE is used, use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
document.documentElement.doScroll(left);
}
catch (error) {
setTimeout(arguments.callee,
10);
return;
}
dequeue();
}
}
}

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas,”Courier New”,courier,monospace; background-color: rgb(255, 255, 255); }.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }早期的做法是用window.onload或是defer,defer并不是所有的浏览器都支持,可以排除。 window.onload事件是待到页面上的所有资源被加载才激活,如果页面上有许多图片或音乐,而我们要操作的元素在的它们的下方呢?因此,W3C搞 了DOMContentLoaded与addEventListener,只是可惜IE又不支持。还好,我们又找到readystatechange。知 道页面的内容加载完成,我们再用到Diego Perini提供的doScroll来判断document节点是否渲染到页面。这样我们就能够让js在页面渲染完成后再执行了。至些我们就实现了以下的 功能:

  1. 异步加载,加快了页面的加载时间,同时能够按需加载,节省流量
  2. 有序加载,解决js依赖问题
  3. 延时执行,在页面渲染完成后再执行js,防止undefined情况
附上最后实现的代码:/Files/simon4545/nc.loader.rar

[转载].Net 之匿名对象(AnonymousObject)

mikel阅读(995)

[转载].Net 之匿名对象(AnonymousObject) – RyanDing – 博客园.

前言:最近园子里面大谈程序员基础知识的文章,基础知识的重要性无需多论了。看到大家对基础知识的评论后突然想到这么一个场景。曾经有个同事问我:有一个 匿名对象想做为一个函数的返回值,可以么?我当时坚定的回答—不可以。而他也深信不疑,似乎真的实现不了。如果我没记错的话当时是.NET3.5环 境,如果当时是.NET4.0的话我就大错特错了。

开始切入主题,我对.NET 匿名对象的认识如下:

一、.NET3.5时代开始引入匿名对象概念


.NET3.0 时,微软加入了匿名对象到.net 中。JavaScript 程序员对匿名对象并不陌生。但是对于当时的.NET程序员而言,应该属于一个新鲜事物。.NET4.0可以让我们实例化对象而不需要为该对象定义类。如何理解“实例化对象而不需要为该对象定义类”,还是用代码来解释吧:

1 private static Object GetAnonymousObj()
2 {
3 Object anonymousObj = new { UserName = "admin", Password = "1111" };
4 return anonymousObj;
5 }

这里并未在实例化对象前为anonymousObj对象建立相关的类,所以称anonymousObj对象为匿名对象。


二、.NET 4.0中将匿名对象作为函数返回值


回到本文开头引入的前言部分– 匿名对象可以作为函数返回值么?在.NET4.0中这个已经成为现实。

请看一下代码范例,将匿名对象作为返回值:

1 private static Object GetAnonymousObj()
2 {
3 Object anonymousObj = new { UserName = "admin", Password = "1111" };
4 return anonymousObj;
5 }

很简单的一段代码,让我们将anonymousObj这个匿名对象作为了函数返回值。但是在调用该函数时发现了问题,我们没法这样做:

Object obj = GetAnonymousObj();

Console.WriteLine(obj.UserName); // 这里将无法编译。因为obj对象是Object类型。

因此在.net3.5时,我告诉同事这个还是不要这样实现的好。换个方法吧?当.NET4.0时代来临时,我们的问题得到妥善的解决 —- 首先由Dynamic 类型隆重登场:(Dynamic简单介绍

先将上文中的GetAnonymousObj函数改造成返回dynamic 类型:

1 private static dynamic GetAnonymousObj()
2 {
3 dynamic anonymousObj = new { UserName = "admin", Password = "1111" };
4 return anonymousObj;
5 }

接着调用GetAnonymousObj()方法:

1 static void Main(string[] args)
2 {
3 dynamic o = GetAnonymousObj();
4 Console.WriteLine(o.UserName);
5 Console.ReadKey();
6 }

输出>>admin.

匿名对象也许在您的项目中运用的并不是很多,但我的项目大部分是建立在LinqToSQL的基础上,所以我们常常会写出这样的代码:在LINQ查询语句中使用匿名对象。比如:

1 var user = from user in Users
2 select new { user.Name, user.Id} //匿名对象


三、.NET中匿名对象的其他注意事项


  • 匿名对象继承于System.Object。
  • 匿名对象属性均为只读属性(ReadOnly)。
  • 在同一个程序集内,两个匿名对象具有相同的属性、相同的属性顺序。编译器将认为这两个匿名对象是相同的。

最后希望本篇文章可以给您带来帮助,如有不足之处欢迎指出,谢谢!

[转载]实现checkbox的全选/全不选/点选/行内点选(原生JS版和jQ版)

mikel阅读(883)

[转载]实现checkbox的全选/全不选/点选/行内点选(原生JS版和jQ版) – Mr.Think的博客@MrThink.net… – 博客园.

日常项目中, 对于列表类文章或数据, 大概都会用到checkbox的全选或全不选的功能, 以前的项目中也写过checkbox的选择js, 但都没有整理过. 正好前几天一个兄弟遇到了这个问题, 索性, 我花了点时间, 用原生JS与jQuery分别写了一个版本, 考虑到使用时灵活性问题, 未封装, 需要的童鞋使用时自行改下相关参数.
实现checkbox的全选/全不选/点选/行内点选
功能介绍点此查看DEMO
1. 全选/全不选 选框一体实现, 即列表中选框的状态与全选/全不选框前的选框状态一致;
2. 自动更改 全选/全不选 选框的状态, 即列表中选框都选中时, 全选/全不选 选框也选中, 反之亦然;
3. 列表行内点击也可选中行内的checkbox, 并与1,2中的功能联动.
另,本文重在写全选, 鼠标划入划出背景变色为简易实现, 更加完善的请参考: http://mrthink.net/javascript-tagnames-highlight/.
原生JS版本核心代码

var js_chk = document.forms[js].chk_can;
var jsitems = document.forms[js].jsitems;
var jsrows = document.getElementById(js).getElementsByTagName(dd);

//判断选中个数与实际选框个数实现全选/全不选框的状态
var chk_canle = function(){
var checkedLen = 0;
//计算列表中选中状态的选框个数
for (var m = 0; m < jsitems.length; m++) {
if (jsitems[m].checked) {
checkedLen += 1;
}
}
//判断选中个数与实际个数是否相同,以确定全选/全不选状态
for (var m = 0; m < js_chk.length; m++) {
js_chk[m].checked = (jsitems.length == checkedLen);
}
}

//全选与全不选一体实现
for (var i = 0; i < js_chk.length; i++) {
js_chk[i].onclick = function(){
//列表中选框与全选选框统一状态
for (var m = 0; m < jsitems.length; m++) {
jsitems[m].checked = this.checked;
}
//全选选框统一状态
for (var m = 0; m < js_chk.length; m++) {
js_chk[m].checked = this.checked;
}
}
}

//列表中选框点击
for (var i = 0; i < jsitems.length; i++) {
jsitems[i].onclick = function(e){
//阻止冒泡,避免行点击事件中,直接选择选框无效
e && e.stopPropagation ? e.stopPropagation() : window.event.cancelBubble=true;
chk_canle();
}
}

//行内点击
for (var i = 0; i < jsrows.length; i++) {
jsrows[i].onclick = function(){
//行内点击时,行内的选框状态为原状态取反
this.getElementsByTagName(input)[0].checked = !this.getElementsByTagName(input)[0].checked;
chk_canle();
}
}

jQ版本核心代码

var _jq_chk = $(#jq>dl>dt>label>:checkbox);
var _jqitems = $(:checkbox[name=jqitems]);
var _rows = $(#jq>dl>dd);

//全选与全不选一体实现
_jq_chk.click(function(){
//列表中选框和全选选框统一状态
_jqitems.add(_jq_chk).attr(checked, this.checked);
});

//选框的点击事件
_jqitems.click(function(e){
//阻止冒泡,避免行点击事件中,直接选择选框无效
e.stopPropagation();
//判断选中个数与实际个数是否相同,以确定全选/全不选状态
_jq_chk.attr(checked, _jqitems.size() == _jqitems.filter(:checked).size());
});

//点选行时选中行内的checkbox
_rows.bind({
mouseenter: function(){
$
(this).addClass(hover);
},
mouseleave: function(){
$
(this).removeClass(hover);
},
//点选
click: function(){
//行内点击时,行内的选框状态为原状态取反
$
(this).find(:checkbox).attr(checked, !$(this).find(:checkbox).get(0).checked)
//判断选中个数与实际个数是否相同,以确定全选/全不选状态
_jq_chk.attr(checked, _jqitems.size() == _jqitems.filter(:checked).size());
}
});

[转载]Android 2.3 SDK 配置方法

mikel阅读(758)

[转载]Android 2.3 SDK 配置方法 – 青竹少年 – 博客园.

安装Android 2.3有两种方法:1.从官方直接下载Android 2.3 SDK。 2.从SDK 2.2更新到2.3。

在这,我就不说第一种了,没什么可说的。我就说说第二种吧。

我是在Windows下的Android-sdk-windows里更新的,并没有去Eclipse更新,我就给大家说说我更新的步骤吧:

第一步:打开你的Android SDK and AVD Manager,先别急着下载什么的,因为你现在什么也下载不了,因为你必须先在Setting里面的Misc下面勾上Force https://……sources to be fetched using http://….这一项。看下面的截图:

第二步:现在你可以点击Available packages选项了。然后,把那些需要更新的东东都打上对勾,再点击Install Selected按钮。然后,按照它的指示进行操作就行了,挺简单的。下面是截图:

第三步:当你上面的工作做完后,你会发现在你的C:\android-sdk-windows\tools目录下并没有adb.exe这个可执 行文件。并且当你打开Eclipse时,有关Android的插件会报错。这时你就应该这样做:首先,改一下你的环境变量,把原来的 C:\android-sdk-windows\tools改成C:\android-sdk-windows\platform-tools。其次,把 C:\android-sdk-windows\platform-tools目录下的 adb.exe,AdbWinApi.dll,AdbWinUsbApi.dll 这三个文件拷到你C:\android-sdk-windows\tools目录下,然后把其它文件都拷贝到C:\android-sdk- windows\platforms\android-9\tools目录下,这时你就有疑问了,在C:\android-sdk-windows \platforms\android-9 这个目录下并没有tools文件夹,这时就需要你建一个新的文件夹,把它们放到里面。到这,大部分工作已经完成,现在你可以重启你Eclipse了,不过 它会提示你更新Android插件,这时,你按照提示做就OK了。

下面是我成功安装后,Android 2.3的界面:

看上去也没太大的变化啊!不过图标变的好看多了。好吧,写到这,也就写完了,如果大家有什么问题,可以问我,我会尽量回答的。

[转载]IPC框架分析 Binder,Service,Service manager

mikel阅读(948)

[转载]IPC框架分析 Binder,Service,Service manager – 手中无模式 心中有模式 – 博客园.

我首先从宏观的角度观察Binder,Service,Service Manager,并阐述各自的概念。从Linux的概念空间中,Android的设计Activity托管在不同的的进程,Service也都是托管在不 同的进程,不同进程间的Activity,Service之间要交换数据属于IPC。Binder就是为了Activity通讯而设计的一个轻量级的 IPC框架。

在代码分析中,我发现Android中只是把Binder理解成进程间通讯的实现,有点狭隘,而是应该站在公共对象请求代理这个高度来理解 Binder,Service的概念,这样我们就会看到不一样的格局,从这个高度来理解设计意图,我们才会对Android中的一些天才想法感到惊奇。从 Android的外特性概念空间中,我们看不到进程的概念,而是Activity,Service,AIDL,INTENT。一般的如果我作为设计者,在 我们的根深蒂固的想法中,这些都是如下的C/S架构,客户端和服务端直接通过Binder交互数据,打开Binder写入数据,通过Binder读取数 据,通讯就可以完成了。

该注意到Android的概念中,Binder是一个很低层的概念,上面一层根本都看不到Binder,而是Activity跟一个Service的对象直接通过方法调用,获取服务。

这个就是Android提供给我们的外特性:在Android中,要完成某个操作,所需要做的就是请求某个有能力的服务对象去完成动作,而无需知道 这个通讯是怎样工作的,以及服务在哪里。所以Andoid的IPC在本质上属于对象请求代理架构,Android的设计者用CORBA的概念将自己包装了 一下,实现了一个微型的轻量级CORBA架构,这就是Andoid的IPC设计的意图所在,它并不是仅仅解决通讯,而是给出了一个架构,一种设计理念,这 就是Android的闪光的地方。Android的Binder更多考虑了数据交换的便捷,并且只是解决本机的进程间的通讯,所以不像CORBA那样复 杂,所以叫做轻量级。

所以要理解Android的IPC架构,就需要了解CORBA的架构。而CORBA的架构在本质上可以使用下面图来表示:

在服务端,多了一个代理器,更为抽象一点我们可以下图来表示。

分析和CORBA的大体理论架构,我给出下面的Android的对象代理结构。

在结构图中,我们可以较为清楚的把握Android的IPC包含了如下的概念:

•设备上下文什(ContextObject)
设备上下文包含关于客服端,环境或者请求中没有作为参数传递个操作的上下文信息,应用程序开发者用ContextObject接口上定义的操作来创建和操作上下文。

• Android代理:这个是指代理对象
• Binder Linux内核提供的Binder通讯机制

Android的外特性空间是不需要知道服务在那里,只要通过代理对象完成请求,但是我们要探究Android是如何实现这个架构,首先要问的是在 Client端要完成云服务端的通讯,首先应该知道服务在哪里?我们首先来看看Service Manger管理了那些数据。Service Manager提供了add service,check service两个重要的方法,并且维护了一个服务列表记录登记的服务名称和句柄。

Service manager service使用0来标识自己。并且在初始化的时候,通过binder设备使用BINDER_SET_CONTEXT_MGR ioctl将自己变成了CONTEXT_MGR。Svclist中存储了服务的名字和Handle,这个Handle作为Client端发起请求时的目标 地址。服务通过add_service方法将自己的名字和Binder标识handle登记在svclist中。而服务请求者,通过 check_service方法,通过服务名字在service list中获取到service 相关联的Binder的标识handle,通过这个Handle作为请求包的目标地址发起请求。

我们理解了Service Manager的工作就是登记功能,现在再回到IPC上,客服端如何建立连接的?我们首先回到通讯的本质:IPC。从一般的概念来讲,Android设计 者在Linux内核中设计了一个叫做Binder的设备文件,专门用来进行Android的数据交换。所有从数据流来看Java对象从Java的VM空间 进入到C++空间进行了一次转换,并利用C++空间的函数将转换过的对象通过driver\binder设备传递到服务进程,从而完成进程间的IPC。这 个过程可以用下图来表示

这里数据流有几层转换过程。

(1) 从JVM空间传到c++空间,这个是靠JNI使用ENV来完成对象的映射过程。

(2) 从c++空间传入内核Binder设备,使用ProcessState类完成工作。

(3) Service从内核中Binder设备读取数据。

Android设计者需要利用面向对象的技术设计一个框架来屏蔽掉这个过程。要让上层概念空间中没有这些细节。Android设计者是怎样做的呢?我们通过c++空间代码分析,看到有如下空间概念包装(ProcessState@(ProcessState.cpp)

在ProcessState类中包含了通讯细节,利用open_binder打开Linux设备dev\binder,通过ioctrl建立的基本的通讯 框架。利用上层传递下来的servicehandle来确定请求发送到那个Service。通过分析我终于明白了Bnbinder,BpBinder的命 名含义,Bn-代表Native,而Bp代表Proxy。一旦理解到这个层次,ProcessState就容易弄明白了。

下面我们看JVM概念空间中对这些概念的包装。为了通篇理解设备上下文,我们需要将Android VM概念空间中的设备上下文和C++空间总的设备上下文连接起来进行研究。

为了在上层使用统一的接口,在JVM层面有两个东西。在Android中,为了简化管理框架,引入了ServiceManger这个服务。所有的服 务都是从ServiceManager开始的,只用通过Service Manager获取到某个特定的服务标识构建代理IBinder。在Android的设计中利用Service Manager是默认的Handle为0,只要设置请求包的目标句柄为0,就是发给Service Manager这个Service的。在做服务请求时,Android建立一个新的Service Manager Proxy。Service Manager Proxy使用ContexObject作为Binder和Service Manager Service(服务端)进行通讯。

我们看到Android代码一般的获取Service建立本地代理的用法如下:

IXXX  mIxxx=IXXXInterface.Stub.asInterface(ServiceManager.getService(“xxx”));

例如:使用输入法服务:

IInputMethodManager mImm=

IInputMethodManager.Stub.asInterface(ServiceManager.getService(“input_method”));
这些服务代理获取过程分解如下:

(1) 通过调用GetContextObject调用获取设备上下对象。注意在AndroidJVM概念空间的ContextObject只是 与Service Manger Service通讯的代理Binder有对应关系。这个跟c++概念空间的GetContextObject意义是不一样的。

注意看看关键的代码

BinderInternal.getContextObject()    @BinderInteral.java
NATIVE JNI:getContextObject()   @android_util_Binder.cpp

Android_util_getConextObject            @android_util_Binder.cpp
ProcessState::self()->getCotextObject(0)  @processState.cpp
getStrongProxyForHandle(0)  @
NEW BpBinder(0)

注意ProcessState::self()->getCotextObject(0) @processtate.cpp,就是该函数在进程空间建立
了ProcessState对象,打开了Binder设备dev\binder,并且传递了参数0,这个0代表了与Service Manager这个服务绑定。

(2) 通过调用ServiceManager.asInterface(ContextObject)建立一个代理ServiceManger。

mRemote= ContextObject(Binder)

这样就建立起来ServiceManagerProxy通讯框架。

(3)客户端通过调用ServiceManager的getService的方法建立一个相关的代理Binder。

ServiceMangerProxy.remote.transact(GET_SERVICE)

IBinder=ret.ReadStrongBinder() -》这个就是JVM空间的代理Binder

JNI Navite: android_os_Parcel_readStrongBinder()    @android_util_binder.cpp

Parcel->readStrongBinder()  @pacel.cpp

unflatten_binder  @pacel.cpp

getStrongProxyForHandle(flat_handle)

NEW BpBinder(flat_handle)-》这个就是底层c++空间新建的代理Binder。

整个建立过程可以使用如下的示意图来表示:


Activity为了建立一个IPC,需要建立两个连接:访问Servicemanager Service的连接,IXXX具体XXX Service的代理对象与XXXService的连接。这两个连接对应c++空间ProcessState中BpBinder。对IXXX的操作最后就 是对BpBinder的操作。由于我们在写一个Service时,在一个Package中写了Service Native部分和Service Proxy部分,而Native和Proxy都实现相同的接口:IXXX Interface,但是一个在服务端,一个在客服端。客户端调用的方式是使用remote->transact方法向Service发出请求,而 在服务端的OnTransact中则是处理这些请求。所以在Android Client空间就看到这个效果:只需要调用代理对象方法就达到了对远程服务的调用目的,实际上这个调用路径好长好长。

我们其实还一部分没有研究,就是同一个进程之间的对象传递与远程传递是区别的。同一个进程间专递服务地和对象,就没有代理BpBinder产生,而只是对 象的直接应用了。应用程序并不知道数据是在同一进程间传递还是不同进程间传递,这个只有内核中的Binder知道,所以内核Binder驱动可以将 Binder对象数据类型从BINDER_TYPE_BINDER修改为BINDER_TYPE_HANDLE或者 BINDER_TYPE_WEAK_HANDLE作为引用传递。

[转载]谈.net开发人员应该熟悉的开发模式

mikel阅读(892)

[转载]谈.net开发人员应该熟悉的开发模式 – 无疆_炎戎 – 博客园.

我们总会有这样一个经验:一个系统最不容易也最不应该变化的部分是领域逻辑,最容易变化也最应该变化的是数据的呈现方式。

在java的各种应用中可以说是到处可见mvc,j2ee贯穿mvc的概念,Android的开发方式也是类mvc的,mvc结构对于做过java 应用的人而言简直就是司空见惯。而在.net这边,由于之前微软为大家提供的各种winform、ASP.NET项目典范(比如那个petshop series)将“三层”概念很好的灌输到了.net程序员的大脑中,许多.net开发者凡是做个东西都要搬出自己最拿手的IModel、IDAL这样的 神器。

其实mvc与所谓的“三层架构”是两个层次上的东西,前者是一种结构模式,而后者则是分层的角度去说。

一件很奇怪的事情,许多人知道“三层”却不知道mvc,其实这要归结与.net的早期开发技术ASP.NET和winform这些page controller的典范让许多人对三层夸夸其谈却对mvc视而不见甚至一无所知。什么是page controller模式呢?搞.net的大多都用过winform和webform,这种xxxform用起来很直观,我们想要做一个程序,ok,最简 单的方式就是拖拖拽拽几个控件,然后在一个叫code behind的东西里写这些UI事件的处理逻辑,加一大堆变量用于记录数据和状态,这样一个程序就能出炉。这种开发方式对于一些小软件系统的开发其实效率 还是蛮高的,后来人们看到其弊端—一旦修改UI,事件处理就要跟着变,但是业务还是那个业务,凭什么要修改非UI的代码?于是有人提出“三层”,最朴 素的理解就是将原本那堆事件处理里的code分成业务代码和数据库访问代码并转移到其它类中,做多了就把那坨UI叫做UI,那坨业务代码叫做BLL,那坨 DAO叫做DAL。也就是这种架构:

image

而对于j2ee的开发者来说熟悉的是下图。

image

(说明:这两幅图copy自是daxnet文)

MVC是什么

MVC是一个很经典的结构,并且其又其思想衍生出很多变种比如MVP,MVVP。传统的MVC结构之一是这样的(拿主动型mvc来说):

image

比如web开发(比如asp.net mvc或者是java的web开发方式),view就是纯web页面或者webservice,当提交一个表单/调用webservice或者ajax后 会将数据提交给controller(当然期间可能会经过各种filterchain、listener这样的东西)controller调用相应的业务 模块来处理这个请求,最终结果会更新View的显示。

MVP

对于非天然mvc的框架

对于asp.net/winform而言,虽然可以通过改造让其支持mvc结构的开发(比如通过定制IHttpModule、 IHttpHandler云云),但是在企业看来这些都算是邪门武功(因为这样会丧失xxxform在开发上的很多特性比如快速开发)。大多数使用的是 mvp模式。什么是mvp呢?其实mvp是mvc的一个变种。因为用winform或者webform的话form始终是个阻碍mvc开发的问题。那么 好,我们仍然使用designer和codebehind,其实一个架构设计的好坏是取决于人而不是具体的技术的,只要我们OO一时强page controller一样好用。

image

在MVP模式中我们需要自己定制各个View(web页面或者窗体)对应的IView和IPresenter、IModel。IView要对 IPresenter暴露操作UI、数据绑定的接口,IPresenter对IView要暴露当UI事件触发需要调用的接口,IPresenter根据 IView传递过来的请求调用业务接口并根据结果操作UI。举个简单的例子,一个计算“x+y=?”的程序。如果我们这样定义IPresenter和 IView

public interface IPresenter
{
IView View { get; set; }
void CalculateResult();
}

public interface IView
{
IPresenter Presenter { get; set; }
void ShowResult(string result);
int ValueOne { get; }
int ValueTwo { get; }
}

IPresenter的实现如下(这里从简把IModel去掉了)

Presenter

namespace ClientLibrary
{
public class Presenter : IPresenter
{
private IView _view;
public IView View
{
get
{
return _view;
}
set
{
_view
= value;
_view.Presenter
= this;
}
}

private static readonly string RESULT_FORMATTER = {0}+{1},the result is {2};
public void CalculateResult()
{
if (_view != null)
{
var result
= string.Format(RESULT_FORMATTER, _view.ValueOne, _view.ValueTwo, _view.ValueOne + _view.ValueTwo);
_view.ShowResult(result);
this.A = 123;
}
}
private int _a;
public int A
{
set
{
A
= value;
}
}
}
}

View的实现如下(那silverlight为例,换成别的也行)

MainPage

namespace Debug
{
public partial class MainPage : UserControl, IView
{
public MainPage()
{
InitializeComponent();
}

private IPresenter _presenter;

private void btn_Click(object sender, RoutedEventArgs e)
{
if (_presenter != null)
{
_presenter.CalculateResult();
}
#region hidden
/*int total = 0;
try
{
total = int.Parse(tb1.Text) + int.Parse(tb2.Text);
MessageBox.Show(“计算结果:” + total.ToString());
}
catch (Exception ex)
{
MessageBox.Show(“出错啦” + ex.ToString());
}
finally
{
tb1.Text = string.Empty;
tb2.Text = string.Empty;
}
*/
#endregion

}

public IPresenter Presenter
{
get
{
return _presenter;
}
set
{
_presenter
= value;
}
}

public void ShowResult(string result)
{
MessageBox.Show(result);
}

public int ValueOne
{
get { return int.Parse(tb1.Text); }
}

public int ValueTwo
{
get { return int.Parse(tb2.Text); }
}
}
}

一个很简单的东西,看上去写成的要多些那么一坨东西,但是好处是显而易见的,就是更换view非常方便,根本不用去改你的IPresenter、Presenter和业务。一切都是接口调用而不依赖具体实现,这就是好处。

你必须要懂的MVVM

对于.NET平台的开发人员,托微软的福分我们拥有一种更为强大的模型—MVVM。这应该算是做WPF/Silverlight应用的人必懂的 一种结构,WPF/silverlight天生支持数据绑定和命令绑定(不过sl在命令绑定上还比较弱),这就为我们使用MVVM创造了可能。

View是什么呢,纯的View只有xaml或者附带必要的只与View本身相关逻辑代码。ViewModel,你可以把它理解为View具体呈现 内容所依赖数据的一个抽象,在MVVM中View与ViewModel总会有一种绑定关系,一旦ViewModel中被绑定的数据发生改变View上的数 据就会跟着变,相反也有可能,比如你的账号密码框内容发生变化,关联的ViewModel中的数据就会被框架自动通知到。

在wpf/silverlight中,绑定是通过xaml语法来完成(虽然你可以选择用C#来写但不符合mvvm的宗旨),并且绑定双方的通知机制 是有框架来完成,也就是说一个会xaml和blend的美工只需事先和coder商量下“咱们的xx和xx是在哪个ViewModel上叫XXX的属性的 XXX属性……”问题之后就可以各干各的了。那么ViewModel怎么写,咋view中又怎么绑定到viewmodel呢?首先我们谈 ViewModel。

说道ViewModel你需要知道依赖属性和依赖对象的概念,这是wpf/silverlight的基础所以不多说。有两种方式写 ViewModel。第一种是自己去实现INotifyPropertyChanged接口,并在属性变化时去调用 NotifyPropertyChanged事件。

为了方便我们定义一个ViewModelBase的抽象基类,然后让其他ViewModel继承这个基类。

ViewModelBase

public abstract class ViewModelBase : System.ComponentModel.INotifyPropertyChanged, IDisposable
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
var arg
= new System.ComponentModel.PropertyChangedEventArgs(propertyName);
PropertyChanged(
this, arg);
}
}
public virtual void Dispose()
{

}
}

DemoViewModel
public class DemoViewModel : ViewModelBase     {         #region fields         private string _propertyA;         #endregion         #region presentation properties         public string PropertyA         {             get             {                 return _propertyA;             }             set             {                 if (_propertyA != value)                 {                     _propertyA = value;                     base.OnPropertyChanged("PropertyA");                 }             }         }         #endregion     }
 

第二种是利用DependencyObject和DependencyProperty。

PeopleItemViewModel

public class PeopleItemViewModel : DependencyObject, IPeopleItemViewModel
{
public PeopleItemViewModel()
{

}
public static readonly DependencyProperty SimpleUserDataProperty = DependencyProperty.Register(SimpleUserData, typeof(SimpleUserData), typeof(PeopleItemViewModel));
public static readonly DependencyProperty RelativeSimpleUserDataProperty = DependencyProperty.Register(RelativeSimpleUserData, typeof(ObservableCollection<SimpleUserData>), typeof(PeopleItemViewModel));
public static readonly DependencyProperty AllSimpleUserDataProperty = DependencyProperty.Register(AllSimpleUserData, typeof(ObservableCollection<SimpleUserData>), typeof(PeopleItemViewModel));

public SimpleUserData SimpleUserData
{
get
{
return (SimpleUserData)base.GetValue(SimpleUserDataProperty);
}
set
{
if (!base.CheckAccess())
{
Dispatcher.Invoke(
new Action(
()
=>
{
SimpleUserData
= value;
}));
}
else
base.SetValue(SimpleUserDataProperty, value);
}
}
public ObservableCollection<SimpleUserData> RelativeSimpleUserData
{
get
{
return (ObservableCollection<SimpleUserData>)base.GetValue(RelativeSimpleUserDataProperty);
}
set
{
if (!base.CheckAccess())
{
Dispatcher.Invoke(
new Action(
()
=>
{
RelativeSimpleUserData
= value;
}));
}
else
{
base.SetValue(RelativeSimpleUserDataProperty, value);
var collectionView
= CollectionViewSource.GetDefaultView(value);
collectionView.SortDescriptions.Add(
new SortDescription(Distance, ListSortDirection.Ascending));
}
}
}
public ObservableCollection<SimpleUserData> AllSimpleUserData
{
get
{
return (ObservableCollection<SimpleUserData>)base.GetValue(AllSimpleUserDataProperty);
}
set
{
if (!base.CheckAccess())
{
Dispatcher.Invoke(
new Action(
()
=>
{
AllSimpleUserData
= value;
}));
}
else
{
base.SetValue(AllSimpleUserDataProperty, value);
var collectionView
= CollectionViewSource.GetDefaultView(value);
collectionView.SortDescriptions.Add(
new SortDescription(Distance, ListSortDirection.Ascending));
}
}
}
}

在View中绑定ViewModel。

为了方便,我们可以在app.xaml中将需要的viewmode放到全局资源字典中。

image

然后再我们的vs视图设计器Properties(中文版显示的是“属性”)页上选择为绑定源设置绑定目标(包括source和path等)以及必要的值转换器等等即可。

image image image

(PS:虽然vs很强大,但个人还是建议熟悉xaml的绑定语法,想当初用vs2008搞wpf的时候貌似还没有这么方便的设计器。。。)

[转载]dedecms5.6内容页面调用当前文章的相关文章解决办法

mikel阅读(982)

[转载]dedecms5.6内容页面调用当前文章的相关文章解决办法 – 北方的雪 – 博客园.

在使用dedecms5.6时发现如果在内容页面调用相关文章,简单的使用dedecms arclist不能够实现,但是arclist有一个字段keyword,如果把当前文章的keyword调用出来,就能够得到当前相关文章的列表.我的 做法是对keyword附一个固定的值

复制代码

  1. {dede:arclist keyword=’relation’ row=’5′}
  2. <li><a href=”[field:arcurl/]”>[field:title/]</a></li>
  3. {/dede:arclist}

如果是在终端页面调用相关文章,就将keyword的值固定,其他的参数也都是可以用的,默认的keyword值是当前页面得关键字,有兴趣的可以去我网站看看 游戏联盟114 可以对比下面相关文章的关键字和当前页面得关键字

具体的实现方式是:
1.修改include/arc.archives.class.php找到MakeOneTag($this->dtp, $this, ‘N’,$reltag);将其替换为

复制代码

  1. $reltag = explode(‘,’,$this->Fields[‘keywords’]);
  2. $reltag = $reltag[0];
  3. MakeOneTag($this->dtp, $this, ‘N’,$reltag);

2.修改include/下的channelunit.func.php找到函数MakeOneTag函数 MakeOneTag(&$dtp, &$refObj, $parfield=’Y’)在函数的最后加上一个参数

复制代码

  1. MakeOneTag(&$dtp, &$refObj, $parfield=’Y’,$reltag)

仔细看下有什么区别吧
在函数中找到

复制代码

  1. if(in_array($tagname,$alltags))
  2. {
  3. $filename = DEDEINC.’/taglib/’.$tagname.’.lib.php’;
  4. include_once($filename);
  5. $funcname = ‘lib_’.$tagname;
  6. $dtp->Assign($tagid,$funcname($ctag,$refObj));
  7. }

将其修改为

复制代码

  1. if(in_array($tagname,$alltags))
  2. {
  3. $filename = DEDEINC.’/taglib/’.$tagname.’.lib.php’;
  4. include_once($filename);
  5. $funcname = ‘lib_’.$tagname;
  6. if($tagname == ‘arclist’){
  7. foreach($ctag->CAttribute->Items as $key => $val){
  8. if($key == ‘keyword’){
  9. if($val==’Relation’){
  10. $ctag->CAttribute->Items[$key]=$reltag;
  11. }
  12. }
  13. }
  14. }
  15. $dtp->Assign($tagid,$funcname($ctag,$refObj));
  16. }

呵呵,去生成静态吧,不要忘记了 keyword=’relation’  relation 不要变哦,变了可就调不出来了

[转载]GSM蜂窝基站定位基本原理浅析

mikel阅读(1093)

[转载]GSM蜂窝基站定位基本原理浅析 – Mobile On Line 镜像站点 – 博客园.

位置服务已经成为越来越热的一门技术,也将成为以后所有移动设备(智能手机、掌上电脑等)的标配。随着人们对BLS(Based Location Serices,基于位置的服务)需求的飞速增长,无线定位技术也越来越得到重视。GSM蜂窝基站定位,以其定位速度快、成本低(不需要移动终端上添加额外的硬件)、耗电少、室内可用等优势,作为一种轻量级的定位方法,也越来越常用。本文简单介绍一下各种基于GSM蜂窝基站的定位方法及基本原理,给开发人员作为参考。我将尽量尝试用开发人员熟悉的方式来描述问题。

预备知识:GSM蜂窝网络基础结构

我 们知道,GSM网络的基础结构是由一系列的蜂窝基站构成的,这些蜂窝基站把整个通信区域划分成如图所示的一个个蜂窝小区(当然实际上,一个基站往往不并不 只是对应一个小区,但是这个与我们讨论的主题关系不大,我们不做深究)。这些小区小则几十米,大则几千米。如下图所示,我们用移动设备在GSM网络中通 信,实际上就是通过某一个蜂窝基站接入GSM网络,然后通过GSM网络进行数据(语音数据、文本数据、多媒体数据等)传输的。也就是说我们在GSM中通信 时,总是需要和某一个蜂窝基站连接的,或者说是处于某一个蜂窝小区中的。那么GSM定位,就是借助这些蜂窝基站进行定位。

2009698297_thumb1

1.COO(Cell of Origin)定位

COO 定位是一种单基站定位,即根据设备当前连接的蜂窝基站的位置来确定设备的位置。那么很显然,定位的精度就取决于蜂窝小区的半径。在基站密集的城市中心地 区,通常会采用多层小区,小区划分的很小,这时定位精度可以达到50M以内;而在其他地区,可能基站分布相对分散,小区半径较大,可能达到几千米,也就意 味着定位精度只能粗略到几千米。目前Google地图移动版中,通过蜂窝基站确定“我的位置”,基本上用的就是这种方法。

从原理上我们 可以看出,COO定位其精度是不太确定的。但是这却是GSM网络中的移动设备最快捷、最方便的定位方法,因为GSM网络端以及设备端都不需要任何的额外硬 件投入。只要运营商支持,GSM网络中的设备都可以以编程方式获取到当前基站的一个唯一代码,我们可以称之为基站ID,或CellID。在一般的设备中, 可能都存在一个类似如下的GetCurrentCellID()方法的接口来提供当前GSM蜂窝基站ID:

CellID = GetCurrentCellID();

通过这个接口获取到CellID后,我们还需要根据这个CellID查出该蜂窝基站所在的具体地理坐标。这时,我们可能就需要调用一些包含 [CellID,地理坐标]对应关系的外部数据以确定相应的地理坐标。这个外部数据,通常可以由一些第三方Web服务来提供。这些Web服务的接口可能类 似于如下形式:

Position=GetPosition(CellID);

当然,再次说明,上面的GetCurrentCellID方法、GetPosition方法都是我虚构的,只是为了说明逻辑关系,并不一定实际存在。关于COO方法在Windows Mobile环境下的具体编程方法,请参考《为Windows Mobile设备创建位置感知的应用程序》。

2.七号信令定位

该技术以信令监测为基础,能够对移动通信网中特定的信令过程,如漫游、切换以及与电路相关的信令过程进行过滤和分析,并将监测结果提供给业务中心, 以实现对特定用户的个性化服务。该项技术通过对信令进行实时监测,可定位到一个小区,也可定位到地区。故适用对定位精确度要求不高的业务,如漫游用户问候 服务,远程设计服务、平安报信和货物跟踪等。目前,国内各省和地区移动公司的短信欢迎系统采用的就是此种技术。

7.TOA/TDOA定位

TOA(Time of Arrival,到达时间)、TDOA(Time Difference of Arrival,到达时间差)都是基于电波传播时间的定位方法。同时也都是三基站定位方法,二者的定位都需要同时有三个位置已知的基站合作才能进行。

TOA_DTOA_thumb6

如上图所示,TOA/DTOA定位方法都是通过三对[Positioni,Ti](i=1,2,3)来确定设备的位置Location。二者的不同只是GetLocation()函数的具体算法上的不同。

TOA电波到达时间定位基本原理是得到Ti(i=1,2,3)后,由Ti*c得到设备到基站i之间的距离Ri,然后根据几何只是建立方程组并求解,从而求得Location值。如下图所示。

TOA_thumb1

由于图中距离的计算完全依赖于时间,因此TOA算法对系统的时间同步要求很高,任何很小的时间误差都会被放大很多倍,同时由于多径效应的影响又会带来很大的误差,因而单纯的TOA在实际中应用很少。

DTOA电波到达时间差定位是对TOA定位的改进,与TOA的不同之处在于,得到Ti后不是立即用Ti去求距离Ri,而是先对T1,T2,T3两两求差,然后通过一些巧妙的数学算法建立方程组并求解,从而得到Location值。如下图所示。

DTOA_thumb

DTOA由于其中巧妙设计的求差过程会抵消其中很大一部分的时间误差和多径效应带来的误差,因而可以大大提高定位的精确度。

由于DTOA对网络要求相对较低,并且精度较高,因而目前已经成为研究的热点。

4.AOA定位

AOA(Angle of Arrival,到达角度)定位是一种两基站定位方法,基于信号的入射角度进行定位。

AOA_thumb

如上图所示,知道了基站1到设备之间连线与基准方向的夹角α1,就可以画出一条射线L1;同样知道了知道了基站2到设备之间连线与基准方向的夹角α2,就可以画出一条射线L2。那么L1月L2的交点就是设备的位置。这就是AOA定位的基本数学原理。用函数调用表达如下。

Location=GetLocation([Pisition1,α1],[Position2,α2]);

AOA定位通过两直线相交确定位置,不可能有多个交点,避免了定位的模糊性。但是为了测量电磁波的入射角度,接收机必须配备方向性强的天线阵列。

5.基于场强的定位

该方法是通过测出接收到的信号场强和已知的信道衰落模型及发射信号的场强值估计收发信短的距离,根据多个三个距离值就可以得到设备的位置。从数学模型上看,和TOA算法类似,只是获取距离的方式不同。场强算法虽然简单,但是由于多径效应的影响,定位精度较差。

6.混合定位

混合定位就是同时使用两种以上的定位方法来进行定位。通过各种定位方法之间结合使用,互补短长,以达到更高的定位精度。

A-GPS定位(辅助GPS定位)就是一种混合定位,是GPS定位技术与GSM网络的结合。A-GPS具有很高的定位精度,目前正被越来越广泛的使用。

GPS定位作为一种传统的定位方法,仍是目前应用最广泛、定位精度最高的定位技术。但是相对而言,GPS定位成本高(需要终端配备GPS硬件)、定 位慢(GPS硬件初始化通常需要3~5分钟甚至10分钟以上的时间)、耗电多(需要额外硬件自然耗电多),因此在一些定位精度要求不高,但是定位速度要求 较高的场景下,并不是特别适合;同时因为GPS卫星信号穿透能力弱,因此在室内无法使用(关于GPS的定位原理可参考GPS定位基本原理浅析)。相比之下,GSM蜂窝基站定位快速、省电、低成本、应用范围限制小,因此在一些精度要求不高的轻型场景下,也大有用武之地。关于在Windows Mobile环境下GPS定位和GSM蜂窝基站定位的相关开发技术,可参考为Windows Mobile设备创建位置感知的应用程序

[转载]web开发Javascript点点小技巧

mikel阅读(1047)

[转载]【web开发】Javascript点点小技巧 – 275095923 – 博客园.

1、特性检测而非浏览器检测

因为某某特性某浏览器不支持,我们经常的做法是在代码中直接先做浏览器判断如:

1 if(Broswer.isFirfox){
2 //do something
3 }

其实这样做是不科学的,因为很可以其它浏览器上可以支持你要用的特性,所以,比较好的做法为是直接做特性判断。

如:

1 if(window.localStorage){
2 //使用本地存储功能
3 }

2、使用方括号来访问属性或方法

这一点到不是强制的,只不过如果使用方括号来访问可以享受一些动态特性带来的好处,而且命名方面有少了许多限制。

如:

1 for(var i=0;i<9;i++){
2 obj["method"+i]();
3 }

3、表单的那些事

这里主要提两个很有用的属性,可以让你操作表单方便不少。

1、form有一个elements属性。作用呢就是可以得到一个表单下的所有表单元素,这样在批量处理表单元素时就很有用,如:表单元素的序列化,或者验证之类的很有帮助。

2、对于第一个表单元素也有一个类似属性为form.这样可以很轻松得到一个表单元素属于哪个表单。
4、a元素onclick的那些事。

1 <a href="cool.html" onclick = "doSomething()">做点什么吧</a>

点击这样一个链接时会有两种情况发生,1、如果dosomthing返回true,那么浏览器就会发生跳转到cool.html,相返如果返回false的话,就会忽略href。因此借住这个特性,我们可以写这样的代码。

1 function doSomething(){
2 return confirm("您确认要离开吗");
3 }

有没有发现比起下面这样的代码爽了很多。

1 function doSomething(){
2 var ret = confirm("确认要离开吗?");
3 if(ret){
4 window.loaction.href = "cool.html";
5 }
6 }

5、类型转换时不一样的做法。

怎样把字符串转换成数字呢?我知道parseInt(“123”,10),除此之外还有什么好办法没,看下面的。

1 var num1 = "123";
2 num1 = num1*1;
3 var num2 = "123";
4 num2 = +num2;

这两种方法是不是很不错。

还是很简洁吧。祝学习愉快!

1 var a = 1;
2 a = !!a;

那转换为bool类型有没有什么好办法呢?有啊!

[转载]ASP.Net MVC探索之路 Model的比较验证

mikel阅读(1128)

[转载]ASP.Net MVC探索之路 – Model的比较验证 – Catcher In The Rye – 博客园.

WebForm有一个比较验证服务器控件CompareValidator ,可以比较两个控件的值大小或一个控件与某一个具体值的大小。可以进行string,int,double,DateTime,decimal这些数据类 型的==、!=、>、<、>=、<=比较。在ASP.NET MVC 2.0中,我们已经可以使用基于DataAnnotations的校验方式,对Model的值进行空校验(Required)、范围校验(Range)、 字符串长度校验(StringLengthAttribute)等。在ASP.NET MVC 3中,我们还可以使用CompareAttribute进行简单的相等(==)校验,但仅此而已,其他校验就没有了。
这里我们定义一个类似WebForms验证服务器控件CompareValidator的Attribule。

首先定义一个枚举表示我们要比较的数据类型:

public enum ValidationDataType : byte
{
String,Integer,Double,Date,Currency
/*Decimal*/
}

再定义一个枚举表示比较方式:

public enum ValidationCompareOperator : byte
{
Equal, NotEqual,GreaterThan,GreaterThanEqual,LessThan,LessThanEqual
}

然后当然就是主角CompareAttribute:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class CompareAttribute : ValidationAttribute
{
public string SourceProperty { get; private set; }
public string OriginalProperty { get; private set; }
public ValidationCompareOperator Operator { get; private set; }
public ValidationDataType Type{ get; private set; }

private const string _defaultErrorMessage = ‘{0}’ 与 ‘{1}’ 进行 {2} 比较失败;

public CompareAttribute(string sourceProperty, string originalProperty
, ValidationCompareOperator op, ValidationDataType type)
:
base(_defaultErrorMessage)
{
SourceProperty
= sourceProperty;
OriginalProperty
= originalProperty;
Operator
= op;
Type
= type;
}

public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
SourceProperty, OriginalProperty, Operator.ToString());
}

public override bool IsValid(object value)
{
PropertyDescriptorCollection properties
= TypeDescriptor.GetProperties(value);
object sourceProperty = properties.Find(SourceProperty, true /* ignoreCase */)
.GetValue(value);
object originalProperty = properties.Find(OriginalProperty, true /* ignoreCase */)
.GetValue(value);

return compare(sourceProperty,originalProperty);
}

private bool compare(object sourceProperty, object originalProperty)
{
int num = 0;
switch (this.Type)
{
case ValidationDataType.String:
num
= string.Compare((string)sourceProperty
, (
string)originalProperty, false, CultureInfo.CurrentCulture);
break;

case ValidationDataType.Integer:
num
= ((int)sourceProperty).CompareTo(originalProperty);
break;

case ValidationDataType.Double:
num
= ((double)sourceProperty).CompareTo(originalProperty);
break;

case ValidationDataType.Date:
num
= ((DateTime)sourceProperty).CompareTo(originalProperty);
break;

case ValidationDataType.Currency:
num
= ((decimal)sourceProperty).CompareTo(originalProperty);
break;
}
switch (this.Operator)
{
case ValidationCompareOperator.Equal:
return (num == 0);

case ValidationCompareOperator.NotEqual:
return (num != 0);

case ValidationCompareOperator.GreaterThan:
return (num > 0);

case ValidationCompareOperator.GreaterThanEqual:
return (num >= 0);

case ValidationCompareOperator.LessThan:
return (num < 0);

case ValidationCompareOperator.LessThanEqual:
return (num <= 0);
}
return true;

}
}

在Model中就可以这样使用了,以下校验两次输入的密码是否一致:

[Compare(PasswordConfirm, Password, ValidationCompareOperator.Equal
,ValidationDataType.String, ErrorMessage
= 请确认两次输入的密码一致)]
public class UserInputEdit
{
[DisplayName(
登录密码)]
public string Password { get; set; }

[DisplayName(确认密码)]
public string PasswordConfirm { get; set; }
}

如果果要Model中有两个时间,开始时间和结束时间,要求结束时间必须大于开始时间:

[Compare(TimeEnd, TimeStart, ValidationCompareOperator.GreaterThan
, ValidationDataType.Date, ErrorMessage
= 结束时间必须大于开始时间)]
public class UserInputEdit
{
[DisplayName(
开始时间)]
public DateTime TimeStart { get; set; }

[DisplayName(结束时间)]
public DateTime TimeEnd { get; set; }
}

ASP.NET MVC 3也有一个CompareAttribute,但它只能进行Equals比较。同样的比较两次密码是否一致,以上代码可以写成这样:
public class UserInputEdit
{
[DisplayName(
登录密码)]
public string Password { get; set; }

[DisplayName(确认密码)]
[Compare(
Password, ErrorMessage = 请确认两次输入的密码一致)]
public string PasswordConfirm { get; set; }
}

在 我们自定义的CompareAttribute中,必须将Attribute应用于class上。如果您想像ASP.NET MVC 3中,将标记写在属性,很遗憾不行。因为ValidationResult IsValid(…)的方法在.Net 4.0才有,MVC 3也才有相应的支持。当然也可以修改我们自定义的CompareAttribute,但请注意以下代码我没测试过:

代码

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field
, AllowMultiple
= true)]
public class CompareAttribute : ValidationAttribute
{
public string OriginalProperty { get; private set; }
public ValidationCompareOperator Operator { get; private set; }
public ValidationDataType Type{ get; private set; }

private const string _defaultErrorMessage = ‘{0}’ 与 ‘{1}’ 进行 {2} 比较失败;

public CompareAttribute(string originalProperty
, ValidationCompareOperator op, ValidationDataType type)
:
base(_defaultErrorMessage)
{
OriginalProperty
= originalProperty;
Operator
= op;
Type
= type;
}

public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
name, OriginalProperty, Operator.ToString());
}

protected override ValidationResult IsValid(object value
, ValidationContext validationContext)
{
PropertyInfo originalProperty
= validationContext.ObjectType
.GetProperty(
this.OriginalProperty);
object originalValue = originalProperty.GetValue(validationContext.ObjectInstance, null);

if(!compare(value, originalValue))
return new ValidationResult(this.FormatErrorMessage(validationContext
.DisplayName));

return null;
}

private bool compare(object sourceProperty, object originalProperty)
{
int num = 0;
switch (this.Type)
{
case ValidationDataType.String:
num
= string.Compare((string)sourceProperty
, (
string)originalProperty, false, CultureInfo.CurrentCulture);
break;

case ValidationDataType.Integer:
num
= ((int)sourceProperty).CompareTo(originalProperty);
break;

case ValidationDataType.Double:
num
= ((double)sourceProperty).CompareTo(originalProperty);
break;

case ValidationDataType.Date:
num
= ((DateTime)sourceProperty).CompareTo(originalProperty);
break;

case ValidationDataType.Currency:
num
= ((decimal)sourceProperty).CompareTo(originalProperty);
break;
}
switch (this.Operator)
{
case ValidationCompareOperator.Equal:
return (num == 0);

case ValidationCompareOperator.NotEqual:
return (num != 0);

case ValidationCompareOperator.GreaterThan:
return (num > 0);

case ValidationCompareOperator.GreaterThanEqual:
return (num >= 0);

case ValidationCompareOperator.LessThan:
return (num < 0);

case ValidationCompareOperator.LessThanEqual:
return (num <= 0);
}
return true;

}
}