[转载]Flex性能优化常用手法总结

mikel阅读(1573)

转载Flex性能优化常用手法总结 – 爱笑的眼睛 – 博客园.

随着Flex越来越多的被人们所熟知,越来越多的互联网也开始了RIA应用。众所周知,目前国内的宽带应用并不是像很多发达国家发达,个人应用带宽基本上都是2M以下的,怎么样能够使你的Flex应用能够流畅的运行在客户端的问题,成为了制约每个Flex应用开发程序员的大难题。
在这里,我收集整理了一下网络上关于这方面经验,欢迎大家补充。

基本原则:
1. 从外部加载媒体(Media)
      Heider提到了一个常用的Flex最佳实践——限制嵌入到应用/SWF文件中的媒体的数量,如图像、影片及mp3等资源都可以从外部的SWF文件加载。 
Flex框架可以直接将图片、mp3及字体等资源编译到SWF中。当你想让最终用户获得全部资源时,这种方式确实能派上用场,但是这会导致你的应用长时间停留在“Loading”阶段。 

2. 在嵌入式字体中限制字符集
Heider建议在嵌入式字体中限制字符集以降低SWF文件的总下载时间: 
当你在Flex中嵌入一种字体时,你就会获得该字体的全部字符的支持。尽管这可能是你想要的,但你确信你需要全部字符么?例如,在一个只面向英文的应用中,你确信你真的想花时间下载中文字符数据么?
3. 缓存框架 
        Heider回顾了Flex 3 support for runtime-shared-libraries (RSL)这篇文章:

从Flex 3开始,你可以将Adobe签 名的框架——RSLs缓存到Flash Player的cache中。这有两个好处。首先,缓存在Flash Player cache中的签名的框架RSLs可由所有配置好的Flex应用共享。换句话说,如果某人的应用已经下载了500k的签名的框架RSL,并且该RSL仍旧 在Flash Player cache中,那么你的应用就可以使用缓存下来的RSL。其次,即使某人清空了其浏览器缓存,对Flash Player cache也没有任何影响。
4. 考虑模块化 
Heider谈到了将Flex应用划分成模块的好处:减少字体加载时间的另一种方式就是将你的Flex应用划分成模块。使用模块的一个好处在于当加载和卸载模块时你能完全操控它。
之所以要划分成模块的最后一个原因是他们更快,而且我能即时加载它们。换句话说,在启动时唯一需要加载的模块就是 Step1.swf模块。因此,在使用模块的情况下,最终用户节省了启动时间,但是当他从一个模块切换到另一个模块时却需要花更多时间,因为每个模块都需 要以JIT形式加载。在我的应用中,只有当用户首次在steps 1-5之间切换时需要花更多时间。 
5. 推迟实例化 
      Heider围绕着Flex组件的“creationPolicy”属性及何时实例化应用的不同部分给出了很多建议。
      如果你想减少从数据下载到用户真正可以使用的总时间,当务之急就是推迟实例化。这项技术背后的理念就是直到应用真正使用的时候才在内存中创建对象。
      尽管推迟实例化技术会在应用的整个使用过程中导致少许——通常不那么明显——的延迟,但与长时间的启动延迟相比,它还是可接受的。推迟实例化的另一个好处在于内存使用的优化。

以上原则来自Jun Heider在O’Reilly的InsideRIA站点上发表了一篇精彩的文章,该文章就如何加快Flex应用的启动速度提 出了很多建议,以帮助用户减少看见讨厌的“Loading”对话框的出现时间。他深入探讨了问题的不同方面,并对每种技术的优势和劣势进行了评判。 Heider还谈到了一个“实验性”的条款——“使用流”,这是他在讨论Dirk Eismann的帖子(Building monolithic Flex SWFs that still startup quickly.”)时谈及的。Eismann提出一项技术以利用Flash Player中的多个frames以在部分应用中达到流的目的。查看所有的帖子以更多地了解该技术及关于加快Flex启动速度的建议。

内存释放优化原则
1. 被删除对象在外部的所有引用一定要被删除干净才能被系统当成垃圾回收处理掉;
2. 父对象内部的子对象被外部其他对象引用了,会导致此子对象不会被删除,子对象不会被删除又会导致了父对象不会被删除;
3. 如果一个对象中引用了外部对象,当自己被删除或者不需要使用此引用对象时,一定要记得把此对象的引用设置为null;
4. 本对象删除不了的原因不一定是自己被引用了,也有可能是自己的孩子被外部引用了,孩子删不掉导致父亲也删不掉;
5. 除了引用需要删除外,系统组件或者全局工具、管理类如果提供了卸载方法的就一定要调用删除内部对象,否则有可能会造成内存泄露和性能损失;
6. 父对象立刻被删除了不代表子对象就会被删除或立刻被删除,可能会在后期被系统自动删除或第二次移除操作时被删除;
7. 如果父对象remove了子对象后没有清除对子对象的引用,子对象一样是不能被删除的,父对象也不能被删除;
8. 注册的事件如果没有被移除不影响自定义的强行回收机制,但有可能会影响正常的回收机制,所以最好是做到注册的事件监听器都要记得移除干净。
9. 父对象被删除了不代表其余子对象都删除了,找到一种状态的泄露代码不等于其他状态就没有泄露了,要各模块各状态逐个进行测试分析,直到测试任何状态下都能删除整个对象为止。
内存泄露举例:
1. 引用泄露:对子对象的引用,外部对本对象或子对象的引用都需要置null;
2. 系统类泄露:使用了系统类而忘记做删除操作了,如BindingUtils.bindSetter(),ChangeWatcher.watch()函数 时候完毕后需要调用ChangeWatcher.unwatch()函数来清除引用 ,否则使用此函数的对象将不会被删除;
类似的还有MUSIC,VIDEO,IMAGE,TIMER,EVENT,BINDING等。
3. 效果泄露:当对组件应用效果Effect的时候,当本对象本删除时需要把本对象和子对象上的Effect动画停止掉,然后把Effect的target对象置null; 如果不停止掉动画直接把 Effect置null将不能正常移除对象。
4. SWF泄露:要完全删除一个SWF要调用它的unload()方法并且把对象置null;
5. 图片泄露:当Image对象使用完毕后要把source置null;(为测试);
6. 声音、视频泄露: 当不需要一个音乐或视频是需要停止音乐,删除对象,引用置null;
内存泄露解决方法:
1. 在组件的REMOVED_FROM_STAGE事件回掉中做垃圾处理操作(移除所有对外引用(不管是VO还是组件的都需要删除),删除监听器,调用系统类的清除方法)
先remove再置null, 确保被remove或者removeAll后的对象在外部的引用全部释放干净;
2. 利用Flex的性能优化工具Profile来对项目进程进行监控,可知道历史创建过哪些对象,目前有哪些对象没有被删除,创建的数量,占用的内存比例和用量,创建过程等信息;
总结:关键还是要做好清除工作,自己设置的引用自己要记得删除,自己用过的系统类要记得做好回收处理工作。 以上问题解决的好的话不需要自定义强制回收器也有可能被系统正常的自动回收掉。

