[SEO]SEO专题之三:SEO与网站开发

mikel阅读(843)

  继续来发第三篇文章.在发文之前.回应一下前面几位朋友的留言,关于怎么提高PR 值,我在后面的系列中会具体讲解.PR值google的更新时间是无法确定的.一般二到四个月更新一次,SEO是一门技术.不是投机取巧.也不是钻空子, 虽然也有很多站点也确实钻了SEO的空子(但这样做的代价危险性可想而知).步入正题:
    这篇我从程序开发的角度来讲解在网站开发的过程中从SEO的角度需要注意一些什么方面,我只会C#下的ASP.NET,所以本章也是从ASP.NET开发的角度来说.但从事asp/php的程序员也可以基本可以照搬.
    我们先把google的设计指南中的话先copy过来,然后再一步一步讲解
     设计与内容指南

* 网站应具有清晰的层次结构和文本链接。 每个网页应至少可以通过一个静态文本链接打开。
* 为用户提供网站地图,列出指向网站重要部分的链接。 如果网站地图上的链接超过或大约为 100 个,则需要将网站地图拆分为多个网页。
* 网站应实用且信息丰富,网页文字应清晰、准确地表述要传达的内容。
* 要考虑到用户会使用哪些字词来查找您的网页,确保网站上确实包含了这些文字。
* 尽量使用文字而不是图片来显示重要的名称、内容或链接。 Google 抓取工具无法识别图片中所含的文字。
* 确保 <标题> 元素和 ALT 属性具有描述性且准确无误。
* 检查链接是否损坏,并确保 HTML 格式正确。
* 如果要使用动态网页(即网址中包含"?" 字符),请注意并非每一个搜索引擎抓取工具都能抓取动态和静态网页。缩短参数长度和减少参数数目都会对这种情况有帮助。
* 将特定网页上的链接限制在合理的数量内(少于 100 个)
质量指南 – 具体指南
* 请不要使用隐藏文本或隐藏链接。
* 请不要使用隐藏真实内容或欺骗性的重定向手段。

* 请不要向 Google 发送自动查询。
* 请不要加载使用无关关键字的网页。
* 请不要创建包含大量重复内容的多个网页、子域或域。
* 请不要制作欺诈性或安装有病毒、特洛伊木马或其他有害软件的网页。
* 请不要专门针对搜索引擎创建"桥页",或使用其他如联属计划这类原创内容很少或几乎没有原创内容的俗套 (cookie cutter) 方法。
* 如果您的网站参与联属计划,请确保您的网站可为其增添价值。 请提供独特且相关的内容,使用户有理由首先访问您的网站。
这篇文章的出处:http://www.google.com/support/webmasters/bin/answer.py?answer=35769,
我在原有的基础上补充几点:

1.文件命名不宜过长.网站设计中不能出现死页面.死链接.错误链接.空链接.文件名应该富有意义(这点的出发点似乎是为了更容易理解文件的大概内容)
2.除非呈现数据.否则布局不要用table

3.URL地址,通过搜索得知.静态页面以及伪静态页面和动态页面google是一样可以收录的.但google建议参动态页面参数不宜过长.我的建议一般不要超过三个,但哪种页面google最亲睐,我们得先来了解一下google收录的基本原理和规律.

    google会根据你的网站首页(或其它页)来遍历该页面的其它链接.例如:你的首页有100个超链接.那么google会遍历你的这100个链接页面. 再根据其它的页面的链接一个一个遍历直至整站遍历完成.如果中途出现很多死链接,错误链接或死页的话.google就会认为你这个网站错误百出.可能中止 遍历,从而影响你页面的收录效果.那么带参数的动态页面或伪静态页是否会影响收录呢?答安是肯定的.再举个例子:如有一个显示产品详细内容的页 面:www.a.com/products.aspx?id=x的网页.那么有100种产品是存在数据库里面的.根据不同的ID值来通过 products.aspx?id={1-100}中的<%#Eval("数据库字段")%>来显示相应的产品内容.那么看似有100个页 面.而实际上只有products.aspx这一个页面.而google会遍历你的数据库记录吗?不会.所以你有100个产品介绍.可能只能收录一个页 面!(这种形式是很多cms或企业站所采用的方式),那么有办法能收录这100个页面吗?答安是:可以.折中的选择方案是做一个产品列表页如 productlist.html(aspx)(这个页面应该是手工静态页或后台生成静态页.,只要不从数据库里面读数据就可以),该页面内容如下:

<a href="products.aspx?id=1">球阀</a>
<a href="products.aspx?id=2">闸阀</a>
…….
上面的内容应该静态生成.那么google在遍历productlist.aspx(html)页面的时候.就会遍历这些产品相应的地址了.遍历地址时会根据这个地址是否返回404错误而缓存相应的页面内容..(原理很简单..说了一大堆.不知道大家能否理解)
4.不要轻易网站内页面文件名
   google一旦收录了这个页面.而不久你又将这个页面删除或改名.那么GOOGLE再次收录的时候发现这个页面不见了.肯定对你的PR值是有影响的
5.图片的alt属性不要为空.而且内容与你的关键字有关(后面详解)
6.一定要在web.config里面定义404错误转向.这个转向页面里面最好还要定义转向各个重要页面的链接
再加一点:
 7.无论在站点内的哪个页面.你必须保证你可以在当前页通过页面与页面之间的链接能跳转到任何一个页面(好像有点别扭.就是说你的页面必须不可缺少正确的导航)

 

   

非淡泊无以明志,非宁静以致无以致远

[SEO]seo专题之二:网站pr值

mikel阅读(911)

    什么是Pr值?pr是英文PageRank的缩写,中文翻译过来中网页级别.网页排名.wiki的解释:是一种由搜索引擎根据网页之间相互的超链接计算的 网页排名技术,以Google公司创办人拉里·佩奇(Larry Page)之姓来命名。此技术通常和搜索引擎优化有关,Google用它来体现网页的相关性和重要性。Google的创始人拉里·佩奇和谢尔盖·布林于 1998年在斯坦福大学发明了这项技术.
  我们来通俗的解释一下这段话:pr值是google特有的网页评级标准,它仅对google有效.baidu,yahoo等其它是没有这个评级标准的.这 个评级的范围是0-10,那么这个值对网站和网页能够起到什么决定性的作用呢?通常来讲.网站PR值越高google就认为该网站对浏览者来说越有价值, 资料越具有权威性,则搜索引擎对该站点的抓取和收录频率越高,页面PR值越高则排名越靠前.那么该怎么样查看站点和页面的PR值呢?下面我们隆重请上 serachstatus(一款firefox下的插件,没有装firefox的要先装上了),官方下载地址:http://www.quirk.biz /searchstatus/,安装之后在浏览器状态栏右下角就可以看到如下图标, 现在大家看到的绿色条就是cnblogs.com的PR值哦 .该值为6,蓝色条为alexa的排名(暂且不表),一般网站PR值能大于或等于6就已经相当不错了(作弊的除外),当我们访问cnblogs里面的不同 页面时.pagerank都会准确的显示出来 ,大家在浏览的时候发现不同页面的PR值是不一样的.那么站点PR值与页面PR值到底有什么区别和联系呢.下面我们来举例说明:
   假设:www.a.com站点PR值为6,www.a.com/xx.html的页面PR值为5,该页面的关键字为"阀门"
        www.b.com站点PR值为5,www.b.com/xx.html的页面PR值为6,该页面的关键字同样也为"阀门".
     那么当用户在搜索"阀门"关键词时哪个站点的xx.html页面的排名会靠前呢?一般来说(90%的可能),www.b.com/xx.html 的页面排名会靠前,因为它的PR值比较a站点的高.google会认为这个页面比b站点的xx.html页面更具有参考价值和权威性.那么还有10%的不 确定性在哪里?那就是google会分析你这个页面的关键词密度,创建时间,页面相似性等因素.来判断你是否有作弊嫌疑,如果认为有则有可能PR值高的页 面可会排在后面(这点我们在后面文章将进一步分析).

    我们都知道.网站PR值一般就是指首页的PR值了.因为打开网址自动加载首页.我们可以看到博客园首页是看不到地址的.但根据PR值大概也可以分析出来: 博客园首页的默认转向文件名应该是index.html或index.htm(大家可以尝试不同的默认首页文件名来判断),那么网站PR值高有什么好处 呢?还是以上面两个网址来举例:
   a站点和b站点在同一时间同时更新了网站内容,添加了不少新的页面,那么PR值高的站点的新的页面google会先收录.当然两个站点的PR值差距越大.收录时间的差距一般来说也就越大(不能绝对),同时页面PR值差距越大.页面在搜索中的排名也就差距越大.
   综上所述:页面PR值是影响当前页面排名的.网站PR值(一般指首页PR值)是影响站内所有页面收录频率的.当然它也影响页面排名了.它不也是指首页嘛!!
   下一篇:SEO与网站开发,敬请期待!!

