[转载]html5 实时股票数据展示控件- HumbleFinance

mikel阅读(960)

[转载]html5 实时股票数据展示控件- HumbleFinance – 分享web开发的最新技术、代码等 – 博客园.

HumbleFinance 一个HTML5数据可视化控件, 它 利用html5 的新特性来实现可交互的图表展示. 他的功能类似于 http://finance.google.com/上的股票展示控件. 该控件完全由 JavaScript 写的, 采用了 Prototype 和 Flotr 代码库. 可用来展示任何两组2维数据集, 只要他们的横坐标度量是一致的就可以.

附件的demo显示了google 2010年3月15号IPO的历史数据。 数据来自Google 财经频道。
值得一提的是,用户可以移动鼠标来显示某个时间点上的更详细的数据值,也可以使用鼠标放大、缩小、移动图表(类似百度地图)
组件在 FireFox, Chrome, Safari等浏览器上表现良好。

网站
http://www.humblesoftware.com/finance/index
下载地址
http://www.humblesoftware.com/finance/source
开源协议
MIT

[转载]如何向妻子解释OOD

mikel阅读(1002)

[转载]如何向妻子解释OOD – 倪大虾 – 博客园.

前言

此文译自CodeProject上<How I explained OOD to my wife>一文,该文章在Top Articles上排名第3,读了之后觉得非常好,就翻译出来,供不想读英文的同学参考学习。

作者(Shubho)的妻子(Farhana)打算重新做一名软件工程师(她本来是,后来因为他们孩子出生放弃了),于是作者就试图根据自己在软件开发设计方面的经验帮助她学习面向对象设计(OOD)。

自作者从事软件开发开始,作者常常注意到不管技术问题看起来多复杂,如果从现实生活的角度解释并以对答的方式讨论,那么它将变得更简单。现在他们把在OOD方面有些富有成效的对话分享出来,你可能会发现那是一种学习OOD很有意思的方式。

下面就是他们的对话:

OOD简介

Shubho:亲爱的,让我们开始学习OOD吧。你了解面向对象原则吗?

Farhana:你是说封装,继承,多态对吗?我知道的。

Shubho:好,我希望你已了解如何使用类和对象。今天我们学习OOD。

Farhana:等一下。面向对象原则对面向对象编程(OOP)来说不够吗?我的意思是我会定义类,并封装属性和方法。我也能根据类的关系定义它们之间的层次。如果是,那么还有什么?

Shubho:问得好。面向对象原则和OOD实际上是两个不同的方面。让我给你举个实际生活中的例子帮你弄明白。

再你小时候你首先学会字母表,对吗?

Farhana:嗯

Shubho:好。你也学了单词,并学会如何根据字母表造词。后来你学会了一些造句的语法。例如时态,介词,连词和其他一些让你能造出语法正确的句子。例如:

“I” (代词) “want” (动词) “to” (介词) “learn” (动词) “OOD”(名词)。

看,你按照某些规则组合了单词,并且你选择了有某些意义的正确的单词结束了句子。

Farhana:OK,这意味着什么呢?

Shubho:面向对象原则与这类似。OOP指的是面向对象编程的基本原则和核心思路。在这里,OOP可以比作英语基础语法,这些语法教你如何用单词构造有意义且正确的句子,OOP教你在代 码中构造类,并在类里封装属性和方法,同时构造他们之间的层次关系。

Farhana:嗯..我有点感觉了,这里有OOD吗?

Shubho:马上就有答案。现在假定你需要就某些主题写几篇文章或随笔。你也希望就几个你擅长主体写基本书。对写好文章/随笔或书来说,知道如何造句是不够的,对吗?为了使读者能更轻   松的明白你讲的内容,你需要写更多的内容,学习以更好的方式解释它。

Farhana:看起来有点意思…继续。

Shubho:现 在,如果你想就某个主题写一本书,如学习OOD,你知道如何把一个主题分为几个子主题。你需要为这些题目写几章内容,也需要在这些章节中写前言,简介,例 子和其他段落。   你需要为写个整体框架,并学习一些很好的写作技巧以便读者能更容易明白你要说的内容。这就是整体规划。

在软件开发中,OOD是整体思路。在某种程度上,设计软件时,你的类和代码需能达到模块化,可复用,且灵活,这些很不错的指导原则不用你重新发明创造。确实有些原则你已经在你的类和对象中已经用到了,对吗?

Farhana:嗯…有个大概的印象了,但需要继续深入。

Shubho:别担心,你马上就会学到。我们继续讨论下去。

为什么要OOD?

Shubho:这是一个非常重要的问题。当我们能很快地设计一些类,完成开发并发布时,为什么我们需要关心OOD?那样子还不够吗?

Farhana:嗯,我早先并不知道OOD,我一直就是开发并发布项目。那么关键是什么?

Shubho:好的,我先给你一句名言:

走在结冰的河边不会湿鞋,开发需求不变的项目畅通无阻(Walking on water and developing software from a specification are easy if both are frozen)

Edward V. Berard

Farhana:你的意思是软件开发说明书会不断变化?

Shubho:非常正确!软件开发唯一的真理是“软件一定会变化”。为什么?

因为你的软件解决的是现实生活中的业务问题,而现实生活中得业务流程总是在不停的变化。

假设你的软件在今天工作的很好。但它能灵活的支持“变化”吗?如果不能,那么你就没有一个设计敏捷的软件。

Farhana:好,那么请解释一下“设计敏捷的软件”。

Shubho:”一个设计敏捷的软件能轻松应对变化,能被扩展,并且能被复用。”

并且应用好”面向对象设计”是做到敏捷设计的关键。那么,你什么时候能说你在代码中很好的应用了OOD?

Farhana:这正是我的问题。

Shubho:如果你代码能做到以下几点,那么你就正在OOD:

  • 面向对象
  • 复用
  • 能以最小的代价满足变化
  • 不用改变现有代码满足扩展

Farhana:还有?

Shubho:我们并不是孤立的。很多人在这个问题上思考了很多,也花费了很大努力,他们试图做好OOD,并为OOD指出几条基本的原则(那些灵感你能用之于你的OOD)。他们最终也确实总结出了一些通用的设计模式(基于基本的原则)。

Farhana:你能说几个吗?

Shubho:当然。这里有很多涉及原则,但最基本的是叫做SOLID的5原则(感谢Uncle Bob,伟大OOD导师)。

S = 单一职责原则 Single Responsibility Principle
O = 开放闭合原则 Opened Closed Principle 
L = Liscov替换原则 Liscov Substitution Principle
I = 接口隔离原则 Interface Segregation Principle
D = 依赖倒置原则 Dependency Inversion Principle

接下去,我们会仔细探讨每一个原则。

单一职责原则

Shubho:我先给你展示一张海报。我们应当谢谢做这张海报的人,它非常有意思。

单一职责原则海报

它说:”并不是因为你能,你就应该做”。为什么?因为长远来看它会带来很多管理问题。

从面向对象角度解释为:”引起类变化的因素永远不要多于一个。

或者说”一个类有且只有一个职责”。

Farhana:能解释一下吗?

Shubho:当然,这个原则是说,如果你的类有多于一个原因会导致它变化(或者多于一个职责),你需要一句它们的职责把这个类拆分为多个类。

Farhana:嗯…这是不是意味着在一个类里不能有多个方法?

Shubho:不。你当然可以在一个类中包含多个方法。问题是,他们都是为了一个目的。如今为什么拆分是重要的?

那是因为:

  • 每个职责是轴向变化的;
  • 如果类包含多个职责,代码会变得耦合;

Farhana:能给我一个例子吗?

Shubho:当然,看一下下面的类层次。当然这个例子是从Uncle Bob那里得来,再谢谢他。

违反单一职责原则的类结构图

这里,Rectangle类做了下面两件事:

  • 计算矩形面积;
  • 在界面上绘制矩形;

并且,有两个应用使用了Rectangle类:

  • 计算几何应用程序用这个类计算面积;
  • 图形程序用这个类在界面上绘制矩形;

这违反了SRP(单一职责原则);

Farhana:如何违反的?

Shubho:你看,Rectangle类做了两件事。在一个方法里它计算了面积,在另外一个方法了它返回一个表示矩形的GUI。这会带来一些有趣的问题:

在计算几何应用程序中我们必须包含GUI。也就是在开发几何应用时,我们必须引用GUI库;

图形应用中Rectangle类的变化可能导致计算几何应用变化,编译和测试,反之亦然;

Farhana:有点意思。那么我猜我们应该依据职责拆分这个类,对吗?

Shubho:非常对,你猜我们应该做些什么?

Farhana:当然,我试试。下面是我们可能要做的:

拆分职责到两个不同的类中,如:

  • Rectangle:这个类应该定义Area()方法;
  • Rectangle:这个类应继承Rectangle类,并定义Draw()方法。