[转载]两种库存设计思路

mikel阅读(1387)

关于库存表的设计,有两种思路,不知那种更严格,应该用哪个,请大家看看:
关于库存表的设计,有两种思路,不知那种更严格,应该用哪个,请大家看看:
1.建立一个库存表,这个库存表不是实时的,而是每个月结帐时生成的月末的库存,这个表有个会计期字段,每月一个库存.查询实时库存时,用这个库存表做为期初值,然后加上本月增加数量,再减去本月减少的数量.就是当前的库存数量.
这是论坛上很多人建议的做法,可是这种方法有几个问题如何办呢?
a.每种材料的成本如何得到到,我用的是移动加权法.数量好办,每次根据上月末的库存算一下就行了.可是成本如何得到呢?总不能也用移动加权的公式一个材料一个材料的算吧.我的库中有几千种材料呀.

2.建立一个实时的库存表,反映当前的最新状态.同时建一个出入库明细帐.每次出库和入库时记录当时的结存数,和成本.同时将这个结存数和成本写入到实时库存表中去,这样查询库存和成本时比较方便.
这种方法的主要特点是,每次出入库时都将当时的库存数和成本记到明细帐中去,同时也写入实时库存表中去.这样成本也很容易得到.

[转载]日志式库存管理的设计思路解释

mikel阅读(977)

.

日志式的管理是一种流水帐式的登记方式,其核心思路是对所有操作进行记录,特征是只登记不修改与删除。

 

一. 日志结构

 

图一

 

单据名称:如:入库单,出库单,调整单等

 

单据编号:某一特定单据的编号

 

库存状态:记录各种单据对库存的影响方式为加或减

 

操作类型:记录当前操作是审核还是撤审。如同样是减库存,也要分是撤审的减库存还是退货的减库存或者调拨仓库的减库存

 

登记时间:是记录产生加减库存的日期。它在日库存统计起重要作用

 

物料编码:库存管理中会对每个材料进行唯一编码,以便库存进行管理

 

物料名称:如 已有物料编码,其名称只是对编码的详细中文注释。如工厂对物料编码不能不能理解,信息化基础很差,那么中文名称将是工厂操作人员通俗的理解方式,系统在此 做预留也就是可以满足此类用户的需求,库存在统计时同样可以按物料名称来统计。物料名称因此可以广义的理解为用户可接受的扩展属性。如:幅宽,规模,颜 色,尺码等。只要是用户可以记忆的信息,都可以归入名称范畴。

 

发生数量:是一个正数字。本次数量通过发生数量*库存状态来产生。

 

创建人和创建时间: 通常是记录本单的审核人和审核时间。它与登记时间是有区分的,由于库存统计时对时间的界定一般有两种方式,一种是按审核时间来进行统计库存,另一种是按单 据发生时间来统计,两种方式主要区别是发生时间则用户可自行调整,审核时间要求人员操作及时准确。所以通常用户都会采用第一种方式。

 

二. 日志式结构的优点与缺点

 

1. 优点:

 

(1) 所有影响库存的单据全部统一在一张表里进行管理,查询方便。还要方便进行导出备份。

 

(2) 在做库存查询与统计时的速度优于散在各处相加再进行统计的方式。

 

(3) 如果日志完整,可根据日志反向生成各种单据。配合第一条优点,可快速进行单据还原。

 

2. 缺点:

 

(1) 日志信息目前只适合记录库存增减,不适合记录业务流向。

 

(2) 日志量庞大,使用一段时间后,做通库查询速度与性能降低。

 

(3) 日志信息普通操作人员不易看懂,必须是对整个系统较为熟悉的人员才能读懂。

 

3. 解决方案:

 

对于业务流程的数据展示通过前面界面做针对性强的开发,而后台库存运算则采用库存日志的方式实现。

 

针对日志表做数据库存储文件的优化,根据时间段多做结果表,生成日结,月结,加快查询速度,将经常查询区域的数据进行分区域管理,提升整体查询性能。原则上以十万条数据作为一个查询速度减慢的监界点。十万条以内的查询速度还是可以接受的。

 

最终操作用户不需要看懂此日志表,他们看到的都是在日志表基础上经过加工的、用户可能看得懂的界面信息。如果一个有经验的程序员,在看过本文的介绍以后就可以马上理解日志表的作用。

 

三. 其它

 

1. 一条SQL产生期初、期末库存表。(从技术上可以达到求期初,但从实际应用考虑不推荐此法,此仅应用于数据稽核时)

 

select tb.pm,tb.frq,tb.gg,

 

       decode(row_number() over(partition by tb.pm order by tb.PM,tb.RQ,tb.gg),1,0,

 

       LAG(l_bqlj, 1, 0) OVER (ORDER BY tb.PM,TB.RQ,tb.gg)) AS prev_BQJC,

 

       tb.bqrk,tb.bqck,tb.l_bqlj

 

 from

 

