网站开发

asp.net
随笔 - 30, 文章 - 0, 评论 - 9, 引用 - 0
数据加载中……

2006年11月7日

提高网站在Google中的排名 ——面向搜索引擎的网站设计

Google排名优化-面向搜索引擎的网站设计


内容摘要:
目前中文网站在整个互联网中的影响还比较小,这主要是由于中文网站总体的水平(技术上,内容上)都还相对落后造成的,最主要的表现有:

  1. 行业知识:不知道搜索引擎对吸引的新用户的重要性,在搜索引擎排名服务中追求“傻瓜相关”,购买一些其实没有太多实际意义的行业关键词。其实能够用户输入的关键词越多,其目标性越强,这样的客户价值越高。用户能够直接定位到产品具体内容页比到网站首页有价值的多;
  2. 发布技术:网站的网页进入Google的索引量非常小,主要是由于大量使用动态网页造成的。需要将动态页面链接改写成静态链接
  3. 页面设计:页面标题重复,关键词不突出,过渡使用JavaScript脚本/图片/Flash等不适合搜索引擎索引的非文本形式;
而以上这些问题的根本原因还是网站自身缺乏日志统计分析造成:因此无法看到SEO对网站自身产生的巨大影响。

SEO的目的是通过一些简单的策略,让网站自身的水平在互联网中有一个真实的体现。
网站结构设计中面向搜索引擎的优化注意事项包括:

(注意:本网站设计本身就利用了其中一些方法)。

什么是PageRank

Google等新一带搜索引擎的优势之一在于不仅索引量很大,而且还将最好的结果排在搜索结果的最前面,具体的原理可以参考Google の秘密 - PageRank 徹底解説一文,PageRank简单的说类似于科技论文中的引用机制:谁的论文被引用次数多,谁就是权威。在互联网上PageRank就是基于网页中相互链接关系的分析得出的。

此外,从计算方法角度阐述PageRank机制还有这篇文章:http://pr.efactory.de/里面有更详细的PageRank算法说明和各种清晰的小型个案分析:

比如:子页中导航条的重要性
B <=> A => C  
Vs  
B <=> A <=> C   (好)

页面数量因素:
B <=> A <=> C
Vs
F <=\   /=> G
B <=> A <=> C   (好)
D <=/   \=> E

一个意想不到的结论:
(B <=> A <=> C)      ( E <=> D <=> F)
Vs
(B <=> A <=> C)  <=> ( E <=> D <=> F)
PageRank升高的只是被链接的2个首页A和D,而网站子页面的PageRank平均会略有下降。同时:一个网站进入Google的索引量越大其受类似因素影响越小。
PageRank不对称的页面互链:
Google会用BadRank之类的算法进行纠正,而且一个网页着有来自“与其自身不相称”的高PageRank站点的链接,而自身又缺少足够数量和质量的反相链接的话,其PageRank会自动降低为0,A(pr=7) <=> B(pr=0)
简单的说就是:偶尔要被权威站点反相链接不算数,要被足够多的权威站点引用才能提高自身网页的PageRank。
Web超链分析算法综述(南京大学论文) 更多论文可以通过搜索:“filetype:pdf google pagerank anchor text bayesian”得到

链接就是一切

在互联网的海洋中,最重要的就是互联互通,不被其他网站引用的网站就是“信息孤岛”。“酒好也怕藏的深”,也许这话说起来有点像垃圾邮件广告,但事实就是这样。所以如果做网站的目的不是孤芳自赏,就需要积极的推广自己的网站。
通过搜索引擎推广自己需要注意以下几个方面:

  1. 以量取胜:不一定加入传统门户网站的分类目录才是网站推广,来自其他网站的任何反相链接都是有用的
    网站推广比较经典的方式就是加入比较大型门户网站的分类目录,比如:Yahoo!dmoz.org等。其实这里有一个误区:不一定非要加入大型网站的分类目录才是网站推广,因为现在搜索引擎已经不再只是网站目录的索引,而是更全面的网页索引,所以无论来自其他网站任何地方的反相链接都是非常有价值的,哪怕是出现在新闻报道,论坛,邮件列表归档中。Blogger(Weblog的简称)们也许最深刻地理解了“链接就是一切”这句话的含义,由于Blog的内容之间有大量的相互链接(他们之间利用RSS很容易进行链接大量传播),因此最经常被引用的Blog页面在搜索引擎中的排名往往比一些大型商业网站的页面还要高。而WIKI这样的文档管理系统更加突出了良好结构,良好引用的特征。而目前很多网站通过在各种WIKI/留言簿中灌注垃圾留言的方法,这种方式是非常不可取的。这种链接不稳定:
  2. 以质取胜:被PageRank高的网站引用能更快地提高PageRank
    数量只是关键因素之一,来自PageRank高的页面的链接还能更快的提高被链接目标的PageRank,我只是将一些文章投稿在了ZDNet中国上,由于页面上有文章出处链接,相应网页和网站整体的PageRank过了一段时间后就有了很大的提升。有时候被什么样的网站引用有时候比引用次数多更重要。这里我要特别感谢的是,当时ZDNet中国是唯一遵循了我的版权声明的要求表明了文章出处,并且有反相链接的网站。
    按照这个原则:能够名列Yahoo!和DMOZ这样的大型权威目录的头2层是非常有价值的。
  3. 了解搜索引擎的"价值观":
    Lucene简介这篇文章被Jakarta.apache.org的lucene项目引用以后,这篇文章就成为了所有页面中PageRank最高的页面,而Google支持的一些项目,比如:Folding@Home,让我一直怀疑他们对政府,教育和非盈利组织的站点有特别加分,毕竟.org .edu才代表了互联网的实质:分权和共享。
    但更合理的解释是:.org很多都是开放技术平台的开发者,他们会在首页这样的地方加入Powered By Apache, Power by FreeBSD之类的链接表示对其他开源平台的尊重,所以象Apache, PHP, FreeBSD这样的开源站点在GOOGLE中都有非常高的PageRank。而在.edu这些站点中,很多都是学术性比较强的文档,以超链接形式标明参考文献的出处已经成为一种习惯,而这也无疑正是PageRank最好的依据。
    注意:不要通过Link Farm提高自身的站点排名:Google会惩罚那些主动链接到Link Farm站点以提高自身排名站点,相应站点的页面将不会被收入到索引中。但如果你的页面被别的Link Farm链接了也不必担心,因为这种被动的链接是不会被惩罚的。
  4. 不要吝啬给其他网站的链接:如果一个网页只有大量的进入链接,而缺乏导出链接,也会被搜索引擎认为是没有价值的站点。保证你的网站能够帮助搜索引擎更准确地判断哪些是对用户最有价值的信息,也就是说如果你的网站只有外部反向链接而没有导出链接的话,也会对你的网站在搜索结果中的表现带来负面影响。当然网站中连一个导出链接都没有的情况非常罕见,除非你是刻意这么做。正常情况下大家都会自然地在网页中加上一些其他网站的链接,带领访问者去到我们认为重要或能够提供更多有价值信息的地方,

另外在推广自己网站之前也许首先需要了解自己网站目前在一些搜索引擎中的知名度,原理非常简单,可以参考如何评价网站的人气一文。

网站推广只是手段,如何突出内容、让需要相关信息的用户能够尽快的找到你的网站才是目的,PageRank高并不代表像Yahoo!这样的门户站点就能在所有搜索结果中排名靠前,因为搜索引擎的结果是搜索关键词在页面中的匹配度和页面的PageRank相结合的排名结果。因此第二个要点:如何突出关键词。

如何突出关键词:面向主题(Theme)的关键词匹配

  1.  Theme Engine正在逐步超过PR,成为结果排序中更主要的因素,可以比较一下以下现象:
    • 为什么查“新闻”,“汽车”之类的文字,各种门户网站的首页不在第一位?要知道他们的页面中都有相应频道的链接文字的
    • 一篇新闻页面中,搜索引擎如何不通过模板匹配,自动将新闻的主体和页面中的页头,栏目导航条,页尾等部分的内容区分开的?
    其实以上问题都可以归结为网页内容摘要的提取策略和关键词在其中的命中:
    首先将能够描述一个页面内容的分成以下几种类型:
    1. 链入文字描述:inbound link text
      http://www.searchenginedictionary.com/terms-inbound-link.shtml
    2. HTML页面标题:title 好标题一般会将页面中最重要的关键词放在最前面,比如:ABC-10型吸尘器 - XX家电城
    3. HTML内容主体:content text
    4. 链出文字:outbound link text
    如果按照以下规则:
    一个页面中关键词命中权重:链入文字 > HTML标题文字 > HTML页面主体内容 >> 出链文字,以上现象就比较好解释了。
    链入文字是页面上看不见的,但链入文字对被链接页面有巨大的作用:在现代搜索引擎在关键词匹配的过程中,匹配的过程不只看当前页面的内容摘要:很大程度上,不只看这个网页自己说自己有什么内容,还要看别人如何链接时,如何描述你的网站别人怎么称呼你,比你自己说自己更重要。。
    比如查:“世界卫生组织”,返回结果中有 http://www.who.int/ 而这个页面中是没有中文的,之所以能匹配上,是因为很多链接它的中文网站使用了:<a href="http://www.who.int/">世界卫生组织</a>,所以,这个页面中不存在的中文关键词也成为了页面摘要的一部分。这样一想,就可以知道链出链接的文字其实是为被链接的子频道首页或者内容详情页服务的。对自身页面的关键词密度只有负面的影响,这也是Google建议一个页面中链接不要超过100个的原因:他根本不索引100个链接以后的内容。按照以上规则,搜索引擎将一篇新闻详情页中的新闻内容提取出来就是把页面上所有带HTTP链接的文字都去掉,就是新闻的内容部分了,更精确一些可以通过取最长文本段落等策略实现;而很多网站首页或频道首页中几乎全部都是链接,经过搜索引擎分析的结果就是:什么内容也没有,能够被命中的关键词仅仅是别人链接你用的“网站首页”和频道标题Title中的站点名称等的几个关键词,而页面中其他的文字则远远不如相应子频道和具体内容页面的匹配度高,而搜索引擎能够通过以上规则,让用户更快的直接定位到有具体内容的内容详情页面。因此希望通过一个首页,尽可能多的命中所有希望推广的关键词是不可能的。让网页尽可能多的进入搜索引擎的索引,然后把握好整个网站的主题风格是非常重要的,让网站的主题关键词能够比较均匀的按照金字塔模式分布到网站中可以参考:网站的主题金字塔设计          网站名称(用户通过1-2个抽象关键词)
             /       \
        子频道1   子频道2 (用户通过2-3个关键词命中)
       /    \      /    \
    产品1  产品2 文章1 文章2 (用户通过3-4个关键词命中:这种用户最有价值)

  2. 不要空着标题:空着<title></title>无异于浪费了最有价值的一块阵地;
    不要使用meta keywords/description
    传统的页面中,HTML页面中会有类似以下的隐含信息,用于说明当前网页的主要内容关键字:
    <header>
        <meta name="keyword" content="mp3 download music...">
    </header>
    后来由于这种人工添加关键词的方式被滥用,大量网页中为了提高被搜索引擎命中的概率,经常添加一些和实际网页内容无关的热门关键比如:“music mp3 download”等,所以新一代的搜索引擎已经不再关心页面头文件中的人工meta keyword声明,而页面标题在搜索引擎的关键词的命中命中过程中往往有着更高的比重,如果一个关键词在标题中命中会比在页面中命中有更高的得分,从而在相应的搜索结果排名中更靠前。
  3. 标题长度和内容:不要过长,一般在40个字(80个字节)以内,并充分突出关键词的比重;
    如果更长的标题搜索引擎一般会忽略掉,所以要尽可能将主要关键词放在标题靠前的位置。省略掉不必要的形容词吧,毕竟用户主要通过名词来找到需要的内容。标题内容:尽量用一些别人可以通过关键词找到的字眼(也别太过头,如果标题中的字眼超过1半内容中都没有,有可能被搜索引擎排除出索引),因此基于web日志中来自其他搜索引擎的关键词查询统计非常必要。
  4. 如果网页很多的话,尽量使用不同的网页标题,争取让自己网站的内容更多的进入搜索引擎索引范围;
    因为搜索引擎会根据页面内容的相似度把一些内容当成重复页面排除出索引范围;
    http://www.chedong.com/phpMan.php是我的网站上的一个小应用:一个web界面的unix命令手册(man page),在以前的设计中所有动态页面使用的都是同样的标题:"phpMan: man page /perldoc /info page web interface" ,Google索引了大约3000多个页面,后来我将页面标题改成了"phpMan:  [命令名]"这样的格式,比如:"phpMan: ls",这样大部分动态页面的标题就都不一样了,一个月后Google从这个页面入口索引了大约6000个页面。因此,如果网站中很多网页都使用相同的标题,比如:“新闻频道”,“论坛”,这些页面中很大一部分就会被排重机制忽略掉。
  5. 除了<title></title>外,还可以用<h1></h1>标题行突出内容主题,加强标题的效果;
    在我的网站设计中:我会把用<h1>[标题]</h1>这样的模板把标题突出显示,而不是通过改变字体的方式突出标题。

其他网站设计提示

  1. 静态链接:Blog网站另外一个优势在于其网页是静态链接:动态网页占到整个互联网内容的90%以上。各个搜索引擎其实能够表现的都只不过是互联网的冰山一角(3-5%),不同的只是谁让优质网页排名靠前的策略更优秀而已:大部分搜索引擎都认为静态链接的网页是优质网页,Google在优先抓取索引的网页中70%以上是不带参数链接的静态网页。而且即使同样的内容,静态网页也会比动态网页权重高:很容易理解:query?a=1&b=2这样的链接由于参数顺序颠倒的query?b=2&a=1完全相同。尽量使用静态网页:目前能够动态网页进行全面的索引还比较难,而即使是Google也更不会索引所有的内容,因此很少会对参数超过2个的动态网页进行进一步的抓取和分析。以下是一个phpBB论坛页面返回的HTTP头信息:
    HTTP/1.1 200 OK
        Date: Wed, 28 Jan 2004 12:58:54 GMT
        Server: Apache/1.3.29 (Unix) mod_gzip/1.3.26.1a PHP/4.3.4
        X-Powered-By: PHP/4.3.4
        Set-Cookie: phpBB_data=a%3A0%3A%7B%7D; expires=Thu, 27-Jan-2005 12:58:54 GMT; path=/;
        Set-Cookie: phpBB_sid=09f67a83ee108ecbf11e35bb6f36fcec; path=/;
        Content-Encoding: gzip
        Cache-Control: private, pre-check=0, post-check=0, max-age=0
        Expires: 0
        Pragma: no-cache
        Connection: close
        Content-Type: text/html
        
    为了避免隐私问题:Googlebot可以通过对页面http header中的session id和session cookie进行一些甄别,这样很多需要认证信息的论坛内容就无法进入索引了。
    总体上说Google喜欢新的,静态的内容。因此无论从效率上讲还是方便搜索引擎收录:使用内容发布系统将网站内容发布成静态网页都是非常必要的,从某种程度上说google friendly = anonymous cache friendly。
    比如:http://www.chedong.com/phpMan.php/man/intro/3
    比http://www.chedong.com/phpMan.php?mode=man&parameter=intro&section=3这样的链接更容易进入搜索引擎的索引。而且在URL中的命中也能突出关键词。
  2. 能够进入Google索引的页面数量越多越好。用类似以下的脚本可以统计自己的网站被搜索引擎索引的情况。
    #!/bin/sh
        YESTERDAY=`date -d yesterday +%Y%m%d`
        # for FreeBSD: YESTERDAY=`date -v-1d +%Y%m%d`
        LOG_FILE='/home/apache/logs/access_log'
        grep -i Googlebot $LOG_FILE.$YESTERDAY|awk '{print $7}' |sort | uniq -c | sort -rn > spider/$YESTERDAY.googlebot.txt
        grep -i baiduspider $LOG_FILE.$YESTERDAY|awk '{print $7}' |sort | uniq -c | sort -rn > spider/$YESTERDAY.baiduspider.txt
        grep -i msnbot $LOG_FILE.$YESTERDAY|awk '{print $7}' |sort | uniq -c | sort -rn > spider/$YESTERDAY.msnbot.txt
        grep -i inktomi $LOG_FILE.$YESTERDAY|awk '{print $7}' |sort | uniq -c | sort -rn > spider/$YESTERDAY.inktomi.txt
        grep -i openbot $LOG_FILE.$YESTERDAY|awk '{print $7}' |sort |uniq -c | sort -rn > spider/$YESTERDAY.openbot.txt
        
  3. 网站目录结构要扁平,因为每深一级目录,PAGERANK降低1-2个档次。假设首页是3,其子可能目录就是1了,更深可能就无法列入评级范围了。
  4. 表现和内容的分离:“绿色”网页
    网页中的javascript和css尽可能和网页分离,一方面提高代码重用度(也方便页面缓存),另外一方面,由于有效内容占网页长度的百分比高,也能提高相关关键词在页面中的比重也增加了。总之,应该鼓励遵循w3c的规范,使用更规范的XHTML和XML作为显示格式便于内容更长时间的保存。
  5. 让所有的页面都有能够快速入口:站点地图,方便网页爬虫(spider)快速遍历网站所有需要发布的内容。如果首页就是用Flash或图片进入的话,无异于将搜索引擎拒之门外,除了UI设计的用户友好外,spider friendly也是非常重要的。
  6. 保持网站自身的健康:经常利用坏链检查工具检查网站中是否有死链。
  7. 保持网页内容/链接的稳定性和持久性:在搜索引擎索引中网页存在的历史也是一个比较重要的因素,而且历史比较久的网页被链接的几率越高。为了保证自己网页能够被比较持久的被其他网站的页面引用,如果自己网页中有链接更新时,最好能保留旧的页面并做好链接转向,以保持内容的连续性。要知道,把一个网站和内容在搜索引擎中的排名“培养”的很高是一件非常不容易的事情,谁都不希望好不容易自己的内容被别人找到了,点击却是“404 页面不存在”吧,因此站点管理员对自身站点error.log的分析也是非常必要的。
  8. 文件类型因素:Google有对PDF, Word(Power Point, Excel), PS文档的索引能力,由于这种文档的内容比一般的HTML经过了更多的整理,学术价值一般比较高,所以这些类型的文档天生就比一般的HTML类型的文档 PageRank要高。因此,对于比较重要的文档:技术白皮书,FAQ,安装文档等建议使用PDF PS等高级格式存取,这样在搜索结果中也能获得比较靠前的位置。
  9. 常常能发现门户站点的一条新闻往往比其他站点的首页排名还要靠前。因此一个站点总体PageRank提高了以后,往往自身一些并不重要的内容也会被同那些高PageRank的内容一起带入被搜索引擎优先查询的列表中。这样经常造成很多大的开发站点的邮件列表归档往往比其他站点的首页PageRank还要高。