Shubho:非常好。在这里,Rectangle类被计算几何应用使用,而RectangleUI被图形应用使用。我们甚至可以分离这些类到两个独立的DLL中,那会允许我们在变化时不需要关心另一个就可以实现它。

Farhana:谢谢,我想我明白SRP了。SRP看起来是把事物分离成分子部分,以便于能被复用和集中管理。我们也不能把SRP用到方法级别吗?我的意思是,我们可以写一些方法,它们包含做很多事的代码。这些方法可能违反SRP,对吗?

Shubho:你理解了。你应当分解你的方法,让每个方法只做某一项工作。那样允许你复用方法,并且一旦出现变化,你能购以修改最少的代码满足变化。

开放闭合原则

Shubho:这里是开放闭合原则的海报

开放闭合原则海报

从面向对象设计角度看,它可以这么说:”软件实体(类,模块,函数等等)应当对扩展开放,对修改闭合。

通俗来讲,它意味着你应当能在不修改类的前提下扩展一个类的行为。就好像我不需要改变我的身体而可以穿上衣服。

Farhana:有趣。你能够按照你意愿穿上不同的衣服来改变面貌,而从不用改造身体。你对扩展开放了,对不?

Shubho:是的。在OOD里,对扩展开发意味着类或模块的行为能够改变,在需求变化时我们能以新的,不同的方式让模块改变,或者在新的应用中满足需求。

Farhana:并且你的身体对修改是闭合的。我喜欢这个例子。当需要变化时,核心类或模块的源代码不应当改动。你能用些例子解释一下吗?

Shubho:当然,看下面这个例子。它不支持”开放闭合”原则。

违反开发闭合原则的类结构

你看,客户端和服务段都耦合在一起。那么,只要出现任何变化,服务端变化了,客户端一样需要改变。

Farhana:理解。如果一个浏览器以紧耦合的方式按照指定的服务器(比如IIS)实现,那么如果服务器因为某些原因被其他服务器(如Apache)替换了,那么浏览器也需要修改或替换。这确实很可怕!

Shubho:对的。下面是正确的设计。

遵循开放闭合原则的类结构

在这个例子中,添加了一个抽象的服务器类,客户端包含一个抽象类的引用,具体的服务类实现了抽象服务类。那么,因任何原因引起服务实现发生变化时,客户端都不需要任何改变。

这里抽象服务类对修改是闭合的,实体类的实现对扩展是开放的。

Farhana:我明白了,抽闲是关键,对吗?

Shubho:是的,基本上,你抽象的东西是你系统的核心内容,如果你抽象的好,很可能在扩展功能时它不需要任 何修改(就像服务是一个抽象概念)。如果在实现里定义了抽象的东西(比如IIS服务器实现的服务),代码要尽可能以抽象(服务)为依据。这会允许你扩展抽 象事物,定义一个新的实现(如Apache服务器)而不需要修改任何客户端代码。

Liskov’s 替换原则

Shubho:”Liskov’s替换原则(LSP)”听起来很难,却是很有用的基本概念。看下这幅有趣的海报:

Liskov替换原则海报

这个原则意思是:”子类型必须能够替换它们基类型。

或者换个说法:”使用基类引用的函数必须能使用继承类的对象而不必知道它。

Farhana:不好意思,听起来有点困惑。我认为这个OOP的基本原则之一。也就是多态,对吗?为什么一个面向对象原则需要这么说呢?

Shubho:问的好。这就是你的答案:

在基本的面向对象原则里,”继承”通常是”is a“的关系。如果”Developer” 是一个”SoftwareProfessional“,那么”Developer“类应当继承”SoftwareProfessional“类。在类设计中”Is a“关系非常重要,但它容易冲昏头脑,结果使用错误的继承造成错误设计。

Liskov替换原则“正是保证继承能够被正确使用的方法。

Farhana:我明白了。有意思。

Shubho:是的,亲爱的,确实。我们看个例子:

Liskov替换原则类结构图

这里,KingFisher类扩展了Bird基类,并继承了Fly()方法,这看起来没问题。

现在看下面的例子:

违反Liskov替换原则类结构图

Ostrich(鸵鸟)是一种鸟(显然是),并从Bird类继承。它能飞吗?不能,这个设计就违反了LSP。

所以,即使在现实中看起来没问题,在类设计中,Ostrich不应该从Bird类继承,这里应该从Bird中分离一个不会飞的类,Ostrich应该继承与它。

Farhana:好,明白了。那么让我来试着指出为什么LSP这么重要:

  • 如果没有LSP,类继承就会混乱;如果子类作为一个参数传递给方法,将会出现未知行为;
  • 如果没有LSP,适用与基类的单元测试将不能成功用于测试子类;

对吗?

Shubho:非常正确。你能设计对象,使用LSP做为一个检查工作来测试继承是否正确。

接口分离原则

Shubho:今天我们学习”接口分离原则”,这是海报:

接口分离原则海报

Farhana:这是什么意思?

Shubho:它的意思是:”客户端不应该被迫依赖于它们不用的接口。”

Farhana:请解释一下。

Shubho:当然,这是解释:

假设你想买个电视机,你有两个选择。一个有很多开关和按钮,它们看起来很混乱,且好像对你来说没必要。另一个只有几个开关和按钮,它们很友好,且适合你使用。假定两个电视机提供同样的功能,你会选哪一个?

Farhana:当然是只有几个开关和按钮的第二个。

Shubho:对,但为什么?

Farhana:因为我不需要那些看起来混乱又对我没用的开关和按钮。

Shubho:以便外部能够指导这些类有哪些可用的功能,客户端代码也能根据接口来设计.现在,如果接口太大,包含很多暴露的方法,在外界看来会很混乱.接口包含太多的方法也使其可用性降低,像这种包含了无用方法的”胖接口”会增加类之间的耦合.你通过接口暴露类的功能,对.同样地,假设你有一些类,

这也引起了其他问题.如果一个类想实现该接口,那么它需要实现所有的方法,尽管有些对它来说可能完全没用.所以说这么做会在系统中引入不必要的复杂度,降低可维护性或鲁棒性.

接口隔离原则确保实现的接口有他们共同的职责,它们是明确的,易理解的,可复用的.

Farhana:你的意思是接口应该仅包含必要的方法,而不该包含其它的.我明白了.

Shubho:非常正确.一起看个例子.

下面是违反接口隔离原则的一个胖接口

注意到IBird接口包含很多鸟类的行为,包括Fly()行为.现在如果一个Bird类(如Ostrich)实现了这个接口,那么它需要实现不必要的Fly()行为(Ostrich不会飞).

Farhana:确实如此。那么这个接口必须拆分了?

Shubho:是的。这个”胖接口”应该拆分未两个不同的接口,IBirdIFlyingBird,IFlyingBird继承自IBird.

这里如果一种鸟不会飞(如Ostrich),那它实现IBird接口。如果一种鸟会飞(如KingFisher),那么它实现IFlyingBird.

Farhana:所以回头看包含了很多开关和按钮的电视机的例子,电视机制造商应该有一个电视机的图纸,开关和按钮都在这个方案里。不论任何时候,当他们向制造一种新款电视机时,如果他们想复用这个图纸,他们将需要在这个方案里添加更多的开关和按钮。那么他们将没法复用这个方案,对吗?

Shubho:对的。

Farhana:如果他们确实需要复用方案,它们应当把电视机的图纸份为更小部分,以便在任何需要造新款电视机的时候复用这点小部分。

Shubho:你理解了。

依赖倒置原则

Shubho:这是SOLID原则里最后一个原则。这是海报

它的意思是:高层模块不应该依赖底层模块,两者都应该依赖其抽象

Shubho:考虑一个现实中的例子。你的汽车是由很多如引擎,车轮,空调和其它等部件组成,对吗?

Farhana:是的

Shubho:好,它们没有一个是严格的构建在一个单一单元里;换句话说,它们都是可插拔的,因此当引擎或车轮出问题时,你可以修理它(而不需要修理其它部件),甚至可以换一个。

在替换时,你仅需要确保引擎或车轮符合汽车的设计(如汽车能使用任何1500CC的引擎或任何18寸的车轮)。

当然,汽车也可能允许你在1500CC引擎的地方安装一个2000CC的引擎,事实上对某些制造商(如丰田汽车)是一样的。

现在,如果你的汽车的零部件不具备可插拔性会有什么不同?

Farhana:那会很可怕!因为如果汽车的引擎出故障了,你可能修理整部车或者需要买一个新的。

Shubho:是的,那么该如何做到”可插拔性”呢?

Farhana:这里抽象是关键,对吗?

Shubho:是的,在现实中,汽车是高级模块或实体,它依赖于低级模块或实体,如引擎或车轮。

相比直接依赖于引擎或车轮,汽车应依赖于某些抽象的有规格的引擎或车轮,以便于如果任何引擎或车轮符合抽象,那么它们都能组合到汽车中,汽车也能跑动。