[SEO]SEO专题之四:如何合理有效选定关键字

mikel阅读(821)

   上一篇我们讲到了SEO网站开发的中的注意事项,这篇文章继续结合上一篇内容继续讲解,如何合理有效的选定关键字.
    我们都知道.搜索引擎的工作是按你在文本框内输入的关键字来查找内容相匹配的网页.如果查找到则在搜索列表中显示出来,那么是不是你只要指定了相应的关键 字浏览者就一定能看到你的网页呢.非也!!如果你的页面排到十几页之后.那么有谁能翻到十几页之后再查找符合要求的信息网页呢.按照我们一般的搜索习惯. 一般最多看前两页,或许大部分浏览者只是看第一页,所以我们要想方设法将自己的页面排到第一页.或第一页前几位(这对于一个新站来说做通用关键字非常难).还是通过例子来说明这件事情
   先来了解HTML页面结构
   <html>

   <head>

   <title>xx</title>
   <meta name="keywords" content=""/>
   <meta name="descritpion" content="">

   </head>
   <body>
       xx
   </body>

   </html>
   搜索引擎在区配关键词同时会按HTML页面结构来区配关键字内容(有点像遍历二叉树,不知道这种说法对不对.本人数据结构没学好.),按此理解.它的搜索 顺序为html-head-title-meta-body-div..由此看来title是指定关键字最重要的部分(这点我们在下一章再重点讲解),关 键字如何选?同样我们也要结合一个工具来做分析,,下载地址:http://ff.SEOquake.com/,安装过程就不多说了.运行成功之后在 google搜索时界面截图如下:
   
    在每个页面下都有相应的PR值.外链接.和收录页面提示.通过这个页面我们就可以分析到各个关键字的强度及排名情况,以及重点竞争对手的排名情况.分析关键字应该从哪几方面着手呢?
   1.关键字收录页面数,相比较之下.收录页面数越多.关键字越难做
   2.搜索列表首页网站的PR值,一页只有10个名额.如果这10个网站的PR值普遍越高.关键字越难做
   3.搜索列表首页网站的外链数,外链数越多,超过它越难.
   4.google收录的该站点页面数.收录数越多,超过它越难.|
  这是几个基本的分析点.但我们可以看到.有些PR值低的页面也排到了PR值高的网站前面.这是因为google排名不仅仅依据上面所列举的这四点,我们还 应该分析排在前面的页面的关键字放置的位置.以及页面关键字的密度.这个我们可以通过SEOquake的pageinfo可以分析到(见图):
 

   所以.要想排名靠前.要考虑的因素很多.而google的排名算法一直是没有公开的.这点我们就需要平时经验的不断积累了.我们不能够太相信PR值对网站排名的绝对影响.但也不能忽视,所以选一个好的关键词比做1000个外链来的效果要好得多.
下一节将重点关键词在title,keywords,description中的应用..

非淡泊无以明志,非宁静以致无以致远

[CSS]解决IE和Mozilla的布局差别的利器:“!important”

mikel阅读(781)

初学div+css网页布局的设计者常常会被一个问题困扰着。
在IE和其他(Mozilla、Opera等)浏览器里显示的效果常常会偏差2px。
这是因为IE对盒之间距离的解释的bug造成的。一个技巧提示帮我们找到了解决的方法:用!important。
!important是CSS1就定义的语法,作用是提高指定样式规则的应用优先权。
语法格式{ sRule!important },即写在定义的最后面,如:

div css xhtml xml Example Source Code Example Source Code [www.52css.com]
box{color:red !important;} 

最重要的一点是:IE一直都不支持这个语法,而其他的浏览器都支持。我们就可以利用这一点来分别给IE和其它浏览器不同的样式定义。如:

div css xhtml xml Example Source Code Example Source Code [www.52css.com]
.colortest { 
border:20px  solid #000 !important;
border:20px  solid #CCC;
padding: 30px;
width : 300px;

在Mozilla中浏览时候,能够理解!important的优先级,因此显示#000的颜色;
在IE中浏览时候,不能够理解!important的优先级,因此显示#CCC的颜色。
可以看到,利用!important,我们可以针对IE和非IE浏览器设置不同的样式,只要在非IE浏览器样式的后面加上!important。 
!important必定成为CSS布局的利器,请记住它、掌握它、并合理的加以利用!

[IIS]使用Microsoft URL Rewrite Module for IIS 7.0修改W

mikel阅读(842)

在IIS5和IIS6时代,我们使用URL REWRITING可实现URL重写,使得WEB程序实现伪静态,但默认情况下只能实现.ASPX的伪静态,如果要实现伪静态*.HTML的页面,需要将 ISAPI里面的*.HTML应用程序映射改为.NET的ISAPI。但在IIS 7时代,这一切已经变得非常简单了,您在WEB.CONFIG中就可以管理这一切了。

 

在服务器上安装好Microsoft URL Rewrite Module,具体下载地址如下:

Microsoft URL Rewrite Module for IIS 7.0 (x86)

下载地址:http://www.iis.net/downloads/default.aspx?tabid=34&g=6&i=1691 

Microsoft URL Rewrite Module for IIS 7.0 (x64)

下载地址:http://www.iis.net/downloads/default.aspx?tabid=34&g=6&i=1692

 

安装好之后如图:

 

 

可以直接在IIS 7里面进行添加重写规则,也可以直接在WEB.CONFIG中设置:

 

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    
<system.webServer>
        
<rewrite>
            
<rules>
                
<rule name="Rewrite to article.aspx">
                    
<match url="^article/([0-9]+).html$" />
                    
<action type="Rewrite" url="article.aspx?newid={R:1}" />
                
</rule>
            
</rules>
        
</rewrite>
    
</system.webServer>
</configuration>

 

然后,访问 /article/366.html 就是 访问/article.aspx?newid=366的结果了,无需像以前IIS 6时代一样去添加*.HTML的ISAPI的应用程序映射了。(这对使用虚拟主机的站点来说可是个福音,不用再叫管理员去帮你加设置了,自己在 WEB.CONFIG中可以设置这一切了)

 

下面还附一个重写的规则转换工具。

可以将您原有的第三方组件或apache的.htaccess规则配置,快速按规范转化为IIS7.0的使用web.config重写规则。

 

 

[Javascript]FileSystemObject 对象

mikel阅读(669)

js用FileSystemObject 对象实现文件控制

一、功能实现核心:FileSystemObject 对象

其实,要在JavaScript中实现文件操作功能,主要就是依靠FileSystemobject对象。在详细介绍FileSystemobject对象的各个属性和方法的使用细节前,先来看看这个对象包括哪些相关对象和集合:

Driver 对象类型。包括收集系统中驱动器相关信息的方法和属性,不如共享名、可用空间等。一个“drive”对象不一定代表一个物理硬盘,还可以是一个CD-ROM驱动器、一个RAM盘或者一个通过网络逻辑连接的资源。
Drivers 集合类型。提供系统中一系列以物理方式或者逻辑方式存在的驱动器对象。
File 对象类型。包括对文件进行创建、删除或者移动操作的相关方法和属性,还包括查询文件名称、路径以及其他文件属性的方法和属性。
Files 集合类型。提供包含在文件夹中的一系列File对象。
Folder 对象类型。包括对文件夹进行创建、删除或者移动操作的相关方法和属性。
Folders 集合类型。提供包含在文件夹中的一系列Folder对象。
TextStream 对象类型。提供文本文件的读写功能。

 

二、FileSystemObject编程三部曲
使用FileSystemObject 对象进行编程很简单,一般要经过如下的步骤: 创建FileSystemObject对象、应用相关方法、访问对象相关属性 。
(一)创建FileSystemObject对象
创建FileSystemObject对象的代码只要1行:
var fso = new ActiveXObject("Scripting.FileSystemObject");
上述代码执行后,fso就成为一个FileSystemObject对象实例。
(二)应用相关方法
创建对象实例后,就可以使用对象的相关方法了。比如,使用CreateTextFile方法创建一个文本文件:
var fso = new ActiveXObject("Scripting.FileSystemObject");
var f1 = fso.createtextfile("c:\\myjstest.txt",true");
(三)访问对象相关属性
要访问对象的相关属性,首先要建立指向对象的句柄,这就要通过get系列方法实现:GetDrive负责获取驱动器信息,GetFolder负责获取文件夹信息,GetFile负责获取文件信息。比如,指向下面的代码后,f1就成为指向文件c:\test.txt的句柄:
var fso = new ActiveXObject("Scripting.FileSystemObject");
var f1 = fso.GetFile("c:\\myjstest.txt");
然后,使用f1访问对象的相关属性。比如:
var fso = new ActiveXObject("Scripting.FileSystemObject");
var f1 = fso.GetFile("c:\\myjstest.txt");
alert("File last modified: " + f1.DateLastModified);
执行上面最后一句后,将显示c:\myjstest.txt的最后修改日期属性值。
但有一点请注意:对于使用create方法建立的对象,就不必再使用get方法获取对象句柄了,这时直接使用create方法建立的句柄名称就可以:
var fso = new ActiveXObject("Scripting.FileSystemObject");
var f1 = fso.createtextfile("c:\\myjstest.txt",true");
alert("File last modified: " + f1.DateLastModified);
三、操作驱动器(Drives)
使用FileSystemObject对象来编程操作驱动器(Drives)和文件夹(Folders)很容易,这就象在Windows文件浏览器中对文件进行交互操作一样,比如:拷贝、移动文件夹,获取文件夹的属性。
(一)Drives对象属性
Drive对象负责收集系统中的物理或逻辑驱动器资源内容,它具有如下属性:
l TotalSize:以字节(byte)为单位计算的驱动器大小。
l AvailableSpace或FreeSpace:以字节(byte)为单位计算的驱动器可用空间。
l DriveLetter:驱动器字母。
l DriveType:驱动器类型,取值为:removable(移动介质)、fixed(固定介质)、network(网络资源)、CD-ROM或者RAM盘。
l SerialNumber:驱动器的系列码。
l FileSystem:所在驱动器的文件系统类型,取值为FAT、FAT32和NTFS。
l IsReady:驱动器是否可用。
l ShareName:共享名称。
l VolumeName:卷标名称。
l Path和RootFolder:驱动器的路径或者根目录名称。
(二)Drive对象操作例程
下面的例程显示驱动器C的卷标、总容量和可用空间等信息:
var fso, drv, s ="";
fso = new ActiveXObject("Scripting.FileSystemObject");
drv = fso.GetDrive(fso.GetDriveName("c:\\"));
s += "Drive C:" + " – ";
s += drv.VolumeName + "\n";
s += "Total Space: " + drv.TotalSize / 1024;
s += " Kb" + "\n";
s += "Free Space: " + drv.FreeSpace / 1024;
s += " Kb" + "\n";
alert(s);
执行后出现下面的信息框:
四、操作文件夹(Folders)
涉及到文件夹的操作包括创建、移动、删除以及获取相关属性。

(一) Folder对象的相关属性和方法列表

FileSystemObjec.CreateFolder 创建文件夹
Folder.Delete 或FileSystemObjec.DeleteFolder 删除文件夹
Folder.Move 或FileSystemObjec.MoveFolder 移动文件夹
Folder.Copy 或FileSystemObjec.CopyFolder 拷贝文件夹
Folder.Name 获得文件夹名称
FileSystemObjec.FolderExists 判断文件夹是否存在
FileSystemObjec.GetFolder 取得存在Folder对象的实例
FileSystemObjec.GetParentFolderName 取得文件夹的父文件夹名称
FileSystemObjec.GetSpecialFolder 取得系统文件夹路径信息

 

(二)Folder对象操作例程
下面的例程将练习获取父文件夹名称、创建文件夹、删除文件夹、判断是否为根目录等操作:
var fso, fldr, s = "";
// 创建FileSystemObject对象实例
fso = new ActiveXObject("Scripting.FileSystemObject");
// 获取Drive 对象
fldr = fso.GetFolder("c:\\");
// 显示父目录名称
alert("Parent folder name is: " + fldr + "\n");
// 显示所在drive名称
alert("Contained on drive " + fldr.Drive + "\n");
// 判断是否为根目录
if (fldr.IsRootFolder)
alert("This is the root folder.");
else
alert("This folder isn't a root folder.");
alert("\n\n");
// 创建新文件夹
fso.CreateFolder ("C:\\Bogus");
alert("Created folder C:\\Bogus" + "\n");
// 显示文件夹基础名称,不包含路径名
alert("Basename = " + fso.GetBaseName("c:\\bogus") + "\n");
// 删除创建的文件夹
fso.DeleteFolder ("C:\\Bogus");
alert("Deleted folder C:\\Bogus" + "\n");
五、操作文件(Files)
对文件进行的操作要比以上介绍的驱动器(Drive)和文件夹(Folder)操作复杂些,基本上分为以下两个类别:对文件的创建、拷贝、移动、删除操作和对文件内容的创建、添加、删除和读取操作。下面分别详细介绍。
(一)创建文件
一共有3种方法可用于创建一个空文本文件,这种文件有时候也叫做文本流(text stream)。
第一种是使用CreateTextFile方法。代码如下:
var fso, f1;
fso = new ActiveXObject("Scripting.FileSystemObject");
f1 = fso.CreateTextFile("c:\\testfile.txt", true);
第二种是使用OpenTextFile方法,并添加上ForWriting属性,ForWriting的值为2。代码如下:
var fso, ts;
var ForWriting= 2;
fso = new ActiveXObject("Scripting.FileSystemObject");
ts = fso.OpenTextFile("c:\\test.txt", ForWriting, true);
第三种是使用OpenAsTextStream方法,同样要设置好ForWriting属性。代码如下:
var fso, f1, ts;
var ForWriting = 2;
fso = new ActiveXObject("Scripting.FileSystemObject");
fso.CreateTextFile ("c:\\test1.txt");
f1 = fso.GetFile("c:\\test1.txt");
ts = f1.OpenAsTextStream(ForWriting, true);
(二)添加数据到文件
当文件被创建后,一般要按照“打开文件->填写数据->关闭文件”的步骤实现添加数据到文件的目的。
打开文件可使用FileSystemObject对象的OpenTextFile方法,或者使用File对象的OpenAsTextStream方法。
填 写数据要使用到TextStream对象的Write、WriteLine或者WriteBlankLines方法。在同是实现写入数据的功能下,这3者 的区别在于:Write方法不在写入数据末尾添加新换行符,WriteLine方法要在最后添加一个新换行符,而WriteBlankLines则增加一 个或者多个空行。
关闭文件可使用TextStream对象的Close方法。
(三)创建文件及添加数据例程
下面的代码将创建文件、添加数据、关闭文件几个步骤结合起来进行应用:
var fso, tf;
fso = new ActiveXObject("Scripting.FileSystemObject");
// 创建新文件
tf = fso.CreateTextFile("c:\\testfile.txt", true);
// 填写数据,并增加换行符
tf.WriteLine("Testing 1, 2, 3.") ;
// 增加3个空行
tf.WriteBlankLines(3) ;
// 填写一行,不带换行符
tf.Write ("This is a test.");
// 关闭文件
tf.Close();
(四)读取文件内容
从 文本文件中读取数据要使用TextStream对象的Read、ReadLine或ReadAll 方法。Read方法用于读取文件中指定数量的字符;ReadLine方法读取一整行,但不包括换行符;ReadAll方法则读取文本文件的整个内容。读取 的内容存放于字符串变量中,用于显示、分析。在使用Read或ReadLine方法读取文件内容时,如果要跳过一些部分,就要用到Skip或 SkipLine方法。
下面的代码演示打开文件、填写数据,然后读取数据:
var fso, f1, ts, s;
var ForReading = 1;
fso = new ActiveXObject("Scripting.FileSystemObject");
// 创建文件
f1 = fso.CreateTextFile("c:\\testfile.txt", true);
// 填写一行数据
f1.WriteLine("Hello World");
f1.WriteBlankLines(1);
// 关闭文件
f1.Close();
// 打开文件
ts = fso.OpenTextFile("c:\\testfile.txt", ForReading);
// 读取文件一行内容到字符串
s = ts.ReadLine();
// 显示字符串信息
alert("File contents = '" + s + "'");
// 关闭文件
ts.Close();
(五)移动、拷贝和删除文件
对 于以上三种文件操作,JavaScript各有两种对应的方法:File.Move 或 FileSystemObject.MoveFile用于移动文件;File.Copy 或 FileSystemObject.CopyFile用于拷贝文件;File.Delete 或 FileSystemObject.DeleteFile用于删除文件。
下面的代码演示在驱动器C的根目录下创建一个文本文件,填写一些内容,然后将文件移动到\tmp目录下,再在目录\temp下面建立一个文件拷贝,最后删除这两个目录的文件:
var fso, f1, f2, s;
fso = new ActiveXObject("Scripting.FileSystemObject");
f1 = fso.CreateTextFile("c:\\testfile.txt", true);
// 写一行
f1.Write("This is a test.");
// 关闭文件
f1.Close();
// 获取C:\根目录下的文件句柄
f2 = fso.GetFile("c:\\testfile.txt");
// 移动文件到\tmp目录下
f2.Move ("c:\\tmp\\testfile.txt");
// 拷贝文件到\temp目录下
f2.Copy ("c:\\temp\\testfile.txt");
// 获取文件句柄
f2 = fso.GetFile("c:\\tmp\\testfile.txt");
f3 = fso.GetFile("c:\\temp\\testfile.txt");
// 删除文件
f2.Delete();

f3.Delete();

[浏览器]兼容多种浏览器“复制到剪贴板”的解决方案

mikel阅读(907)

昨天在也一个php程序的时候,要实现“复制到剪贴板”的功能,由于安全问题,浏览器的限制越来越严,实现的方法也越来越有限,这个简单的功能要想实现兼容不同的浏览器还真不容易,根据网上的一些资料结合我测试的经验,相关解决方案如下:

方案一:利用“window.clipboardData”

代码示例:

if (window.clipboardData) {
window.clipboardData.clearData();
window.clipboardData.setData("Text", txt)

};

评论:这个方案只适合IE浏览器,同时在IE7下有个如下安全提示的对话框。

image

由于IE的浏览器的占有率比较大,这个方案是使用最广的,也是最简单的方法。

支持浏览器:IE5.5 IE6.0 IE7.0

方案二:利用“Components.interfaces.nsIClipboard”

代码示例:

if (window.netscape) {
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
} catch (e) {
if (flag == 0) {
alert("You are using the Firefox browser, copy the function browser refuse!
\nPlease in the browser address bar enter'about:config' and Enter \n and set'signed.applets.codebase_principal_support' to 'true'"
);

} else {
alert("你使用的是Firefox 浏览器,复制功能被浏览器拒绝!\n请在浏览器地址栏输入'about:config'
并回车\n然后将'signed.applets.codebase_principal_support'设置为'true'"
);

}
}
var clip = Components.classes['@mozilla.org/widget/clipboard;1'].createInstance
(Components.interfaces.nsIClipboard);
if (!clip)
return;
var trans = Components.classes['@mozilla.org/widget/transferable;1'].createInstance
(Components.interfaces.nsITransferable);
if (!trans)
return;
trans.addDataFlavor('text/unicode');
var str = new Object();
var len = new Object();
var str = Components.classes["@mozilla.org/supports-string;1"].createInstance
(Components.interfaces.nsISupportsString);
var copytext = txt;
str.data = copytext;
trans.setTransferData("text/unicode", str, copytext.length * 2);
var clipid = Components.interfaces.nsIClipboard;
if (!clip)
return false;
clip.setData(trans, null, clipid.kGlobalClipboard);

}

评论:这个方案只适合Firefox,同时也有安全的问题,如果想使用这个方法,必须手动开启Firefox的 一个配置:signed.applets.codebase_principal_support。方法是:在地址栏输输入 “about:config”,设置“signed.applets.codebase_principal_support”为true,默认该项是false

image

这个方法虽然可以在Firefox使用,但让一个普通用户去开启这个配置似乎太专业了。

把这个方案和方案一结合,可以适用80%以上的用户。

支持浏览器:Firefox1.5  firefox2.0  firefox3.0

方案三:JavaScript + flash

代码示例:

var flashcopier = 'flashcopier';
if (!document.getElementById(flashcopier)) {
var divholder = document.createElement('div');
divholder.id = flashcopier;
document.body.appendChild(divholder);
}
document.getElementById(flashcopier).innerHTML = '';
var divinfo = '<embed src="clipboard.swf" FlashVars="clipboard=' + text2copy + '"
width="0" height="0" type="application/x-shockwave-flash"></embed>'
;
document.getElementById(flashcopier).innerHTML = divinfo;}

评论:这个方案是一个最流行的方法,著名的Clipboard Copy解决方案 利用一个clipboard.swf作为桥梁,复制内容到剪贴板。原理是:创建一个隐藏的flash文件,同时给给flash的变量FlashVars 赋值“clipboard=..”,通过这个赋值flash就会把复制的内容放到剪贴板。这个方法兼容IE、Firefox、OperachromeSafari,真可谓“万能”的解决方案。浏览器Flash的安装率非常高,这几乎是一个完美的解决方案。

clipboard.swf 的下载地址:http://watch-life.googlecode.com/files/clipboard.swf

但是,世界上有完美的事么?这个方法只支持flash 9 ,不支持最新的flash 10。因为flash10中规定了只有在swf上进行了真实的操作(比如鼠标点击)才能访问剪切板,而上述方法只是使用了一个隐藏的swf文件,通过 JavaScript操作flash的剪贴板,用户并没有对swf文件进行真实的操作,因此这个方法也就失效了。

那么如何解决这个“真实操作”的问题呢?可以使用一个JavaScript库:Zero Clipboard, 利用这个js库可以支持利用flash 10 实现复制到剪贴板。这个方法原理是在一个透明的flash(对用户来说是不可见的)上覆盖一个dom元素比如button或div,当点击这个dom时, 实际点击的是flash,从而访问flash的剪贴板。真够曲折的。:-)