(select ta.pm,ta.gg,ta.rq,ta.bqrk,ta.bqck,

 

       SUM(ta.bqlj) OVER (PARTITION BY ta.pm,ta.gg

 

                           ORDER BY ta.PM,ta.gg,ta.rq

 

                           RANGE UNBOUNDED PRECEDING) l_bqlj

 

 from

 

(SELECT LSZ.物料名称 pm, LZS.规格 GG,to_char(LSZ.登记时间,’yyyy-mm’) rq,

 

       sum(decode(LSZ.库存状态,1, LSZ.数量,0)) bqrk,

 

       sum(decode(LSZ.库存状态,-1,LSZ.数量,0)) bqck,

 

       sum(decode(LSZ.库存状态,1, LSZ.数量,0)) –

 

       sum(decode(LSZ.库存状态,-1,LSZ.数量,0)) bqlj

 

  FROM (select 物料名称,登记时间,规格,库存状态,数量

 

        from 日志结构 b

 

       where to_char(登记时间,’yyyymm’) >= ‘200810’

 

       union

 

       SELECT a.物料名称,to_date(‘200810′,’yyyymm’) 登记时间,a.规格,0 库存状态,0 数量

 

       FROM 日志结构 a

 

       GROUP BY a.物料名称,a.登记时间,a.规格

 

 group by to_char(LSZ.登记时间,’yyyy-mm’),LSZ.物料名称

 

 order by to_char(LSZ.登记时间,’yyyy-mm’),LSZ.物料名称) TA) tB

 

分析:

 

1.  竖值变横使用DECODE把满足条件的记录数量保留下来,不满足条件的过滤为0

 

2.  SUM进行累计运算。

 

3.  LAG错位进行结转运算。

4.  UNION做结转表

[转载]encode,decode,urlencode和urldecode辨析

mikel阅读(1046)

[转载]encode, decode, urlencode和urldecode辨析 – cuberub – 博客园.

  本文对encode、decode、urlencode和urldecode几个概念的含义进行对比分析。


encoding和decoding

在计算机科学中,encoding指把一个字符序列,按照某种特定的规则,转换为一种特定的格式。decoding的含义反之。

在文本处理的编码转换中,encoding指把字符流按照某种编码转换为字节流,decoding指把字节流根据其编码还原为字符流。

一些编程语言中,内置了相应的函数调用。

如在python中,内部的字符串编码是unicode。当读取一个UTF-8编码的文件内容后,实际保存的是字节流。如果要转换为字符流,需要调用decoding函数进行解码。如下例子所示:

file = open(file_path)
line = file.readline()
line = line.decode("UTF-8")

这段代码执行后,line的内容是 以unicode格式保存的字符串。

再看一下Perl。Perl 5.6之后内部字符串以UTF-8方式存在。上述的逻辑,用Perl实现是这样的:

open INPUT $file_path
my $line = <INPUT>
$line = decode("utf8", $line)

这里需要使用use Encode引入Encode包。如果在读取文件时已经指定了编码,那么就不需要再单独做decode解码了。如下所示,

 open INPUT ”<:utf8" $file_path
 my $line = <INPUT>

对于非IO读入,而在代码中硬编码的字符串,可以理解为字节流,其编码与文件编码一致。

在进行字符串操作前,建议统一采用decode解码,这样可以保证内部处理的一致性和正确性。

在python或者Perl中,如果要把字符串输出,最好不要直接输出,而是先编码为字节流再输出。否则,有可能报错。

如在python中输出某一行到屏幕,

 line = line.encode("UTF8")
 print line

urlencode和urldecode

urlencode对URL进行编码。URL是一个字符串,urlencode把它理解为一个字节流,直接对每个字节进行转换,转换规则是把每个字节转换 为%HH的形式,HH等于字节的ASCII码值。如果URL中包含了中文参数,根据中文参数编码的区别,会决定urlecode的结果有所不同。

urldecode是把经过urlencode编码后得到的字符串还原为原始状态,根据urlencode的规则可知,urldecode的输入字符都在ASCII编码的范围内。

在python中,提供了urlencode包。

下面是一段实际的代码:

  from urllib import urlencode
  url = "http://www.google.com/search?" + "%s"
  key = "字符串";
  mass = {}
  mass["key"] = key
  params = urlencode(mass)
  encode_url = url % params

这一段程序源码的编码是UTF-8,encode_url即为encode之后的url:

http://www.google.com/search?key=%E5%AD%97%E7%AC%A6%E4%B8%B2

用Perl语言的话,通过调用unpack函数实现。

 my $url = "http://www.google.com/search?key=";
 my $query = "字符串";
 $query =~ s/(\W)/sprintf("%%%02x", unpack("C", $1))/eg;
 my $encode_url = $url . $query;

Author: shiqi.cui <cuberub@gmail.com>

Date: 2009-05-17

[转载]细说SQL Server中的加密

mikel阅读(990)

[转载]细说SQL Server中的加密 – CareySon – 博客园.

简介

加密是指通过使用密钥或密码对数据进行模糊处理的过程。在SQL Server中,加密并不能替代其他的安全设置,比如防止未被授权的人访问数据库或是数据库实例所在的Windows系统,甚至是数据库所在的机房,而是 作为当数据库被破解或是备份被窃取后的最后一道防线。通过加密,使得未被授权的人在没有密钥或密码的情况下所窃取的数据变得毫无意义。这种做法不仅仅是为 了你的数据安全,有时甚至是法律所要求的(像国内某知名IT网站泄漏密码这种事在中国可以道歉后不负任何责任了事,在米国妥妥的要破产清算)。

 

SQL Server中的加密简介

在SQL Server2000和以前的版本,是不支持加密的。所有的加密操作都需要在程序中完成。这导致一个问题,数据库中加密的数据仅仅是对某一特定程序有意义,而另外的程序如果没有对应的解密算法,则数据变得毫无意义。

到了SQL Server2005,引入了列级加密。使得加密可以对特定列执行,这个过程涉及4对加密和解密的内置函数

SQL Server 2008时代,则引入的了透明数据加密(TDE),所谓的透明数据加密,就是加密在数据库中进行,但从程序的角度来看就好像没有加密一样,和列级加密不同 的是,TDE加密的级别是整个数据库。使用TDE加密的数据库文件或备份在另一个没有证书的实例上是不能附加或恢复的。

 

加密的一些基础知识

加密是指通过使用密钥或密码对数据进行模糊处理的过程。加密解密最简单的过程如图1所示。

1

图1.一个简单的加密解密过程

 

通常来说,加密可以分为两大类,对称(Symmetric)加密和非对称(Asymmetric)加密。

对称加密是那些加密和解密使用同一个密钥的加密算法,在图1中就是加密密钥=解密密钥。对称加密通常来说会比较羸弱,因为使用数据时不仅仅需要传输数据本身,还是要通过某种方式传输密钥,这很有可能使得密钥在传输的过程中被窃取。

非对称加密是那些加密和解密使用不同密钥的加密算法,在图1中就是加密密钥!=解密密钥。用于加密的密钥称之为公钥,用于解密的密钥称之为私钥。因此安全性相比对称加密来说会大大提高。当然有一长必有一短,非对称加密的方式通常算法会相比对称密钥来说复杂许多,因此会带来性能上的损失。

因此,一种折中的办法是使用对称密钥来加密数据,而使用非对称密钥来加密对称密钥。这样既可以利用对称密钥的高性能,还可以利用非对称密钥的可靠性。

 

加密算法的选择

现在流行的很多加密算法都是工业级的,比如对称加密的算法有:DES、3DES、IDEA、FEAL、BLOWFISH.而非对称加密的算法比如经典的RSA。因为这些算法已经公布了比较长的时间,并且经受了很多人的考验,所以通常来说都是比较安全的。

SQL Server提供了比如:DES、Triple DES、TRIPLE_DES_3KEY、RC2、RC4、128 位 RC4、DESX、128 位 AES、192 位 AES 和 256 位 AES这些加密算法,没有某种算法能适应所有要求,每种算法都有长处和短处,关于每种加密算法的细节,请Bing…

但选择算法有一些共通之处:

  • 强加密通常会比较弱的加密占用更多的 CPU 资源。
  • 长密钥通常会比短密钥生成更强的加密。
  • 非对称加密比使用相同密钥长度的对称加密更强,但速度相对较慢。
  • 使用长密钥的块密码比流密码更强。
  • 复杂的长密码比短密码更强。
  • 如果您正在加密大量数据,应使用对称密钥来加密数据,并使用非对称密钥来加密该对称密钥。
  • 不能压缩已加密的数据,但可以加密已压缩的数据。如果使用压缩,应在加密前压缩数据。

 

SQL Server中的加密层次结构

在SQL Server中,加密是分层级的.根层级的加密保护其子层级的加密。概念如图2所示。

2

图2.SQL Server加密的层级

 

由图2可以看出,加密是分层级的。每一个数据库实例都拥有一个服务主密钥(Service Master Key),对应图2中的橙色部分。这个密钥是整个实例的根密钥,在实例安装的时候自动生成,其本身由Windows提供的数据保护API进行保护 (Data Pertection API),服务主密钥除了为其子节点提供加密服务之外,还用于加密一些实例级别的信息,比如实例的登录名密码或者链接服务器的信息。

在服务主密钥之下的是数据库主密钥(Database Master Key),也就是图2中土黄色的部分,这个密钥由服务主密钥进行加密。这是一个数据库级别的密钥。可以用于为创建数据库级别的证书或非对称密钥提供加密。 每一个数据库只能有一个数据库主密钥,通过T-SQL语句创建,如代码1所示。

CREATE MASTER KEY ENCRYPTION BY PASSWORD ='Pa$$word'

代码1.创建数据库主密钥

 

数据库主密钥由代码1所示的密码和服务主密钥共同保护。当数据库主密钥创建成功后,我们就可以使用这个密钥创建对称密钥,非对称密钥和证书了。如代码2所示。

--创建证书
CREATE CERTIFICATE CertTest 
with SUBJECT = 'Test Certificate'
GO
--创建非对称密钥
CREATE ASYMMETRIC KEY TestAsymmetric
    WITH ALGORITHM = RSA_2048 
    ENCRYPTION BY PASSWORD = 'pa$$word'; 
GO
--创建对称密钥
CREATE SYMMETRIC KEY TestSymmetric
    WITH ALGORITHM = AES_256
    ENCRYPTION BY PASSWORD = 'pa$$word';
GO

代码2.创建证书,非对称密钥和对称密钥

 

在代码2中我们看出,并没有显式指定使用数据库主密钥加密证书,对称密钥和非对称密钥。这是因为每个数据库只能有一个数据库主密钥,所以无需指定。创建成功后我们可以在SSMS中查看到刚刚创建的证书,非对称密钥和对称密钥,如图3所示。

3

图3.查看刚刚创建成功的证书,非对称密钥和对称密钥

 

由这个加密层级不难推断,如果数据库主密钥被破解,则由其所创建的证书,对称密钥,非对称密钥都有可能被破解。

由图2的层级我们还可以看出,对称密钥不仅仅可以通过密码创建,还可以通过其它对称密钥,非对称密钥和证书创建。如代码3所示。

--由证书加密对称密钥
CREATE SYMMETRIC KEY SymmetricByCert
    WITH ALGORITHM = AES_256
    ENCRYPTION BY CERTIFICATE CertTest;
GO
--由对称密钥加密对称密钥
OPEN SYMMETRIC KEY TestSymmetric
    DECRYPTION BY PASSWORD='pa$$word'

CREATE SYMMETRIC KEY SymmetricBySy
    WITH ALGORITHM = AES_256
    ENCRYPTION BY SYMMETRIC KEY TestSymmetric;
GO
--由非对称密钥加密对称密钥

CREATE SYMMETRIC KEY SymmetricByAsy
    WITH ALGORITHM = AES_256
    ENCRYPTION BY ASYMMETRIC KEY TestASymmetric;
GO

代码3.由几种不同的加密方式创建对称密钥

 

SQL Server中的数据列加密(Column-level Encryption)

SQL Server在2005引入了列加密的功能。使得可以利用证书,对称密钥和非对称密钥对特定的列进行加密。在具体的实现上,根据加密解密的方式不同,内置了4对函数用于加密解密:

  • EncryptByCert()  和DecryptByCert()—利用证书对数据进行加密和解密
  • EncryptByAsymKey()  and DecryptByAsymKey()—利用非对称密钥对数据进行加密和解密
    EncryptByKey()  and DecryptByKey()—利用对称密钥对数据进行加密和解密
  • EncryptByPassphrase()  and DecryptByPassphrase()—利用密码字段产生对称密钥对数据进行加密和解密

 

因此,加密数据列使用起来相对比较繁琐,需要程序在代码中显式的调用SQL Server内置的加密和解密函数,这需要额外的工作量,并且,加密或解密的列首先需要转换成Varbinary类型。

下面我们来看一个例子:

在AdventureWorks示例数据库中,我们找到Sales.CreditCard表,发现信用卡号是明文显示的(怎么AdventureWorks也像泄漏密码的某IT网站这么没节操)。因此希望对这一列进行加密。

4

图5.和国内某知名IT网站一样没节操的明文保存重要信息

 

首先我们需要将CardNumber列转为Varbinary类型。这里通过Select Into新建个表,如代码4所示。

SELECT CreditCardID, 
CardType,
CardNumber_encrypt = CONVERT(varbinary(500), CardNumber), 
ExpMonth, 
ExpYear, 
ModifiedDate
INTO Sales.CreditCard_Encrypt 
FROM Sales.CreditCard 
WHERE 1<>1

代码4.通过Select Into创建新表

 

此时我们利用之前创建的由证书加密的对称密钥来进行列加密,如代码5所示。

--打开之前创建的由证书加密的对称密钥
OPEN SYMMETRIC KEY SymmetricByCert
DECRYPTION BY CERTIFICATE CertTest
--利用这个密钥加密数据并插入新建的表
insert Sales.CreditCard_encrypt (
CardType,
CardNumber_encrypt, 
ExpMonth, 
ExpYear, 
ModifiedDate
) 
select top 10
CardType,
CardNumber_encrypt = EncryptByKey(KEY_GUID('SymmetricByCert'), CardNumber),
ExpMonth,
ExpYear, 
ModifiedDate
from Sales.CreditCard

代码5.利用证书加密过的对称密钥加密数据

 

此时加密列无法直接进行查看,如图6所示:

6

图6.无法直接查看加密的列

 

此时可以通过对应的解密函数查看数据,如代码6所示。

OPEN SYMMETRIC KEY SymmetricByCert
DECRYPTION BY CERTIFICATE CertTest

select CardType,
CardNumber = convert(nvarchar(25), DecryptByKey(CardNumber_encrypt)), 
ExpMonth, 
ExpYear, 
ModifiedDate
from Sales.CreditCard_encrypt

图6.由对应的解密函数查看加密的数据

 

所得到的结果如图7所示。

7

图7.解密后结果可以正确显示

 

利用非对称密钥和证书进行加密解密只是函数不同,这里就不测试了。

 

透明数据加密(Transparent Data Encryption)

在SQL Server 2008中引入了透明数据加密(以下简称TDE),之所以叫透明数据加密,是因为这种加密在使用数据库的程序或用户看来,就好像没有加密一样。TDE加密 是数据库级别的。数据的加密和解密是以页为单位,由数据引擎执行的。在写入时进行加密,在读出时进行解密。客户端程序完全不用做任何操作。

TDE的主要作用是防止数据库备份或数据文件被偷了以后,偷数据库备份或文件的人在没有数据加密密钥的情况下是无法恢复或附加数据库的。

TDE使用数据加密密钥(DEK)进行加密。DEK是存在Master数据库中由服务主密钥保护,由的保护层级如图8所示。

3

图8.TDE的加密层次

 

开启TDE的数据库的日志和备份都会被自动加密。

因为TDE使得数据库在写入时加密,在读出时解密,因此需要额外的CPU资源,根据微软的说法,需要额外3%-5%的CPU资源。

下面我们来看如何开启TDE

开启TDE非常简单,只需创建数据加密密钥(DEK)后,将加密选项开启就行,如代码7所示。

--基于我们之前创建的证书CertTest,创建DEK
--CertTest需要在Master数据库中
USE AdventureWorks
GO 
CREATE DATABASE ENCRYPTION KEY 
WITH ALGORITHM = AES_256 
ENCRYPTION BY SERVER CERTIFICATE CertTest
GO
--开启TDE
ALTER DATABASE AdventureWorks
SET ENCRYPTION ON

代码7.创建DEK后,开启TDE

 

这里值得注意的是,DEK是存在所开启TDE的数据库中的。当然,这个操作我们也可以通过在SSMS中右键点击需要开始TDE的数据库,选择任务–管理数据库加密来进行。如图9所示。

8

图9.在SSMS中开启TDE

 

开启TDE后,我们可以通过图10的语句查看TDE的状态。

9

图10.查看数据库加密状态

 

 

总结

本文介绍了加密的基本概念,SQL Server中加密的层级,以及SQL Server中提供的两种不同的加密方式。SQL Server的TDE是一个非常强大的功能,在用户程序中不做任何改变就能达到数据库层面的安全。在使用SQL Server提供的加密技术之前,一定要先对加密的各个功能概念有一个系统的了解,否则很有可能造成的后果是打不开数据库。准备在后续文章中再写关于证 书,密钥的备份和恢复….

[转载]解決一个頁面有多个FCKeditor时无法使用FCKeditorAPI.GetInstance的问题(推荐)

mikel阅读(1001)

.

FCKeditor跟使用的TinyMCE WordPress的一样,都是开源的WYSIWYG编辑器(所见即所得编辑器) ,一直以来比较习惯用FCKeditor这套编辑器来帮网页加入可以编辑的HTML的功能,但今天突然发现如果一个页面有两个FCKeditor且在 Firefox的运行时,若要使用FCKeditorAPI.GetInstance ( )来取得编辑器的实例,只有第一个可以正确取得,第二个将会是未定义的,第三个以上没试但应该也难逃厄运了。

运行环境:

  • Firefox 3.0.1
  • FCKeditor 2.6.3

发生错误可能原因:

  1. fckeditorcode_gecko.js 有Bug
  2. Firefox有Bug或与FCKeditor有小部份冲突

解決概念:

如果是第一个原因,我实在没兴趣把压缩过的 fckeditorcode_gecko.js解回来检查(压缩过都还有253K啊! ) ,更别说有缺陷的Firefox能帮上什么忙了,不过幸好在FCKeditor JavaScript的空气污染指数有提到,可以建立一个名为FCKeditor_OnComplete的回调函数,当FCKeditor载入完毕后将会 呼叫这个函数并传入一个参数,这个参数就是目前装载完毕的FCKeditor例如,只要把这个参数记录下来,就可以直接使用例如,不需要用 FCKeditorAPI.GetInstance ( )来取得,这样一来便解决问题了。

 

解決方式:

在FCKeditor载入之前加入以下JavaScript

  1. //修正FckEditor错误
  2. var __fckEditorInstance_ = new Array();//用来储存FCKeditor Instance的阵列
  3. function FCKeditor_OnComplete( editorInstance )
  4. {
  5. __fckEditorInstance_[editorInstance.Name] = editorInstance;
  6. }
  7. function getFckInst(idname)
  8. {
  9. return __fckEditorInstance_[idname];
  10. }

之后就可以利用getFckInst()來代替FCKeditorAPI.GetInstance()取得FCKeditor Instance

[转载]Asp.net 页面基本对象

mikel阅读(1155)

[转载]Asp.net 页面基本对象 – 强子哥 – 博客园.

一、Response对象

 

1、输出信息到客户端。

 

2、主要有Write()和Redirect()两个方法。Write()方法:输出信息到浏览器。Redirect()方法:地址转向,将浏览器引导到新的WEB页或网站
3、作用:使用Write()方法,动态地向浏览器输出信息。例:Response.Wrtie(“这是我的第一个网页”);Response.Write(“现在的时间是:”+DateTime.Now.ToString());

 

注:与C#语言中的Console.Write()相似。输出回车:Response.Write(“<br>”);

 

二、Request对象

 

1、作用:从客户端得到数据信息。常用的方法有:Form[]方法和QueryString[]方法。

 

2、Form[]方法:用来获取客户端以post方式提交的数据。格式:Request.Form[“数据名称”];
3、QueryString[]方法:用来获取客户端以get方式提交的数据。格式:Request.QueryString [“数据名称”];

 

4、例:有网址:parm.aspx?tid=3452   则可用Request.QueryString[“tid”]来获取3452这个值

 

Request常用属性

 

1、Request.UserAgent:用来获取客户端浏览器版本。

 

2、Request.UserHostAddress:获取客户端ip地址。

 

3、Request.ApplicaionPath:获取当前网站的相对路径。

 

4、Request.PhysicalApplicationPath:获取当前网站的绝对路径。

 

三、Server对象

 

1、作用:访问服务器信息。

 

2、Server.MachineName:获取服务器名称
3、Server.HtmlEncode():将字符串进行html编码。
4、Server.Transfer();终止当前页的执行,开始执行新页。与Response.Redirect()相似。
5、Server.MapPath();返回物理路径。

 

返回网站及文件夹路径

 

Server.MapPath(“~”);
Server.MapPath(“.”);
Server.MapPath(“ ”);
Server.MapPath(“~/image/”);

 

四、Application对象

 

1、作用::用来存储一个值,供所有用户共享。
2、Application存储的值,一直不会消失,直到网站关闭。
3、主要用来统计在线人数,聊天室发言,网站访问量等。
4、主要有Lock()和UnLock()方法。

 

5、格式:Application[“属性名”]
例:
Application[“count”]=1;
Application[“userOnLine”]=x;
Application[“name”]=“tonny”;
Response.Write(Application[“name”].ToString());

 

6、Application对象是多用户共享的,它并不会因为某一个用户的离开而消失,一旦创建了Application对象,那么它就会一直存在,直到网站关闭。

 

五、Session对象

 

1、与Application刚好相反,它只存储单个用户的值。

 

2、作用:存储每个用户的私有信息。当用户在网页之间跳转时,存储在Session里面的变量值不会清除。因此,常用Session来对用户的登录情况进行判断和存储。

 

3、例:
Session[“username”]=“admin”;
Session[“username”]=TextBox1.Text.Trim();
Session对象存储的值,时间到了就会消失。可用TimeOut属性设置它的生存时间。
网页中可以有无数多个Session对象,每个Session都有自己独立的SessionId,因此相互间不影响。

 

4、当用户登录时,系统会自动为其分配一个SessionID,这个id随机分配,不会重复,用来区分不同用户。
Response.Write(“你的SessionID是:”+this.Session.SessionID.ToString());

 

5、格式:Session[“属性名”]
例:
Session[“name”]=TextBox1.Text.Trim();
Session[“password”]=TextBox2.Text.Trim();
Response.Write(“你的用户名为:”+Session[“name”].ToString());

 

可在网站的配置文件web.config里设置过期时间
<configuration>
<system.web>
<sessionState timeout=”30″/>
</system.web>
</configuration>

Session对象也可用Abandon()方法来结束。
如:Session.Abandon();
退出登录或注销就需要用到此方法。
五、Cookie对象

 

1、作用:与Session对象相似,也可以保存信息。不同的是,Session对象的所有信息保存在服务器上,而Cookie对象的信息保存在客户机上。

 

2、很多网站用Cookie来保存用户的登录信息:如登录次数、登录时间等。可以用IE的选项工具来清除Cookie.

 

3、使用格式

 

Cookie 对象需要利用HttpCookie类重新定义,不能直接使用。HttpCookie cookie=new HttpCookie(name);服务器使用Response对象将Cookie信息发送客户机上进行保存。
Response.Cookie.Add();
服务器不能删除Cookie,但可以创建过期的Cookie进行覆盖,让浏览器自动删除。
cookie.expire=DateTime.Now.AddDay(-1);

 

4、创建Cookie对象例子:

 

String name=TextBox1.Text.Trim();
HttpCookie cookie=new HttpCookie(name);
cookie.Values[“username”]=name;
cookie.Values[“visittime”]=Datetime.Now.ToString();
cookie.expires=DateTime.Now.AddDays(30);
Response.Cookie.Add(cookie);
5、访问Cookie

利用Request对象来访问。
如:
Response.Write(”你的登录名为:”+Request.Cookie[name][“username”].ToString());
Response.Write(“你上次登录时间为:”+Request.Cookie[name][“visittime”].ToString());

[转载]网站使用QQ登陆

mikel阅读(1818)

[转载]网站使用QQ登陆 – 追夢 – 博客园.

上几天我朋友说他知道怎么实现网站使用QQ号登陆或者注册了,于是要他发给我学习,感觉好复杂,而且运行不了(主要万原因是因为这东西得上传到空间是实验),于是在博客园找了相关的资料,发现在也并不是很复杂的,比他的简单多了。

首先请到这里下载最新版本的SDK:http://opensns.codeplex.com/

SDK依赖于Newtonsoft.Json和RestSharp两个程序集,具体可以参考使用RestSharp 库消费Restful Service。 主要是两个类QzoneContext(QQ登陆的上下文数据)  和 QOpenClient (QQ互联API入口),其他类主要是模型,配置类。

我这里新建的项目是在.net framwork 4.0,请你转换成 .net framwork 4.0,否则可能会出错,运行不了

1、你得去http://connect.qq.com/ 申请一个账号,会得到一个APP ID和App Key,这两个东东会在生成请求的时候用到。你的去填一些资料,还要提交一些资料审核。

在配置文件web.config加入QQ登陆所需要的一些配置参数,下面的代码请加到如下图选中的地方


<section name="QzoneSection" type="System.Configuration.NameValueSectionHandler,System, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089"></section><section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.3.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"></section>&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

 

AppKey是申请QQ登录成功后,分配给应用的appid;AppSecret是申请QQ登录成功后,分配给网站的appkey;CallBackURI是QQ登陆成功后的回调地址:AuthorizeURL是QQ互联的OAth2认证地址。

在项目中添加上面下载的SDK文件夹中三个引用Newtonsoft.Json.dll、RestSharp.dll和 QConnectSDK.dll, 在页面上放置按钮,打开qq登录的页面,然后登录成功之后回调您的网站的页面。此时如果用户在你的网站有账号,那就可以绑定现有账号,或者新注册一个账 号。如果你是新建站,也可以完全使用qq登录来作为用户体系。

登陆页面:

using QConnectSDK.Context;//请加入这个命名空间

public partial class LoginToQQ : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
GetRequestToken();
}