一起看下面的类图

Shubho:注意到上面Car类有两个属性,它们都是抽象类型(接口)。引擎和车轮是可插拔的,因为汽车能接受任何实现了声明接口的对象,并且Car类不需要做任何改动。

Farhana:所以,如果代码中不用依赖倒置,我们将面临如下风险:

  • 使用低级类会破环高级代码;
  • 当低级类变化时需要很多时间和代价来修改高级代码;
  • 产生低复用的代码;

Shubho:你完全掌握了,亲爱的!

总结

Shubho:除SOLID原则外还有很多其它的面向对象原则。如:

“组合替代继承”:这是说相对于继承,要更倾向于使用组合;

“笛米特法则”:这是说”你的类对其它类知道的越少越好”;

“共同封闭原则”:这是说”相关类应该打包在一起”;

“稳定抽象原则”:这是说”类越稳定,越应该由抽象类组成”;

Farhana:我应该学习那些原则吗?

Shubho:当然可以。你可以从整个网上学习。仅仅需要Google一下那些原则,然后尝试理解它。当然如果有需要,尽管问我。

Farhana:在那些设计原则之上我听说过很多设计模式。

Shubho:对的。设计模式只是对一些经常出现的场景的一些通用设计建议。这些灵感主要来自于面向对象原则。你可以把设计模式看作”框架”,把OOD原则看作”规范”.

Farhana:那么接下去我将学习设计模式吗?

Shubho:是的,亲爱的。

Farhana:那会很有意思,对吗?

Shubho:是,那确实令人兴奋。

[转载]风讯dotNETCMS源码分析—数据存取篇

mikel阅读(1017)

[转载]风讯dotNETCMS源码分析—数据存取篇 – 寂寞沙洲 – 博客园.

前几天突然对CMS感兴趣,就去下载了风讯dotNETCMS源码。当前版本是dotnetcms1.0 sp5免费版,风讯的官方主页上可以下载。

用Visual Studio 2008打开后,初步分析了它的数据存取技术。风讯dotNETCMS采取的是基于抽象工厂模式的三层架构。
Foosun.Model是数据实体层,用于在各层之间传递数据,Foosun.Web是表示层,Foosun.CMS是业务逻辑层,数据访问层有多 个:Foosun.AccessDAL、Foosun.SQLServerDAL,分别用于支持Access、SQL Server数据库。
具体支持哪种数据库,只需在表示层的Web.config文件中进行配置,配置字符串如下:
<appSettings>
<add key=”WebDAL” value=”Foosun.SQLServerDAL”/>
</appSettings>
Foosun.DALFactory是接口层,定义了Foosun.AccessDAL、Foosun.SQLServerDAL都要实现的接口,如public interface IAdmin,示意图

如下:

Foosun.DALFactory接口层同时还包含实现抽象工厂模式的类DataAccess。在DataAccess类中创建对象通过配置文件和反射 技术来实现。通过配置文件(即上面提到的Web.config)的程序集名,决定加载具体的哪个DAL的程序集,并通过动态组合类名来动态创建DAL对 象,并返回接口对象。

DataAccess类图如下:

示例代码如下:
public static IAdmin CreateAdmin()
{
string className = path + “.Admin”;  //类名
return (IAdmin)Assembly.Load(path).CreateInstance(className);//反射,并进行类型转换。
}
而path的定义如下:
private static readonly string path =Foosun.Config.UIConfig.WebDAL;
Foosun.Config.UIConfig.WebDAL的定义如下:
public static string WebDAL = ConfigurationManager.AppSettings[“WebDAL”];的确是读取了Web.config文件中的WebDAL配置。
事实上,微软的PetShop的数据访问也是采用了基于抽象工厂模式的三层架构。通过使用基于抽象工厂模式的三层架构,很好的实现了系统的重用性、灵活性和扩展性。

[转载]新浪微博开放平台OAuth授权解决方案(含代码)

mikel阅读(1135)

[转载]新浪微博开放平台OAuth授权解决方案(含代码) – 勤劳的渔网工作者 – 博客园.

前几日一位朋友项目中需要使用新浪微博的接口,故和这位朋友一同研究了新浪微博开放平台上面所提供的资料,首先要使用这些接口是需要用户登录并且授权的,新浪微博开放平台其实是提供两种授权方式的,第一种是:OAuth授权方式,第二种是:HTTP普通鉴权方式,我们使用了第一种方式来授权,但是在执行过程中遇到了许多问题,觉得单对新浪微博开放平台还是有一些代表性,所以共享下经验,下面可以下载我的Demo。

OAuth是一种国际授权方式,它不需要用户在第三方应用输入用户名及密码,所以安全性很高,那么在新浪微博开放平台中通过OAuth授权的流程图是这样的:

其实在程序中步骤表现就只有4步:

1、获取Request token。

2、用户认证。

3、获取Access token。

4、获取用户信息。

在处理OAuth授权过程中我也碰到几个在新浪开放平台论坛中常见的几个问题,在这里总结下,在后面讲解中会讲到我的想法和解决办法:

1、requesttoken时callback问题。

2、401错误。

3、403错误。

4、500错误。

5、未经授权错误。

在这里顺便讲一下调用新浪微博接口必须是要申请一个应用的,申请应用成功之后会得到一个App key号和App Secret号,我们也需要通过这两个参数来请求授权,还有就是网上有OAuthBase下载,但是要下对版本,我的Demo中也有,我们的授权主要的代 码是在OAuthBase.cs文件中的。

1、获取Request token:

直接上代码:

我在请求Request token的时候遇到了401错误地址返回错误, 地址返回错误比较好解决,一般都是地址错误,所以我直接用了Request.Url,那么401错误了我出错是在签名 的地方,最开始的OAuthBase文件下载错了,下给最新的就可以了,还有就是在请求参数中的oauth_version参数,有很多值是1.0a,这 样好像是不行的,全部改成1.0就能避免很多错误。

2、用户认证:

在Request token请求成功之后,平台自动跳到登录页面,进行用户认证,认证通过之后平台会将oauth_token和oauth_verifier返回到指定的 callback来,将两个参数保存下来用于请求Access token,在这里如果地址不正确是会报错的。

3、获取Access token:

这个请求的重点还是在签名,必须要将用户认证后返回的oauth_token和oauth_verifier一并签名才能正确,有些OAuthBase中是没有将verifier加入签名当中当时让我好生郁闷,如果这点错了应该会报未经授权或者403错误,请求成功之后需要将oauth_token和oauth_token_secret重新保存下,下面是代码:

4、获取登录用户信息:

步骤简单和以上几个请求方式也一样,主要是要将oauth_token和oauth_token_secret加入签名,下面是代码:

到这里你可以获取用户的个人信息,那么OAuth授权也就成功,其实步骤是比较简单的,主要要注意的就是签名,签名不正确一定是通过不了的,还有就是一些 细节,如地址,版本号,请求方式这些细心点就能避免,由于时间原因这里讲的比较简单,希望大家互相交流下,这里是Demo:SinaOAuthSinaOAuth

[转载]ASP.NET MVC中的扩展点(九)验证

mikel阅读(1008)

[转载]MVC中的扩展点(九)验证 – xfrog – 博客园.

验证用于保证用户输入的正确性,及时阻止用户提交错误数据,确保数据符合业务规则。

MVC中可直接在控制器Action方法中进行验证:检查传入参数,如果传入参数不符合业务规则,则通过控制器的ModelState属性的 AddModelError方法向模型状态添加错误消息,通过ModelState.IsValid判断模型是否全部通过验证,随后,在视图中通过 Html.ValidationSummary、Html.ValidationMessage、Html.ValidationMessageFor辅 助方法生成验证消息。

在Controller中验证并不是最好的方法:验证过于分散,容易造成重复代码,不利于维护与扩展。验证应该是和模型紧密相关的,如果我们将模型独立为 一个单独的程序集,那么验证也应该包含在模型的程序集中。从用户录入的数据到具体的模型,这个映射过程就是模型绑定,所以在绑定过程中实现验证是一个不错 的选择,这也是MVC中最重要的验证方式。

MVC默认绑定器(DefaultModelBinder)中包含了验证架构,具体来说,默认绑定器在将值提供器的数据填充到模型的时候,遵循以下流程: 调用默认绑定器的OnModelUpdating方法—>从值提供器获取值并填充到模型—>调用默认绑定器的OnModelUpdated方 法。DefaultModelBinder类中OnModelUpdating方法始终返回true,即默认绑定器在填充模型之前没有验证,在填充数据 后,OnModelUpdated中实现相应的验证。默认绑定器中验证体系类关系图如下:

Validator

模型绑定中的验证体系包含三个主要类:

验证器提供者:ModelValidatorProvider,用于产生验证器