知己知彼——站点访问统计/日志分析挖掘的重要性

网站设计不仅仅只是被动的迎合搜索引擎的索引,更重要是充分利用搜索引擎带来的流量进行更深层次的用户行为分析。目前,来自搜索引擎关键词统计几乎是各种WEB日志分析工具的标准功能,相信商业日志统计工具在这方面应该会有更强化的实现。WEB日志统计这个功能如此重要,以至于新的RedHat 8中已经将日志分析工具webalizer作为标准的服务器配置应用之一。

以Apache/webalizer为例,具体的做法如下:
  1. 记录访问来源:
    在Apache配置文件中设置日志格式为combined格式,这样的日志中会包含扩展信息:其中有一个字段就是相应访问的转向来源: HTTP_REFERER,如果用户是从某个搜索引擎的搜索结果中找到了你的网页并点击过来,日志中记录的HTTP_REFERER就是用户在搜索引擎结果页面的URL,这个URL中包含了用户查询的关键词。
  2. 在webalizer中缺省配置针对搜索引擎的统计:如何提取HTTP_REFERER中的关键词
    webalizer中缺省有针对yahoo, google等国际流行搜索引擎的查询格式:这里我增加了针对国内门户站点的搜索引擎参数设置
    SearchEngine yahoo.com p=
    SearchEngine altavista.com q=
    SearchEngine google.com q=
    SearchEngine    sina.com.cn word=
    SearchEngine    baidu.com   word=
    SearchEngine    sohu.com    word=
    SearchEngine    163.com q=

通过这样设置webalizer统计时就会将HTTP_REFERER中来自搜索引擎的URL中的keyword提取出来,比如:所有来自 google.com链接中,参数q的值都将被作为关键词统计下来:,从汇总统计结果中,就可以发现用户是根据什么关键词找到你的次数,以及找到你的用户最感兴趣的是那些关键词等,进一步的,在webalizer中有设置还可以将统计结果倒出成CSV格式的日志,便于以后导入数据库进行历史统计,做更深层次的数据挖掘等。

以前通过WEB日志的用户分析主要是简单的基于日志中的访问时间/IP地址来源等,很明显,基于搜索引擎关键词的统计能得到的分析结果更丰富、更直观。因此,搜索引擎服务的潜在商业价值几乎是不言而喻的,也许这也是Yahoo! Altavista等传统搜索引擎网站在门户模式后重新开始重视搜索引擎市场的原因,看看Google的年度关键词统计就知道了,在互联网上有谁比搜索引擎更了解用户对什么更感兴趣呢?

请看本站的反相链接统计:http://www.chedong.com/log/2003_6.log
需要注意的是:由于Google针对Windows 2000中的IE使用的是UTF-8方式的编码,因此很多统计有时候需要在UTF-8方式下查看才是正确字符显示。从统计中能够感受到:在使用水平比较高的IT开发人员中Google已经成为最常用的搜索引擎。而使用百度的用户也已经大大超过了传统的搜狐,新浪等门户站点,因此传统门户网站在搜索引擎上的优势将是非常脆弱的。而从技术的发展趋势来看,以后还会有更多的利用互联网媒体做更深层次数据挖掘的服务模式出现:

 

转载自cnblog.org——“突发”文字可能揭示社会趋势

在“新科学家”(New Scientist)在线杂志上,公布了康奈尔大学的一个新研究成果,引人注目,也许与Google 收购Pyra 的动机有关。

这所大学的计算机科学家 Jon Klenberg 开发了一个计算机算法,能够识别一篇文章中某些文字的“突发”增长,而且他发现,这些“突发”增长的文字可以用来快速识别最新的趋势和热点问题,因此能够更有效地筛选重要信息。过去很多搜索技术都采用了简单计算文字/词组出现频率的方法,却忽略了文字使用增加的速率。

Jon 特别指出,这种方法可以应用到大量Weblog上,以跟踪社会趋势,这对商业应用也很有潜力。例如,广告商可以从成千上万的个人Blog 中快速找到潜在的需求风尚。而且只要Blog 覆盖话题范围足够大(实际上发展趋势确实如此),这项技术对政治、社会、文化和经济等领域也都会有实际意义了。

虽然Google 新闻的内部算法至今没有公开,但是人们猜测这种完全由机器所搜集的头条新闻应当不是Google搜索引擎中惯用的鸽子算法,很可能与这种“突发”判断算法有关。如此说来,Google收购Blog工具供应商的举动确实还有更深层次的远见了

- NewScientist.com news, Word 'bursts' may reveal online trends
- 还没有写完这些介绍,在 SlashDot 上也看到了很多有关这个发现的讨论

附:Google官方的站点设计指南

  • Make a site with a clear hierarchy and text links. Every page should be reachable from at least one static text link.  让网站有着清晰的结构和文本链接,所有的页面至少要有一个静态文本链接入口
    批注:尽量不要用图片和JAVASCRIPT
  • Offer a site map to your users with links that point to the important parts of your site. If the site map is larger than 100 or so links, you may want to break the site map into separate pages.
    为用户提供一个站点地图:转向网站的重要部分。如果站点地图页面超过100个链接,则需要将页面分成多个页面。
    批注:索引页不要超过100个链接:SPIDER只考虑页面中头100个链接
  • Create a useful, information-rich site and write pages that clearly and accurately describe your content.
    用一些有用的,信息量丰富的站点,清晰并正确的描述你的信息。
  • Think about the words users would type to find your pages, and make sure that your site actually includes those words within it.
    想像用户可能用来找到你的关键词,并保证这些关键词在网站中出现。
    批注:少用“最大”,“最好”之类的形容词,用用户最关心的词,比如:下载,歌星名字,而不是一些抽象名词。
  • Try to use text instead of images to display important names, content, or links. The Google crawler doesn't recognize text contained in images.
    尽可能使用文本,而不是图片显示重要的名称,内容和链接。GOOGLE的机器人不认识图片中的文字。
  • Make sure that your TITLE and ALT tags are descriptive and accurate.
    保证:页面的TITLE和ALT标记正确的精确描述
  • Check for broken links and correct HTML.
    检查坏链并修正这些HTML错误。
  • If you decide to use dynamic pages (i.e., the URL contains a '?' character), be aware that not every search engine spider crawls dynamic pages as well as static pages. It helps to keep the parameters short and the number of them small.
    如果你打算使用动态页面:链接中包含"?",必须了解:并非所有的搜索引擎的机器人能想对待静态页面一样对待动态页面,保持动态页面的参数尽可能的少也会很有帮助。
  • Keep the links on a given page to a reasonable number (fewer than 100).
    让一个页面中的链接少于100个。
    批注:用lynx -dump http://www.chedong.com 可以模拟从robot角度看到的页面。其最后有链接统计

输出类似:

   [1]Google Free Search _______________________________ Google Search
   (_) Search WWW (_) Search chedong.com

   更 新 [2]站点地图 / Site Map [3]留 言簿 / Guest Book [4]意见反馈 /
   Feed Back
    ...
References

   Visible links
   1. http://www.google.com/services/free.html
   2. http://www.chedong.com/sitemap.html#sitemap
   3. http://www.chedong.com/guestbook/
   4. http://www.chedong.com/formmail.htm
    ...
   Hidden links:
  50. http://www.chedong.com/bbcweb/
    ...

搜索引擎的宗旨在于提取互联网中质量最好的内容提供给用户,任何有利于帮助用户获得相对公正,优质内容的策略都是搜索引擎追求目标。PageRank是一个非常好的策略,但是并非所有策略都是基于非常复杂的算法。
从搜索引擎看来什么是互联网中“好”的内容呢?

  • 首先:互联网大约是8G个网页,而且以每天2M的速度增长的。其中80%以上是动态网页,而占总量20%的静态网页就是一个相对比较简单的过滤规则。
  • 其次:用户友好(User friendly)也是很重要的方面,搜索引擎利用算法帮助提升这些优质网站,包括:通过CSS将内容和表现分离:较少的javascript和frame结构,spider本身也很难深入抓取这些网页:Javascript和frame结构大部分是广告。
  • 标题明确:无标题,重复标题或者标题SPAM(类似于:游戏游戏游戏游戏游戏游戏这样的标题)进行过滤或降低得分页面大小:因为页面过大会导致用户下载缓慢,所以很多引擎只计算页面大小在100k以内的网页。
  • 链接引用:不仅需要有链接链入,也需要帮助用户找到其他更有价值的内容;
  • 文件类型:PDF和DOC等专业文档和来自edu,gov等非赢利网站的内容;
  • 链入网站的文字:所有用户不可见的因素全部被忽略。此外:用户搜索的行为本身也被Google记录,可能对目标网站的主题相关度有帮助。

 

参考资料:

面向Google搜索引擎的网站设计优化
http://www.google-search-engine-optimization.com/


关于Google排名优化的7个误区,如:"Meta tag", "泛域名",首页等

如何评价一个网站的人气
http://www.chedong.com/tech/link_pop_check.html

如何提高网站在Google中的排名——面向搜索引擎的广告模式
http://www.chedong.com/tech/google_ads.html

如何提高网站在Google中的排名——面向搜索引擎的网站链接设计
http://www.chedong.com/tech/google_url.html

Google不断改进相应的算法:HillTop
Hilltop: A Search Engine based on Expert Documents

Google の秘密 - PageRank 徹底解説
http://www.kusastro.kyoto-u.ac.jp/~baba/wais/pagerank.html
这篇文章是在查"Google PageRank"的时候查到的,这篇文章不仅有一个算法说明,也是一个Google的weblog,记录了很多关于Google的新闻和一些市场动态信息。
Google 的秘密- PageRank 彻底解说 中文版

更详细的PageRank算法说明:
http://pr.efactory.de/

WEB日志统计工具AWStats的使用:增加了Unicode的解码和中国主要门户搜索的定义
http://www.chedong.com/tech/awstats.html

Robots的说明:
http://bar.baidu.com/robots/
http://www.google.com/bot.html
搜索引擎通过一种程序robot(又称spider),自动访问互联网上的网页并获取网页信息。您可以在您的网站中创建一个纯文本文件 robots.txt,在这个文件中声明该网站中哪些内容可以被robot访问,哪些不可以。

反Google站点:观点也很有趣
http://www.google-watch.org/

关于Google的WebLog
http://google.blogspace.com/

关于Google的HillTop算法

搜索引擎相关论坛
http://searchengineforums.com/
http://searchenginewatch.com
http://www.webmasterworld.com/

posted @ 2008-01-04 09:43 风雨兼程 阅读(253) | 评论 (0)编辑 收藏

开发电子商务网站对数据库表的设计

系统分析

  功能需求分析

  (1):产品展示,按照分类展示全部产品,和对应的相关信息。

  (2):增加产品的展示相关度,诸如最新产品报道,网站的新闻,促销信息。

  (3):购物车,跟踪用户的购物情况

  (4):结算中心:处理用户帐单,购物处理

  (5):反馈互动区,可以留言

  (6):会员信息模块,可以注册

  数据需求分析

  数据库表设计定为8个表实现:

  ccdot_user{用户信息表}:

  ------szd_userid[PK]

  ------szd_username[用户ID]

  ------szd_password[用户密码]

  ------szd_name[用户真是信息]

  ------szd_question[丢失密码问题]

  ------szd_answer[用户回答答案,用于找密码]

  ------szd_sex[性别]

  ------szd_phone[电话]

  ------szd_email[电子邮件]

  ------szd_address[地址]

  ------szd_post[邮编]

  ------szd_province[省份]

  ------szd_city[城市]

  ------szd_mark[标记]

  ccdot_aclass{产品大类}

  ------szd_aclassid[PK]

  ------szd_aclassname[名称]

  ccdot_bclass{产品小类}

  ------szd_bclassid[pk]

  ------szd_aclassid[关联大类szd_aclassid]

  ------szd_bclassname[名称]

  ccdot_poduct{产品信息}

  ------szd_productid[pk]

  ------szd_bclassid[关联小类的szd_bclassid]

  ------szd_pname[名称]

  ------szd_pdetail[产品信息]

  ------szd_pprice[一般价格]

  ------szd_pmemderprice[会员价]

  ------szd_pfewprice[差价]

  ------szd_ppicture[图片]

  ------szd_ptime[添加时间]

  ------szd_stock[产品库存]

  ------szd_phit[产品点击次数]

  ------szd_pdetail1[其他产品描述]

  ccdot_news{新闻}

  ------szd_newsid[PK]

  ------szd_title[标题]

  ------szd_content[内容]

  ------szd_time[添加时间]

  ------szd_type[类型]

  ccdot_guest{评论}

  ------szd_gid[PK]

  ------szd_gname[昵称]

  ------szd_gcontent[留言内容]

  ------szd_productid[关联产品的主键]

  ------szd_gtime[留言时间]

  ------szd_gip[留言者ip]

  ccdot_orderlist{订单列表}

  ------szd_orderlistid[PK]

  ------szd_orderid[关联详细订单的主键]

  ------szd_productid[关联产品的主键]

  ------szd_quantity[所定数目]

  ------szd_unitcost[单价]

  ------szd_productname[产品名称]

  ccdot_orders{订单信息表}

  ------szd_orderid[PK]

  ------szd_userid[关联用户主键]

  ------szd_orderdate[订单日期]

  ------szd_linkman[联系人]

  ------szd_email[联系人email]

  ------szd_phone[联系电话]

  ------szd_postalcode[送货处邮编]

  ------szd_address[送货地址]

  ------szd_result[处理结果]

  ------szd_remark[备注]

  ------szd_songhuoqixian[送货期限]

  ------szd_songhuofangshi[发货方式]

  ------szd_fukuanfangshi[付款方式]

  ------szd_error[意外说明]

posted @ 2008-01-02 15:20 风雨兼程 阅读(341) | 评论 (0)编辑 收藏

petshop4.0设计说明asp.net初学者

petshop4.0设计说明
一、项目名称及描述:(实现步骤为:4-3-6-5-2-1)
1、WEB=表示层
2、BLL=业务逻辑层
3、IDAL=数据访问层接口定义
4、Model=业务实体
5、DALFactory=数据层的抽象工厂(创建反射)
6、SQLServerDAL=SQLServer数据访问层 / OracleDAL=Oracle数据访问层

DBUtility 数据库访问组件基础类

二、项目引用关系
1、Web 引用 BLL。
2、BLL 引用 IDAL,Model,使用DALFactory创建实例。
3、IDAL 引用 Model。
4、Model 无引用。
5、DALFactory 引用IDAL,通过读取web.config里设置的程序集,加载类的实例,返回给BLL使用。
6、SQLServerDAL 引用 Model和IDAL,被DALFactory加载的程序集,实现接口里的方法。


三、实现步骤
1、创建Model,实现业务实体。
2、创建IDAL,实现接口。
3、创建SQLServerDAL,实现接口里的方法。
4、增加web.config里的配置信息,为SQLServerDAL的程序集。
5、创建DALFactory,返回程序集的指定类的实例。
6、创建BLL,调用DALFactory,得到程序集指定类的实例,完成数据操作方法。
7、创建WEB,调用BLL里的数据操作方法。

注意:
1、web.config里的程序集名称必须与SQLServerDAL里的输出程序集名称一致。
2、DALFactory里只需要一个DataAccess类,可以完成创建所有的程序集实例。
3、项目创建后,注意修改各项目的默认命名空间和程序集名称。
4、注意修改解决方案里的项目依赖。
5、注意在解决方案里增加各项目引用。

posted @ 2007-12-26 17:05 风雨兼程 阅读(1553) | 评论 (0)编辑 收藏

Getting Real 读书笔记

无论是创业还是做项目,无论是单机软件还是 Web app,都需要遵循的就是满足需求

需求和开发的矛盾,至少在我看来,是产品开发中的主要矛盾。提需求的觉得产品没有满足需求,开发的觉得需求不明确或者胡来。最后的结果,要么是产品延期,要么是个破烂

是否有一些方法或者经验,可以指导我们更成功地开发 Web app 呢?或者不仅仅是开发,还包括设计,推广,人事……作为创业或者实践的一整套指导

这就是我想推荐给大家的《Getting Real》。

Getting Real 是著名的 37signals 公司(Ruby on Rails 的缔造者)去年撰写的一本书,主要内容是指导小型公司/团体成功地商业运作起 Web 产品,提供了包括相关理念,思路,人员,技术,方法论,实例等等许多信息,非常适合 Web 开发人员,产品人员和创业者参考。

书专注于以下几点:
1. 成功的商业产品,以客户利益和感受至上。
2. 小型的团队,高灵活性和适应性。
3. 精简的产品,够用好用的功能。
4. 高效的设计和开发过程,杜绝浪费时间,减少需求和开发的矛盾。
5. 巧妙的推广和售后支持。
……

posted @ 2007-12-26 15:12 风雨兼程 阅读(250) | 评论 (0)编辑 收藏

层随着鼠标移动

var c = document.getElementById("divTool");
 c.style.position = "absolute";
 var eT = obj.offsetTop; 
    var eH = obj.offsetHeight+eT; 
 c.style.left = window.event.clientX-event.offsetX+document.documentElement.scrollLeft-20;
    c.style.top = window.event.clientY-event.offsetY+document.documentElement.scrollTop+eH-8;


 c.style.display = "block";

posted @ 2007-12-26 14:44 风雨兼程 阅读(555) | 评论 (0)编辑 收藏