具体的实现详见如下链接:

DEMO页面 :  http://bowser.macminicolo.net/~jhuckaby/zeroclipboard/

Zero Clipboard项目主页: http://code.google.com/p/zeroclipboard/

支持浏览器:Firefox1.5  firefox2.0  firefox3.0 IE5.5 IE6.0 IE 7.0 opera 8 opera 9 chorme 1.0 chorme 2.0 safari 3

[JQuery]Ajax+jQuery实现LightBox与服务器通信

mikel阅读(817)

  LightBox效果又叫windows关机效果。就是像windows关机时一样,桌面上的应用失效而只能点击关机等框内的几个按钮。在网页中其实就是利用了两个层。其中一个设置成半透明遮住整个屏幕,另一个放在其上面用来显示内容。

     在写之前看过了cloudgamer的效果,自认JavaScript功力远不及他。参考了他的效果之后写了这个简单版并加入了LightBox与服务器的通信。
     为了方便演示,我把代码都写到一个文件里了,css也直接写在行内。
     首先是覆盖层:

<div id="coverLayer" style=" display:none; background:#000000;  position:absolute; "></div>    

     先不让他显示、黑色背景、绝对定位。

     然后是LightBox:

1    <div id="lightBox" style=" display:none; width:300px; height:200px;position:absolute;z-index:1001; background:#ffffff; left:50%; top:50%; margin-left:-150px; margin-top:-100px; border:#00FFFF double 4px;">
2        <id="discover" href="#" onclick="discover();">关闭</a>
3        <form id="formLogin">
4        <p>用户名:<input id="tbUserName" name="tbUserName" type="text" /></p>
5        <p>密码:<input id="tbPassword" name="tbPassword" type="password" /></p>
6        <p><input id="btnLogin" name="" type="button" value="登录" onclick="Login();" /></p>
7        </form>
8    </div>
9

     要与数据库通信,我放了一个表单在里面获取输入。两个文本框、一个按钮。还有一个关闭LightBox的连接。对于LightBox的样式。首先还是不让其显示,高、宽这样的就不用说了。绝对定位。设置z-index为1001保证他显示在最上面。白色背景。left:50%; top:50%; margin-left:-150px; margin-top:-100px;让其居中。这是css中一种比较常见的设置块状容器居中的方法。当然,这和容器的定位有关。cloudgamer的文章中也讲到了这种方法。要了解更多的关于css的东西可以去标准之路 http://www.aa25.cn/ 或者蓝色理想 http://www.blueidea.com/等地方。当然园子里也很多。(好像有点啰嗦-_-)之后是为了好看设置了一个4像素的边框。

     再是要一个开启LightBox的链接