验证器:ModelValidator,用于实现具体的验证逻辑,其中GetClientValidationRules用于返回客户端验证脚本使用的验证规则(ModelClientValidationRule),Validate方法用于实现服务端的验证逻辑。

特性:通常由提供者使用,根据特性指定的规则产生相应的验证器

ModelValidatorProviders是一个静态类,他包含一个用于保存MVC中默认验证器提供者的集合(静态属性Proviers),MVC 在填充完模型数据之后,依次对每一个模型属性,从Providers中获取所有的针对该属性的验证器,然后调用验证器上的Validate方法产生验证结 果(ModelValidationResult),绑定器根据该结果数据向ModelState中添加验证消息。

MVC中实现了三个默认的验证器提供者(相应产生三个验证器):

DataAnnotationsModelValidatorProvider: 用于实现.NET中的验证特性,即System.ComponentModel.DataAnnotations命名空间下的多种验证特性,包含用于限制 属性区间的RangeAttribute,用于验证数据是否符合正则表达式的RegularExpressionAttribute,用于指定必需项的 RequiredAttribute,用于限制字符串长度的 StringLengthAttribute,DataAnnotationsModelValidatorProvider通过桥接模式,将.NET的 验证特性转换为ModelValidator,所以我们可以直接在MVC中使用.NET验证特性来实现验证。

DataErrorInfoClassModelValidatorProvider: 此提供器主要是为了向后兼容,用于实现基于IDataErrorInfo接口的验证方式(MVC 1.0),即你可以为模型实现IDataErrorInfo接口,这样默认绑定器同样可以通过该接口来调用你的验证逻辑。

ClientDataTypeModelValidatorProvider: 此提供器只用于客户端对数值型数据的验证(产生相应的客户端验证脚本),他的Validate方法不会返回任何验证结果。

MVC中另外实现了一个抽象类:AssociatedValidatorProvider,它从ModelValidatorProvider继承,并重 写了GetValidator方法,增加传入了附加在模型属性上的特性集合。所以,如果我们需要实现基于特性的验证方式,则应该从此类继承实现自己的验证 器及相应的提供者类。当然我们也可以使用默认的DataAnnotationsModelValidatorProvider,这样我们只需要从 ValidationAttribute特性继承,并实现自己的验证逻辑。

客户端验证

ModelValidator中GetClientValidationRules方法可以返回用于客户端的验证规则,这些规则可以在客户端脚本中访问,客户端脚本根据这些验证规则检查用户录入资料,并将验证结果反馈给用户。

下例将实现一个ConfirmValidator,用于验证用户注册时两次密码必须输入一致:

1、创建一个空MVC项目

2、添加用户信息模型UserInfo.cs

显示行号 复制代码 UserInfo
  1. public class UserInfo
    
  2. {
    
  3.     public string UserName { get; set; }
    
  4.     public string Password { get; set; }
    
  5.     public string ConfirmPassword { get; set; }
    
  6.     public string Email { get; set; }
    
  7. }
    

.src_container { background-color: rgb(231, 229, 220); width: 99%; overflow: hidden; margin: 12px 0pt ! important; padding: 0px 3px 3px 0px; }.src_container .titlebar { background-color: rgb(212, 223, 255); border-width: 1px 1px 0pt; border-style: solid solid none; border-color: rgb(79, 129, 189) rgb(79, 129, 189) -moz-use-text-color; padding: 3px 24px; margin: 0pt; width: auto; line-height: 120%; overflow: hidden; text-align: left; font-size: 12px; }.src_container .toolbar { display: inline; font-weight: normal; font-size: 100%; float: right; color: rgb(0, 0, 255); text-align: left; overflow: hidden; }.toolbar span.button { display: inline; font-weight: normal; font-size: 100%; color: rgb(0, 0, 255); text-align: left; overflow: hidden; cursor: pointer; }.src_container div.clientarea { background-color: white; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; height: auto; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,courier,monospace,serif; }.src_container ol.mainarea { padding: 0pt 0pt 0pt 52px; margin: 0pt; background-color: rgb(247, 247, 255) ! important; }.number_show { padding-left: 52px ! important; list-style: decimal outside none ! important; }.number_show li { list-style: decimal outside none ! important; border-left: 1px dotted rgb(79, 129, 189); }.number_hide { padding-left: 0px ! important; list-style-type: none ! important; }.number_hide li { list-style-type: none ! important; border-left: 0px none; }ol.mainarea li { display: list-item ! important; font-size: 12px ! important; margin: 0pt ! important; line-height: 18px ! important; padding: 0pt 0pt 0pt 0px ! important; background-color: rgb(247, 247, 255) ! important; color: rgb(79, 129, 189); }ol.mainarea li pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap ol.mainarea li pre { white-space: pre-wrap; word-wrap: break-word; }ol.mainarea li pre.alt { background-color: rgb(247, 247, 255) ! important; }3、创建一个特性,用于指定与属性关联的另一个属性的名称

显示行号 复制代码 ConfirmValidatorAttribute
  1. public class ConfirmValidatorAttribute : Attribute
    
  2. {
    
  3.     public String ConfirmPropertyName { get; set; }
    
  4.     public ConfirmValidatorAttribute(string name)
    
  5.     {
    
  6.         ConfirmPropertyName = name;
    
  7.     }
    
  8. }
    

.src_container { background-color: rgb(231, 229, 220); width: 99%; overflow: hidden; margin: 12px 0pt ! important; padding: 0px 3px 3px 0px; }.src_container .titlebar { background-color: rgb(212, 223, 255); border-width: 1px 1px 0pt; border-style: solid solid none; border-color: rgb(79, 129, 189) rgb(79, 129, 189) -moz-use-text-color; padding: 3px 24px; margin: 0pt; width: auto; line-height: 120%; overflow: hidden; text-align: left; font-size: 12px; }.src_container .toolbar { display: inline; font-weight: normal; font-size: 100%; float: right; color: rgb(0, 0, 255); text-align: left; overflow: hidden; }.toolbar span.button { display: inline; font-weight: normal; font-size: 100%; color: rgb(0, 0, 255); text-align: left; overflow: hidden; cursor: pointer; }.src_container div.clientarea { background-color: white; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; height: auto; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,courier,monospace,serif; }.src_container ol.mainarea { padding: 0pt 0pt 0pt 52px; margin: 0pt; background-color: rgb(247, 247, 255) ! important; }.number_show { padding-left: 52px ! important; list-style: decimal outside none ! important; }.number_show li { list-style: decimal outside none ! important; border-left: 1px dotted rgb(79, 129, 189); }.number_hide { padding-left: 0px ! important; list-style-type: none ! important; }.number_hide li { list-style-type: none ! important; border-left: 0px none; }ol.mainarea li { display: list-item ! important; font-size: 12px ! important; margin: 0pt ! important; line-height: 18px ! important; padding: 0pt 0pt 0pt 0px ! important; background-color: rgb(247, 247, 255) ! important; color: rgb(79, 129, 189); }ol.mainarea li pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap ol.mainarea li pre { white-space: pre-wrap; word-wrap: break-word; }ol.mainarea li pre.alt { background-color: rgb(247, 247, 255) ! important; }4、创建用于实现验证逻辑的ConfirmValidator类

显示行号 复制代码 ConfirmValidator
  1. public class ConfirmValidator : ModelValidator
    
  2. {
    
  3.     private string  confirmPropertyName;
    
  4.     public ConfirmValidator(ModelMetadata metaData, ControllerContext context, string confirmProperty)
    
  5.         : base(metaData, context)
    
  6.     {
    
  7.         confirmPropertyName = confirmProperty;
    
  8.     }
    
  9.     public override IEnumerable<ModelValidationResult> Validate(object container)
    
  10.     {
    
  11.         if (container == null)
    
  12.             yield break;
    
  13.         PropertyInfo pi = container.GetType().GetProperty(confirmPropertyName);
    
  14.         if (pi != null)
    
  15.         {
    
  16.             string confirmValue = (string)pi.GetValue(container, null);
    
  17.             if ( !(Metadata.Model??String.Empty).Equals(confirmValue??String.Empty))
    
  18.             {
    
  19.                 yield return new ModelValidationResult()
    
  20.                 {
    
  21.                     Message = "两次输入不一致!"
    
  22.                 };
    
  23.             }
    
  24.         }
    
  25.         else
    
  26.         {
    
  27.             throw new InvalidOperationException("属性" + confirmPropertyName + "不存在");
    
  28.         }
    
  29.     }
    
  30. }
    