解决downlist 等优先极高于其它控件,浮在其它控件上面

 <iframe style="position:absolute;z-index:9;width:expression(this.nextSibling.offsetWidth);height:expression(this.nextSibling.offsetHeight);top:expression(this.nextSibling.offsetTop);left:expression(this.nextSibling.offsetLeft);" frameborder="0" ></iframe>
  <div id="divTool" onmouseover="javascript:showdiv123()" onmouseout="javascript:HiddivTool()" class="class_title"  style="display:none">
<table width="100" border="0"  align="center" cellpadding="0" cellspacing="0">
  <tr>
    <td height="20" colspan="2" bgcolor="ffffff" style="width: 100px" >&nbsp;&nbsp;<span class="t1" style="cursor:hand" name="layout/AddPage.aspx" onclick="javascript:openMyPage(this)">More tools</span></td>
  </tr>
  <tr>
    <td height="20" colspan="2"  bgcolor="ffffff"  style="width: 100px">&nbsp;&nbsp;<a href="#">About this tools</a></td>
  </tr><tr>
    <td height="8" colspan="2" bgcolor="ffffff" style="width: 100px"></td>
  </tr>
 
  </table>
</div>

posted @ 2007-12-26 14:35 风雨兼程 阅读(319) | 评论 (0)编辑 收藏

在PetShop 4.0中ASP.NET缓存的实现

PetShop作为一个B2C的宠物网上商店,需要充分考虑访客的用户体验,如果因为数据量大而导致Web服务器的响应不及时,页面和查询数据迟迟得不到结果,会因此而破坏客户访问网站的心情,在耗尽耐心的等待后,可能会失去这一部分客户。无疑,这是非常糟糕的结果。因而在对其进行体系架构设计时,整个系统的性能就显得殊为重要。然而,我们不能因噎废食,因为专注于性能而忽略数据的正确性。在PetShop 3.0版本以及之前的版本,因为ASP.NET缓存的局限性,这一问题并没有得到很好的解决。PetShop 4.0则引入了SqlCacheDependency特性,使得系统对缓存的处理较之以前大为改观。

4.3.1  CacheDependency接口

PetShop 4.0引入了SqlCacheDependency特性,对Category、Product和Item数据表对应的缓存实施了SQL Cache Invalidation技术。当对应的数据表数据发生更改后,该技术能够将相关项从缓存中移除。实现这一技术的核心是SqlCacheDependency类,它继承了CacheDependency类。然而为了保证整个架构的可扩展性,我们也允许设计者建立自定义的CacheDependency类,用以扩展缓存依赖。这就有必要为CacheDependency建立抽象接口,并在web.config文件中进行配置。

在PetShop 4.0的命名空间PetShop.ICacheDependency中,定义了名为IPetShopCacheDependency接口,它仅包含了一个接口方法:
public interface IPetShopCacheDependency
{      
    AggregateCacheDependency GetDependency();
}

AggregateCacheDependency是.Net Framework 2.0新增的一个类,它负责监视依赖项对象的集合。当这个集合中的任意一个依赖项对象发生改变时,该依赖项对象对应的缓存对象都将被自动移除。
AggregateCacheDependency类起到了组合CacheDependency对象的作用,它可以将多个CacheDependency对象甚至于不同类型的CacheDependency对象与缓存项建立关联。由于PetShop需要为Category、Product和Item数据表建立依赖项,因而IPetShopCacheDependency的接口方法GetDependency()其目的就是返回建立了这些依赖项的AggregateCacheDependency对象。

4.3.2  CacheDependency实现

CacheDependency的实现正是为Category、Product和Item数据表建立了对应的SqlCacheDependency类型的依赖项,如代码所示:
public abstract class TableDependency : IPetShopCacheDependency
{
    // This is the separator that's used in web.config
    protected char[] configurationSeparator = new char[] { ',' };

    protected AggregateCacheDependency dependency = new AggregateCacheDependency();
    protected TableDependency(string configKey)
    {
        string dbName = ConfigurationManager.AppSettings["CacheDatabaseName"];
        string tableConfig = ConfigurationManager.AppSettings[configKey];
        string[] tables = tableConfig.Split(configurationSeparator);

        foreach (string tableName in tables)
            dependency.Add(new SqlCacheDependency(dbName, tableName));
    }
    public AggregateCacheDependency GetDependency()
   {
        return dependency;
    }
}

需要建立依赖项的数据库与数据表都配置在web.config文件中,其设置如下:
<add key="CacheDatabaseName" value="MSPetShop4"/>
<add key="CategoryTableDependency" value="Category"/>
<add key="ProductTableDependency" value="Product,Category"/>
<add key="ItemTableDependency" value="Product,Category,Item"/>

根据各个数据表间的依赖关系,因而不同的数据表需要建立的依赖项也是不相同的,从配置文件中的value值可以看出。然而不管建立依赖项的多寡,其创建的行为逻辑都是相似的,因而在设计时,抽象了一个共同的类TableDependency,并通过建立带参数的构造函数,完成对依赖项的建立。由于接口方法GetDependency()的实现中,返回的对象dependency是在受保护的构造函数创建的,因此这里的实现方式也可以看作是Template Method模式的灵活运用。例如TableDependency的子类Product,就是利用父类的构造函数建立了Product、Category数据表的SqlCacheDependency依赖:
public class Product : TableDependency
{
    public Product() : base("ProductTableDependency") { }
}

如果需要自定义CacheDependency,那么创建依赖项的方式又有不同。然而不管是创建SqlCacheDependency对象,还是自定义的CacheDependency对象,都是将这些依赖项添加到AggregateCacheDependency类中,因而我们也可以为自定义CacheDependency建立专门的类,只要实现IPetShopCacheDependency接口即可。

4.3.3  CacheDependency工厂

继承了抽象类TableDependency的Product、Category和Item类均需要在调用时创建各自的对象。由于它们的父类TableDependency实现了接口IPetShopCacheDependency,因而它们也间接实现了IPetShopCacheDependency接口,这为实现工厂模式提供了前提。

在PetShop 4.0中,依然利用了配置文件和反射技术来实现工厂模式。命名空间PetShop.CacheDependencyFactory中,类DependencyAccess即为创建IPetShopCacheDependency对象的工厂类:
public static class DependencyAccess
{       
    public static IPetShopCacheDependency CreateCategoryDependency()
    {
        return LoadInstance("Category");
    }
    public static IPetShopCacheDependency CreateProductDependency()
    {
        return LoadInstance("Product");
    }
    public static IPetShopCacheDependency CreateItemDependency()
    {
        return LoadInstance("Item");
    }
    private static IPetShopCacheDependency LoadInstance(string className)
    {
        string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
        string fullyQualifiedClass = path + "." + className;
        return (IPetShopCacheDependency)Assembly.Load(path).CreateInstance(fullyQualifiedClass);
    }
}
整个工厂模式的实现如图4-3所示:

4-3.gif
 图4-3 CacheDependency工厂

虽然DependencyAccess类创建了实现了IPetShopCacheDependency接口的类Category、Product、Item,然而我们之所以引入IPetShopCacheDependency接口,其目的就在于获得创建了依赖项的AggregateCacheDependency类型的对象。我们可以调用对象的接口方法GetDependency(),如下所示:
AggregateCacheDependency dependency = DependencyAccess.CreateCategoryDependency().GetDependency();

为了方便调用者,似乎我们可以对DependencyAccess类进行改进,将原有的CreateCategoryDependency()方法,修改为创建AggregateCacheDependency类型对象的方法。

然而这样的做法扰乱了作为工厂类的DependencyAccess的本身职责,且创建IPetShopCacheDependency接口对象的行为仍然有可能被调用者调用,所以保留原有的DependencyAccess类仍然是有必要的。

在PetShop 4.0的设计中,是通过引入Facade模式以方便调用者更加简单地获得AggregateCacheDependency类型对象。

4.3.4  引入Facade模式

利用Facade模式可以将一些复杂的逻辑进行包装,以方便调用者对这些复杂逻辑的调用。就好像提供一个统一的门面一般,将内部的子系统封装起来,统一为一个高层次的接口。一个典型的Facade模式示意图如下所示:

4-4.gif
图4-4 Facade模式

Facade模式的目的并非要引入一个新的功能,而是在现有功能的基础上提供一个更高层次的抽象,使得调用者可以直接调用,而不用关心内部的实现方式。以CacheDependency工厂为例,我们需要为调用者提供获得AggregateCacheDependency对象的简便方法,因而创建了DependencyFacade类:
public static class DependencyFacade
{
    private static readonly string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
    public static AggregateCacheDependency GetCategoryDependency()
    {
        if (!string.IsNullOrEmpty(path))
            return DependencyAccess.CreateCategoryDependency().GetDependency();
        else
            return null;
    }
    public static AggregateCacheDependency GetProductDependency()
    {
        if (!string.IsNullOrEmpty(path))
            return DependencyAccess.CreateProductDependency().GetDependency();
        else
            return null;
        }
    public static AggregateCacheDependency GetItemDependency()
    {
        if (!string.IsNullOrEmpty(path))
            return DependencyAccess.CreateItemDependency().GetDependency();
        else
            return null;
    }
}

DependencyFacade类封装了获取AggregateCacheDependency类型对象的逻辑,如此一来,调用者可以调用相关方法获得创建相关依赖项的AggregateCacheDependency类型对象:
AggregateCacheDependency dependency = DependencyFacade.GetCategoryDependency();

比起直接调用DependencyAccess类的GetDependency()方法而言,除了方法更简单之外,同时它还对CacheDependencyAssembly配置节进行了判断,如果其值为空,则返回null对象。

在PetShop.Web的App_Code文件夹下,静态类WebUtility的GetCategoryName()和GetProductName()方法调用了DependencyFacade类。例如GetCategoryName()方法:
public static string GetCategoryName(string categoryId)
{
     Category category = new Category();
     if (!enableCaching)
            return category.GetCategory(categoryId).Name;

     string cacheKey = string.Format(CATEGORY_NAME_KEY, categoryId);

     // 检查缓存中是否存在该数据项;
     string data = (string)HttpRuntime.Cache[cacheKey];
     if (data == null)
     {
           // 通过web.config的配置获取duration值;
           int cacheDuration = int.Parse(ConfigurationManager.AppSettings["CategoryCacheDuration"]);
           // 如果缓存中不存在该数据项,则通过业务逻辑层访问数据库获取;
           data = category.GetCategory(categoryId).Name;
           // 通过Facade类创建AggregateCacheDependency对象;
           AggregateCacheDependency cd = DependencyFacade.GetCategoryDependency();
           // 将数据项以及AggregateCacheDependency 对象存储到缓存中;
           HttpRuntime.Cache.Add(cacheKey, data, cd, DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
      }
      return data;
}

GetCategoryName()方法首先会检查缓存中是否已经存在CategoryName数据项,如果已经存在,就通过缓存直接获取数据;否则将通过业务逻辑层调用数据访问层访问数据库获得CategoryName,在获得了CategoryName后,会将新获取的数据连同DependencyFacade类创建的AggregateCacheDependency对象添加到缓存中。

WebUtility静态类被表示层的许多页面所调用,例如Product页面:
public partial class Products : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Page.Title = WebUtility.GetCategoryName(Request.QueryString["categoryId"]);
    }
}

显示页面title的逻辑是放在Page_Load事件方法中,因而每次打开该页面都要执行获取CategoryName的方法。如果没有采用缓存机制,当Category数据较多时,页面的显示就会非常缓慢。

4.3.5  引入Proxy模式

业务逻辑层BLL中与Product、Category、Item有关的业务方法,其实现逻辑是调用数据访问层(DAL)对象访问数据库,以获取相关数据。为了改善系统性能,我们就需要为这些实现方法增加缓存机制的逻辑。当我们操作增加了缓存机制的业务对象时,对于调用者而言,应与BLL业务对象的调用保持一致。也即是说,我们需要引入一个新的对象去控制原来的BLL业务对象,这个新的对象就是Proxy模式中的代理对象。

以PetShop.BLL.Product业务对象为例,PetShop为其建立了代理对象ProductDataProxy,并在GetProductByCategory()等方法中,引入了缓存机制,例如:
public static class ProductDataProxy
{

    private static readonly int productTimeout = int.Parse(ConfigurationManager.AppSettings["ProductCacheDuration"]);
    private static readonly bool enableCaching = bool.Parse(ConfigurationManager.AppSettings["EnableCaching"]);
       
    public static IList
GetProductsByCategory(string category)
    {
        Product product = new Product();

        if (!enableCaching)
            return product.GetProductsByCategory(category);

        string key = "product_by_category_" + category;
        IList data = (IList )HttpRuntime.Cache[key];

        // Check if the data exists in the data cache
        if (data == null)
        {
            data = product.GetProductsByCategory(category);

            // Create a AggregateCacheDependency object from the factory
            AggregateCacheDependency cd = DependencyFacade.GetProductDependency();

            // Store the output in the data cache, and Add the necessary AggregateCacheDependency object
            HttpRuntime.Cache.Add(key, data, cd, DateTime.Now.AddHours(productTimeout), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
        }
        return data;
    }
}

与业务逻辑层Product对象的GetProductsByCategory()方法相比,增加了缓存机制。当缓存内不存在相关数据项时,则直接调用业务逻辑层Product的GetProductsByCategory()方法来获取数据,并将其与对应的AggregateCacheDependency对象一起存储在缓存中。

引入Proxy模式,实现了在缓存级别上对业务对象的封装,增强了对业务对象的控制。由于暴露在对象外的方法是一致的,因而对于调用方而言,调用代理对象与真实对象并没有实质的区别。

从职责分离与分层设计的角度分析,我更希望这些Proxy对象是被定义在业务逻辑层中,而不像在PetShop的设计那样,被划分到表示层UI中。此外,如果需要考虑程序的可扩展性与可替换性,我们还可以为真实对象与代理对象建立统一的接口或抽象类。然而,单以PetShop的表示层调用来看,采用静态类与静态方法的方式,或许更为合理。我们需要谨记,“过度设计”是软件设计的警戒线。

如果需要对UI层采用缓存机制,将应用程序数据存放到缓存中,就可以调用这些代理对象。以ProductsControl用户控件为例,调用方式如下:
productsList.DataSource = ProductDataProxy.GetProductsByCategory(categoryKey);

productsList对象属于自定义的CustomList类型,这是一个派生自System.Web.UI.WebControls.DataList控件的类,它的DataSource属性可以接受IList集合对象。
不过在PetShop 4.0的设计中,对于类似于ProductsControl类型的控件而言,采用的缓存机制是页输出缓存。我们可以从ProductsControl.ascx页面的Source代码中发现端倪:
<%@ OutputCache Duration="100000" VaryByParam="page;categoryId" %>

与ASP.NET 1.x的页输出缓存不同的是,在ASP.NET 2.0中,为ASP.NET用户控件新引入了CachePolicy属性,该属性的类型为ControlCachePolicy类,它以编程方式实现了对ASP.NET用户控件的输出缓存设置。我们可以通过设置ControlCachePolicy类的Dependency属性,来设置与该用户控件相关的依赖项,例如在ProductsControl用户控件中,进行如下的设置:
protected void Page_Load(object sender, EventArgs e)
{
    this.CachePolicy.Dependency = DependencyFacade.GetProductDependency();
}

采用页输出缓存,并且利用ControlCachePolicy设置输出缓存,能够将业务数据与整个页面放入到缓存中。这种方式比起应用程序缓存而言,在性能上有很大的提高。同时,它又通过引入的SqlCacheDependency特性有效地避免了“数据过期”的缺点,因而在PetShop 4.0中被广泛采用。相反,之前为Product、Category、Item业务对象建立的代理对象则被“投闲散置”,仅仅作为一种设计方法的展示而“幸存”与整个系统的源代码中。

posted @ 2007-12-26 12:07 风雨兼程 阅读(1265) | 评论 (1)编辑 收藏

IList GetCategories();的问题

petshop4.0中的一段代码  
   
  public   interface   ICategory   {  
   
  ///   <summary>  
  ///   Method   to   get   all   categories  
  ///   </summary>  
                  ///   <returns>Interface   to   Model   Collection   Generic   of   categories</returns>  
             
                 
  IList<CategoryInfo>   GetCategories();  
                 
   
                  ///   <summary>  
                  ///   Get   information   on   a   specific   category  
                  ///   </summary>  
                  ///   <param   name="categoryId">Unique   identifier   for   a   category</param>  
                  ///   <returns>Business   Entity   representing   an   category</returns>  
                  CategoryInfo   GetCategory(string   categoryId);  
  }  
   
   
  我知道IList<CategoryInfo>是范型  
  表示list中的item是CategoryInfo对象  
   
   
  请问为什么用IList<CategoryInfo>  
  用List<CategoryInfo>可以吗?  
   
  两者有什么区别?谢谢  

没有什么区别,这样写灵活性大,实现ilist接口的类很多,你写成list后,也许以后你要改成非list的,就会要改很多代码
举个例子  
   