private void GetRequestToken()
{
var context = new QzoneContext();
string state = Guid.NewGuid().ToString().Replace("-", "");
string scope = "get_user_info,add_share,list_album,upload_pic,check_page_fans,add_t,add_pic_t,del_t,get_repost_list,get_info,get_other_info,get_fanslist,get_idolist,add_idol,del_idol,add_one_blog,add_topic,get_tenpay_addr";
var authenticationUrl = context.GetAuthorizationUrl(state, scope);
//request token, request token secret 需要保存起来
//在demo演示中,直接保存在全局变量中.真实情况需要网站自己处理
Session["requeststate"] = state;
Response.Redirect(authenticationUrl);

}

}

回调页面:

using QConnectSDK;//请加入这个命名空间
using QConnectSDK.Models;
public partial class QQCallback : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.Params["code"] != null)
{
QOpenClient qzone = null;
User currentUser = null;

var verifier = Request.Params["code"];
string state = Session["requeststate"].ToString();
qzone = new QOpenClient(verifier, state);
currentUser = qzone.GetCurrentUser();
if (null != currentUser)
{
this.result.Text = "成功登陆";
this.Nickname.Text = currentUser.Nickname;
this.Figureurl.ImageUrl = currentUser.Figureurl;
}
Session["QzoneOauth"] = qzone;
}
}
}
本地测试
  • 前提准备,了解本地Host文件的作用
  1. 找到C:\WINDOWS\system32\drivers\etc\hosts这个文件
  2. 用文本方式打开
  3. 增加一行:127.0.0.1 www.****.com 你注册时写的网址,不是回调地址(不过也许你的回调地址和你的网址一样,也是可以的)这就是所谓的域名劫持,嘿嘿!
  4. 启动本地服务器
  5. 启动浏览器访问 http://www.****.com/