.src_container { background-color: rgb(231, 229, 220); width: 99%; overflow: hidden; margin: 12px 0pt ! important; padding: 0px 3px 3px 0px; }.src_container .titlebar { background-color: rgb(212, 223, 255); border-width: 1px 1px 0pt; border-style: solid solid none; border-color: rgb(79, 129, 189) rgb(79, 129, 189) -moz-use-text-color; padding: 3px 24px; margin: 0pt; width: auto; line-height: 120%; overflow: hidden; text-align: left; font-size: 12px; }.src_container .toolbar { display: inline; font-weight: normal; font-size: 100%; float: right; color: rgb(0, 0, 255); text-align: left; overflow: hidden; }.toolbar span.button { display: inline; font-weight: normal; font-size: 100%; color: rgb(0, 0, 255); text-align: left; overflow: hidden; cursor: pointer; }.src_container div.clientarea { background-color: white; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; height: auto; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,courier,monospace,serif; }.src_container ol.mainarea { padding: 0pt 0pt 0pt 52px; margin: 0pt; background-color: rgb(247, 247, 255) ! important; }.number_show { padding-left: 52px ! important; list-style: decimal outside none ! important; }.number_show li { list-style: decimal outside none ! important; border-left: 1px dotted rgb(79, 129, 189); }.number_hide { padding-left: 0px ! important; list-style-type: none ! important; }.number_hide li { list-style-type: none ! important; border-left: 0px none; }ol.mainarea li { display: list-item ! important; font-size: 12px ! important; margin: 0pt ! important; line-height: 18px ! important; padding: 0pt 0pt 0pt 0px ! important; background-color: rgb(247, 247, 255) ! important; color: rgb(79, 129, 189); }ol.mainarea li pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap ol.mainarea li pre { white-space: pre-wrap; word-wrap: break-word; }ol.mainarea li pre.alt { background-color: rgb(247, 247, 255) ! important; }5、创建用于产生ConfirmValidator的提供者类:ConfirmValidatorProvider

显示行号 复制代码 ConfirmValidatorProvider
  1. public class ConfirmValidatorProvider : AssociatedValidatorProvider
    
  2. {
    
  3.     protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
    
  4.     {
    
  5.         foreach (ConfirmValidatorAttribute attr in attributes.OfType<ConfirmValidatorAttribute>())
    
  6.         {
    
  7.             yield return new ConfirmValidator(metadata, context, attr.ConfirmPropertyName);
    
  8.         }
    
  9.     }
    
  10. }
    

.src_container { background-color: rgb(231, 229, 220); width: 99%; overflow: hidden; margin: 12px 0pt ! important; padding: 0px 3px 3px 0px; }.src_container .titlebar { background-color: rgb(212, 223, 255); border-width: 1px 1px 0pt; border-style: solid solid none; border-color: rgb(79, 129, 189) rgb(79, 129, 189) -moz-use-text-color; padding: 3px 24px; margin: 0pt; width: auto; line-height: 120%; overflow: hidden; text-align: left; font-size: 12px; }.src_container .toolbar { display: inline; font-weight: normal; font-size: 100%; float: right; color: rgb(0, 0, 255); text-align: left; overflow: hidden; }.toolbar span.button { display: inline; font-weight: normal; font-size: 100%; color: rgb(0, 0, 255); text-align: left; overflow: hidden; cursor: pointer; }.src_container div.clientarea { background-color: white; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; height: auto; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,courier,monospace,serif; }.src_container ol.mainarea { padding: 0pt 0pt 0pt 52px; margin: 0pt; background-color: rgb(247, 247, 255) ! important; }.number_show { padding-left: 52px ! important; list-style: decimal outside none ! important; }.number_show li { list-style: decimal outside none ! important; border-left: 1px dotted rgb(79, 129, 189); }.number_hide { padding-left: 0px ! important; list-style-type: none ! important; }.number_hide li { list-style-type: none ! important; border-left: 0px none; }ol.mainarea li { display: list-item ! important; font-size: 12px ! important; margin: 0pt ! important; line-height: 18px ! important; padding: 0pt 0pt 0pt 0px ! important; background-color: rgb(247, 247, 255) ! important; color: rgb(79, 129, 189); }ol.mainarea li pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap ol.mainarea li pre { white-space: pre-wrap; word-wrap: break-word; }ol.mainarea li pre.alt { background-color: rgb(247, 247, 255) ! important; }6、创建用于测试的控制器及视图

显示行号 复制代码 HomeController
  1. public class HomeController : Controller
    
  2. {
    
  3.     public ActionResult Index()
    
  4.     {
    
  5.         return View(new UserInfo());
    
  6.     }
    
  7.     [HttpPost]
    
  8.     public ActionResult Index(UserInfo ui)
    
  9.     {
    
  10.         return View(ui);
    
  11.     }
    
  12. }
    
<body>
    <div>
    <%using(Html.BeginForm()){ %>
        <%= Html.ValidationSummary(true) %>
        <%= Html.EditorFor(x=>x) %>
        <input type="submit" value="提交" />
    <%} %>
    </div>
</body>

.codearea { color: black; background-color: white; line-height: 18px; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,”BitStream Vera Sans Mono”,courier,monospace,serif; }.codearea pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap pre { white-space: pre-wrap; word-wrap: break-word; }.codearea pre.alt { background-color: rgb(247, 247, 255) ! important; }.codearea .lnum { color: rgb(79, 129, 189); line-height: 18px; }
 

7、修改UserInfo.cs,在ConfirmPassword属性上添加ConfirmValidator特性。

[ConfirmValidator("Password")]
public string ConfirmPassword { get; set; }

.codearea { color: black; background-color: white; line-height: 18px; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,”BitStream Vera Sans Mono”,courier,monospace,serif; }.codearea pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap pre { white-space: pre-wrap; word-wrap: break-word; }.codearea pre.alt { background-color: rgb(247, 247, 255) ! important; }.codearea .lnum { color: rgb(79, 129, 189); line-height: 18px; }

8、在Global Application_Start中添加ConfirmValidatorProvider

ModelValidatorProviders.Providers.Add(new ConfirmValidatorProvider());

实现客户端验证:

1、修改ConfirmValidator类,添加GetClientValidationRules方法。

public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
    ModelClientValidationRule rule = new ModelClientValidationRule()
    {
        ErrorMessage = "两次输入不一致!",
        ValidationType = "ConfirmValidator"
    };
    rule.ValidationParameters["ConfirmPropertyName"] = confirmPropertyName;
    yield return rule;
}

.codearea { color: black; background-color: white; line-height: 18px; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,”BitStream Vera Sans Mono”,courier,monospace,serif; }.codearea pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap pre { white-space: pre-wrap; word-wrap: break-word; }.codearea pre.alt { background-color: rgb(247, 247, 255) ! important; }.codearea .lnum { color: rgb(79, 129, 189); line-height: 18px; }2、修改Index.aspx,添加对Ajax脚本的引用

<script type="text/javascript" src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js"></script>
<script type="text/javascript" src="http://www.cnblogs.com/Scripts/MicrosoftMvcValidation.js"></script>

3、添加自定义验证脚本

<script type="text/javascript">
    Sys.Mvc.ValidatorRegistry.validators.ConfirmValidator = function (rule) {
        var propertyName = rule.ValidationParameters.ConfirmPropertyName;
        return function (value, context) {
            var confirmValue = document.getElementsByName(propertyName)[0].value;
            return (value == confirmValue);
        }
    };
</script>

.codearea { color: black; background-color: white; line-height: 18px; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,”BitStream Vera Sans Mono”,courier,monospace,serif; }.codearea pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap pre { white-space: pre-wrap; word-wrap: break-word; }.codearea pre.alt { background-color: rgb(247, 247, 255) ! important; }.codearea .lnum { color: rgb(79, 129, 189); line-height: 18px; }
4、开启客户端验证功能

<%Html.EnableClientValidation(); %>

5、将ConfirmPassword属性加入客户端验证

<% Html.ValidateFor(x => x.ConfirmPassword); %>

修改后完整视图代码:

<body>
    <div>
    <%Html.EnableClientValidation(); %>
    <%using(Html.BeginForm()){ %>
        <%= Html.ValidationSummary(true) %>
        <% Html.ValidateFor(x => x.ConfirmPassword); %>
        <%= Html.EditorFor(x=>x) %>
        <input type="submit" value="提交" />
    <%} %>
    </div>
    <script type="text/javascript" src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js"></script>
    <script type="text/javascript" src="http://www.cnblogs.com/Scripts/MicrosoftMvcValidation.js"></script>
    <script type="text/javascript">
        Sys.Mvc.ValidatorRegistry.validators.ConfirmValidator = function (rule) {
            var propertyName = rule.ValidationParameters.ConfirmPropertyName;
            return function (value, context) {
                var confirmValue = document.getElementsByName(propertyName)[0].value;
                return (value == confirmValue);
            }
        };
    </script>
</body>

源代码下载

[转载]ASP.NET MVC 3.0学习系列文章—Model in ASP.NET MVC 3.0

mikel阅读(1190)

[转载]ASP.NET MVC 3.0学习系列文章—Model in ASP.NET MVC 3.0 – 爱因斯坦的小脑 – 博客园.

系列文章

ASP.NET MVC 3.0学习系列文章—序