<id="cover" href="#" onclick="cover();">登录</a>

     单击他就执行cover();覆盖函数。

     最后加了一个select用于在IE中屏蔽的测试

1  <form >     
2     <label>select
3        <select name="select" id="select" >
4            <option>测试Select覆盖</option>
5            <option>测试Select覆盖</option>
6        </select> 
7     </label>
8 </form>

 

     之后是JavaScript
     先看一下cover();覆盖函数,这个函数只是让遮盖层和LightBox显示出来,并影藏所有的select,以免在IE中效果有误。

 1<script type="text/javascript" >
 2    function cover()
 3    {
 4        $("select").each(function(){this.style.visibility="hidden";})
 5        //选择所有的select并设置为隐藏
 6         $("#coverLayer").fadeTo("fast",0.5,function(){$("#coverLayer").css("display","block");})
 7                        .width(Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth))
 8                        .height(Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight));
 9        //显示覆盖层 并设置其高和宽
10        $("#lightBox").show();
11        //显示LightBox层
12    }

13</script>

     关于遮盖层的高和宽的设置cloudgamer已经有很详细的讲述了。我也是参考他的做法。这里就不多讲了。有兴趣大家可以直接去看:http://www.cnblogs.com/cloudgamer/archive/2008/09/15/1290954.html

     再是discover();故名思议,这个函数的功能刚好和上一个相反,所以只需要把两个层设成影藏并重新显示刚才影藏的select就可以了。

1    function discover()
2    {
3        $("select").each(function(){this.style.visibility="visible";})
4        $("#coverLayer").fadeOut("normal",function(){$("#coverLayer").css("display","none");})
5        $("#lightBox").fadeOut("normal",function(){$("#lightBox").css("display","none");})
6    }

7
8

 

 

     到这里LightBox效果就基本实现了。接下来要做的是用Ajax让LightBox和服务端通信。首先创建XMLHttpRequest对象:

 1var xmlHttp;
 2
 3function CreateXMLHttpRequest()
 4{
 5    if (window.ActiveXObject)
 6    {
 7        xmlHttp=new ActivXObjct("Microsoft.XMLHTTP");
 8    }

 9    else
10        if (window.XMLHttpRequest)
11        {
12            xmlHttp=new XMLHttpRequest();
13        }

14        
15}

16

    这里的变量xmlHttp是全局的,下面还会再用到。因为IE和其他标准浏览器的XMLHttpRequest对象不一样,所以我用了两个条件判断来兼容IE。

     创建了XMLHttpRequest对象以后就要用它向服务端发送请求了

 1function Login()
 2{
 3    CreateXMLHttpRequest();
 4
 5    var strUserName=$("#tbUserName").val();
 6    var strPassword=$("#tbPassword").val();
 7    var url="/test/Ajax.aspx?userName="+strUserName+"&password="+strPassword;
 8    xmlHttp.open("GET",url,true);                
 9    xmlHttp.onreadystatechange=CallBack;
10    xmlHttp.send(null);
11}

12

     首先获取表单上的值,并根据获得的值构造请求用的url。再用get方法发送请求。这些似乎都是Ajax的标准代码了。发送了请求我们再看服务器要怎么响应了。

 1    protected void Page_Load(object sender, EventArgs e)
 2    {
 3        //string strAction = Request.QueryString["action"];
 4        //switch (strAction)
 5        //{
 6        //    case login:
 7        //        {
 8        string strUserName = Request.QueryString["userName"];
 9        string strPassword = Request.QueryString["password"];
10        LoginCallBack(strUserName,strPassword);
11                    //break;
12        //        }
13        //}
14
15
16    }