  RenderControlToString(DataList   L//Control   C)  
   
  你认为是写control还是写datalist代码的通用性高?

  OOP编码原则:尽可能用接口编程

posted @ 2007-12-26 11:44 风雨兼程 阅读(461) | 评论 (0)编辑 收藏

关于sqlhelper.cs

public abstract class SqlHelper
          {
              public static readonly string connectionString = ConfigurationManager.ConnectionStrings["SqlConnString"].ConnectionString;
        
              SqlConnection conn;

              #region open SqlConnection
              public static void Open() {
                   conn = new SqlConnection(connectionString);
                   if (conn.State != ConnectionState.Open)
                      conn.Open();
              }    
              #endregion

              #region close SqlConnection
              public static void Close() {
                  if (conn != null)
                  {
                      conn.Close();
                      conn.Dispose();
                  }
              }        
              #endregion

              #region prepare SqlCommand
              private static void PrepareCommand(SqlCommand cmd, CommandType cmdType, string cmdText, SqlParameter[] cmdParms) {
                  Open();
                  cmd.Connection = conn;
                  cmd.CommandType = cmdType;
                  cmd.CommandText = cmdText;

                  if (cmdParms != null)
                  {
                      foreach (SqlParameter parm in cmdParms)
                          cmd.Parameters.Add(parm);
                  }
              }
              #endregion

              #region parm cache
              /*
               使用一个哈希表来保存缓存的参数 只缓存参数名
               哈希表的特点:一个键对应一个值key对value(为object需要类型转化)        不能出现两个相同的键 否则error
         
               下面的哈希表parmCache定义为static即一次定义全局使用
               所以可能会出现有人在读的时候,有人在写,一般会用Lock就像Asp中用Application["count"]来统计点击数一样
               要先锁后解锁
               但.net框架提供了Synchroized          sync和syncroize中文意思 同步,同时发生
               来提供这一操作
              */
              private static Hashtable parmCache = Hashtable.Synchronized(new Hashtable());

              public static void CacheParameters(string cacheKey, params SqlParameter[] commandParameters) {
                  parmCache[cacheKey] = commandParameters;
              }

              /*
              1、为何要克隆呢 为何不直接return cachedParms
         
              有一个参数数组
              SqlParameter[] parms={
              new SqlParameter("@num1",SqlDbType.Int,4),
              new SqlParameter("@num2",SqlDbType.Int,4)
              }
              缓存该数组
              用户a和b都执行插入操作
              a用户插入了1,1
              b用户插入了2,2
              如果不用克隆的话,参数数组只有一份
              而2个用户需要根据不同的情况赋于不同的值
              所以就用了克隆了
        
              2、(ICloneable)cachedParms[i]先将HashTable转为ICloneable这样HashTable就具有了Clone()克隆方法了
              克隆一份后是什么类型呢,,当然要强制转化为(SqlParameter)了
              最后将它赋值给clonedParms[i]
              */       
              public static SqlParameter[] GetCachedParameters(string cacheKey) {
                  SqlParameter[] cachedParms = (SqlParameter[])parmCache[cacheKey];

                  if (cachedParms == null)
                      return null;
                  SqlParameter[] clonedParms = new SqlParameter[cachedParms.Length];

                  for (int i = 0; i < cachedParms.Length; i++)
                      clonedParms[i] = (SqlParameter)((ICloneable)cachedParms[i]).Clone();

                  return clonedParms;
              }
              #endregion        
        
              //below method support sqltext and procedure

              #region ExecuteReader
              /*
               parms的作用,这也是一个知识点
               举例:
               ExecuteReader(*,*,null)成功运行
               ExecuteReader(*,*,new SqlParameter(*))成功运行
               ExecuteReader(*,*,new SqlParameter(*),new SqlParameter(*))成功运行
               ExecuteReader(*,*,{new SqlParameter(*),new SqlParameter(*),})成功运行
               它让参数类型和参数个数任意
               这可给了不是一般的好处,你不必为SqlParameter和SqlParameter[]进行重载,写上两个函数
               又为null写上一个函数,因为null会不明确调用SqlParameter的函数还是SqlParameter[]的函数
               啥你不知道我在说什么,打屁屁,那回去看看c++的函数重载
               */
              public static SqlDataReader ExecuteReader(CommandType cmdType, string cmdText, params SqlParameter[] commandParameters) {
            
                  SqlCommand cmd = new SqlCommand();

                  try {
                      PrepareCommand(cmd, null, cmdType, cmdText, commandParameters);
                      SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
                      cmd.Parameters.Clear();
                      return rdr;
                  }
                  catch {
                      Close();
                      throw;
                  }
              }
              #endregion

              #region ExecuteNonQuery
              public static void ExecuteNonQuery(CommandType cmdType, string cmdText, params SqlParameter[] commandParameters) {

                  SqlCommand cmd = new SqlCommand();

                  PrepareCommand(cmd, cmdType, cmdText, commandParameters);
                  cmd.ExecuteNonQuery();
                  cmd.Parameters.Clear();

                  Close();
              }
              #endregion

              #region ExecuteScalar
              public static object ExecuteScalar(CommandType cmdType, string cmdText, params SqlParameter[] commandParameters) {

                  SqlCommand cmd = new SqlCommand();

                  PrepareCommand(cmd, cmdType, cmdText, commandParameters);
                  object val = cmd.ExecuteScalar();
                  cmd.Parameters.Clear();

                  Close();
                  return val;
              }
              #endregion


          }
}

posted @ 2007-12-26 10:30 风雨兼程 阅读(895) | 评论 (0)编辑 收藏

框架怎样实现高度随内容自动增高

main.htm:

<html> 
    <head> 
       <meta  http-equiv='Content-Type'  content='text/html;  charset=gb2312' /> 
       <meta  name='author'  content='F.R.Huang(meizz梅花雪)//www.meizz.com' /> 
       <title>iframe自适应加载的页面高度</title> 
    </head> 
    
    <body>
        <div><iframe src="child.htm"></iframe></div>
    </body>
</html>

child.htm:

<html> 
<head> 
    <meta  http-equiv='Content-Type'  content='text/html;  charset=gb2312' /> 
    <meta  name='author'  content='F.R.Huang(meizz梅花雪)//www.meizz.com' /> 
    <title>iframe  自适应其加载的网页(多浏览器兼容)</title> 
    <script type="text/javascript">
    <!--
    function iframeAutoFit()
    {
        try
        {
            if(window!=parent)
            {
                var a = parent.document.getElementsByTagName("IFRAME");
                for(var i=0; i<a.length; i++) //author:meizz
                {
                    if(a[i].contentWindow==window)
                    {
                        var h1=0, h2=0;
                        a[i].parentNode.style.height = a[i].offsetHeight +"px";
                        a[i].style.height = "10px";
                        if(document.documentElement&&document.documentElement.scrollHeight)
                        {
                            h1=document.documentElement.scrollHeight;
                        }
                        if(document.body) h2=document.body.scrollHeight;

                        var h=Math.max(h1, h2);
                        if(document.all) {h += 4;}
                        if(window.opera) {h += 1;}
                        a[i].style.height = a[i].parentNode.style.height = h +"px";
                    }
                }
            }
        }
        catch (ex){}
    }
    if(window.attachEvent)
    {
        window.attachEvent("onload",  iframeAutoFit);
        //window.attachEvent("onresize",  iframeAutoFit);
    }
    else if(window.addEventListener)
    {
        window.addEventListener('load',  iframeAutoFit,  false);
        //window.addEventListener('resize',  iframeAutoFit,  false);
    }
    //-->
    </script> 
</head> 
<body>
    <table border="1" width="200" style="height: 400px; background-color: yellow">
        <tr>
            <td>iframe  自适应其加载的网页(多浏览器兼容,支持XHTML)</td>
        </tr>
    </table>
</body> 
</html>

 很多人反应在IE7里使用它会死机,那是因为在自适应高度时触发了 window.onresize 事件,而这个事件又去调用这个调整 <iframe> 高度的函数,产生了死循环调用。

posted @ 2007-04-29 18:03 风雨兼程 阅读(989) | 评论 (1)编辑 收藏

怎样向xml文档插入HTML标记

CDATA 区段
       因为网页中要显示HTML的源代码。而在XML中,要实现这样的功能,就必须使用CDATA标记。在CDATA标记中的信息被解析器原封不动地传给应用程序,并且不解析该段信息中的任何控制标记。CDATA区域是由:![CDATA[”为开始标记,以“]]为结束标记。例如:例2中的源码,除了![CDATA[”“]]符号,其余的内容解析器将原封不动地交给下游的应用程序,即使CDATA区域中的开始和结尾的空白以及换行字符等,都同样会被转交(注意CDATA是大写的字符)。 

    如同你在第三章中学到的,你不能直接将(<)或(&)符号放置在元素内容中的字符数据里。要避开这个限制的一种方法是使用字符参照(& #60;或& #38;)或预先定义的普通实体参照(& lt;或& amp;),如同你在第六章中将学到的。然而,如果你需要加入多个(<)或(&)字符,使用这些参照将变得不合适且会让资料变得难以阅读。在这种情况下,将包含限制字符的文字放置在CDATA 区段中将会比较容易完成工作。
    CDATA 区段的类型
    CDATA 区段以字符「<![CDATA[」开始,并以「]]>」字符结束。在这两个定义符号之间,你可以输入任何字符(包括「<」或「&」),除了「]]>」之外。(因为,该字符会被解释成CDATA区段的结束。)所有位在CDATA 区段中的字符都被视为元素字符数据的字义 (literal) 部分,而不是XML 标签。
    下面是一个合法CDATA 区段的范例:
<![CDATA [
Here you can type any characters except two right brackets followed
by a greater-than symbol.
]]>
注意
    关键词CDATA,就像其它你所见到的XML 关键词,必须以大写字母撰写。
    如果你想包含一段原始码或标签当作被显示在浏览器中元素的真实字符数据的一部份,你可以使用CDATA 区段来防止XML 解析器将「<」或「&」字符解释成XML 卷标。下面就是一个范例:
<A-SECTION>
The following is an example of a very simple HTML page:
<![CDATA[
<HTML>
<HEAD>
<T99vLE>R.Jones &Sons</T99vLE>
</HEAD>
<BODY>
<P>Welcome to our home page!</P>
</BODY>
</HTML>
]]>
</A-SECTION>
    例如,若没有CDATA 区段,处理器会假设<HTML>是套迭的元素的起始部分,而不是A-SECTION 元素的字符数据的起始部分。