ASP.NET MVC 3.0学习系列文章–Razor and ASP.NET MVC 3.0

ASP.NET MVC 3.0学习系列文章—Controllers in ASP.NET MVC 3.0

image

这篇文章主要介绍如下基本分内容:

image

1. Introduction:

ASP.NET MVC 3.0中, Model模块主要是在Validation这部分有很大的增强。包括ASP.NET MVC开发团队自己写的使用JQuery来进行客户端验证的JQuery.validate.unobtrusive.js,以及Remote validation等。刚才在园子看到一个同学写了一篇文章介绍了Remote validation,写的挺详细的,大家可以看看。

http://www.cnblogs.com/serafin/archive/2011/01/25/1944848.html

我也会在这篇文章中稍微给大家介绍如何使用remote 验证。

总的来说ASP.NET MVC 3.0中的Validation主要的改动有如下三个方面:

a.New attributes b. New interfaces, c. Improved client validation story.

2. Validation in MVC 3

在验证方面分为如下几部分:

image

3.Data Annotations

微软的的System.ComponentModel.DataAnnotations就是一个好的验证方法集合。

你可以看看它主要包含的类:

image

从 它包含的这些方法中可以看出它主要是用来做字段的验证。在ASP.NET MVC或者是Silverlight中微软都是使用它来做数据的合法性验证。当然,在ASP.NET MVC 3.0中结合jQuery的客户端验证,使Data Annotation发挥的功能更加完美。

4.Custom Validation Attributes

在上面的System.ComponentModel.DataAnnotations命名空间中,有ValidationAttibute这个类,所有的attribute类型的验证方法所在的类都继承了它。

比如:

image

上图中的Required和StringLength,我们看看它们的所在的类:

image

这么看来我们可以自定义Validation类,它也继承ValidationAttribute。

自定义类的结构如下:

image

我们现在自定义一个验证,来验证输入的时间是否是在合法的范围呢。

image

当你输入的时间不是在当前月的前6各月内或者是大于当前时间7天,就给出提示信息。

image

5.Self Validation models

这种方式是在model中来结合 ValidationContext和ValidationResult来提供验证。

例如我们的model中有一个startdate和一个enddate字段,要求输入的enddate不能小于startdate,那么我们可以使用Self validation model这种验证模式:

image

运行结果:

image

上面两种验证方式都是需要整个Form提交数据时post数据,大家估计都不可能接受,所以ASP.NET MVC团队在ASP.NET MVC 3中添加了下面两种验证方式,

Client Validation和Remote Validation。

6.Client Validation:

明眼人都会发现,Microsoft这几年和jQuery团队走的很近,ASP.NET MVC项目中更是使用了jQuery来实现客户端验证。

image

很欣慰的是微软终于放弃了之前自己的AJAX库。。。。。

在使用jQuery的AJAX认证,需要注意如下几点:

1.Web.Config中添加设置:

image

2.添加js文件:

image

3.在Html.FormBegain之前添加:

image

我这里有个例子,大家可以看看使用jquery.validate.unobtrusive.mis.js后,客户端生成的html有什么区别:

image

本身html的节点中就有了验证信息。

7.Custom Client Validation

ASP.NET MVC团队提供的这个客户端验证方式也是个扩展性非常强的。

image

例子如下:

image

8.remote validation:

remote validation部分我就不作说明了,文章够长了。。。。。Smile

大家可以看看http://www.cnblogs.com/serafin/archive/2011/01/25/1944848.html

我个人觉得这个remote validation的原理就是实时向某个Action Post数据,然后再看返回结果是否为空,不为空就显示Validation Message。主要还是使用了jQUery的ajax。

Nick

[转载]通过源代码研究ASP.NET MVC(八)

mikel阅读(1338)

[转载]通过源代码研究ASP.NET MVC(八) – namespace Ivony – 博客园.

通过源代码研究ASP.NET MVC中的Controller和View(一)

通过源代码研究ASP.NET MVC中的Controller和View(二)

通过源代码研究ASP.NET MVC中的Controller和View(三)

通过源代码研究ASP.NET MVC中的Controller和View(四)

通过源代码研究ASP.NET MVC中的Controller和View(五)

通过源代码研究ASP.NET MVC中的Controller和View(六)

通过源代码研究ASP.NET MVC(七)

第 八篇,上一篇发布后,有朋友说这个系列更新太慢了,不给力。有几个原因,首先是Jumony M2的计划中,并不包括MVC Model的支持,换言之对于Jumony而言,到第六篇的时候,所得到的结论已经足够了。其次是最近忙于Jumony的开发,以及Jumony入门系列 的撰写。当然,ASP.NET MVC是一个非常优秀的框架,事实也证明通过源代码研究ASP.NET MVC并非天方夜谭,我越来越觉得,这个系列的文章以及这种实践,存在比Jumony项目更为重要的意义,所以,我会继续这个系列,也希望能获得大家的支 持。

通过源代码来研究任何东西。用脚趾头想想都能知道是一件非常枯燥和艰难的事情,但我想这个实践却很有意义。我们常说一个好的源代码 就是最好的文档,那么就让我们来真正验证一下,对于MVC如此庞大和复杂的框架,一个好的源代码是不是真的可以当作资料来学习呢?不试,又怎么知道?同 时,我一直在强调程序设计语言本身对于程序的重要性,通过这个实践,也能补充不少论据。

在这个系列中,我尽可能的当作自己一无所知(事 实上也基本是这样),仅仅通过源代码源代码的而不借助任何其他资料来研究。你会看到我在尽可能细致的考证每一个推论,每一个猜测,尽管结论是那么的明显。 同时你也会看到我不可避免的犯错,卡壳。这也许是因为我的能力所限,当然也有可能是不是源代码本身出了一些毛病。

通过这个系列,对于如何让别人看代码的时候完整的理解自己的意图。方法名称,程序结构以及细枝末节的那些东西的重要性,通过这个不可能完成的任务旅程,是否会有更深的理解呢?

废话到此结束。

在上一篇中,我们进展神速的从Controller或者说ControllerActionInvoker的世界忽然穿越到了Model的世界,IModelBinder地球人都知道这将会是Model的大门。进去之前先来回顾一下。

故事的原点,还得回到ControllerActionInvoker的核心逻辑:

  • 查找Action(FindAction)
  • 获取参数
  • InvokeActionMethod
  • InvokeActionResult

在 第六篇,我们弄明白了查找Action的逻辑。接下来经过一系列的手续后,ControllerActionInvoker调用了 GetPrameterValues方法,这个方法在获取了参数描述符后,转手调用GetParameterValue(没有s)来获取值。而 GetParameterValue的逻辑也已经理清,其实很简单:

  • 获取Binder
  • 构造BindingContext
  • 绑定Model

获取Binder的详细流程已经在第七篇中摸清。那么

GetParameterValue(s)的结果,显然是会被当作Action的参数被传入(注1),而这个结果则是通过符合条件的IModelBinder来获取。

注1:具体的代码分析可以参考:深入理解ASP.NET MVC(8),当ControllerActionInvoker的主线走完后,我会回过头来将过滤器的工作原理完整的分析清楚,因为事实上所有过滤器的工作原理几乎是一样的。

一 般看到接口,我立即能想到两个词,抽象和契约。事实上通过前面的分析能知道,MVC的核心抽象都是通过非常简单的接口来定义的。抽象的东西一定是简单的。 Controller和View的抽象就是IController和IView接口,尽管Model部分并没有所谓的IModel接口,但事实上MVC是 直接将object当作了Model的抽象。

那么来看看IModelBinder接口长什么样子:

namespace System.Web.Mvc
{
 
  public interface IModelBinder
  {
    object BindModel( ControllerContext controllerContext, ModelBindingContext bindingContext );
  }
}

很好,这个接口就一个方法,ControllerContext我们知道其包含了控制器和HTTP上下文的信息,而ModelBindingContext则是在GetParameterValue方法中被构建。

那么,IModelBinder的职责也被明确:

通过控制器和绑定上下文获取(绑定)模型的值。

ModelBindingContext从名称来看就是绑定上下文,他存放着绑定的时候需要的信息,但这些属性通过名称很难了解其具体用途,所以还是从IModelBinder入手。先来看看IModelBinder有一些什么实现:

image

老实说ModelBinder的实现类有点少得出乎我的意料,因为ModelBinder只有两种指定方式,在参数上通过 CustomModelBinderAttribute指定或者通过类型来匹配。在参数上通过Attribute指定并不是一种特别靠谱的方式,事实上在 范例和实际应用中,我们都很少看到这个特性的运用,譬如说MVC的示例网站代码:

    [HttpPost]
    public ActionResult LogOn( LogOnModel model, string returnUrl )
    {
      if ( ModelState.IsValid )
      {
        //...
      }
 
      // 如果我们进行到这一步时某个地方出错,则重新显示表单
      return View( model );
    }