参考博文:http://www.cnblogs.com/shanyou/archive/2012/02/05/2338797.html

http://www.devtext.com/blogs/detail/43/devtext-com-source-code 以及这个网站提供下载的网站源码。

本示例Demo下载地址:http://files.cnblogs.com/koeltp/WebSite14.rar

[转载]ASP.NET MVC中Unobtrusive Ajax的妙用

mikel阅读(990)

[转载]ASP.NET MVC中Unobtrusive Ajax的妙用 – Junfeng Liu – 博客园.

Unobtrusive JavaScript有三层含义:一是在HTML代码中不会随意的插入Javsscript代码,只在标签中加一些额外的属性值,然后被引用的脚本文件识 别和处理;二是通过脚本文件所增加的功能是一种渐进式的增强,当客户端不支持或禁用了Javsscript时网页所提供的功能仍然能够实现,只是用户体验 会降低;三是能够兼容不同的浏览器。

 

启用Unobtrusive JavaScript的步骤:

 

1.在web.config文件中加入

 

<configuration> 
  <appSettings> 
    <add key="ClientValidationEnabled" value="true"/>  
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
  </appSettings>

 

2.在网页中加入

 

<script src="@Url.Content("~/Scripts/jquery-1.6.2.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

 

使用Unobtrusive Ajax主要有两个用途:做客户端的输入验证和异步的表单提交。客户端验证基本上是自动的,不用做特别的处理。下面用一个例子重点说一下提交表单。

 