注意
    既然你可以直接将「<」与「&」字符加入CDATA 区段中,你就不必使用字符参照(& #60;与&)或预先定义的普通实体参照(& lt;与& amp;),笔者将在第六章中解释字符参照与实体参照。事实上,如果你使用这类的参照,解析器会将参照中的每个字符照字面解释,且不会更换掉含有「<」或「&」字符的参照。
    你可以放置CDATA 区段的地方
    你可以将CDATA 区段放置在字符数据可以出现的任何地方-也就是说,在元素的内容中却不在XML 标签中。下面是一个合法放置CDATA 区段的范例
<?xml version="1.0"?>
<MUSICAL>
<T99vLE_PAGE>

 

<![CDATA[
<Oklahoma!>
By
Rogers &Hammerstein
]]>
</T99vLE_PAGE>
<!--Other elements here...-->
</MUSICAL>
    显示于下页的错误型式XML 文件包含了两个违法的CDATA 区段。第一个不是在元素的内容中。第二个则是位在文件元素的内容之中,但却不是在起始标签中。
<?xml version="1.0"?>
<![CDATA[ ILLEGAL::not within element content!]]>
<DOC_ELEMENT>
<SUB_ELEMENT <![CDATA[ ILLEGAL::inside of markup!]]>>
sub-element content...
</SUB_ELEMENT>
</DOC_ELEMENT>
注意
    CDATA 区段不能成巢状套迭。亦即,你不能将一个CDATA 区段放到另一个区段中。

posted @ 2007-04-29 17:42 风雨兼程 阅读(1837) | 评论 (0)编辑 收藏

三 生 三 世

前生,你以绯红的思念,偎依我的窗前,将一生的爱恋开放成桃花一片。

    你深情的伫立在早春三月,以灿烂温暖我冬季曾经冰封的心河。你将铮铮的誓言,以千枝万条缠绕在我的窗前,你将依恋深锁在每一朵花心,呈以幽香亲吻我的气息。

    隔窗,是我纤纤的倩影,以一双翦水的眼眸将你一片迷醉的桃红粉成痴痴的凝望。在微风细雨里,你致以我呢喃的细语,那雨滴落在花瓣上的颤音是你为我谱写的相思曲。懵懂的我,总是凭窗而立,期待一袭青衫从幽香袅绕的花树下,以热切的眼眸向我走来。红尘中的我,却看不穿你纠结满树的想我的情结。终究,你在属于自己季节的尾声,隔窗,用最后眷恋的眼光,在不舍与哀伤中碎落我的窗下,看着你飘零的身躯,撞入我心扉的是来得深刻而猛烈的疼痛,急促地伸出手,想接住你,在错过的瞬间,有泪从眼中滴落……。爱,总在失去的瞬间才知道它的存在。含泪,我祈祷来生也盛开成一簇桃花,与你临风偎依,还你一生的情。 
  
    前生,我是桃花一片,以最美的姿色开满你路过的青石小径,将一生的眷恋开放成寂寞的等待。等你潇洒的身影路过时,轻抚我的脸庞,给我一声爱怜的赞叹,我以香为魂,伴你在花丛中留恋,听你为我赋诗,为我吹笛。当你每次转身离去时,我在微风中心碎的声音,你是否听见?总在无你的夜晚独自浴着冷月的清辉想你,思念便凋落成满地憔悴的容颜,清晨,花瓣上的露珠便是我为你流了一夜的泪。 

   我寂寞而又充满期待地等着你经过,贪恋地呼吸着有你的空气,眷恋着你身上发出来的味道,有一种依偎的感觉和莫名的幸福。虽然我不能表达对你的所有的爱恋,只能无语地看着你来去匆忙的身影,你为我的每次驻足和停留,都让我感动和欣喜万分,你的忧郁的长叹总牵动我的心弦,在你看不见里为你暗自落泪。如果可以,我多么希望就这样在这里静默地守侯,守侯朝来暮去的时光里有你的身影陪伴,与你偕老。

   我只有一季短暂的生命和一颗爱你的心,在不属于我的季节即将来临之际,我将离去。虽然我有深深的眷恋和离别的伤痛,可是,生命不容我为你停留。就让我在季节最后的晨光里,以飞舞的姿势婆娑而落,在你呜咽的笛音里慢慢凋零在你的指尖……。我祈祷来生不再为花,许我能语言可以向你倾诉。

   今生,我是桃树下画花的女子,就着一袭白色长裙,拈一朵桃花簪入发,染几笔丹青,将你我的姻缘写进花瓣,等着能解花语的你飘然而来,执我之手,与我倾诉,共我缱绻。

   不许愿来世相见,不求一生拥有,只愿今生有缘能与你携手相拥,走过一段人生。我以三生的情,换你一生的缘,只是不愿再错过生命中的一段情缘。

posted @ 2007-02-26 14:05 风雨兼程 阅读(184) | 评论 (0)编辑 收藏

一个关于模态弹出窗口刷新父窗体的问题。急

问:
我在做一套系统的时候碰到这样一个问题。
弹出的模态窗口不能刷新父窗体。例如我新增数据是在模态窗口而添加成功了关闭后父窗体只能手动刷新。这样就严重影响了一些效果。
我尝试过一些方法但是始终不对。请各位高手进来一下。

用OPEN打开的不管是在关闭窗体时自动刷新都没有任何问题。见下例

父窗体代码
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title></title>
</head>
<body>
<a href="javascript:void(0)" onclick="window.open('2.html','','')">open</a>
</body>
</html>

子窗体代码
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title></title>
</head>
<body onUnload="opener.location.reload()">
<!-
用按纽直接刷新父窗体
<a href="javascript:opener.location.reload()">刷新</a>
->
</body>
</html>

 

换做模态后的程序。
父窗体代码
<%@ page contentType="text/html; charset=GBK" %><HEAD>
<body onUnload="opener.location.reload()">
<script language="JavaScript">
function showabout()
{
  var returnValue=showModalDialog('2.html','','dialogWidth:520px;dialogHeight:510px;help:no;center:yes;resizable:no;status:no;scroll:no');

void(0);
</script>
<a href="javascript:void(0)" onClick="showabout()">open</a>

子窗体代码
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title></title>
</head>
<body onUnload="opener.location.reload()">
<a href="javascript:opener.location.reload()">刷新</a>
</body>
</html>

 

请各位参考一下上面的代码,我感觉错误好象是出在
opener.location.reload()

谢谢
______________________________________________________________________________________________
答1:
模态窗口不能在子窗口中直接刷新父窗口

父窗体代码
<%@ page contentType="text/html; charset=GBK" %><HEAD>
<body onUnload="opener.location.reload()">
<script language="JavaScript">
function showabout()
{
  var returnValue=showModalDialog('2.html','','dialogWidth:520px;dialogHeight:510px;help:no;center:yes;resizable:no;status:no;scroll:no');
  location.reload()//模态窗口传值下来后就可以刷新了.

void(0);
</script>
<a href="javascript:void(0)" onClick="showabout()">open</a>

子窗体代码
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title></title>
</head>
<body onUnload="opener.location.reload()">
<a href="javascript:opener.location.reload()">刷新</a>//???在子窗口中不能刷新父窗口,只有window.close下去后才可刷新.
</body>
</html>

______________________________________________________________________________________________
答2:
模态对话框是不能用opener引用父窗口的,而是需要父窗口主动传递指针变量的
这个window.showModalDialog有第二个参数,就是做这个用的,你需要把window穿过去,就是这样
var returnValue=showModalDialog('2.html',window,'dialogWidth:520px;dialogHeight:510px;help:no;center:yes;resizable:no;status:no;scroll:no');
这样在对话框中就可以引用父窗口对象,怎么引用呢,通过window.dialogArgument就可以应用父窗口了。

______________________________________________________________________________________________
答3:
谢谢。我终于搞定了。
上面的dialogArgument差个S
我调了半天。发现在body里面应该加上location
也就是<body onUnload="window.dialogArguments.location.reload()">

posted @ 2007-02-09 14:48 风雨兼程 阅读(5863) | 评论 (4)编辑 收藏

asp.net中动态变更CSS

在asp.net中,有的时候要动态变换CSS,比如有的时候做个性化页面,可以这样做
<head>
<link id="MyStyleSheet" rel="stylesheet" type="text/css" runat="server" />
</head>
之后,在要更换CSS的页面中,使用如下代码
Sub Page_Load(Sender As Object, E As EventArgs)
If Not (IsPostBack)
MyStyleSheet.Attributes.Add("href","/css/flostyle.css")
End If
End Sub  

posted @ 2007-02-09 13:09 风雨兼程 阅读(218) | 评论 (0)编辑 收藏

动态改变Asp.net页面标题

1.引用命字空间
use System.Web.UI.HtmlControls.HtmlGenericControl
在Asp.net 中你可以使用HtmlGenericControl()来动态改变页面Title(标题)



2.为HTML Title标签设置ID,并将其作为服务器端运行
<HTML>
  <HEAD>
    <TITLE ID=MyPageTitle Runat=Server> </TITLE>
  </HEAD>
 <BODY>
  </BODY>
</HTML>



3.将Title标设设为Runat=server后,就可以后台代码进行访问~~,通过InnerText或InnerHtml
来改变其值.
public class Use_This : System.Web.UI.Page
 {
  
Private System.Web.UI.HtmlControls.HtmlGenericControl SetMyTitle;
  private void Page_Load(object sender, System.EventArgs e)
  {

   SetMyTitle.InnerText = "~哈哈~";

  }
}


posted @ 2007-02-09 13:08 风雨兼程 阅读(395) | 评论 (1)编辑 收藏

怎样实现动态添加删除表单中的行

<script LANGUAGE="JAVASCRIPT">
var i= 0;
var a = 0;
function insertRow(){
 var otr=myTable.insertRow(myTable.rows.length);
 var ocell=otr.insertCell(0);
 ocell.innerHTML="<input type=file name='f1'+a+''>"
 var ocell=otr.insertCell(1);
   ocell.innerHTML="<input type=file name='f2'+a+''>"
   var ocell=otr.insertCell(2);
   ocell.innerHTML="<input type=button name='del'+a+'' value=删除 onclick=myTable.deleteRow(event.srcElement.parentElement.parentElement.rowIndex)>"
   i++;
   a++;
}
</SCRIPT>
</HEAD>
<BODY>
<table id="myTable" border=1 width=600 >
</table>
<input type=button onclick="insertRow()"  value="插入行">
</BODY>

2
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>添加删除行</title>
</head>

<body><script LANGUAGE="JAVASCRIPT">
var j= 1;
var i= 0;
var a = 0;
function insertRow(){
 var otr=myTable.insertRow(myTable.rows.length);
     var ocell=otr.insertCell(0);                                         
  ocell.innerHTML="<input onkeydown='if(event.keyCode==13){event.keyCode=9;return true}'type=text  size='12' maxlength='7'name='num"+a+"'>"
     var ocell=otr.insertCell(1);
   ocell.innerHTML="<select name='adsl"+a+"'><option value='1'>ADSL</option><option value='2'> LAN</option></select>"
      var ocell=otr.insertCell(2);                                         
   ocell.innerHTML="<input onkeydown='if(event.keyCode==13){event.keyCode=9;return true}'type=text size='20'name='aa"+a+"'value='"+j+"'>"
      var ocell=otr.insertCell(3);                                         
   ocell.innerHTML="<input onkeydown='if(event.keyCode==13){event.keyCode=9;return true}'type=text size='20'name='aa"+a+"'>"
      var ocell=otr.insertCell(4);                                         
   ocell.innerHTML="<input onkeydown='if(event.keyCode==13){event.keyCode=9;return true}'type=text size='20'name='aa"+a+"'>"
      var ocell=otr.insertCell(5); 
   ocell.innerHTML="<input onkeydown='if(event.keyCode==13){event.keyCode=9;return true}'type=text size='20'name='aa"+a+"'>"     
     var  ocell=otr.insertCell(6);
   ocell.innerHTML="<input onkeydown='if(event.keyCode==13){event.keyCode=9;return true}'type=button name='del'+a+'' value=删除 onclick=myTable.deleteRow(event.srcElement.parentElement.parentElement.rowIndex)>"
   j++;
   i++;
   a++;
}
</SCRIPT>
</HEAD>
<BODY>
<table id="myTable"width="778" border="1" align="center" cellspacing="0" bordercolorlight="#66CC33" bordercolordark="ffffff">
  <tr>
    <td><div align="center">号码</div></td>
    <td nowrap><div align="center">宽度类型</div></td>
    <td nowrap><div align="center">起始时间</div></td>
    <td nowrap><div align="center">优惠时间</div></td>
    <td nowrap><div align="center">优惠金额</div></td>
    <td nowrap><div align="center">备注</div></td>
    <td nowrap><div align="center">删除</div></td>
  </tr>
</table>
<p align="center">
  <input type=button onclick="insertRow()"value="插入行">
</p>

<p>&nbsp;</p>
</BODY>

</body>
</html>

3
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>添加删除行</title>
</head>

<body><script LANGUAGE="JAVASCRIPT">
var i= 0;
var a = 0;
function insertRow(){
 var otr=myTable.insertRow(myTable.rows.length);
     var ocell=otr.insertCell(0);                                         
  ocell.innerHTML="<input onkeydown='if(event.keyCode==13){event.keyCode=9;return true}'type=text  size='12' maxlength='7'name='num"+a+"'>"
     var ocell=otr.insertCell(1);
   ocell.innerHTML="<select onkeydown='if(event.keyCode==13){event.keyCode=9;return true}'name='adsl"+a+"'><option value='1'>ADSL</option><option value='2'> LAN</option></select>"
      var ocell=otr.insertCell(2);                                         
   ocell.innerHTML="<input onkeydown='if(event.keyCode==13){event.keyCode=9;return true}'type=text size='20'name='aa"+a+"'value='"+a+"'>"
      var ocell=otr.insertCell(3);                                         
   ocell.innerHTML="<input onkeydown='if(event.keyCode==13){event.keyCode=9;return true}'type=text size='20'name='aa"+a+"'>"
      var ocell=otr.insertCell(4);                                         
   ocell.innerHTML="<input onkeydown='if(event.keyCode==13){event.keyCode=9;return true}'type=text size='20'name='aa"+a+"'>"
      var ocell=otr.insertCell(5); 
   ocell.innerHTML="<input onkeydown='if(event.keyCode==13){event.keyCode=9;return true}'type=text size='20'name='aa"+a+"'>"     
     var  ocell=otr.insertCell(6);
   ocell.innerHTML="<input type=button name='del'+a+'' value=删除 onclick=myTable.deleteRow(event.srcElement.parentElement.parentElement.rowIndex)>"

   i++;
   a++;
}
function deleteRow()
{
   tr=document.getElementById("myTable").rows;
   if(tr.length>1)tr[tr.length-1].removeNode();
}
</SCRIPT>
</HEAD>
<BODY>
<table id="myTable"width="778" border="1" align="center" cellspacing="0" bordercolorlight="#66CC33" bordercolordark="ffffff">
  <tr>
    <td><div align="center">号码</div></td>
    <td nowrap><div align="center">宽度类型</div></td>
    <td nowrap><div align="center">起始时间</div></td>
    <td nowrap><div align="center">优惠时间</div></td>
    <td nowrap><div align="center">优惠金额</div></td>
    <td nowrap><div align="center">备注</div></td>
    <td nowrap><div align="center">删除</div></td>
  </tr>
</table>
<p align="center">
  <input type=button onclick="insertRow()"value="插入行">
  <input type=button onclick="deleteRow()"value="删除行">
</p>

<p>&nbsp;</p>
</BODY>

</body>
</html>

posted @ 2007-01-29 18:58 风雨兼程 阅读(1211) | 评论 (0)编辑 收藏

文件上传

 function OutputUpload(id,title) { var tblMain=document.getElementById("Table2"); var tbl=document.getElementById("Table3"); var isExsitfile=document.getElementById("file"+id); var text=document.createTextNode("上传:"+title+"虚拟形象图片"); if(isExsitfile==null) { var str= ""; tbl.rows[id].cells[0].insertAdjacentHTML("beforeEnd",str) ; tbl.rows[id].cells[0].appendChild(text); } else { tbl.rows[id].cells[0].removeChild(isExsitfile); tbl.rows[id].cells[0].innerText=""; } }

if(Request.Form["CHK"]!=null)
   {   string chkvalue=Request.Form["CHK"].ToString();
    string[] mylayer=chkvalue.Split(new char[]{','});
    string layer="";
    for(int i=0;i<mylayer.Length;i++)
    {     
     
     if(layer!="")
     {
      layer+="_";
     }
     layer+=mylayer[i];
     
    }
    ViewState["layer"]=layer;
      
    //得到数据库编号
    if(itemid1.ToString()==null)
    {
     itemid1=itemid1+1;
    }
    else
    {
     itemid1=GetItemID()+1;
    }
    System.Web.HttpFileCollection files=System.Web.HttpContext.Current.Request.Files;
    try
    {
     for(int i=0;i<files.Count;i++)
     {  
      string fid=itemid1+".gif";
      HttpPostedFile postedFile=files[i];
      
      if(postedFile.FileName.Length>0)
      {
       string fileName,fileExtension;
       fileName=System.IO.Path.GetFileName(postedFile.FileName);
       fileExtension=System.IO.Path.GetExtension(fileName);

       if(fileExtension!=GlobalVars.FILETAILNAME[0])
       {
        Response.Write("<script>alert(\"文件格式不正确!!!\")</script>");
        return;
       }
       if(i==0)
       {
        //上传图片到服务器上  
        BLL.Visual_Items bll=new BLL.Visual_Items();
        bool tf=bll.Exists(this.txtName.Text);
        if(tf==false)
        {  
         if(System.IO.Directory.Exists(Server.MapPath(Request.ApplicationPath) + "\\images\\img_Visual\\show\\0"))
         {
          postedFile.SaveAs(Server.MapPath(Request.ApplicationPath) + "\\images\\img_Visual\\show\\0\\" + fid); 
         }
         else
         {
          System.IO.Directory.CreateDirectory(Server.MapPath(Request.ApplicationPath) + "\\images\\img_Visual\\show\\0");
          postedFile.SaveAs(Server.MapPath(Request.ApplicationPath) + "\\images\\img_Visual\\show\\0\\" + fid); 
         }
         // 把图片的信息记录保留到数据库中
         string s="images/img_Visual/show/0/"+fid;
         AddData(s,itemid1,layer);

        }
        else
        {
         this.Response.Write("<script>alert('数据库已存在此条数据!')</script>");
        }

       }
       else
       {
        if(System.IO.Directory.Exists(Server.MapPath(Request.ApplicationPath) + "\\images\\img_Visual\\show\\"+mylayer[i-1]))
        {
         postedFile.SaveAs(Server.MapPath(Request.ApplicationPath) + "\\images\\img_Visual\\show\\"+mylayer[i-1]+"\\" + fid);
        }
        else
        {
         System.IO.Directory.CreateDirectory(Server.MapPath(Request.ApplicationPath) + "\\images\\img_Visual\\show\\"+mylayer[i-1]);
         postedFile.SaveAs(Server.MapPath(Request.ApplicationPath) + "\\images\\img_Visual\\show\\"+mylayer[i-1]+"\\" + fid);

        }
       }
      }
      else
      {
       Response.Write("<script>alert(\"虚拟形象图片不能为空!\")</script>");
       return ;

      }
     } 
    }
    catch(Exception ex)
    {
     string sRawURL = Request.RawUrl;

     if(sRawURL.IndexOf("?") > -1)
     {
      sRawURL = sRawURL.Substring(0,sRawURL.IndexOf("?"));
     }    
     
     Response.Write(ex.ToString());
    } 
   }
   else
   {
               Response.Write("<script>alert(\"请选择虚拟形象所属层!!\")</script>");
   }

posted @ 2006-12-16 11:31 风雨兼程 阅读(235) | 评论 (0)编辑 收藏

构建安全的数据访问

     摘要: 本页内容http://www.microsoft.com/china/technet/security/guidance/secmod87.mspx 本模块内容 ...  阅读全文

posted @ 2006-11-17 15:08 风雨兼程 阅读(423) | 评论 (0)编辑 收藏

DataGrid使用技巧小总结-个性化分页及复杂表头

  在使用DataGrid 的时候,总是会有各种各样的需求,在和数据库打交道的项目中用的最多的恐怕就属DataGrid 了吧,微软有时候做的就差那么一点点,所以就需要我们自己来动手实现啦.

DataGrid 自定义分页导航

无需任何其他第三方控件,在DataGrid 自己分页的基础上再个性化一点.
效果:  

让DataGrid自己的分页实现这样的效果
[1][2][3][4][5][6]

  

让DataGrid自己的分页实现这样的效果
[1][2][3][4][5][6]

  

private void grid_ItemCreated(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
  {
   if(e.Item.ItemType==ListItemType.Pager)
   {    
    foreach (Control c in e.Item.Cells[0].Controls)
    {
     if (c is Label)  //
当前页数
     {
        Label lblpage=(Label)c;
//      lblpage.ForeColor= System.Drawing.ColorTranslator.FromHtml("#e78a29"); //#e78a29 ,#FF0000     
//      lblpage.Font.Bold=true;
        lblpage.Text="[<font color=#e78a29><b>"+lblpage.Text+"</b></font>]";     
      //((Label)c).ForeColor = System.Drawing.Color.Green;      
//      break;
     }
     if(c is LinkButton) //
链接的其他页数
     {      
         LinkButton linkButton = (LinkButton)c;       
         linkButton.Text = "[" + linkButton.Text+"]";
     }
    }    
   }
  }

 

 

DataGrid等控件中的自动编号:

添加一个模版列:

<asp:TemplateColumn HeaderText="No.">
    
<ItemStyle HorizontalAlign="Center"></ItemStyle>
    
<ItemTemplate>
        
<asp:Label runat="server" Text='<%# dgCustomize.CurrentPageIndex*dgCustomize.PageSize+dgCustomize.Items.Count+1 %>'>
        
</asp:Label>
    
</ItemTemplate>
</asp:TemplateColumn>

 

 

DataGrid中创建复杂表头

方法一:用table实现

  <form id="Form1" method="post" runat="server">
   <TABLE id="Table1" runat="server" cellSpacing="1" cellPadding="2" width="580" border="1"
    bgcolor="#cc6633" bordercolor="#cc9966" style="FONT-SIZE:9pt;BORDER-BOTTOM:0px">
    <TR align="center">
     <TD colspan="2" width="380" style="HEIGHT: 21px"></TD>
     <TD width="200" colspan="2" style="HEIGHT: 21px"></TD>
    </TR>
    <TR align="center">
     <TD width="200" bgcolor="#66cc99"><FONT face="
宋体"></FONT></TD>
     <TD width="180" bgcolor="white"><FONT face="
宋体"></FONT></TD>
     <TD width="160" bgcolor="#99cccc"></TD>
     <TD width="40" bgcolor="#009999"></TD>
    </TR>
   </TABLE>
   <asp:DataGrid id="DataGrid1" width="580px" AlternatingItemStyle-BackColor="#6699ff" CellPadding="2"
    CellSpacing="1" BorderWidth="1" BorderColor="#cc9966" Font-Size="9pt" runat="server" ShowHeader="False"
    AutoGenerateColumns="False">
    <Columns>
     <asp:BoundColumn DataField="Title">
      <ItemStyle Width="200px"></ItemStyle>
     </asp:BoundColumn>
     <asp:BoundColumn DataField="CreateDate">
      <ItemStyle Width="180px"></ItemStyle>
     </asp:BoundColumn>
     <asp:BoundColumn DataField="pid">
      <ItemStyle Width="160px"></ItemStyle>
     </asp:BoundColumn>
     <asp:BoundColumn DataField="HitCount">
      <ItemStyle Width="40px"></ItemStyle>
     </asp:BoundColumn>
    </Columns>
   </asp:DataGrid>   
  </form> 

窗体顶端

 

 

 

 

 

 

窗体底端

方法二:动态生成表头

生成双层表头:
  private void grid_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
  {

   if (e.Item.ItemType == ListItemType.Header)
   {
//    e.Item.Cells[0].ColumnSpan = 1;//
这是第一列的跨列数
    StringBuilder strtext=new StringBuilder();
    strtext.Append("\\</td>");
    strtext.Append("<td colspan=4>
生活照明</td>");
    strtext.Append("<td colspan=2>
一般照明</td>");
    strtext.Append("<td colspan=2>
工付业</td>");
    strtext.Append("<td colspan=2>
农业</td>");
    strtext.Append("<td colspan=2>
合计</td>");
    strtext.Append("</tr>");
    strtext.Append("<tr>");
    strtext.Append("<td>" + e.Item.Cells[0].Text);               
    e.Item.Cells[0].Text =strtext.ToString();

   }

}

整个表头内容:<tr><td>  e.Item.Cells[0].Text =的内容  </td></tr>

加起来就是表头的样式。

生活照明

一般照明

工付业

农业

合计

 

 

 

 

 

 

 

 

 

 

 

 

生成三层表头:

if (e.Item.ItemType == ListItemType.Header)
   {    
    StringBuilder strtext=new StringBuilder();
    strtext.Append("</td>");
    strtext.Append("<td colspan= 6>
当月</td>");
    strtext.Append("</tr>");

    strtext.Append("<tr>"); 
    strtext.Append("<td colspan=2>
居民</td>");
    strtext.Append("<td colspan=2>
一般</td>");
    strtext.Append("<td colspan=2>
工付业</td>");
    strtext.Append("</tr>");

    strtext.Append("<tr>");
    strtext.Append("<td>" + e.Item.Cells[0].Text);               
    e.Item.Cells[0].Text =strtext.ToString();
   }

当月

居民

一般

工付业

 

 

 

 

 

 

posted @ 2006-11-10 14:31 风雨兼程 阅读(268) | 评论 (0)编辑 收藏

Datagrid怎么根据选择的checkbox编辑和更新多行记录?

     摘要: 网友问这个问题,写了一个简单的例子:前台:  1<%@ Page language="c#" Codebehind="WebForm4.aspx.cs" AutoEventWireup="false" Inherits="WebApplication25.WebForm4" %>  2<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Tran...  阅读全文

posted @ 2006-11-10 14:28 风雨兼程 阅读(381) | 评论 (0)编辑 收藏

2006年it人士必去的10个网站

1、chinaunix
网址:http://www.chinaunix.net/
简介:中国最大的linux/unix技术社区。

2、itpub
网址:http://www.itpub.net/
简介:有名气的IT技术论坛,看看它的alexa排名就知道有多火了,尤其以数据库技术讨论热烈而闻名。ITPUB论坛的前身是建立在smiling的oracle小组。

3、51cto
网址:http://www.51cto.com/
简介:由国内知名IT门户网站管理团队,获近千万风险投资,于2005年8月正式创立,是国内首家定位于网络技术人员的综合性服务平台,是中国最大的网络技术网站。

4、csdn
网址:http://www.csdn.net/
简介:于1999年3月成立,是中国最大的软件开发人员网站,社区热心高手众多,并有不少MVP(微软最有价值专家)长期活跃在这里,类似悬赏的积分制度,也使论坛增添不少乐趣。

5、落伍者
网址:http://www.im286.com/
简介:网站站长都应该知道的地方,只是论坛id需要手工审核。

6、蓝色理想
网址:http://www.blueidea.com/
简介:有名的关于网站设计的网站,拥有大量忠实网友。

7、it写作社区
网址:http://www.donews.com/
简介:一个可以让你的思维活跃起来的地方,在这里it评论人和撰稿人可以找到很多的文字素材。

8、博客堂
网址:http://blog.joycode.com/
简介:众多MVP交流的地方,这里有各类最新技术,只是网站成员采用邀请制,不提供注册或者申请功能。

9、it英雄榜
网址:http://www.itheroes.cn/
简介:网站以介绍it界人士为主,广大从事it的人员可以从中获取他们的经验。

10、邪恶八进制
网址:http://www.eviloctal.com/
简介:目前为数不多的一个讨论气氛浓厚,技术水平高的网络安全网站,邪恶八进制信息安全团队也是一个管理规范、人员素质高的网络安全小组。

posted @ 2006-11-08 15:14 风雨兼程 阅读(230) | 评论 (0)编辑 收藏

datagrid的正反双向排序

呵呵,本没有写文章之意,看到好友阿好(hbzxf)的文章,也决定把平时的一些东西写出来和大家分享!有疏漏之处,敬请大家指教!

     在asp.net中利用datagrid控件按列进行排序很是方便。可是我们只能单项排序!如果我们需要正反排序那么就需要加入一些代码控制一下,下面我来详细讲解一下这个过程。

  首先我们需要将datagird控件的属性设置为 AllowSorting="True",且需要排序列需要制定排序表达式 eg: SortExpression="kmdm"。设置好这些,我们进入代码文件,来编写响应排序的事件.

  首先在Page_Load时间中加入如下代码:

   if (!IsPostBack)
   {
    if(this.kjkm_dg.Attributes["SortExpression"]==null) //这里kjkm_dg为datagrid   ID
    {
     this.kjkm_dg.Attributes["SortExpression"]="kmdm";  //这里给datagrid增加一个排序属性,且默认排序表达式为kmdm;
     kjkm_dg.Attributes["SortDirection"]="ASC"; //这里给datagrid增加一个排序方向属性,且默认为升序排列;
    }
    mikecatbind(); //绑定函数,下面介绍
   }

  protected void mikecatbind()
  {
   string sqlStr="select * from zc_kjkm";
   DataView dv=new DataView();
   
   string SortExpression=kjkm_dg.Attributes["SortExpression"];
   string SortDirection=kjkm_dg.Attributes["SortDirection"];
   dv=us.Bind(sqlStr).Tables[0].DefaultView;  //来自web service的dataset,这里随便一个ds就可以;
   dv.Sort=SortExpression+" "+SortDirection; //指定视图的排序方式;
   kjkm_dg.DataSource=dv; //指定数据源
   kjkm_dg.DataBind(); //数据绑定

}

   进行完上面的设置后我们进入重要的环节,排序事件的编写:

  private void kjkm_dg_SortCommand(object source, System.Web.UI.WebControls.DataGridSortCommandEventArgs e)
  {
   string SortExpression=e.SortExpression.ToString();  //获得当前排序表达式
   string SortDirection="ASC"; //为排序方向变量赋初值
   if(SortExpression==kjkm_dg.Attributes["SortExpression"])  //如果为当前排序列
   {
    SortDirection=(kjkm_dg.Attributes["SortDirection"].ToString()==SortDirection?"DESC":"ASC");     //获得下一次的排序状态

 }
   kjkm_dg.Attributes["SortExpression"]=SortExpression;
   kjkm_dg.Attributes["SortDirection"]=SortDirection;
   mikecatbind();
  }

posted @ 2006-11-08 14:53 风雨兼程 阅读(202) | 评论 (0)编辑 收藏

Web的桌面提醒(Popup)

大多数Windows程序都有桌面提醒(Popup)功能,如Msn Messenger,Outlook2003,FoxMail,SharpReader等,对用户来说可以非常的方便最新的信息。

在Web中借用IE5.5+的Popup,也可以实现类似的功能,在CnForums1.2中新增了一种特性就是类似于Outlook2003一样,当有新帖子/私人留言时就有Popup提示,如图:
现在就技术问题介绍一下:

一,什么是Popup

在IE5.5+中开始支持的Popup窗口有很多很特别的特性:

  • Popup窗口可以超出浏览器窗口区域;
  • 可以不用担心被下拉框、flash、Iframe等这些东西遮挡;
  • 即时窗口没有焦点,Popup窗口也可以弹出并显示
  • 一个Popup窗口打开后,当在它的区域以外点击或者另一个Popup窗口被打开时都会自动关闭;
  • Popup窗口是没有焦点的;
  • 用户不能改变Popup窗口大小;
  • Popup窗口中的内容是不能被选择的;
  • ......

 只是Web下的Popup比起Windows中的Popup还是弱一些,但是已经够用了。Msdn上对Popup有非常详细的介绍“Using the PopupObject (Internet Explorer - DHTML)

二,怎么创建一个Popup

Msdn上的示例代码:
				
						
// 创建Popup对象 var oPopup = window.createPopup(); // Popup对象和Window对象一样,里面包含了一个完整的HTML文档 var oPopupBody = oPopup.document.body; // 在Body中插入HTML oPopupBody.innerHTML = " Display some <B>HTML</B> here. " ; // 设置显示的位置、大小、参照物 oPopup.show( 100 , 100 , 200 , 50 , document.body);

三,Popup的显示

多个Popup不能共存(Popup中创建Popup除外),所以web中的Popup不方便像Msn Messenger的Popup提示那样“爬楼梯”,所以最简单有效的方法就是像Outlook2003的Popup一样,逐个Popup提醒。
(参考WebMessenger的实现)用Js建个队列,将需要Popup提示的内容都放在队列中,用一个定时器,定时从队列中取一个Popup,每个Popup显示7秒。

四,怎么知道有新帖子

在Web程序中,只能通过“拉”的技术,即定时向服务器发送请求——通过定时刷新或者是xmlhttp的Get。
我们需要有个时间戳来记录最后请求时间,这样就好根据这个时间戳来获取时间戳之后的帖子。
如果每次根据时间戳来获取最新帖子,服务器负荷比较大,因为并不是每次请求都有新帖子的,优化一下,再给每个在线用户设置有无新帖子、有无新留言的状态位,当发表新帖子后更新每个人的“有无新帖”状态位。这样每次请求时先判断有没有新帖子,如果有新帖子,才获取所有帖子的标题和内容简要。

五,具体实现

客户端代码:http://webuc.net/MyProject/Popup/popup.htm
http://webuc.net/MyProject/Popup/popup.rar

和论坛程序的结合:
demo: http://bbs.openlab.net.cn (登录后有新帖子即提示(IE5.5+))
http://bbs.openlab.net.cn/PostAttachment.aspx?PostID=376725

参考文章:无限菜单之 xml+popup 版(IE5.5+)

posted @ 2006-11-07 17:32 风雨兼程 阅读(523) | 评论 (0)编辑 收藏

asp.net2.0中使用客户端回调实现无刷新应用!

在asp.net1.1中我们可以通过JS调用Web服务来实现无刷新应用,现在asp.net2.0可以用Client Callback的方法实现,这样变得更加方便和容易使用了.客户端回调功能的实现需要两步:
1.为控件或页面实现ICallbackEventHandler接口,该接口有一个方法public string RaiseCallbackEvent(string eventArgument),这个方法的原理是接收客户端发送过来的参数,然后返回一个结果字符串给客户端进行处理.例如:
public partial class ShowReview_ascx : ICallbackEventHandler
{
    public string RaiseCallbackEvent(string eventArgument)
    {
        int reviewId = int.Parse(eventArgument);
        Discussion discussion = AspNetCommerce.DiscussionManager.GetDiscussion(reviewId);
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat("<table class='{0}>", "ShowReview_ascx_DisTable");
        foreach (DiscussionItem item in discussion)
        {
            //输出html
        }
        sb.Append("</table>");
        return sb.ToString();
    }
}
该方法接收客户端的传来的一个参数,转换为 reviewId,根据reviewId查询出相关数据以html形式返回给客户端.
2.实现两个客户端脚本,并使用Page.GetCallbackEventReference说明客户端如何处理返回结果.
例如:
Page.GetCallbackEventReference(this, "topicid", "HandleResultFromServer", "context", "HandleErrorResultFromServer")
this表示实现ICallbackEventHandler的控件的实例,
topicid表示客户端传入的字符串参数数
HandleResultFromServer表示处理成功调用后的结果的客户端脚本函数名
context表示回调的启动方法处与处理回调结果的方法之间的传递参数
HandleErrorResultFromServer表示处理失败调用后的结果的客户端脚本函数名
部分客户端脚本如:
    void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack || !this.EnableViewState)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("function loadtree(topicid){");
            sb.Append("var targetImg = document.all('img_'+topicid);");
            sb.Append("var targetDiv = document.all('tr_'+topicid);");
            sb.Append("if (targetDiv.style.display != 'block'){");
            sb.Append("targetDiv.style.display = \"block\";");
            sb.AppendFormat("targetImg.src = \"{0}\";", AspNetCommerce.CommerceContext.GetThemesImagePathForImgTag("ShowReview_ascx/Expand-Open.gif"));
            sb.Append("targetImg.alt = \"点击收回\";");
            sb.Append("var context = topicid;");
            sb.AppendFormat("{0}", Page.GetCallbackEventReference(this, "topicid", "HandleResultFromServer", "context", "HandleErrorResultFromServer"));
            sb.Append("}else{");
            sb.Append("targetDiv.style.display = \"none\";");
            sb.AppendFormat("targetImg.src = \"{0}\";", AspNetCommerce.CommerceContext.GetThemesImagePathForImgTag("ShowReview_ascx/Expand-Closed.gif"));
            sb.Append("targetImg.alt = \"点击查看讨论\";");
            sb.Append("}");
            sb.Append("}");
            sb.Append("function HandleResultFromServer(result,context)");
            sb.Append("{");
            sb.Append("var targetTd = document.all('td_'+context);");
            sb.Append("targetTd.innerHTML = result");
            //sb.Append("alert(result);");
            sb.Append("}");
            sb.Append("function HandleErrorResultFromServer(result,context)");
            sb.Append("{");
            sb.Append("var targetTd = document.all('td_'+context);");
            sb.AppendFormat("targetTd.innerHTML = '<font color=red>{0}</font>'",
                "数据读错误,请重新再试或通知管理员!");
            sb.Append("}");
            Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "ClientCallBack", sb.ToString(), true);
        }
    }

posted @ 2006-11-07 14:59 风雨兼程 阅读(223) | 评论 (0)编辑 收藏

Asp.Net Forums中对.Net中序列化和反序列化的应用

在Forums中,有些内容是不固定的,例如用户资料,除了一些基本资料,可能还要有一些其他资料信息,例如MSN、个人主页、签名档等,一般对于这样的都是每一个属性对应于数据库中的一个字段。但是如果以后我们因为需要增加一些属性,例如QQ号、Blog地址等,如果还是用这种增加数据表字段的方法,那么将会频繁的修改数据库表结构、存储过程、数据库访问的程序。

或许您也遇到过类似问题,看Forums中是怎么借用.Net的序列化和反序列化来解决的:
例如我需要在用户资料里面增加QQ号这个属性,那么我只需要在User类中增加一个属性
public String QQIM 
{
    get { return GetExtendedAttribute("QQIM"); }
    set { SetExtendedAttribute("QQIM", value); }
}
不需要修改数据库表结构,不需要修改存储过程,连数据库访问的程序都不需要动。

其具体实现的主要代码:

//  首先新建在User类中新建一个NameValueCollection对象,将这些扩展属性都保存在NameValueCollection对象中
NameValueCollection extendedAttributes  =   new  NameValueCollection();

//  从NameValueCollection集合中取纪录
public   string  GetExtendedAttribute( string  name)    
{
    
string  returnValue  =  extendedAttributes[name];

    
if  (returnValue     ==   null )
    
return   string .Empty;
    
else
    
return  returnValue;
}


//  设置扩展属性的在NameValueCollection中的键值和值
public   void  SetExtendedAttribute( string     name,  string  value)    
{
    extendedAttributes[name] 
=  value;
}


//  将extendedAttributes对象(前面定义的用来保存所有的用户扩展信息的NameValueCollection对象)序列化为内存流
//  可以用来保存到数据库中
public   byte [] SerializeExtendedAttributes()    
{

    
//  序列化对象
    BinaryFormatter    binaryFormatter     =   new  BinaryFormatter();

    
//  创建一个内存流,序列化后保存在其中
    MemoryStream ms     =   new  MemoryStream();
    
byte [] b;

    
//  将extendedAttributes对象(里面保存了所有的用户扩展信息)序列化为内存流
    
//
    binaryFormatter.Serialize(ms, extendedAttributes);

    
//  设置内存流的起始位置
    
//
    ms.Position     =   0 ;
        
    
//  读入到 byte 数组
    
//
    b  =      new     Byte[ms.Length];
    ms.Read(b, 
0 , b.Length);
    ms.Close();

    
return  b;
}


//  反序列化extendedAttributes对象的内容
//  从数据库中读取出来的
public   void  DeserializeExtendedAttributes( byte [] serializedExtendedAttributes) 
{

    
if  (serializedExtendedAttributes.Length     ==   0 )
    
return ;
    
try     
    
{

    BinaryFormatter    binaryFormatter    
=   new  BinaryFormatter();
    MemoryStream ms    
=   new  MemoryStream();

    
//  将 byte 数组到内存流
    
//
    ms.Write(serializedExtendedAttributes,  0 , serializedExtendedAttributes.Length);

    
//  将内存流的位置到最开始位置
    
//
    ms.Position     =   0 ;

    
//  反序列化成NameValueCollection对象,创建出与原对象完全相同的副本
    
//
    extendedAttributes  =  (NameValueCollection) binaryFormatter.Deserialize(ms);

    ms.Close();
    }
 
    
catch      {}
    
}

实质上序列化机制是将类的值转化为一个一般的(即连续的)字节流,然后就可以将该流保存到数据库的某个字段中(在数据库中forums_UserProfile表中有一个字段“StringNameValues varbinary(7500)”)。读取的过程对对象进行反序列化时,创建出与原对象完全相同的副本。

注意一般这类属性在数据库中是不能被检索到的,并且要这些属性能被序列化。

posted @ 2006-11-07 14:27 风雨兼程 阅读(272) | 评论 (0)编辑 收藏

每个开发人员现在应该下载的十种必备工具

用于编写单元测试的 NUnit

用于创建代码文档资料的 NDoc

用于生成解决方案的 NAnt

用于生成代码的 CodeSmith

用于监视代码的 FxCop

用于编译少量代码的 Snippet Compiler

两种不同的转换器工具:ASP.NET 版本转换器和 Visual Studio .NET 项目转换器

用于生成正则表达式的 Regulator

用于分析程序集的 .NET Reflector

本文使用了下列技术:

.NET、C# 或 Visual Basic .NET、Visual Studio .NET

除非您使用能够获得的最佳工具,否则您无法期望生成一流的应用程序。除了像 Visual Studio®.NET 这样的著名工具以外,还可以从 .NET 社区获得许多小型的、不太为人所知的工具。在本文中,我将向您介绍一些目前可以获得的、面向 .NET 开发的最佳免费工具。我将引导您完成一个有关如何使用其中每种工具的快速教程 — 一些工具在许多时候可以使您节约一分钟,而另一些工具则可能彻底改变您编写代码的方式。因为我要在本篇文章中介绍如此之多的不同工具,所以我无法详尽讨论其中每种工具,但您应该了解到有关每种工具的足够信息,以便判断哪些工具对您的项目有用。

*
本页内容
Snippet Compiler Snippet Compiler
Regulator Regulator
CodeSmith CodeSmith
生成自定义模板 生成自定义模板
NUnit NUnit
编写 NUnit 测试 编写 NUnit 测试
FxCop FxCop
Lutz Roeder 的 .NET Reflector Lutz Roeder 的 .NET Reflector
NDoc NDoc
NAnt NAnt
实际运行的 NAnt 实际运行的 NAnt
转换工具 转换工具
小结 小结

Snippet Compiler

Snippet Compiler 是一个基于 Windows® 的小型应用程序,您可以通过它来编写、编译和运行代码。如果您具有较小的代码段,并且您不希望为其创建完整的 Visual Studio .NET 项目(以及伴随该项目的所有文件),则该工具将很有用。

例如,假设我希望向您说明如何从 Microsoft?.NET 框架中启动另一个应用程序。在 Snippet Compiler 中,我将通过新建一个能够创建小型控制台应用程序的文件开始。可以在该控制台应用程序的 Main 方法内部创建代码片段,而这正是我要在这里做的事情。下面的代码片段演示了如何从 .NET 框架中创建记事本实例:

System.Diagnostics.Process proc = new System.Diagnostics.Process();proc.StartInfo.FileName= "notepad.exe";proc.Start();proc.WaitForExit();

当然该代码片段本身无法编译,而这正是 Snippet Compiler 的用武之地。图 1 显示了 Snippet Compiler 中的这一代码示例。


1 Snippet Compiler

要测试该代码片段,只须按 play(运行)按钮(绿色三角形),它就会在调试模式下运行。该代码片段将生成一个弹出式控制台应用程序,并且将显示记事本。当您关闭记事本时,该控制台应用程序也将关闭。

就我个人而言,我是在尝试为某位向我求助的人士创建一个小型示例时,才发现 Snippet Compiler 是如此宝贵的 — 如果不使用该工具,则我通常必须新建一个项目,确保每个部分都能编译通过,然后将代码片段发送给求助者,并删除该项目。Snippet Compiler 使得这一过程变得更加容易、更加愉快。

Snippet Compiler 由 Jeff Key 编写,并且可以从 http://www.sliver.com/dotnet/SnippetCompiler 下载。

Regulator

Regulator 是最后一个添加到我的头等工具清单中的。它是一种很有特色的工具,能够使生成和测试正则表达式变得很容易。人们对正则表达式重新产生了兴趣,因为它们在 .NET 框架中受到很好的支持。正则表达式用来基于字符、频率和字符顺序定义字符串中的模式。它们最常见的用途是作为验证用户输入有效性的手段或者作为在较大字符串中查找字符串的方法 — 例如,在 Web 页上查找 URL 或电子邮件地址。

Regulator 使您可以输入一个正则表达式以及一些针对其运行该表达式的输入内容。这样,在应用程序中实现该正则表达式之前,您可以了解它将产生什么效果以及它将返回哪些种类的匹配项。图 2 显示了带有简单正则表达式的 Regulator。

文档中包含该正则表达式 — 在该示例中,它是 [0-9]*,应该匹配一行中任意数量的数字。右下侧的框中含有针对该正则表达式的输入,而左下侧的框显示了该正则表达式在输入内容中找到的匹配项。在这样的单独应用程序中编写和测试正则表达式,要比尝试在您的应用程序中处理它们容易得多。

Regulator 中的最佳功能之一是能够在 regexlib.com 搜索联机正则表达式库。例如,如果您在搜索框中输入字符串“phone”,您将找到 20 种以上能够匹配各种电话号码的不同的正则表达式,包括用于英国、澳大利亚的表达式以及其他许多电话号码。Regulator 由 Roy Osherove 编写,并且可以在 http://royo.is-a-geek.com/regulator 下载。

CodeSmith

CodeSmith 是一种基于模板的代码生成工具,它使用类似于 ASP.NET 的语法来生成任意类型的代码或文本。与其他许多代码生成工具不同,CodeSmith 不要求您订阅特定的应用程序设计或体系结构。使用 CodeSmith,可以生成包括简单的强类型集合和完整应用程序在内的任何东西。

当您生成应用程序时,您经常需要重复完成某些特定的任务,例如编写数据访问代码或者生成自定义集合。CodeSmith 在这些时候特别有用,因为您可以编写模板自动完成这些任务,从而不仅提高您的工作效率,而且能够自动完成那些最为乏味的任务。CodeSmith 附带了许多模板,包括对应于所有 .NET 集合类型的模板以及用于生成存储过程的模板,但该工具的真正威力在于能够创建自定义模板。为了使您能够入门,我将快速介绍一下如何生成自定义模板。

生成自定义模板

CodeSmith 模板只是一些可以在任意文本编辑器中创建的文本文件。它们的唯一要求是用 .cst 文件扩展名来保存它们。我将要生成的示例模板将接受一个字符串,然后基于该字符串生成一个类。创建模板的第一步是添加模板头,它可声明模板的语言、目标语言以及简要模板说明:

<%@ CodeTemplate Language="C#"       TargetLanguage="C#"    Description="Car Template" %>

模板的下一部分是属性声明,在这里可声明将在模板每次运行时指定的属性。就该模板而言,我要使用的唯一属性只是一个字符串,因此属性声明如下所示:

<%@ Property Name="ClassName" Type="String" Category="Context"     Description="Class Name" %>

该属性声明将使 ClassName 属性出现在 CodeSmith 属性窗口中,以便可以在模板运行时指定它。下一步是实际生成模板主体,它非常类似于用 ASP.NET 进行编码。您可以在图 3 中查看该模板的主体。[编辑更新 — 6/16/2004:图 3 中的代码已被更新,以便对多线程操作保持安全。]

正如您所见,该模板接受字符串输入并使用该类名生成单独的类。在模板主体中,使用与 ASP.NET 中相同的起始和结束标记。在该模板中,我只是插入属性值,但您还可以在这些标记内部使用任意类型的 .NET 代码。在该模板完成之后,您就可以通过双击它或者从 CodeSmith 应用程序中打开它将其加载到 CodeSmith 中。图 4 显示了已经加载到 CodeSmith 中的该模板。

您可以看到左侧的属性正是我在该模板中声明的属性。如果我输入“SingletonClass”作为类名,并单击 Generate 按钮,则将生成图 3 的底部显示的类。

CodeSmith 使用起来相当容易,如果能够正确应用,则可以产生一些令人难以置信的结果。面向代码生成的应用程序中最常见的部分之一是数据访问层。CodeSmith 包括一个名为 SchemaExplorer 的特殊的程序集,可用来从表、存储过程或几乎任何其他 SQL Server? 对象生成模板。

CodeSmith 由 Eric J. Smith 编写,并且可以在 http://www.ericjsmith.net/codesmith 下载。

NUnit

NUnit 是为 .NET 框架生成的开放源代码单元测试框架。NUnit 使您可以用您喜欢的语言编写测试,从而测试应用程序的特定功能。当您首次编写代码时,单元测试是一种测试代码功能的很好方法,它还提供了一种对应用程序进行回归测试的方法。NUnit 应用程序提供了一个用于编写单元测试的框架,以及一个运行这些测试和查看结果的图形界面。

编写 NUnit 测试

作为示例,我将测试 .NET 框架中 Hashtable 类的功能,以确定是否可以添加两个对象并且随后检索这些对象。我的第一步是添加对 NUnit.Framework 程序集的引用,该程序集将赋予我对 NUnit 框架的属性和方法的访问权。接下来,我将创建一个类并用 TestFixture 属性标记它。该属性使 NUnit 可以知道该类包含 NUnit 测试:

using System;using System.Collections;using NUnit.Framework;namespace NUnitExample{    [TestFixture]    public class HashtableTest {        public HashtableTest() {                    }    }}

下一步,我将创建一个方法并用 [Test] 属性标记它,以便 NUnit 知道该方法是一个测试。然后,我将建立一个 Hashtable 并向其添加两个值,再使用 Assert.AreEqual 方法查看我是否可以检索到与我添加到 Hashtable 的值相同的值,如下面的代码所示:

[Test]public void HashtableAddTest(){    Hashtable ht = new Hashtable();                ht.Add("Key1", "Value1");    ht.Add("Key2", "Value2");    Assert.AreEqual("Value1", ht["Key1"], "Wrong object returned!");    Assert.AreEqual("Value2", ht["Key2"], "Wrong object returned!");}

这将确认我可以首先向 Hashtable 中添加值并随后检索相应的值 — 这是一个很简单的测试,但能够表现 NUnit 的功能。存在许多测试类型以及各种 Assert 方法,可使用它们来测试代码的每个部分。

要运行该测试,我需要生成项目,在 NUnit 应用程序中打开生成的程序集,然后单击 Run 按钮。图 5 显示了结果。当我看到那个大的绿色条纹时,我有一种兴奋和头晕的感觉,因为它让我知道测试已经通过了。这个简单的示例表明 NUnit 和单元测试是多么方便和强大。由于能够编写可以保存的单元测试,并且每当您更改代码时都可以重新运行该单元测试,您不仅可以更容易地检测到代码中的缺陷,而且最终能够交付更好的应用程序。


5 NUnit

NUnit 是一个开放源代码项目,并且可以从 http://www.nunit.org 下载。还有一个优秀的 NUnit Visual Studio .NET 外接程序,它使您可以直接从 Visual Studio 中运行单元测试。您可以在 http://sourceforge.net/projects/nunitaddin 找到它。有关 NUnit 及其在测试驱动开发中的地位的详细信息,请参阅文章“Test-Driven C#: Improve the Design and Flexibility of Your Project with Extreme Programming Techniques”(MSDN ®Magazine 2004 年 4 月刊)。

FxCop

.NET 框架非常强大,这意味着存在创建优秀应用程序的极大可能,但是也同样存在创建劣质程序的可能。FxCop 是有助于创建更好的应用程序的工具之一,它所采用的方法是:使您能够分析程序集,并使用一些不同的规则来检查它是否符合这些规则。FxCop 随附了由 Microsoft 创建的固定数量的规则,但您也可以创建并包括您自己的规则。例如,如果您决定所有的类都应该具有一个不带任何参数的默认构造函数,则可以编写一条规则,以确保程序集的每个类上都具有一个构造函数。这样,无论是谁编写该代码,您都将获得一定程度的一致性。如果您需要有关创建自定义规则的详细信息,请参阅 John Robbins 的有关该主题的 Bugslayer 专栏文章(MSDN ® Magazine 2004 年 6 月刊)。

那么,让我们观察一下实际运行的 FxCop,并且看一下它在我一直在处理的 NUnitExample 程序集中找到哪些错误。当您打开 FxCop 时,您首先需要创建一个 FxCop 项目,然后向其添加您要测试的程序集。在将该程序集添加到项目以后,就可以按 Analyze,FxCop 将分析该程序集。图 6 中显示了在该程序集中找到的错误和警告。

FxCop 在我的程序集中找到了几个问题。您可以双击某个错误以查看详细信息,包括规则说明以及在哪里可以找到更多信息。(您可以做的一件有趣的事情是在框架程序集上运行 FxCop 并查看发生了什么事情。)

FxCop 可以帮助您创建更好的、更一致的代码,但它无法补偿低劣的应用程序设计或非常简单拙劣的编程。FxCop 也不能替代对等代码检查,但是因为它可以在进行代码检查之前捕获大量错误,所以您可以花费更多时间来解决严重的问题,而不必担心命名约定。FxCop 由 Microsoft 开发,并且可以从 http://www.gotdotnet.com/team/fxcop 下载。

Lutz Roeder 的 .NET Reflector

下一个必不可少的工具称为 .NET Reflector,它是一个类浏览器和反编译器,可以分析程序集并向您展示它的所有秘密。.NET 框架向全世界引入了可用来分析任何基于 .NET 的代码(无论它是单个类还是完整的程序集)的反射概念。反射还可以用来检索有关特定程序集中包含的各种类、方法和属性的信息。使用 .NET Reflector,您可以浏览程序集的类和方法,可以分析由这些类和方法生成的 Microsoft 中间语言 (MSIL),并且可以反编译这些类和方法并查看 C# 或 Visual Basic ®.NET 中的等价类和方法。

为了演示 .NET Reflector 的工作方式,我将加载和分析前面已经显示的 NUnitExample 程序集。图 7 显示了 .NET Reflector 中加载的该程序集。


7 NUnitExample 程序集

在 .NET Reflector 内部,有各种可用来进一步分析该程序集的工具。要查看构成某个方法的 MSIL,请单击该方法并从菜单中选择 Disassembler。

除了能够查看 MSIL 以外,您还可以通过选择 Tools 菜单下的 Decompiler 来查看该方法的 C# 形式。通过在 Languages 菜单下更改您的选择,您还可以查看该方法被反编译到 Visual Basic .NET 或 Delphi 以后的形式。以下为 .NET Reflector 生成的代码:

public void HashtableAddTest(){     Hashtable hashtable1;    hashtable1 = new Hashtable();    hashtable1.Add("Key1", "Value1");    hashtable1.Add("Key2", "Value2");    Assert.AreEqual("Value1", hashtable1["Key1"],        "Wrong object returned!");    Assert.AreEqual("Value2", hashtable1["Key2"],       "Wrong object returned!");}

前面的代码看起来非常像我为该方法实际编写的代码。以下为该程序集中的实际代码:

public void HashtableAddTest(){    Hashtable ht = new Hashtable();                ht.Add("Key1", "Value1");    ht.Add("Key2", "Value2");    Assert.AreEqual("Value1", ht["Key1"],        "Wrong object returned!");    Assert.AreEqual("Value2", ht["Key2"],        "Wrong object returned!");}

尽管上述代码中存在一些小的差异,但它们在功能上是完全相同的。

虽然该示例是一种显示实际代码与反编译代码之间对比的好方法,但在我看来,它并不代表 .NET Reflector 所具有的最佳用途 — 分析 .NET 框架程序集和方法。.NET 框架提供了许多执行类似操作的不同方法。例如,如果您需要从 XML 中读取一组数据,则存在多种使用 XmlDocument、XPathNavigator 或 XmlReader 完成该工作的不同方法。通过使用 .NET Reflector,您可以查看 Microsoft 在编写数据集的 ReadXml 方法时使用了什么,或者查看他们在从配置文件读取数据时做了哪些工作。.NET Reflector 还是一个了解以下最佳实施策略的优秀方法:创建诸如 HttpHandlers 或配置处理程序之类的对象,因为您可以了解到 Microsoft 工作组实际上是如何在框架中生成这些对象的。

.NET Reflector 由 Lutz Roeder 编写,并且可以从 http://www.aisto.com/roeder/dotnet 下载。

NDoc

编写代码文档资料几乎总是一项令人畏惧的任务。我所说的不是早期设计文档,甚至也不是更为详细的设计文档;我说的是记录类上的各个方法和属性。NDoc 工具能够使用反射来分析程序集,并使用从 C# XML 注释生成的 XML 自动为代码生成文档资料。XML 注释仅适用于 C#,但有一个名为 VBCommenter 的 Visual Studio .NET Power Toy,它能够为 Visual Basic .NET 完成类似的工作。此外,下一版本的 Visual Studio 将为更多语言支持 XML 注释。

使用 NDoc 时,您仍然在编写代码的技术文档,但您是在编写代码的过程中完成了文档编写工作(在 XML 注释中),而这更容易忍受。使用 NDoc 时,第一步是为您的程序集打开 XML 注释生成功能。右键单击该项目并选择 Properties | Configuration Properties | Build,然后在 XML Documentation File 选项中输入用于保存 XML 文件的路径。当该项目生成时,将创建一个 XML 文件,其中包含所有 XML 注释。下面是 NUnit 示例中的一个用 XML 编写了文档的方法:

/// <summary>/// This test adds a number of values to the Hashtable collection /// and then retrieves those values and checks if they match./// </summary>[Test]public void HashtableAddTest(){    //Method Body Here}

有关该方法的 XML 文档资料将被提取并保存在 XML 文件中,如下所示:

<member name="M:NUnitExample.HashtableTest.HashtableAddTest">  <summary>This test adds a number of values to the Hashtable collection    and then retrieves those values and checks if they match.</summary> </member>

NDoc 使用反射来考察您的程序集,然后读取该文档中的 XML,并且将它们进行匹配。NDoc 使用该数据来创建任意数量的不同文档格式,包括 HTML 帮助文件 (CHM)。在生成 XML 文件以后,下一步是将程序集和 XML 文件加载到 NDoc 中,以便可以对它们进行处理。通过打开 NDoc 并单击 Add 按钮,可以容易地完成该工作。

在将程序集和 XML 文件加载到 NDoc 中并且使用可用的属性范围自定义输出以后,单击 Generate 按钮将启动生成文档资料的过程。使用默认的属性,NDoc 可以生成一些非常吸引人并且实用的 .html 和 .chm 文件,从而以快速有效的方式自动完成原来非常乏味的任务。

NDoc 是一个开放源代码项目,并且可以从 http://ndoc.sourceforge.net 下载。

NAnt

NAnt 是一个基于 .NET 的生成工具,与当前版本的 Visual Studio .NET 不同,它使得为您的项目创建生成过程变得非常容易。当您拥有大量从事单个项目的开发人员时,您不能依赖于从单个用户的座位进行生成。您也不希望必须定期手动生成该项目。您更愿意创建每天晚上运行的自动生成过程。NAnt 使您可以生成解决方案、复制文件、运行 NUnit 测试、发送电子邮件,等等。遗憾的是,NAnt 缺少漂亮的图形界面,但它的确具有可以指定应该在生成过程中完成哪些任务的控制台应用程序和 XML 文件。注意,MSBuild(属于 Visual Studio 2005 的新的生成平台)为每种健壮的生成方案进行了准备,并且由基于 XML 的项目文件以类似的方式驱动。

实际运行的 NAnt

在该示例中,我将为前面创建的 NUnitExample 解决方案创建一个 NAnt 版本文件。首先,我需要创建一个具有 .build 扩展名的 XML 文件,将其放在我的项目的根目录中,然后向该文件的顶部添加一个 XML 声明。我需要添加到该文件的第一个标记是 project 标记:

<?xml version="1.0"?><project name="NUnit Example" default="build" basedir=".">    <description>The NUnit Example Project</description></project>

项目标记还用于设置项目名称、默认目标以及基目录。Description 标记用于设置该项目的简短说明。

接下来,我将添加 property 标记,该标记可用于将设置存储到单个位置(随后可以从文件中的任意位置访问该位置)。在该例中,我将创建一个名为 debug 的属性,我可以随后将其设置为 true 或 false,以反映我是否要在调试配置下编译该项目。(最后,这一特定属性并未真正影响如何生成该项目;它只是您设置的一个变量,当您真正确定了如何生成该项目时将读取该变量。)

接下来,我需要创建一个 target 标记。一个项目可以包含多个可在 NAnt 运行时指定的 target。如果未指定 target,则使用默认 target(我在 project 元素中设置的 target)。在该示例中,默认 target 是 build。让我们观察一下 target 元素,它将包含大多数生成信息:

<target name="build" description="compiles the source code"></target>

在 target 元素内,我将把 target 的名称设置为 build,并且创建有关该 target 将做哪些工作的说明。我还将创建一个 csc 元素,该元素用于指定应该传递给 csc C# 编译器的数据。让我们看一下该 csc 元素:

<csc target="library" output=".\bin\debug\NUnitExample.dll"     debug="${debug}"><references>    <includes name="C:\program files\NUnit V2.1\bin\NUnit.Framework.dll"/></references>    <sources>       <includes name="HashtableTest.cs"/>    </sources></csc>

首先,我必须设置该 csc 元素的 target。在该例中,我将创建一个 .dll 文件,因此我将 target 设置为 library。接下来,我必须设置 csc 元素的 output,它是将要创建 .dll 文件的位置。最后,我需要设置 debug 属性,它确定了是否在调试中编译该项目。因为我在前面创建了一个用于存储该值的属性,所以我可以使用下面的字符串来访问该属性的值:${debug}。Csc 元素还包含一些子元素。我需要创建两个元素:references 元素将告诉 NAnt 需要为该项目引用哪些程序集,sources 元素告诉 NAnt 要在生成过程中包含哪些文件。在该示例中,我引用了 NUnit.Framework.dll 程序集并包含了 HashtableTest.cs 文件。图 8 中显示了完整的生成文件。(您通常还要创建一个干净的 target,用于删除生成的文件,但为了简洁起见,我已经将其省略。)

要生成该文件,我需要转到我的项目的根目录(生成文件位于此处),然后从该位置执行 nant.exe。如果生成成功,您可以在该应用程序的 bin 目录中找到 .dll 和 .pdb 文件。尽管使用 NAnt 肯定不像在 Visual Studio 中单击 Build 那样简单,但它仍然是一种非常强大的工具,可用于开发按自动计划运行的生成过程。NAnt 还包括一些有用的功能,例如能够运行单元测试或者复制附加文件(这些功能没有受到当前 Visual Studio 生成过程的支持)。

NAnt 是一个开放源代码项目,并且可以从 http://nant.sourceforge.net 下载。

转换工具

我已经将两个独立的工具合在一起放在标题“转换工具”下面。这两个工具都非常简单,但又可能极为有用。第一个工具是 ASP.NET 版本转换器,它可用于转换 ASP.NET(虚拟目录在它下面运行)的版本。第二个工具是 Visual Studio Converter,它可用于将项目文件从 Visual Studio .NET 2002 转换到 Visual Studio .NET 2003。

当 IIS 处理请求时,它会查看正在请求的文件的扩展名,然后基于该 Web 站点或虚拟目录的扩展名映射,将请求委派给 ISAPI 扩展或者自己处理该请求。这正是 ASP.NET 的工作方式;将为所有 ASP.NET 扩展名注册扩展名映射,并将这些扩展名映射导向 aspnet_isapi.dll。这种工作方式是完美无缺的,除非您安装了 ASP.NET 1.1 — 它会将扩展名映射升级到新版本的 aspnet_isapi.dll。当在 ASP.NET 1.0 上生成的应用程序试图用 1.1 版运行时,这会导致错误。要解决该问题,可以将所有扩展名映射重新转换到 1.0 版的 aspnet_isapi.dll,但是由于有 18 种扩展名映射,所以手动完成这一工作将很枯燥。这正是 ASP.NET 版本转换器可以发挥作用的时候。使用这一小型实用工具,可以转换任何单个 ASP.NET 应用程序所使用的 .NET 框架的版本。


9 ASP.NET 版本转换器

图 9 显示了实际运行的 ASP.NET 版本转换器。它的使用方法非常简单,只须选择相应的应用程序,然后选择您希望该应用程序使用的 .NET 框架版本。该工具随后将使用 aspnet_regiis.exe 命令行工具将该应用程序转换到所选版本的框架。随着将来版本的 ASP.NET 和 .NET 框架的发布,该工具将变得更为有用。

ASP.NET 版本转换器由 Denis Bauer 编写,并且可以从 http://www.denisbauer.com/NETTools/ASPNETVersionSwitcher.aspx 下载。

Visual Studio .NET 项目转换器(参见图 10)非常类似于 ASP.NET 版本转换器,区别在于它用于转换 Visual Studio 项目文件的版本。尽管在 .NET 框架的 1.0 版和 1.1 版之间只有很小的差异,但一旦将项目文件从 Visual Studio .NET 2002 转换到 Visual Studio .NET 2003,将无法再把它转换回去。虽然这在大多数时候可能不会成为问题(因为在 .NET 框架 1.0 版和 1.1 版之间几乎没有什么破坏性的更改),但在某些时刻您可能需要将项目转换回去。该转换器可以将任何解决方案或项目文件从 Visual Studio 7.1 (Visual Studio .NET 2003) 转换到 Visual Studio 7.0 (Visual Studio .NET 2002),并在必要时进行反向转换。


10 Visual Studio .NET 项目转换器

Visual Studio .NET 项目转换器由 Dacris Software 编写。该工具可以从 http://www.codeproject.com/macro/vsconvert.asp 下载。

小结

本文采用走马观花的方式介绍了上述工具,但我已经试图起码向您提供足够的信息以激起您的好奇心。我相信本文已经让您在某种程度上领悟了几个免费工具,您可以立即开始使用这些工具来编写更好的项目。同时,我还要敦促您确保自己拥有所有其他可以获得的合适工具,无论是最新版本的 Visual Studio、功能强大的计算机还是免费的实用工具。拥有合适的工具将使一切变得大不相同。


posted @ 2006-11-07 14:19 风雨兼程 阅读(357) | 评论 (1)编辑 收藏

在ASP.NET 中实现单点登录

由于某些原因,在我们的应用中会遇到一个用户只能在一个地方登录的情况,也就是我们通常所说的单点登录。在ASP.NET中实现单点登录其实很简单,下面就把主要的方法和全部代码进行分析。

实现思路

利用Cache的功能,我们把用户的登录信息保存在Cache中,并设置过期时间为Session失效的时间,因此,一旦Session失效,我们的Cache也过期;而Cache对所有的用户都可以访问,因此,用它保存用户信息比数据库来得方便。

查看示例

SingleLogin.aspx代码

<%@ Page language="c#" Codebehind="SingleLogin.aspx.cs" AutoEventWireup="false"
 Inherits="eMeng.Exam.SingleLogin" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>单点登录测试</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<meta http-equiv="Author" content="孟子E章">
<meta http-equiv="WebSite" content="http://dotnet.aspx.cc/">
<style>
H3 { FONT: 17px 宋体 }
INPUT { FONT: 12px 宋体 }
SPAN { FONT: 12px 宋体 }
P { FONT: 12px 宋体 }
H4 { FONT: 12px 宋体 }
</style>
</HEAD>
<body MS_POSITIONING="GridLayout">
<form id="Form1" method="post" runat="server">
  <div align="center">
    <h3>单点登录测试</h3>
    <p>用户名称:<asp:TextBox id="UserName" runat="server"></asp:TextBox></p>
    <p>用户密码:<asp:TextBox id="PassWord" runat="server" TextMode="Password"></asp:TextBox></p>
    <p><asp:Button id="Login" runat="server" Text=" 登  录 "></asp:Button></p>
    <p><asp:Label id="Msg" runat="server"></asp:Label></p>
  </div>
</form>
</body>
</HTML>

SingleLogin.aspx.cs代码

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace eMeng.Exam
{
/// <summary>
/// SingleLogin 的摘要说明。
/// 实现单点登录
/// </summary>
public class SingleLogin : System.Web.UI.Page
{
protected System.Web.UI.WebControls.TextBox UserName;
protected System.Web.UI.WebControls.TextBox PassWord;
protected System.Web.UI.WebControls.Label Msg;
protected System.Web.UI.WebControls.Button Login;

private void Page_Load(object sender, System.EventArgs e)
{
  // 实际例子可访问:
  // http://dotnet.aspx.cc/Exam/SingleLogin.aspx
}

#region Web 窗体设计器生成的代码
override protected void OnInit(EventArgs e)
{
  InitializeComponent();
  base.OnInit(e);
}

/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
  this.Login.Click += new System.EventHandler(this.Login_Click);
  this.Load += new System.EventHandler(this.Page_Load);
}
#endregion

private void Login_Click(object sender, System.EventArgs e)
{
  // 作为唯一标识的Key,应该是唯一的,这可根据需要自己设定规则。
  // 做为测试,这里用用户名和密码的组合来做标识;也不进行其它的错误检查。

  // 生成Key
  string sKey = UserName.Text + "_" + PassWord.Text;
  // 得到Cache中的给定Key的值
  string sUser = Convert.ToString(Cache[sKey]);
  // 检查是否存在
  if (sUser == null || sUser == String.Empty)
  {
    // Cache中没有该Key的项目,表名用户没有登录,或者已经登录超时
    // 注意下面使用的TimeSpan构造函数重载版本的方法,是进行是否登录判断的关键。
    TimeSpan SessTimeOut = new TimeSpan(0,0,System.Web.HttpContext.Current.Session.Timeout,0,0);
    HttpContext.Current.Cache.Insert(sKey,sKey,null,DateTime.MaxValue,SessTimeOut,
      System.Web.Caching.CacheItemPriority.NotRemovable,null);
    Session["User"] = sKey;
    // 首次登录,您可以做您想做的工作了。
    Msg.Text="<h4 style='color:red'>嗨!欢迎您访问<a href='http://dotnet.aspx.cc/'>【孟宪会之精彩世界】";
    Msg.Text += "</a>,祝您浏览愉快!:)</h4>";
  }
  else
  {
    // 在 Cache 中发现该用户的记录,表名已经登录过,禁止再次登录
    Msg.Text="<h4 style='color:red'>抱歉,您好像已经登录了呀:-(</h4>";
   return;
  }
}
}
}

posted @ 2006-11-07 14:11 风雨兼程 阅读(309) | 评论 (1)编辑 收藏

ASP.NET应用中十大常见的潜在问题

Jeff Prosise在《MSDN杂志》2006年7月期上的文章历数ASP.NET应用中常见的,容易出错,影响性能和扩缩性的潜在问题

Keep Sites Running Smoothly By Avoiding These 10 Common ASP.NET Pitfalls
http://msdn.microsoft.com/msdnmag/issues/06/07/WebAppFollies/

1。设置输出缓存的用户控件,如果用LoadControl动态装载,LoadControl返回对象属于PartialCachingControl类,其中的CachedControl也许并不存在,无法转换成原用户控件对象类

2。在 IIS 6.0 中,在设置kernel模式输出缓存的情形下,OutputCacheModule模块有时会保留缓存输出的Set-Cookie header,导致会话串门(cross-session),即一个用户能看到其他用户的会话数据

具体参考KB文章
An ASP.NET page is stored in the HTTP.sys kernel cache in IIS 6.0 when the ASP.NET page generates an HTTP header that contains a Set-Cookie response
http://support.microsoft.com/kb/917072

或者禁止kernel模式输出缓存
<httpRuntime enableKernelOutputCache="false" />

具体参考
http://support.microsoft.com/kb/820129

3。 Forms 认证Ticket的存活时间。在ASP.NET 1.*中,在没有用编码设置的情形下,如果是持久保存,存活时间是50年,如果是非持久保存,存活时间是30分钟。这个问题在ASP.NET 2.0中已经解决,默认存活时间会用web.config里的设置。在ASP.NET 1.*中,只能用编码来解决,具体编码参考原文中的例子。

4。 View State,如果滥用的话,是无声的性能杀手,特别是DataGrids和GridViews等,应该设置EnableViewState=false,或者考虑通过更改LoadPageStateFromPersistenceMedium/SavePageStateToPersistenceMedium把View State放在服务器端。

5。如果使用SQL Server做会话状态服务器的话,默认情形下,每个请求会访问状态服务器2次,造成性能下降。解决方案是,在不用会话状态的页面里,设置

<%@ Page EnableSessionState="false" ... %>

在只读会话状态的页面里,设置

<%@ Page EnableSessionState="ReadOnly" ... %>

6。在ASP.NET 2.0应用中,如果在web.config里设置

<roleManager enabled="true" />

默认情形下,角色数据是不缓存的,如果角色管理器需要确认当前用户的角色的话,会访问数据库,导致性能下降,解决方案是设置把角色数据缓存在Cookie里(这个Cookie是加过密的)

<roleManager enabled="true" cacheRolesInCookie="true" />

7。Profile 特性持久化问题,在默认情形下,ASP.NET profile管理器使用XML持久机制持久化自定义Profile类,不保存这些类的私有成员,解决方案是把这些类标为[Serializable]或实现ISerializable ,这样profile管理器会使用binary serializer

8。过长的数据库查询或I/O操作会导致线程池的饱和,导致ASP.NET的性能下降。ASP.NET 2.0提供了异步网页(asynchronous page)机制来缓解这个问题。具体参考Jeff Prosise在《MSDN杂志》2005年10月期上的文章

Asynchronous Pages in ASP.NET 2.0
http://msdn.microsoft.com/msdnmag/issues/05/10/WickedCode/

9。<identity impersonate="true" /> 导致客户端用户的身份模拟,要慎用,避免用身份模拟(Impersonation)替代ACL授权。

10。别太有信心,多用Profiler剖析你的应用对数据库的访问情形。重视数据库的设计,认识到DataSet和DataAdapter对web应用也许并不合适,数据访问层要恰当设计,防止粗劣细分(poor factorization),避免在相对简单的操作上浪费太多的CPU周期,导致性能下降

posted @ 2006-11-07 14:08 风雨兼程 阅读(175) | 评论 (0)编辑 收藏

Asp.net动态生成html页面

此功能适用于后台数据库功能不强的web站点,即大部分文本不是存放在数据库的记录中,而是放在html文件或者xml文件中,仅仅把索引放到数据库中,如文章标题、类别、查询关键字等。这样适合于后台没有诸如MS Sql Server这样的数据库支持的Web站点。

适用于新闻发布系统,比如sina、163等都是采用动态生成html页面的。

适用于需动态定制页面的程序。比如论坛、聊天室等。可以加载定制好的html页面,来加强美观。

思路

1. 利用如Dw-Mx这样的工具生成html格式的模板,在需要添加格式的地方加入特殊标记(如$htmlformat$),动态生成文件时利用代码读取此模板,然后获得前台输入的内容,添加到此模板的标记位置中,生成新文件名后写入磁盘,写入后再向数据库中写入相关数据。

2. 使用后台代码硬编码Html文件,可以使用HtmlTextWriter类来写html文件。

优点

1. 可以建立非常复杂的页面,利用包含js文件的方法,在js文件内加入document.write()方法可以在所有页面内加入如页面头,广告等内容。

2. 静态html文件利用MS Windows2000的Index Server可以建立全文搜索引擎,利用asp.net可以以DataTable的方式得到搜索结果。而Win2000的Index服务无法查找xml文件的内容。如果包括了数据库搜索与Index索引双重查找,那么此搜索功能将非常强大。

3. 节省服务器的负荷,请求一个静态的html文件比一个aspx文件服务器资源节省许多。

缺点

思路二: 如果用硬编码的方式,工作量非常大,需要非常多的html代码。调试困难。而且使用硬编码生成的html样式无法修改,如果网站更换样式,那么必须得重新编码,给后期带来巨大的工作量。

因此这里采用的是第一种思路

示列代码

1.定义(template.htm)html模板页面

<html>

<head>

<title></title>

<meta http-equiv="Content-Type" content="text/html; charset=gb2312">

</head>

<body >

<table $htmlformat[0] height="100%" border="0" width="100%" cellpadding="10" cellspacing="0" bgcolor="#eeeeee" style="border:1px solid #000000">

<tr>

<td width="100%" valign="middle" align="left">

<span style="color: $htmlformat[1];font-size: $htmlformat[2]">$htmlformat[3]</span>

</td>

</tr>

</table>

</body>

</html>

2.asp.net代码:

//---------------------读html模板页面到stringbuilder对象里----

string[] format=new string[4];//定义和htmlyem标记数目一致的数组

StringBuilder htmltext=new StringBuilder();

try

{

using (StreamReader sr = new StreamReader("存放模板页面的路径和页面名"))

{

String line;

while ((line = sr.ReadLine()) != null)

{

htmltext.Append(line);

}

sr.Close();

}

}

catch

{

Response.Write("<Script>alert('读取文件错误')</Script>");

}

//---------------------给标记数组赋值------------

format[0]="background=\"bg.jpg\"";//背景图片

format[1]= "#990099";//字体颜色

format[2]="150px";//字体大小

format[3]= "<marquee>生成的模板html页面</marquee>";//文字说明

//----------替换htm里的标记为你想加的内容

for(int i=0;i<4;i++)

{

htmltext.Replace("$htmlformat["+i+"]",format[i]);

}

//----------生成htm文件------------------――

try

{

using(StreamWriter sw=new StreamWriter("存放路径和页面名",false,System.Text.Encoding.GetEncoding("GB2312")))

{

sw.WriteLine(htmltext);

sw.Flush();

sw.Close();

}

}

catch

{

Response.Write ("The file could not be wirte:");

}

小结

用此方法可以方便的生成html文件。程序使用了是循环替换,因此对需替换大量元素的模板速度非常快。

posted @ 2006-11-07 14:01 风雨兼程 阅读(775) | 评论 (0)编辑 收藏

使用__doPostBack函数回送表单.

在asp.net中服务器控件回送表单是通过调用__doPostBack函数来回送表单,触发事件的,先来看看__doPostBack函数:
function __doPostBack(eventTarget, eventArgument) {
    if (theForm.onsubmit == null || theForm.onsubmit()) {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}
第一个参数是控件名称,第二个参数包含事件的额外信息.
这个使用例子中,含有一个hiddenfield隐含控件和的一个linkbutton控件,hiddenfield用于保存要存接收的值,linkbutton用于触发点击事件void lbtnDeleteDiscussion_Click(object sender, EventArgs e)
例:
****
<asp:hiddenfield id="hidfDiscussionId" runat="server" />
<asp:linkbutton id="lbtnDeleteDiscussion" runat="server" onclick="lbtnDeleteDiscussion_Click"></asp:linkbutton>
*******************
    void lbtnDeleteDiscussion_Click(object sender, EventArgs e)
    {
        string[] roles = SiteSettings.Instance.ReviewAdminRoles.Split(',');
        bool isDelete = false;
        foreach (string role in roles)
        {
            if (HttpContext.Current.User.IsInRole(role))
            {
                isDelete = true;
                break;
            }
        }
        if (isDelete)
        {
            if (!string.IsNullOrEmpty(hidfDiscussionId.Value))
            {
                int discussionId = int.Parse(hidfDiscussionId.Value);
                DiscussionManager.RemoveDiscussionItem(discussionId);
                Response.Redirect(Request.RawUrl);
            }
        }
    }
void Page_Load(object sender, EventArgs e)
{
    ....
    sb.Remove(0, sb.Length);
    sb.Append("function DeleteDiscussion(discussionId){");
    sb.AppendFormat("confirm('{0}');", "确定要删除这条讨论吗?");
    sb.AppendFormat("document.all('{0}').value = discussionId;", hidfDiscussionId.ClientID);
    sb.AppendFormat("__doPostBack('{0}','');", lbtnDeleteDiscussion.ClientID.Replace('_', '$'));
    sb.Append("}");
    Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "DeleteDiscussion", sb.ToString(), true);
}
****************
现在在客户端使用HTML代码也可以可以触发指定的服务器端事件并且传递一些值了.
如:
 sb.AppendFormat("<a href=\"javascript:DeleteDiscussion('{0}');\">",item.DiscussionId);
 sb.AppendFormat("<img src='{0}' alt='{1}' border='{2}' />",
CommerceContext.GetThemesImagePathForImgTag("Common/button_delete.gif"),
 "删除讨论","0");
sb.Append("</a>");

这里使用html代码也以触发一个删除事件,把指定的项删除.
这个例子是我上一篇Blog的相关内容,给合Cilent CallBack可以实现无刷新地动态生成的html代码,并可以触发服务器端事件

posted @ 2006-11-07 11:36 风雨兼程 阅读(244) | 评论 (0)编辑 收藏