17
18    protected void LoginCallBack(string strUserName,string strPassword)
19    {
20        string strCommandText = "Select * FROM admin Where name='" + strUserName + "' AND password='" + strPassword + "'";
21        SQLConnection myConnection = new SQLConnection(strCONNECTION_STRING);
22        SqlCommand myCommand = new SqlCommand(strCommandText, myConnection);
23        SqlDataReader myReader;
24        myConnection.Open();
25        myReader = myCommand.ExecuteReader();
26        if (myReader.Read())
27        {
28            Session["UserName"= myReader.GetString(1);
29            Session["Password"= myReader.GetString(2);
30            Response.ContentType = "text/plain";
31            Response.AppendHeader("Cache-Control""no-cache");
32            Response.AppendHeader("Pragma""no-cache");
33            Response.Write("欢迎:" + Session["UserName"]);
34            Response.End();
35        }

36        else
37        {
38            Response.Write("登录失败");
39        }

40    }

41}
42

     第一步当然是获得查询参数。之后再作为函数的参数用函数对其进行处理。我这里是模仿了一个登录。至于怎么样从数据库中查出数据以及查出什么数据不是我们这篇文章要讨论的问题。这里不多说。关键是下面几句

1             Response.ContentType = "text/plain";
2             Response.AppendHeader("Cache-Control""no-cache");
3             Response.AppendHeader("Pragma""no-cache");
4             Response.Write("欢迎:" + Session["UserName"]);
5             Response.End();
6 

     第一句是设置XMLHttpRequest对象返回的类型。我这里设置的是纯文本。也可以设置成“text/xml”。这样返回的就是 xml类型的。在客户端可以用DOM获取里面的内容。接下去的两句是设置首部,使浏览器不会在本地缓存结果。之所以要设置两个是为了向后兼容。然后那句大 家就应该很熟悉了——输出内容。最后是关闭或者说结束response对象。

     现在服务端已经返回数据了,看在客户端的接收。

 1function CallBack()
 2{
 3    if(xmlHttp.readyState==4)
 4    {
 5        if(xmlHttp.status==200)
 6        {
 7            var strMeesage=xmlHttp.responseText;
 8            InsertMessage(strMeesage);
 9Tag标签: jQuery,JavaScript,C#,LightBox,Ajax

posted @ 2009-03-04 22:34 咖啡不苦

[C#]CodeFx:一站式微软开发技术解决方案

mikel阅读(896)

转载:http://www.cnblogs.com/phinecos/archive/2009/03/04/1403450.html    
晚上在博客堂读完VSTO
写的一篇文章,介绍了CodePlex上面的一个项目,叫做All-In-One Code Framework,代号CodeFx简单的说,就是收集了几乎所有常见的微软开发技术的示例项目,将其打包到这个框架里,而且还使用多各种不同的语言进行实现。比如创建一个ActiveX控件和COM组件,CodeFx里面使用ATLMFCVBC#来实现同样的功能。

适合新手入门,也可以作为一份模板供经验丰富的开发者使用,可节省大量的时间。官方网站上给出了框架的基本结构,如下图所示:

COM组件和ActiveX控件示例

 

数据访问示例

库示例

进程间通信示例 

 

花了2个小时粗略阅读了代码,记录下学习心得:

1)先来说ActiveX这条线,它里面使用了ATL(这里有2种实现,进程内和进程外)MFC,C#,VB四种技术来实现。功能就是四点:一个返回字符串的HelloWorld方法,一个float类型的属性FloatProperty,一个返回进程号和线程号的GetProcessThreadID方法,一个FloatPropertyChanging事件。

2)授权支持是 ActiveX 控件的一项可选功能,它使您得以控制能使用或分发该控件的人。(请参见MFC ActiveX 控件:授权 ActiveX 控件》)。

头文件的修改     

“ActiveX 控件向导将下列代码放置在控件头文件中。声明了 factory 对象的两个成员函数,其中一个成员函数验证控件 .LIC 文件是否存在,而另一个成员函数则对包含该控件的应用程序中使用的许可证密钥进行检索:

    BEGIN_OLEFACTORY(CMFCActiveXCtrl)        // Class factory and guid
        virtual BOOL VerifyUserLicense();
        
virtual BOOL GetLicenseKey(DWORD, BSTR FAR*);
    END_OLEFACTORY(CMFCActiveXCtrl)

 

实现文件的修改

“ActiveX 控件向导将下面两条语句放置在控件实现文件中,以声明许可文件名和许可字符串:

static const TCHAR BASED_CODE _szLicFileName[] = 
   _T(
"License.lic");
static const WCHAR BASED_CODE _szLicString[] =
   L
"Copyright (c) 2000 ";

注意:如果以任何方式修改 szLicString,则必须也修改控件 .LIC 文件的第一行,否则授权将无法正确运行。
“ActiveX 控件向导”将下列代码放置在控件实现文件中,以定义控件类的 VerifyUserLicense 函数和 GetLicenseKey 函数:

// CMFCActiveXCtrl::CMFCActiveXCtrlFactory::VerifyUserLicense –
// Checks for existence of a user license

BOOL CMFCActiveXCtrl::CMFCActiveXCtrlFactory::VerifyUserLicense()
{
    
return AfxVerifyLicFile(AfxGetInstanceHandle(), _szLicFileName,
        _szLicString);
}
// CMFCActiveXCtrl::CMFCActiveXCtrlFactory::GetLicenseKey –
// Returns a runtime licensing key
BOOL CMFCActiveXCtrl::CMFCActiveXCtrlFactory::GetLicenseKey(DWORD dwReserved,
    BSTR FAR
* pbstrKey)
{
    
if (pbstrKey == NULL)
        
return FALSE;
    
*pbstrKey = SysAllocString(_szLicString);
    
return (*pbstrKey != NULL);
}

最后,“ActiveX 控件向导修改控件项目 .IDL 文件。将关键字 licensed 添加到控件的 coclass 声明中,如下例所示:

    [ uuid(E389AD6C4FB647AFB03AA5A5C6B2B820), licensed,
      helpstring(
"MFCActiveX Control"), control ]
    coclass MFCActiveX

  3)作者封装了一个方法AutoWrap来调用COM组件公开出来的属性或方法。

HRESULT AutoWrap(int autoType, VARIANT *pvResult, IDispatch *pDisp, 
                 LPOLESTR ptName, 
int cArgs
{
    
// Begin variable-argument list
    va_list marker;
    va_start(marker, cArgs);
    
if (!pDisp) 
    {
        _putts(_T(
"NULL IDispatch passed to AutoWrap()"));
        _exit(
0);
    }
    
// Variables used
    DISPPARAMS dp = { NULL, NULL, 00 };
    DISPID dispidNamed 
= DISPID_PROPERTYPUT;
    DISPID dispID;
    HRESULT hr;
    
char szName[200];
    
    
// Convert down to ANSI
    WideCharToMultiByte(CP_ACP, 0, ptName, 1, szName, 256, NULL, NULL);
    
    
// Get DISPID for name passed
    hr = pDisp->GetIDsOfNames(IID_NULL, &ptName, 1, LOCALE_USER_DEFAULT,
        
&dispID);
    
if (FAILED(hr))
    {
        _tprintf(_T(
            
"IDispatch::GetIDsOfNames(\"%s\") failed w/err 0x%08lx\n"
            ), szName, hr);
        
return hr;
    }
    
    
// Allocate memory for arguments
    VARIANT *pArgs = new VARIANT[cArgs+1];
    
// Extract arguments
    for(int i=0; i<cArgs; i++
    {
        pArgs[i] 
= va_arg(marker, VARIANT);
    }
    
    
// Build DISPPARAMS
    dp.cArgs = cArgs;
    dp.rgvarg 
= pArgs;
    
    
// Handle special-case for property-puts
    if (autoType & DISPATCH_PROPERTYPUT)
    {
        dp.cNamedArgs 
= 1;
        dp.rgdispidNamedArgs 
= &dispidNamed;
    }
    
    
// Make the call
    hr = pDisp->Invoke(dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT,
        autoType, 
&dp, pvResult, NULL, NULL);
    
if (FAILED(hr)) 
    {
        _tprintf(_T(
            
"IDispatch::Invoke(\"%s\"=%08lx) failed w/err 0x%08lx\n"
            ), szName, dispID, hr);
        
return hr;
    }
    
// End variable-argument section
    va_end(marker);
    
    delete[] pArgs;
    
    
return hr;
}

4)DLL的延迟加载使得我们不需要使用LoadLibrary和GetProcAddress。这样的好处是直到程序调用DLL中的函数时才加载此DLL。

#include <Delayimp.h>

  卸载延迟加载的DLL的代码:

    PCSTR pszDll = "CppDllExport.dll";
    _tprintf(_T(
"__FUnloadDelayLoadedDLL2 => %d\n"),
    __FUnloadDelayLoadedDLL2(pszDll));

posted on 2009-03-04 23:15 Phinecos(洞庭散人)

[C#]请别埋没了URL Routing

mikel阅读(736)

转载:http://www.cnblogs.com/JeffreyZhao/archive/2009/03/05/fully-leverage-url-routing.html

实现分析

  既然Model Binder机制有着明显的缺陷,那么我们又该如何处理这样的问题呢?

   我们再来回顾一下目前问题:对于从URL中表现出来的参数,我们可以把URL Routing捕获到的数据使用Model Binder进行转化(例如上例中的DateTimeModelBinder);但是如果我们在生成URL时直接提供复杂参数,则框架只会把它简单的 ToString后放入URL。这是因为那些与URL有关的HTML Helper会将数据交给URL Ruoting组件来生成URL,而Route规则在生成URL时不知道一个复杂对象该如何转变为URL,因此……

  慢着,你刚才说, 把数据“交给URL Routing组件来生成URL”?URL Routing不是解析URL用的吗?为什么还负责“生成”URL?没错,与Model Binder不同,URL Routing的工作职责是“双向”的。它既负责从URL中提取RouteData,也负责根据Route生成一个URL——可惜微软没有对URL Routing给出足够的资料,有相当多的朋友没有意识到这一点。

  可恶的微软。

  既然问题的原因是Model Binder的“单向性”,那么如果存在一个“双向”的Model Binder就应该可以解决问题。例如,我们可以继承现有的IModelBinder接口进行扩展,那么至少从解析URL到执行Action方法这个流程 中所有的功能都不需要任何额外工作。可惜,这种做法对于大多数HTML Helper来说,我们就必须定义新的扩展,才能利用所谓的“双向Model Binder”。不过其实我们可以有更好的解决方案——成本低廉,通用性强。既然上次提到了传说中的“Model Binder强迫症”,那么我们现在就把目光移到Model Binder以外的地方。

  您一定已经猜到我们要从哪里入手了。没错,就是URL Routing。关于这方面,大名鼎鼎的Scott Hanselman同 学提出将DateTime类型进行分割,也就是将一个DateTime切成年、月、日多个部分进行表示。这个做法老赵颇不赞同,无论从易用性还是通用性等 角度来看,这种做法都是下下之策。说实话,这样的做法其实并没有跳出框架既有功能给定的圈子,它只是通过“迎合框架”来满足自己的需求,而不是让框架为我 们的需求服务。

  那么,我们来分析一下URL Routing组件的运作方式吧,这是必要的预备工作:

  • 首 先,应用程序为RouteCollection类型的RouteTable.Routes集合添加一些Route规则,每个规则即为一个 RouteBase对象。RouteBase是一个抽象类型,其中包含两个抽象方法,GetRouteData和GetVirtualPath。
  • 在 捕获URL中数据的时候,URL Routing组件将调用RouteTable.Routes.GetRouteData方法来获得一个RouteData对象。简单来说,它会依次调用 每个RouteBase对象的GetRouteData方法,直到得到第一个不为null的RouteData对象。
  • 在生成URL 时,URL Routing组件将调用RouteTable.Routes.GetVirtualPath方法来获得一个VirtualPathData对象。简单来 说,它会依次调用每个RouteBase对象的GetVirtualPath方法,直到得到第一个不为null的VirualPathData对象。

   显然,光有RouteBase抽象类型是不足以提供任何有用功能的。因此URL Routing框架还提供了一个具体的Route类型供大家使用。说起Route类,它的功能可谓非常强大。我们在使用ASP.NET MVC框架时用到的MapRoute方法,其实就是在向RouteTable.Routes集合中添加Route对象。而其中的URL占位符,默认值,约 束等功能,实际上完全由Route对象实现了。多么强大的Route类型!如果想要写一个足以匹敌,并且包含额外功能的RouteBase实现可不是一件 容易的事情。幸好我们生活在面向对象的美好世界中,“复用”是我们手中威力非凡的利器。如果我们基于现有的Route类型进行扩展,那么大部分的工作我们 弹指间便可完成。

  现有的Route只能从URL中提取字符串类型的数据,同时也只能把任何对象作为字符串来生成URL。而我们将要构 造RouteBase实现,就要弥补这一缺陷,让Route规则能够直接从URL中提取出复杂对象,并且知道如何将一个复杂对象转化为一个URL。有了前 者,RouteData就能包含复杂类型的对象,以此应对Action方法的参数自然不是问题;有了后者,我们只需要提供一个强类型的复杂对 象,Route规则也能顺利地将其转化为可以识别的URL——多么美好。

Route Formatter

  那么解析字符串,或生成URL的职责由谁来完成呢?于是我们定义一个IRouteFormatter来负责这件事情:

public interface IRouteFormatter
{
bool TryParse(object value, out object output);
bool TryToString(object value, out string output);
}

  TryParse方法负责将一个对象转化为我们需要的复杂类型对象,而TryToString则将一个复杂类型对象转化为字符串(即URL)。 两个方法都返回一个布尔值,以表示这次转化是否合法。您可能会发现,TryToString输出的是一个string,而TryParse……他接受的是 一个object类型的参数,这是怎么回事呢?原因在于Route规则中的“默认值”设置。在Route规则中我们可以为RouteData中的某个“字 段”设定默认值,这样即使URL中无法捕获到这个字段,它也可以出现在RouteData中。从URL中捕获得到的自然是一个字符串,但是默认值则可以设 为任意类型的对象。因此Formatter需要可以接受一个object参数,并设法将其转化为我们需要的复杂类型。

  是不是有点绕?请继续看下去,您会了解它的作用的。虽说TryParse需要接受一个object参数,但是在大多数情况下,我们更多是要处理强类型。因此我们不妨再定一个RouteFormatter抽象类,方便强类型IRouteFormatter对象的编写:

public abstract class RouteFormatter<T> : IRouteFormatter
{
public abstract bool TryParse(string value, out T output);
public abstract bool TryToString(T value, out string output);
bool IRouteFormatter.TryParse(object value, out object output)
{
if (value is T)
{
output = value;
return true;
}
string s = value as string;
if (s == null)
{
output = null;
return false;
}
else
{
T t;
var result = this.TryParse(s, out t);
output = t;
return result;
}
}
bool IRouteFormatter.TryToString(object value, out string output)
{
if (value is T)
{
return this.TryToString((T)value, out output);
}
else
{
output = null;
return false;
}
}
}

  RouteFormater<>类接受一个范型参数,并且准备两个强类型的抽象方法让子类实现。至于接口中的两个类型,它们会处理 一部分逻辑——主要是类型判断——只在合适的时候将操作交给范型方法来实现。TryToString方法朴实无华,而TryParse方法相对较为有趣, 它会首先判断value参数的类型,如果已经符合当前的范型类型,则直接将其转化后返回。这就是为了“默认值”而进行的处理,例如用户准备了一个 DateTime类型的默认值,并被Route规则采纳了,则我们的RouteFormatter<DateTime>就会将其直接返回,不 做任何转化。

  为了解决目前提出的问题,我们会编写一个DateTimeFormatter,它接受一个Format参数表示日期的格式:

public class DateTimeFormatter : RouteFormatter<DateTime>
{
public string Format { get; private set; }
public DateTimeFormatter(string format)
{
this.Format = format;
}
public override bool TryParse(string value, out DateTime output)
{
return DateTime.TryParseExact(value, this.Format, null, DateTimeStyles.None, out output);
}
public override bool TryToString(DateTime value, out string output)
{
output =  value.ToString(this.Format);
return true;
}
}

  那么有没有某个Route Formatter需要直接实现IRouteFormatter接口呢?有。之前提到TryParse方法将在value参数符合范型T的情况下直接返回 “通过”,如果某个Route Formatter不支持这条判断,则自然无法继承于RouteFormatter<>类型。例如下面的RegexFormatter,将使 用正则表达式对某个字段的值进行约束。在我们的RouteBase实现中,RegexFormatter便是Route类中“约束”功能的替代品。如下:

public class RegexFormatter : IRouteFormatter
{
public Regex Regex { get; private set; }
public RegexFormatter(string pattern)
{
this.Regex = new Regex(pattern,
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled);
}
public bool TryParse(object value, out object output)
{
string s;
bool result = this.Try(value, out s);
output = s;
return result;
}
public bool TryToString(object value, out string output)
{
return this.Try(value, out output);
}
private bool Try(object value, out string output)
{
var s = value as string;
if (s != null && this.Regex.IsMatch(s))
{
output = s;
return true;
}
else
{
output = null;
return false;
}
}
}

  RegexFormatter的关键在于Try方法。Try方法首先判断value参数是否为一个字符串,如果是,则使用正则表达式进行验证。当且仅当value为字符串并满足指定的正则表达式时,RegexFormatter才表示“通过”。

FormatRoute实现

  FormatRoute便是我们RouteBase抽象类的实现,它提供了Route类的所有功能,并可以为每个字段设置一个Route Formatter对象,以此对这个字段进行转换或约束。之前提到,我们会将主要功能委托给现有Route类型,这样可以大大简化我们的工作量。因此,我 们会在FormatRoute中包含一个Route类型的对象,此外还会保留所有字段与其Route Formatter的映射关系。请看如下构造函数:

public class FormatRoute : RouteBase
{
private Route m_route;
private IDictionary<string, IRouteFormatter> m_formatters;
public FormatRoute(
string url,
RouteValueDictionary defaults,
IDictionary<string, IRouteFormatter> formatters,
RouteValueDictionary constaints,
RouteValueDictionary dataTokens,
IRouteHandler routeHandler)
{
this.m_formatters = formatters;
this.m_route = new Route(
url,
defaults,
constaints,
dataTokens,
routeHandler);
}
...
}

  RouteBase的关键方法便是GetRouteData和GetVirtualPath。有了Route类型的辅助,这两个方法其实非常简单。如下:

public override RouteData GetRouteData(HttpContextBase httpContext)
{
var result = this.m_route.GetRouteData(httpContext);
if (result == null) return null;
var valuesModified = new Dictionary<string, object>();
foreach (var pair in result.Values)
{
var key = pair.Key;
IRouteFormatter formatter = null;
if (this.m_formatters.TryGetValue(key, out formatter))
{
object o;
if (formatter.TryParse(pair.Value, out o))
{
valuesModified[key] = o;
}
else
{
return null;
}
}
}
foreach (var pair in valuesModified)
{
result.Values[pair.Key] = pair.Value;
}
return result;
}
public override VirtualPathData GetVirtualPath(
RequestContext requestContext, RouteValueDictionary values)
{
var routeValues = new RouteValueDictionary();
foreach (var pair in values)
{
var key = pair.Key;
IRouteFormatter formatter = null;
if (this.m_formatters.TryGetValue(key, out formatter))
{
string s;
if (formatter.TryToString(pair.Value, out s))
{
routeValues[key] = s;
}
else
{
return null;
}
}
else
{
routeValues[key] = pair.Value;
}
}
return this.m_route.GetVirtualPath(requestContext, routeValues);
}

  GetRouteData会接受一个HttpContextBase对象,并调用Route对象的GetRouteData方法获取一个 RouteData对象。如果RouteData不为null,则遍历其中的所有字段,如果指定了对应的Route Formater,则还需要通过Route Formatter的检验及转化——没错,经历了Route Formatter之后的RouteData中已经包含了强类型对象。而GetVirtualPath方法则略有不同,它首先遍历values参数中的所 有字段,将其中的强类型对象转化为字符串,也就是URL片段,这样交给Route对象来生成VirtualPathData时,便可以得到正确的URL 了。

  最后便是FormatRoute的运用:

routes.Add(
"Demo.Date",
new FormatRoute(
"{controller}/{action}/{date}",
new RouteValueDictionary(), // defaults
new Dictionary<string, IRouteFormatter>
{
{"controller", new RegexFormatter("Demo")},
{"action", new RegexFormatter("Date")},
{"date", new DateTimeFormatter("yyyy-MM-dd")}
},
new RouteValueDictionary(), // constaints
new RouteValueDictionary(), // data tokens
new MvcRouteHandler()));

  除了为date字段指定了转化用的DateTimeFormatter之外,我们也为controller和action字段提供了负责约束的 RegexFormatter——这点只是为了演示。更好的做法是直接将URL设为Demo/Date/{date},并在默认值中指定 controller和action的值。此外,您也可以使用传统的方式为字段提供约束,而不是使用RegexFormatter。当然,效果几乎可以说 是一模一样的。

总结

  现在我们完美地解决了之前提出的问题。使用FormatRoute可以轻松地处理URL中特定类型对象的提取,并且可以把特定类型的对象转化为 URL的片段。除了日期时间之外,我们还可以转化语言文化,查询条件等任意复杂类型。而RouteFormatter对象与Route规则的分离,使得我 们可以对RouteFormatter进行独立的单元测试,这也是一件十分理想的事情。这下在视图中,无论是指定Route Values,还是使用强类型的方式,我们都可以正确获得所需的URL了。如下:

<%= Html.ActionLink("Yesterday", "Date", new { date = date.AddDays(-1) }) %>
<span><%= date.ToShortDateString() %></span>
<%= Html.ActionLink<DemoController>(c => c.Date(date.AddDays(1)), "Tomorrow") %>

  那么,从设计上讲,把数据的提取转移到URL Routing上是否合适呢?答案是肯定的。因为URL Routing的职责原本就是从URL中提取数据——任意类型的数据,以及把数据转化为URL,我们现在只是充分利用了URL Routing的功能而已。事实上,我建议任何使用URL表示的数据,都把转化的职责转移到URL Routing这一层,因为这时我们基本上无可避免地需要根据数据来生成URL。一般情况下,我们要尽可能地使用强类型数据。那么Model Binder难道就没有用了吗?当然不是。URL Routing负责从URL中提取数据,而Model Binder则用于从其他方面来获取参数。例如POST来的数据,例如《最佳实践》中的Url Referrer参数。

  打开视野,发挥程序员的敏捷思路,生活就会变得更加美好。