数据模型是这样的:每个类别有很多属性,属性可以分组,属性组可以嵌套。然后在网页创建和编辑属性组,示意图如下:

 

 

这是我半年前写的代码:

 

        $(this).find(".CreatePropertyGroup").click(function () {
            $(".InputGroupName").hide();
            var id = $(this).next().val();
            var td = $(this).parent().parent();
            $.post("/Category/CreatePropertyGroup", { parentId: id, name: $(this).prev().val() }, function () {
                td.load("/Category/PropertyGroup", { "id": id, "resultType": "html" }, loadGroupReady);
            });
        });

 

        $(this).find(".CreateProperty").click(function () {
            $(".InputPropertyName").hide();
            var id = $(this).next().val();
            var name = $(this).parent().find(".PropertyName").val();
            var type = $(this).parent().find("#PropertyDataType").val();
            var unit = $(this).parent().find(".PropertyUnit").val();
            var range = $(this).parent().find(".ValueRange").val();
            var td = $(this).parent().parent();
            $.post("/Category/CreateProperty", { groupId: id, name: name, type: type, unit: unit, range: range }, function () {
                td.load("/Category/PropertyGroup", { "id": id, "resultType": "html" }, loadGroupReady);
            });
        });

 

完全使用JQuery获取控件值和提交,可读性和可维护性不是很好。现在改用Ajax.BeginForm之后,很大地简化了编程:

 

    <div class="InputGroupName" style="display: none">
        @using (Ajax.BeginForm(new AjaxOptions { Url = Url.Action("CreatePropertyGroup"), UpdateTargetId = "PropertyGroup" }))
        { 
            <span>属性组名称:</span>
            <input name="name" class="GroupName" type="text" />
            <input type="hidden" name="categoryId" value="@categoryId" />
            <input type="hidden" name="path" value="@path" />
            <input type="submit" value="确定" />
        }
    </div>

 