这里我们看不到任何的CustomModelBinderAttribute的影子,我原想,或许是LogOnModel有在类型上用 CustomModelBinderAttribute注册自己的LogOnModelBinder,因为在类型上注册比在参数上注册来的靠谱。但这样来 说,对于returnUrl这样的参数,MVC至少也应该内置StringBinder或是Int32Binder之类的东西,但答案是这些都没有。仔细 观察IModelBidner的这几个实现类,我发现他们大部分都是应用于特定而非一般类型的。例如 HttpPostedFileBaseModelBinder,很明显这是用于创建HttpPostedFileBase实例的Binder。 ByteArrayModelBinder多半是应用于byte[]类型。

那么,干脆随便挑一个类型来瞄一眼吧,譬如说HttpPostedFileBaseModelBinder:

  public class HttpPostedFileBaseModelBinder : IModelBinder
  {
 
    public object BindModel( ControllerContext controllerContext, ModelBindingContext bindingContext )
    {
      if ( controllerContext == null )
      {
        throw new ArgumentNullException( "controllerContext" );
      }
      if ( bindingContext == null )
      {
        throw new ArgumentNullException( "bindingContext" );
      }
 
      HttpPostedFileBase theFile = controllerContext.HttpContext.Request.Files[bindingContext.ModelName];
      return ChooseFileOrNull( theFile );
    }
 
    // helper that returns the original file if there was content uploaded, null if empty
    internal static HttpPostedFileBase ChooseFileOrNull( HttpPostedFileBase rawFile )
    {
      // case 1: there was no <input type="file" ... /> element in the post
      if ( rawFile == null )
      {
        return null;
      }
 
      // case 2: there was an <input type="file" ... /> element in the post, but it was left blank
      if ( rawFile.ContentLength == 0 && String.IsNullOrEmpty( rawFile.FileName ) )
      {
        return null;
      }
 
      // case 3: the file was posted
      return rawFile;
    }
 
  }

其实现与猜测的是一样的,直接从Request中获取与ModelName相同的文件,通过前面的分析我们知道,ModelName就是参数名。最 后调用的ChooseFileOrNull方法的注释非常清楚,第一种情况是为了避免我们接受的参数在页面上对应的不是一个文件上传控件,那么返回 null,第二种情况是用户没有选择文件上传,同样返回null,最后一种情况则将HttpPostedFileBase对象直接返回了。

再来看看LogOnModel这个类型,则把我原本的设想全部推翻了。

  public class LogOnModel
  {
    [Required]
    [DisplayName( "用户名" )]
    public string UserName { get; set; }
 
    [Required]
    [DataType( DataType.Password )]
    [DisplayName( "密码" )]
    public string Password { get; set; }
 
    [DisplayName( "记住我?" )]
    public bool RememberMe { get; set; }
  }

没错,事实上LogOnModel就是一个普通的object,并没有任何特别的诸如LogOnModelBinder之类的东西。那么,这是怎么实现的?

回想Binder的获取逻辑,没错,最后一步是,如果找不到合适的Binder,就使用 ModelBinders.Binders.DefaultBinder。换言之不论是我们的LogOnModel model还是我们的string returnUrl,最终都是使用DefaultBinder来获取值的。

DefaultBinder是ModelBinderDictionary类型的一个属性:

    public IModelBinder DefaultBinder
    {
      get
      {
        if ( _defaultBinder == null )
        {
          _defaultBinder = new DefaultModelBinder();
        }
        return _defaultBinder;
      }
      set
      {
        _defaultBinder = value;
      }
    }

类似的代码已经见过太多次,这说明默认情况下,他就是DefaultModelBinder类型的一个实例。

那么来看看DefaultModelBinder的实现:

    public virtual object BindModel( ControllerContext controllerContext, ModelBindingContext bindingContext )
    {
      if ( bindingContext == null )
      {
        throw new ArgumentNullException( "bindingContext" );
      }
 
      bool performedFallback = false;
 
      if ( !String.IsNullOrEmpty( bindingContext.ModelName ) && !bindingContext.ValueProvider.ContainsPrefix( bindingContext.ModelName ) )
      {
        // We couldn't find any entry that began with the prefix. If this is the top-level element, fall back
        // to the empty prefix.
        if ( bindingContext.FallbackToEmptyPrefix )
        {
          bindingContext = new ModelBindingContext()
          {
            ModelMetadata = bindingContext.ModelMetadata,
            ModelState = bindingContext.ModelState,
            PropertyFilter = bindingContext.PropertyFilter,
            ValueProvider = bindingContext.ValueProvider
          };
          performedFallback = true;
        }
        else
        {
          return null;
        }
      }
 
      // Simple model = int, string, etc.; determined by calling TypeConverter.CanConvertFrom(typeof(string))
      // or by seeing if a value in the request exactly matches the name of the model we're binding.
      // Complex type = everything else.
      if ( !performedFallback )
      {
        ValueProviderResult vpResult = bindingContext.ValueProvider.GetValue( bindingContext.ModelName );
        if ( vpResult != null )
        {
          return BindSimpleModel( controllerContext, bindingContext, vpResult );
        }
      }
      if ( !bindingContext.ModelMetadata.IsComplexType )
      {
        return null;
      }
 
      return BindComplexModel( controllerContext, bindingContext );
    }

首先是一个入口检查,接下来先设置performedFallback为false,从字面上看是已经进行了回退。其实fallback到底具体是 什么工作到现在还不清楚。然后是一个判断,如果ModelName不等于空,并且ValueProvider不ContainsPrefix ModelName。

不妨来分析一下这个判断干什么用的,首要条件是ModelName不为空,也就是至少存在一个ModelName。在我们这一路走 来,ModelName几乎是不可能为空的,因为他默认就是参数的名称,那我们先假定这个条件是恒真的。继续看后面,ContainsPrefix从字面 上可以理解为,不包含前缀,参数是ModelName,也就是说,ValueProvider不包含这个ModelName这个前缀。

再来看下面的注释下,大意是,我们不能找到任何条目是以字首开头的,如果这是顶级元素,那么回退到空白的前缀。

然后是再一个if判断,BindingContext.FallbackToEmptyPrefix,看来这个属性是控制DefaultModelBinder是否允许回退到空白前缀的。这个属性的值是什么?翻出上一篇来看看就知道了:

        FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified

还记得上篇的话就会知道,BindingInfo.Prefix是从特性中获取的,那么这里可以简单的理解为,如果没有在参数上利用 BindAttribute设置Prefix的话,那么就是允许回退到空白前缀(原来是这个意思)。如果不允许就直接绑定失败了(return null),那么如果允许回退到空白前缀,那么再创建一个ModelBindingContext,并且将performedFallback设置为 true,继续下面的逻辑。这里重新创建的ModelBindingContext所有属性都是从原来的ModelBindingContext实例中获 取的,创建一个新的实例其主要作用是丢弃一些属性(或者说用默认值)。那么我们来比对一下丢弃了哪些属性:

原本创建ModelBindingContext的逻辑大体上是这样的:

      ModelBindingContext bindingContext = new ModelBindingContext()
      {
        ModelName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName,
        ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType( null, parameterDescriptor.ParameterType ),
        ModelState = controllerContext.Controller.ViewData.ModelState,
 
        FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
        PropertyFilter = GetPropertyFilter( parameterDescriptor ),
        ValueProvider = controllerContext.Controller.ValueProvider
 
      }; 

可以看出来ModelName和FallbackToEmptyPrefix被丢弃了,或者说被设置成默认值了。OK,这真是一段糟糕透顶的代码,事实上搞了这么久我们都无法猜透这里到底想干什么事情。也搞不清这两个属性是要被丢弃还是需要被设置为默认值。

来确定一下ModelName和 FallbackToEmptyPrefix到底是被设置成了怎样的默认值,其实我觉得事实上根本就不会有设置默认值的代码,他们俩很可能简单的一个是 null而另一个是false了,也就是字段被初始化的时候的值。无论怎样,来看看,首先是构造函数:

    public ModelBindingContext()
      : this( null )
    {
    }
 
    // copies certain values that won't change between parent and child objects,
    // e.g. ValueProvider, ModelState
    public ModelBindingContext( ModelBindingContext bindingContext )
    {
      if ( bindingContext != null )
      {
        ModelState = bindingContext.ModelState;
        ValueProvider = bindingContext.ValueProvider;
      }
    }

在构造函数里,显然没有为这两个属性设置任何默认值,那么来看看这两个属性:

    public bool FallbackToEmptyPrefix
    {
      get;
      set;
    } 

果然,FallbackToEmptyPrefix默认值就是false(bool字段的默认值)。

    public string ModelName
    {
      get
      {
        if ( _modelName == null )
        {
          _modelName = String.Empty;
        }
        return _modelName;
      }
      set
      {
        _modelName = value;
      }
    }

ModelName的默认值则是String.Empty。