对于不使用的表单的,直接点链接的可以用Ajax.ActionLink:

 

   <td>
       @Ajax.ActionLink("删除", "DeletePropertyGroup", new { categoryId = categoryId, path = path, name = property.Name },
       new AjaxOptions
       {
           HttpMethod = "Post",
           Url = Url.Action("DeletePropertyGroup", new { categoryId = categoryId, path = path, name = property.Name }),
           Confirm = "确认要删除 '" + property.Name + "' 及其所有属性吗?",
           UpdateTargetId = "PropertyGroup"
       })
   </td>

 

最终运行后生成的代码如下:

 

<form action="/Category/EditCategory/4-2-2" data-ajax="true" data-ajax-mode="replace"
 data-ajax-update="#PropertyGroup" data-ajax-url="/Category/CreatePropertyGroup" id="form5" method="post">
            <span>属性组名称:</span>
            <input name="name" class="GroupName" type="text" />
            <input type="hidden" name="categoryId" value="4-2" />
            <input type="hidden" name="path" value="PG.Props.1.Props" />
            <input type="submit" value="确定" />
</form>

 

<a data-ajax="true" data-ajax-confirm="确认要删除 &#39;外观特征&#39; 及其所有属性吗?" data-ajax-method="Post"
 data-ajax-mode="replace" data-ajax-update="#PropertyGroup" data-ajax-url="/Category/DeletePropertyGroup?categoryId=4-2&amp;path=PG.Props&amp;name=%E5%A4%96%E8%A7%82%E7%89%B9%E5%BE%81"
 href="/Category/DeletePropertyGroup?categoryId=4-2&amp;path=PG.Props&amp;name=%E5%A4%96%E8%A7%82%E7%89%B9%E5%BE%81">删除</a>

可以看到魔力就在于以data-ajax开头的一些属性中,当Javascript被禁用后,表单仍能提交,链接也能点开,只不过不再是异步的了。

[转载]ASP.NET 路由实现页面静态化(附在线Demo和Source)

mikel阅读(1288)

[转载]ASP.NET 路由实现页面静态化(附在线Demo和Source) – keepfool – 博客园.

页面静态化最大的好处是利于SEO,即使是伪静态,搜索引擎也会觉得这是一个较为友好的 Url。Url的友好也取决于其命名,为一篇描述古代文学的页面起名用ancient-literature.html当然比随便起的名字例如 aa.html之流要友好。页面静态化并不代表你一定要用后缀名为.html或.htm的链接来显示你的页面,你完全可以不用任何后缀名(就像MVC一 样),只要Url结构良好。

实现静态化的三个目标:

1. 实现页面静态化,页面中的链接都用.html来表示,但每个.html实际都映射了一个.aspx页面。

例如:当用户请求index.html页面时,实际请求的是Default.aspx页面,index.html的物理路径在网站中并不存在。

2. 实现请求.aspx页面时自动跳转到对应的静态映射页面。

例如:当用户请求Default.aspx页面,自动跳重定向到index.html页面

3. 自定义404页面的实现,当请求的路径既不在映射表中,也不在网站的虚拟路径中时,它将自动跳转到我预先设定好的404页面。

实现以上要点,需要用到ASP.NET Url Routing、HttpHandler和HttpModule技术。

这是一个小系列的文章,这一篇文章将详细解说并实现第1点。

一、项目创建

1. 创建一个ASP.NET Web Application项目

SNAGHTMLf9ef62

image

2. 创建web.config文件

ASP.NET Membership在这里使用不到,所以生成的web.config配置没有用处,删掉它并重新创建一个新的web.config文件

<?xml version="1.0"?> <configuration> <system.web> <compilation debug="true" targetFramework="4.0" /> </system.web> </configuration> 

3. 将网站添加到IIS6或IIS7中

SNAGHTML1048e8d

默认的ASP.NET Web Application已经为我们提供了不少页面,我就在下面的例子中将它们静态化吧。

二、页面静态化实现

1. 添加Routing引用

由于这里需要用到ASP.NET的路由映射(从.NET 3.5开始诞生),所以需要在项目中添加System.Web.Routing引用。

SNAGHTMLfc9503

SNAGHTML1007756

2. 添加WebHandler和WebModule文件夹

这两个文件夹分别用于存放IHttpHandler和IHttpModule。

3. 将所有.aspx后缀的超链接更改为.html

Site.Master文件:

<asp:Menu ID="NavigationMenu" runat="server" CssClass="menu" EnableViewState="false" IncludeStyleBlock="false" Orientation="Horizontal"> <Items> <asp:MenuItem NavigateUrl="~/Index.html" Text="Home"/> <asp:MenuItem NavigateUrl="~/About.html" Text="About"/> </Items> </asp:Menu> 

Account文件夹ChangePassword.aspx文件:

<asp:ChangePassword ID="ChangeUserPassword" runat="server" CancelDestinationPageUrl="~/" EnableViewState="false" RenderOuterTable="false" SuccessPageUrl="ChangePasswordSuccess.html"> 

当然现在这三个静态链接都访问不到,因为它们的物理地址不存在。

下面我们要做的就是:

1) 请求Index.html时实际请求的是Default.aspx

2) 请求About.html时实际请求的是About.aspx

3) 请求Account/Login.html时实际请求的是Account/Login.aspx

4. 添加自定义的IRouteHandler实现

using System.Web;
using System.Web.Compilation;
using System.Web.Routing;
using System.Web.UI;

namespace Routing_Static_Page_Demo.WebHandler
{
    public class CustomRouteHandler : IRouteHandler {
        /// <summary> /// 虚拟路径 /// </summary> public string VirtualPath { get; private set; }

        public CustomRouteHandler(string virtualPath)
        {
            this.VirtualPath = virtualPath;
        }

        /// <summary> /// 返回实际请求页 /// </summary> public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            var page = BuildManager.CreateInstanceFromVirtualPath(VirtualPath, typeof(Page)) as IHttpHandler;
            return page;
        }
    }
}

5. 在Global.asax文件中注册路由

先来个简单的实现:

using System;
using System.IO;
using System.Web.Routing;
using Routing_Static_Page_Demo.WebHandler;

namespace Routing_Static_Page_Demo
{
    public class Global : System.Web.HttpApplication {

        void Application_Start(object sender, EventArgs e)
        {
            RegisterRoutes();
        }

        /// <summary> /// 注册路由 /// </summary> private void RegisterRoutes()
        {

            //将Index.html请求映射为Default.aspx RouteTable.Routes.Add("Default",
                                  new Route("Index.html",
                                            new CustomRouteHandler("~/Default.aspx")));

            // 将About.html请求映射为About.aspx RouteTable.Routes.Add("About",
                                  new Route("About.html",
                                            new CustomRouteHandler("~/About.aspx")));

            // 将Account/Login.html请求映射为/Account/Login.aspx RouteTable.Routes.Add("Login",
                                  new Route("Account/Login.html",
                                            new CustomRouteHandler("~/Account/Login.aspx")));
        }
    }
}

在VS中直接运行站点(VS自带的WebDev服务器),点击这些链接都能够正常访问。

三.  在IIS 7下设置站点

下面的设置很重要,因为上面在VS自带的web服务器中虽然跑通了,但IIS 7下是运行不通过的(IIS 6下的设置很简单,本文的在线Demo是运行在IIS 6下的)

1. 初次在IIS 7下运行该网站,会出现下面的错误。

SNAGHTML2cedf45

这是因为IIS对该Web站点目录没有读写权限。

在IIS下:右键站点 >  Edit Permissions > Security > Edit > Add > 输入IIS_IUSRS > Check Names > OK。

选择完毕后,为IIS_IUSRS用户添加Full Control权限。

image

image

SNAGHTML2f152df[4]

2. 添加完该设置后,再运行一次网站,可能会出现下面的错误。

SNAGHTML2f65a2f

按照上面的步骤添加IUSR用户,为IUSR用户分配Read权限即可。

SNAGHTML2f8d75a

再次运行网站,能够正常访问页面了。

SNAGHTML2fd7e14

3.  配置web.config

网站虽然能运行,但是点击Home或About链接时会出现404错误。

SNAGHTML2ff02fc

i. 首先确保在安装IIS时你已经勾选了HTTP Reirection

如果没有安装这个功能,按照如下设置再配置一遍IIS

Control Panel –> Progams –> Turn off windows features –> World wide web Services –> Common HTTP Features –> HTTP Redirection

ii. 修改web.config文件,在webserver中注册RoutingHandler和RoutingModule

<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.web> <compilation debug="true" targetFramework="4.0" /> </system.web> <system.webServer> <modules runAllManagedModulesForAllRequests="true"> <remove name="UrlRoutingModule"/> <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> </modules> <handlers> <add name="UrlRoutingHandler" 
                                  preCondition="integratedMode" 
                                  verb="*" path="UrlRouting.axd"
                                  type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/> </handlers> </system.webServer> </configuration> 

注意: 如果你采用的是ASP.NET 3.5 Routing或使用IIS 6,web.config配置会不一样。

iii. 确保web站点的应用程序池选择的是集成模式,因为ASP.NET 4.0 Routing并不支持经典模式

SNAGHTML32bdeab

OK,似乎所有的该配置的地方都配置了,那么再去点击Index.html或About.html链接试试吧。

SNAGHTML32d4edb

如果现在去访问Login.html页面,还是会得到一个401.3的错误,更改Account目录下的web.config文件:

<?xml version="1.0"?> <configuration> <location path="Register.aspx"> <system.web> <authorization> <allow users="*"/> </authorization> </system.web> </location> <system.web> <!--<authorization> <deny users="?"/> </authorization>--> </system.web> </configuration> 

如果你不需要这个web.config文件,直接删掉也可以。

四. 更改RegisterRoutes方法

上面提供的注册路由的方式属于硬编码,需要为每一个.aspx页面指定映射路由。Account目录下还有一些.aspx文件,如果增加别的目录也存放.aspx页面,为了让每个页面都静态化,RegisterRoutes方法将会是产生很多重复代码。

using System;
using System.IO;
using System.Web.Routing;
using Routing_Static_Page_Demo.WebHandler;

namespace Routing_Static_Page_Demo
{
    public class Global : System.Web.HttpApplication {

        void Application_Start(object sender, EventArgs e)
        {
            RegisterRoutes();
        }

        /// <summary> /// 注册路由 /// </summary> private void RegisterRoutes()
        {

            //将Index.html请求映射为Default.aspx RouteTable.Routes.Add("Default",
                                  new Route("Index.html",
                                            new CustomRouteHandler("~/Default.aspx")));

            // 将About.html请求映射为About.aspx RouteTable.Routes.Add("About",
                                  new Route("About.html",
                                            new CustomRouteHandler("~/About.aspx")));

            // 遍历页面存放目录,为每个.aspx页面添加路由映射 foreach (string mapPth in _pageMapPath)
            {
                string path = Server.MapPath(mapPth);
                var directoryInfo = new DirectoryInfo(path);
                foreach (FileInfo f in directoryInfo.GetFiles())
                {
                    string fileName = f.Name;
                    if (fileName.EndsWith(".aspx"))
                    {
                        string routeName = fileName.Substring(0, fileName.Length - 5);
                        string url = string.Concat(mapPth.Substring(2), routeName, ".html");
                        RouteTable.Routes.Add(routeName,
                                              new Route(url,
                                                        new CustomRouteHandler(string.Concat(mapPth, fileName))));
                    }
                }
            }

        }

        // 页面存放目录 private readonly string[] _pageMapPath = {@"~/Account/"};
    }
}

以上代码就能实现为每个.aspx页面注册路由实现静态化。