来谈谈这段坑爹的代码,第一眼看到这个方法的实现的时候就感觉非常乱。这段代码就像就是程序员为了完成某个测试而拼凑出来的一团东西。也许对于开发 人员而言,这段代码是一目了然的,但对于直接看源代码的人来说,这可真是坑爹的代码。事实上,我们磕磕绊绊走了这么远,这段代码到底在干啥还是毫无头绪。

好吧我们不在这里纠结,因为无法预测IValueProvider的ContainsPrefix到底会产生什么结果,而现在贸然研究IValueProvider会把战线拉长。所以不妨将真假两种可能代入然后看看后面的逻辑会是怎样。

首先我们假定这个方法返回true,那么天下太平,第一个if里面的所有代码都不会被执行,performedFallback也会是false, 然后if( !performedFallback )成立,进入内部,这时,尝试调用ValueProvider的GetValue方法,如果有结果返回,那么返回BindSimpleModel方法的结 果。如果没有结果返回,那么看ModelMetadata.IsComplexType,字面意思上来看,是判断是不是复杂类型,如果不是,那么返回 null(表示绑定失败)。否则返回BindComplexModel方法的结果。

这个流程走下来尽管还有很多不明白的地方,但我们总算搞清了一个事实,那就是performedFallback只是决定是否尝试绑定简单模型的 (BindSimpleModel),如果进行了回退(performedFallback = true ),那么就不尝试进行简单模型的绑定(BindSimpleModel)。

再来考虑一下performedFallback取决于什么,先考虑一般情况,在一般情况下,ModelName会是参数名,所以不可能为空。其次 我们也不会设置参数上的BindAttribute,所以FallbackToEmptyPrefix也会是true,因为 BindingInfo.Prefix默认就是null(好吧,如果你去翻源代码,你会发现_prefix字段默认情况下根本没有被赋值。由于Model 这一块的源代码质量实在太令人无语,所以这里就不在文章里深究了,否则会让读者大人头昏脑胀的)。

变换一下我们可以看到一般情况下是这样的:

      if ( true && !bindingContext.ValueProvider.ContainsPrefix( bindingContext.ModelName ) )
      {
        // We couldn't find any entry that began with the prefix. If this is the top-level element, fall back
        // to the empty prefix.
        if ( true )
        {
          bindingContext = new ModelBindingContext()
          {
            ModelMetadata = bindingContext.ModelMetadata,
            ModelState = bindingContext.ModelState,
            PropertyFilter = bindingContext.PropertyFilter,
            ValueProvider = bindingContext.ValueProvider
          };
          performedFallback = true;
        }
        else
        {
          return null;
        }
      }

很明显,一般情况下performedFallback完全取决于ValueProvider的ContainsPrefix方法。也就是 performedFallback == !bindingContext.ValueProvider.ContainsPrefix( bindingContext.ModelName ),而在接下来的SimpleModelBinder中,IValueProvider也扮演了重要角色,如果我们精简掉一些东西并将 performedFallback代入,那看起来就会像是这样:

      if ( bindingContext.ValueProvider.ContainsPrefix( bindingContext.ModelName ) )
      {
        ValueProviderResult vpResult = bindingContext.ValueProvider.GetValue( bindingContext.ModelName );
        if ( vpResult != null )
        {
          return BindSimpleModel( controllerContext, bindingContext, vpResult );
        }
      }

这样,不难看出ValueProvider在BindSimpleModel流程中具有核心地位。那么,不碰它是不行的了,他是IValueProvider类型的,先来看看这个接口吧:

namespace System.Web.Mvc
{
  using System;
 
  public interface IValueProvider
  {
    bool ContainsPrefix( string prefix );
    ValueProviderResult GetValue( string key );
  }
}

源代码非常给力,这东西不单单是在BindSimpleModel流程中处于核心地位,事实上估摸着这东西就干这一件事情的。

[转载]二手火车票监控程序 抢票快手 V1.0发布

mikel阅读(837)

[转载]【开源】二手火车票监控程序 抢票快手 V1.0发布 – LanceZhang’s Tech Blog – 博客园.

背景

呃, 春运,不用多说了。。上海局的电话订票只支持8000个并发,基本上都抢不到。我等广大P民只能将眼光投放到赶集网、58同城等门户,关注里面的车票转让 信息,尽管不少是票贩子,但也没办法。前几天自己转让了一张火车票,结果信息发上去之后10分钟内收到了11个电话,自然后来的10个朋友都失望而归。无 疑,这里也成了抢票的战场。。园子里也有不少朋友都写了信息监控程序,如这个,本人加以借鉴,做了这个WinForm的程序。

简介

抢票快手是一个由纯.NET编写的WinForm控件,使用WebClient下载并解析赶集网、58同城、酷讯三家的火车票转让信息(这三家的信息基本上能涵盖所有的车票转让)。源代码完全开放,可随意修改,在CodePlex上开源,地址为:http://qiangpiaokuaishou.codeplex.com/

功能特点

0. 3家网站的基本能覆盖所有转让信息(酷讯本来就聚合了一些小网站的票源)

1. 信息已列表形式展示,提取出车次和张数,可随意排序

2. 双击列表中的信息可在右边Tab中打开该信息的网页

3. 勾选监控选项可监控票源,默认1分钟间隔,可在源码中改

4. 监控到票源后会在右下角托盘处弹出新票源信息

源码下载地址:http://qiangpiaokuaishou.codeplex.com/ (包括Demo程序)

由于本人不是专业的WinForm开发经验浅薄,且开发比较仓促(2小时)如发现bug和功能缺陷,还望大家不吝赐教!

[转载]推荐25个非常优秀的网页表单设计案例

mikel阅读(1516)

[转载]推荐25个非常优秀的网页表单设计案例 – 梦想天空 – 博客园.

表单是网站非常重要的一部分。每个网站都有带自己风格的表单,可能是简洁的,也可能是多彩的、很有创意的。当你设计网站的时候,表 单是你应该重点关注的元素,像登录、注册、留言等等。我们需要牢记的是应尽量吸引用户的注意力,使他们想要往表单里填写信息。这里收集了一些很好的例子供 大家参考。

Christian Sparrow

forms22

Ed Peixoto

forms21

olga designs

forms23

Krista Ganelon

forms24

Forever Heavy

forms25

Cornerd

forms26
forms27

Justdot

forms28
forms29

sikbox

forms30

Justalab

forms31
forms32

Buffalo

forms33
forms34

Sprocket House

forms35

Gardener & Marks

forms36

Awesome

forms06

forms07

Foundation Six

forms09
forms10

Wanken

forms13
forms14

Visual Republic

forms15
forms16

Andrew McClintock

forms17

bio-bak

forms19

Vincent Mazza

forms20

Gams

forms08

All Creative

forms01

forms02

Creditable

forms03

Gowalla

forms04

forms05

Pentagon

forms11
forms12

Chris Woods

forms18

(编译来源:梦想天空 原文来自:25 Excellent Examples of Forms in Web Design

[转载]如何使用Orchard搭建敏捷个人的网站(2)

mikel阅读(1037)

[转载]如何使用Orchard搭建敏捷个人的网站(2) – 周金根 – 博客园.

如何使用Orchard搭建敏捷个人的网站(1)中讲解了如何使用Orchard搭建一个简易的敏捷个人内容网站,第一篇主要讲解了如何下载安装主题、增加blog、制作菜单等,今天我们继续讲解一下如何增加搜索功能、增加自定义的内容类型以及显示内容列表。

增加搜索功能

首先我们需要安装Lucene模块

然后打开功能:Search, Indexing 和Lucene

Search功能打开后,在Settings中可以设置索引的字段,缺省只有body和title作为索引字段

功能都打开后,在后台控制面板【Configuration】节下会增加一个【Search Index】菜单,索引会作为一个后台任务在执行,缺省每分钟一次吧,你也可以选择手动更新

后台索引机制建立好后,我们需要在前台增加一个查询Widget,我们在TheHomepage层的Fetured区域加入Search Form的Widget

设置好后打开主页,界面上增加了一个查询控件,输入”时间管理”后可以查到网站有关内容

增加【书籍推荐】

Orchard内部有有一些内容类型(例如blog等),我们可以自定义一些自己的,为了学习这个功能,我现在要增加一个【书籍推荐】页面,把之前【我的电子书】一个页面改为一个书籍列表来显示,以下就是整个过程。

  1. 增加BookReview类型

  1. 增加一个列表
  2. 添加【推荐书籍】并指定添加到推荐书籍列表中
  3. 更改显示样式
    默认显示是按列表显示,上面书籍的购买地址、封面照片显示的都是字符串,这显得不够友好,我们可以更改显示样式来达到更好的显示效果。
    1. 增加一个文件
    2. 编辑文件内容为
  4. 刷新后界面显示为

参考:http://orchardproject.net/docs/Authoring Web Sites