Thu 21 January, 2010

add to del.icio.us. look up in del.icio.us.
add to furl
前些天的一些突发事件,使得我发现了Z-Blog存在的一个很严重的性能问题,就是当单篇文章日访问量数万的时候,会有大量并发用户同时写Access数据库,会导致数据库死锁,之后的外在表现就是CPU占用猛增。
经过对Z-Blog的代码进行分析,我发现其原因是大并发用户同时写数据库产生的原因。
我这里想到了一个针对这个问题的解决方案,就是时间缓存,定时写数据库,在一定时间内,数据只写入内存,不写入数据库,当缓存时间到了以后,再把内存的数据写入数据库。这样,即使再大的并发量,也不会再发生数据库死锁的问题了。
经过初步的代码修改和测试,发现修改完了以后就没有这个问题了。
修改方法是,打开FUNCTION目录下的c_html_js.asp文件,修改UpdateCountInfo函数为以下代码即可。
Function UpdateCountInfo(id)
Dim strLastUpdate
Dim intArticleCount
Dim aryArticleCount
Dim objRS
Application.Lock
strLastUpdate=Application(ZC_BLOG_CLSID&"LAST_UPDATE")
aryArticleCount=Application(ZC_BLOG_CLSID&"CACHE_ARTICLE_VIEWCOUNT")
aryArticleCount(id)=aryArticleCount(id)+1
Application(ZC_BLOG_CLSID&"CACHE_ARTICLE_VIEWCOUNT")=aryArticleCount
Application.UnLock
If IsEmpty(strLastUpdate) Or Not IsDate(strLastUpdate) Then
Application.Lock
Application(ZC_BLOG_CLSID&"LAST_UPDATE") = Now()
strLastUpdate = Application(ZC_BLOG_CLSID&"LAST_UPDATE")
Application.UnLock
End If
If DateDiff("s",strLastUpdate,Now()) > 30 Then
'如果当前时间与上次保存计数值的时间差大于设定的时间间隔,则把计数值重新写入数据库
Call OpenConnect()
Set objRS=objConn.Execute("SELECT [log_ID],[log_ViewNums] FROM [blog_Article] WHERE [log_ID] =" & id)
If (not objRS.bof) And (not objRS.eof) Then
intArticleCount=objRS("log_ViewNums")
Else
intArticleCount=0
End If
objRS.Close
Set objRS=Nothing
If aryArticleCount(id) > intArticleCount Then
objConn.Execute("UPDATE [blog_Article] SET [log_ViewNums]=" & CStr(aryArticleCount(id)) & " WHERE [log_ID] =" & id)
Application.Lock
Application(ZC_BLOG_CLSID&"LAST_UPDATE") = Now()
Application.UnLock
Else
aryArticleCount(id) = intArticleCount
Application.Lock
Application(ZC_BLOG_CLSID&"CACHE_ARTICLE_VIEWCOUNT")=aryArticleCount
Application.UnLock
End If
Call CloseConnect()
End If
End Function
另外,默认的Z-Blog留言评论是没有用户IP地址显示的,通过一些修改,可以在留言中增加用户IP地址前三位的显示功能,具体实现方法如下。
修改c_system_lib.asp文件的Public Function MakeTemplate(strC)函数,加入下面语句:
ReDim aryTemplateTagsName(12)
ReDim aryTemplateTagsValue(12)
aryTemplateTagsName( 12)="article/comment/ip"
if AuthorID=1 then
aryTemplateTagsValue(12)=""
else
aryTemplateTagsValue(12)="ip:" + Left(IP, InStrRev(IP, ".")) + "*"
end if
修改TArticle的Function Export_CMTandTB()函数,将 objComment.LoadInfoByArray(Array(objRS("comm_ID"),objRS("log_ID"),objRS("comm_AuthorID"),objRS("comm_Author"),objRS("comm_Content"),objRS("comm_Email"),objRS("comm_HomePage"),objRS("comm_PostTime"),"","")) 改为 objComment.LoadInfoByArray(Array(objRS("comm_ID"),objRS("log_ID"),objRS("comm_AuthorID"),objRS("comm_Author"),objRS("comm_Content"),objRS("comm_Email"),objRS("comm_HomePage"),objRS("comm_PostTime"),objRS("comm_IP"),""))
修改 b_article_comment.html ,增加 <#article/comment/ip#>标签。
之后,文章评论就会出现评论者的IP地址栏,不过有个问题是,当评论有人回复的是时候,只能留下最后一个回复者的IP地址。
相关文章:
关于我们: 地址 - www.williamlong.info 我的Google Reader - 我的Twitter - 我的Facebook - 月光博客Twitter月光博客投稿信箱:williamlong.info(at)gmail.com
add to del.icio.us. look up in del.icio.us.
add to furl
2009是挑战与机遇并存的一年。面对来势凶猛、席卷全球的金融危机,谷歌中国积极应对,推出多项措施与服务帮助中国企业摆脱经济危机、走出困境,同时继续在搜索、移动等领域不断创新,引领产业发展,获得了众多中国媒体和有关机构的肯定。
为满足出口外贸型企业在全球经济低迷的环境下进行海外推广的需求,谷歌中国联合授权代理商,推出了“一站式全球互联网广告营销解决方案-出口易”,借助谷歌全球化的平台和精准的匹配技术,帮助广大外贸型企业展开全球营销。针对国内中小企业网络基础薄弱的特点,谷歌还提供了从建设、优化网站到租用海外域名等一站式增值服务,受到了广大中小企业的欢迎。谷歌也籍此获得了《21世纪经济报道》授予的“中国最佳商业模式创新奖”。

此外,谷歌中国还获得了由《商务周刊》杂志颁发的“100快公司”之“最快反应公司奖”。杂志充分肯定了谷歌为帮助中国企业应对经济危机而快速推出的各种举措,尤其是为鼓励中小企业尝试搜索营销而实施的“5000万中小企业激励计划”。

凭借出色的综合表现,谷歌还先后获得了艾瑞授予的“2008-2009中国最佳互联网企业奖”以及易观国际授予的“2009年最佳应用服务提供商:移动互联网奖”。


除了所展示的商业价值,谷歌不断创新的技术和产品也同样得到了媒体的好评。其中,最新推出的Google Wave荣膺《三联生活周刊》“2009年度最佳产品与设计”之一。周刊认为,Google Wave首次颠覆了传统的在线沟通模式,实现了电子邮件、即时通讯、博客、社区网络等网络通讯工具的完美集成,极大地提高了沟通效率。

此外,权威数码杂志《CHIP》新电脑授予了谷歌Android手机操作平台“2009年度CHIP Highlights大奖”。作为全球第一个完全开放的手机平台,谷歌正在与广大运营商、设备开发商和应用开发商共享计算资源和平台技术,共同开发更多更创新更易用的基于Android的移动互联网应用和设备,提升用户的互联网体验。

在《南方周末》的中国年度3G 风云榜评选中,Google手机地图凭着出色的地图质量、创新的实时路况、公交线路等应用,获得了好评,一举荣获“2009年度最受欢迎地图软件奖”。

同时,在IT168主办的“2009年度最受欢迎的软件评选”活动中,经由全国网友投票,Google Chrome OS以同类软件产品中的最高票数荣获2009年度IT168软件“最具创新奖”。

谷歌不仅在技术上秉承着不断创新的理念,在中国的本地化过程中,始终不忘回馈社会和大众。2009年12月,北京市互联网宣传管理办公室、北京网络媒体协会以及北京互联网发展论坛组委会联合授予谷歌中国“2009年度网络媒体爱心公益突出贡献奖”。
众多媒体和机构的奖项是对谷歌中国的肯定,同时也是激励。谷歌中国将不断创新,为了让我们的生活和工作因互联网而变得更加美好而不懈努力。
add to del.icio.us. look up in del.icio.us.
add to furl
过去几天里,我们看到有很多关于谷歌中国以及谷歌员工的不真实的传言,一些报道称我们已经关闭了在中国的办公室,还有一些报道称我们在中国的员工已经接到通知将于近期离职。这些都是不真实的。目前,谷歌中国的员工同过去一样在办公室正常工作,讨论产品开发,与客户进行沟通。尽管谷歌总部管理层近期宣布他们将会在未来的几个星期与中国政府就一些事宜进行商讨,谷歌中国的员工们仍在一如既往地努力向我们的用户和合作伙伴提供最好的产品和服务,用户和合作伙伴对谷歌是非常重要的。
add to del.icio.us. look up in del.icio.us.
add to furl
转载自:Google 中文网站站长博客
处理网站上的重复内容是一项非常困难的工作。随着网站的发展,必须不断增加、改变或删除各种功能;与此同时,不同的网站内容来了又去。一段时间后,许多网站都会有以多个URL网址形式存在的系统化垃圾代码,这些URL都返回同样的内容。在一般情况下,除了会增加搜索引擎的抓取和索引内容的难度以外,你的网站上存在重复内容并不构成问题。此外,通过导入链接而得到的PageRank以及类似信息可能会在我们尚未确定为重复内容的网页间扩散,导致你的首选网页在谷歌中的排名降低。
处理你的网站内部重复内容的步骤
识别你网站上的重复内容是处理重复内容的第一步,也是最重要的一步。使用一种简单的方法可以实现这个目的,即从网页中选取一段独特的文本代码,然后搜索这段文本,并使用谷歌的site:query将搜索结果限制为自己网站上的网页。如此以来,搜索出的含有同样内容的多个结果就是你需要处理的重复内容。
确定你需要的首选URL网址。
在处理重复内容之前,必须确保你的首选URL网址结构。对于这段内容,你希望使用哪一个URL网址?
在必要和可能的情况下使用301永久重定向。
可能的话,可以使用301代码将重复网址重新定向为你选择的网址。此举能够帮助用户和搜索引擎在访问重复URL网址时找到你的首选URL网址。如果你的网站有数个域名,可选择一个域名,使用将其他域名301重定向到这个域名,同时还要确保其转向正确的特定网页,而不仅是域根目录。如果网站同时支持www和非www主机名,可选择其中一种,使用Google网站站长工具中的首选域设置,再进行适当的重定向。
在可能的情况下,在你网页上使用rel="canonical"。
在无法使用301重定向的情况下,可以使用rel="canonical",以方便搜索引擎更好地理解你的网站和首选URL网址。Ask.com、Bing和Yahoo!等主要搜索引擎都支持这种链接标签的使用。
可能的情况下,在Google网站站长工具中使用URL参数处理工具
如果部分或全部网站重复内容来自带有查询参数的URL网址,则此工具将帮助你将URL内的重要参数和不相关参数通知我们。有关此工具的详细信息可参见我们的博客声明。
怎样处理robots.txt文件?
使用robots.txt文件来禁止对重复内容进行抓取不在我们推荐的方法之内。我们建议你不要使用robots.txt文件或其他方式来禁止对你网站上的重复内容的访问。你可以使用rel="canonical" 链接标签、URL参数处理工具或301重定向。如果完全阻止了对重复内容的访问,搜索引擎必须将这些URL作为独立的不同网页处理,因为它们无法分辨出这些URL其实只是指向相同内容的不同网址。更好的解决方法是允许对其进行抓取,同时用我们推荐的方法将这些URL网址明确标记为重复内容。如果你允许我们访问这些URL网址,Google抓取机器人将学会通过查看URL确定其是否为重复内容,在各种情况下都能很好地避免不必要的重复爬行抓取。为了防止重复内容仍然引导我们过多地爬行搜索你的网站,你还可以调整Google网站站长工具里的抓取速度。
我们希望这些方法能够帮助控制你的网站上的重复内容。你也可登录我们的帮助中心查看有关重复内容的基本信息。如有任何问题,欢迎你随时进入Google网站站长帮助论坛参加讨论。
add to del.icio.us. look up in del.icio.us.
add to furl
转载自:Google 中文网站站长博客
今天为大家介绍一些“声誉管理”的小贴士:即如何管理你在网络上的个人形象和隐私信息,并进行所谓的“搜索结果的声誉管理”。
三思而行
声誉管理的第一步是防患于未然:在将隐私信息发布到网上之前三思而行。记住:在某些特定情况下你发布的信息可能并无不妥,但也许很长时间以后,即使脱离当时的语境,人们依然能通过搜索引擎,轻而易举地找到你的信息,而这些人可能并不会访问你最初发布信息的网页。
釜底抽薪
如果你在网上发现一些自己不愿见到的隐私信息,你应该首先想办法让它从其所在的网站上消失。别急着联系Google,你需要做的是先将其从首发网站上删除。因为Google并不拥有互联网上的信息,Google的搜索结果仅仅只是反映网上已有的结果。只要你没有将信息从首发网站上删除,不管你查找的内容有没有显示在Google的搜索结果中,人们都有可能在首发网站上、或通过其他搜索引擎或社交网站,看到你的信息。记住:要釜底抽薪。
- 如果内容是发布在你自己的网站上,那很容易——直接删除就行。Google刷新网页发现变更后,它就会自动从搜索结果中消失。
- 如果是你自己把内容发布到了其他网站上,比如照片或个人资料,要想删除也不难。
- 如果你无法自行删除内容,还可以联系网站站长 ,请他们删除相关的内容或页面。
当你或站长对页面进行删除或编辑操作后,可使用Google的URL清除工具 ,将这些内容从Google搜索结果上删除。
主动出击
不过,你有时也会碰到联系不上网站站长,或者他们拒绝删除相关内容的情况。比如,如果有人在某个餐厅点评网站或消费者投诉网站上对你的业务发表了负面评论,那些网站也许不愿删掉评论。而因为不能从首发网站上删除内容,你就无法将其从Google搜索结果中彻底清除。不过,你可以积极发布有用的、正面的信息,从而尽量减少那些差评在搜索结果中的出现率。如果你能让人们看到的正面信息多于负面信息,就可以降低那些负面的或令人尴尬的内容对你的声誉造成的损害 。
你可以通过多种渠道发布或传播正面信息:
- 创建你的Google个人资料 (Google profile)。如果有人搜索你的名字,Google就可能会在搜索结果中显示你的Google个人资料页面链接,人们点击进去,就能在你的个人页面上看到你想让人们知道的信息。
- 如果有客户对你的业务发表了负面评论,你可以请那些对你的服务很满意的客户为你的业务提供更全面的评价。
- 如果有人在博客中发表了你不想见到的个人照片,你可以挑选几张自己满意的照片发布在博客上。
- 如果新闻网站报道了一起对你不利的案件,但你最终获判无罪,你可以要求该网站更新文章或发布一篇后继报道,澄清你是清白的。(虽然这一条看似不常见,但不管你信不信,我们接到了大量这类请求。)
希望这几个小贴士对您有用!同时欢迎访问我们的网络搜索论坛,就如何管理网络声誉这一问题分享您的意见或故事。
add to del.icio.us. look up in del.icio.us.
add to furl
发表者:Jun Mukai,移动搜索组 软件工程师
转载自:日文网站管理员中心博客
原文:http://googlewebmastercentral-ja.blogspot.com/2009/10/blog-post.html
发表于:2009年11月18日
不久前,我介绍了几种确保您的手机网站能被谷歌正确索引的办法。今天,我想与负责同一网站电脑版和手机版的各位网站站长们分享一些有用的信息。
对于同时管理一个站点的电脑网站和手机网站的站长来说,最常遇到的问题就是用户的电脑上会显示站点的手机版,或通过手机的打开却是电脑版。以下两种方法能够很好地解决这个问题:
将手机用户重定向至正确的版本
当一个手机用户或爬虫(如Googlebot-Mobile)通过手机访问一个URL的电脑版URL 时,您可以将他们重定向至同一网页相应的手机页面 。谷歌会注意到同一个网页 的两个版本间的关系,由此台式机搜索的结果将以电脑网页 显示,而手机设备搜索的将以手机网页显示。
如果您重定向用户,请确保同一URL的手机/电脑版上的内容尽可能相同。比如说,如果您运营了一个购物网站,有用户通过手机访问该网站某产品的电脑版网页,您需要确保将用户重定向到该产品对应的的手机网页 ,而不是该网站手机版的主页。我们有时发现有网站试图利用这种重定向到主页的方式来提升其网站的搜索排名,但实际上这种做法只会影响用户体验,因此网站应该尽量避免这种做法。
通过User-agent切换内容
有些网站的台式机网页内容和手机网页内容使用同一个URL,但可根据User-agent来改变网页格式。换句话说,手机用户和台式机用户都访问同一个URL(也就是无需重定向),但是内容/格式会根据User-agent的不同发生改变。那么,不管是手机搜索还是电脑搜索都将得到同一个URL,电脑用户将浏览到该内容的电脑页面,而手机用户将浏览到该内容的手机页面。
然而,如果您不能正确设置您的网站,您的网站会被视为隐藏网页(cloaking),继而会从我们的搜索结果中消失。所谓隐藏网页,指的是显示给谷歌爬虫的页面与显示给普通浏览者的页面不同,从而提高搜索排名的一种作弊手法。隐藏网页会导致相关度较低的搜索结果(即便页面的实际内容与用户所看到或所需的信息毫不相关,该页面也显示在搜索结果中),因此我们对其采取了非常严格的措施。
那么如果您在同一URL中提供两种版本,“用户看到的网页”究竟是怎样的呢?正如我曾经所提到的,谷歌通过“Googlebot”完成互联网搜索,而通过“Googlebot-Mobile”完成手机搜索。若想严格遵守谷歌的规范,您应该确保向Googlebot显示的内容和一般的电脑用户所看到的内容相同,向Googlebot-Mobile显示的内容和一般移动设备用户所看到的内容相同。对Googlebot和Googlebot-Mobile显示不同的内容是完全可以的。

您的网页也可能会被意外地定义为隐藏网页,举例来说,如果您的网站向电脑浏览器发送回“请通过手机访问”这类信息,但对两种爬虫都返回完整的手机网页(这样Googlebot也接收到含有实际内容的手机版)。在这种情况下,互联网用户浏览到的网页(如“请通过手机访问”)将会和Googlebot看到的网页不一样(如“欢迎访问我们的网站”)。重申一遍,我们探测隐藏网页是为了确保用户不管是通过谷歌爬虫还是 Googlebot-Mobile搜索都能获得同等相关的内容。
支持手机访问的网站内容显示图解
我们每天都在努力提升搜索结果的质量并试图解决各种问题,但是一个网站的电脑网站和手机网站之间的关系可能会非常微妙,因此我们非常希望得到网站站长们的合作。你们的支持将有助于更多的手机内容能够被谷歌索引,从而进一步提升搜索结果的质量。在此我们谢谢你们的配合,良好有效的合作能使移动搜索用户获得更加优质的搜索体验。
add to del.icio.us. look up in del.icio.us.
add to furl
原文:http://googlewebmastercentral.blogspot.com/2009/11/new-software-version-notifications-for.html
转载自:Google 中文网站站长博客
发表者:
发表时间:2009年11月20日
Webmaster级别:全部
利用可观的计算能力完成真正有意义的任务,这正是在谷歌工作最引人入胜的地方。例如,我们试图帮助网站站长了解他们可能受到黑客攻击的网站。我们为实现这个目的初期努力已见成效,因此我们决定进一步扩展工作范围,将其他类型的网络应用软件包括在内——例如,内容管理系统(CMS)、论坛/公告板应用软件和状体追踪的软件等等。
不过,当前我们的目标并不局限于隔离易受攻击或可能被黑客攻破的软件包,我们还会通知站长,告知其网站运行的软件包或插件存在更新版本。例如,
我们通过对抓取的网页进行源代码解析,来确定需要被通知的网站,例如 WordPress和CMS应用软件包含标出了版本号的生成器元标签。事实证明,这种方法能够帮助我们有效地通知站长。因此,如果你是软件开发商,而且需要我们帮助你将软件产品的最新版本通知用户,那么你最好在软件产品中包含一个生成器元标签,标明软件版本。如果你是一位插件或桌面小工具(widget)开发商,那么在为用户提供的源代码中标注版本号码也将大有帮助。
当然,在源代码中包含版本号是否具有安全隐患的问题已经引起了广泛争论 - 因为这种版本号可能反而会帮助黑客或蠕虫病毒编写者了解网站自身的漏洞,从而进行有针对性的攻击。而版本号的优势在于,它能够提醒网站所有者何时需要更新网站。因此,我们认为,增加版本号的做法利大于弊。
希望这种方法能够为站长们提供帮助!如果你有任何问题或反馈,欢迎在此发表评论。
add to del.icio.us. look up in del.icio.us.
add to furl
转载自:Google 中文网站站长博客
你可能遇到过这种情况:好端端地在网上阅读文章或观赏视频,却突然跳出一些毫不相干、莫名其妙的留言,这不禁让人费解到底是怎么回事。有的人可能会听说过“群发链接”,事实上这就是有些网站站长为了提高自己网站的排名,在别的网站的留言板上大量粘贴他们自己网站的网址。有些人则稍做变通,用商业用户名发表一些普通留言(如:“好网站!”),点击就会链接进入他们的网站。
群发垃圾留言链接的危害
事实一:在别的网站的留言区滥发链接传播自己网站是一种糟糕而且有风险的行为。这样做降低了这些网站的质量,无异于破坏他人辛勤劳动的成果;原本也许是一个好的信息,现在却成了一连串毫无意义的关键词。
事实二:垃圾留言群发者妄图通过创建一些令人可疑的外部反向链接来提高自己网站的搜索排名。然而Google掌握了网站链接的曲线图,也能通过算法发现这些可疑变化 并对其进行处理。垃圾链接群发者至少需要花好几个小时到处群发垃圾链接,然而Google却能够很容易地让这些链接变得无效,所以到头来他们不过是白费力 气,倒不如考虑利用这些时间和精力来做一些更有意义的事情,从长远角度让自己的网站升值。
推广自己的网站无需群发垃圾链接
如果你想提高自己网站在搜索结果里的表现,群发垃圾留言链接绝非一个好办法。倒不如好好想想自己的网站是否提供了人们想查找的内容——比如有用的信息和工具。
事实:发表一些原创、有用的内容,让你的网站频频出现在搜索引擎结果中,这才是提高网站排名的制胜之道。如果你的网站内容引人入胜,那么你自然会得到大家的认可,自然而然地将也会有更多的网站主动链接你的网站。
此外,Google为如何提高你的网站的点击率和搜索率提供了几点建议,请查看我们的《Google SEO入门教程》。
如何防范垃圾信息侵袭我的网站?
留言板是获取信息的良好资源,也是聚集网站人气的有效手段。不要垃圾关键词和链接霸占有价值信息的位置。有鉴于此,我们建议几种保护您的努力成果和防范垃圾留言的方法:
- 禁止匿名留言。
- 使用CAPTCHA和其他方法禁止自动生成的垃圾留言。
- 使用留言管理功能。
- 对留言板内的链接添加“ nofollow”属性。
- 禁止在留言板中发表超链接。
- 使用robots.txt 或meta tags 标签阻止留言页面。
欲详细了解这些方法,请查看关于垃圾留言的帮助中心文档 。
我的网站上全是垃圾留言,怎么办?
亡羊补牢,未为晚矣!别让垃圾留言群发者败坏了他人的兴致。采取上述安全措施,阻挡垃圾信息和群发链接的散播,然后花点时间清理垃圾留言,并把这些垃圾留言 群发者加入黑名单。根据你的网站系统构架,你也许一次就能彻底禁止垃圾留言或清空留言,不必一条一条慢慢删,节省时间。
我曾经在第三方网站上发布过垃圾评论,该如何补救?
如果你曾经写过垃圾留言,现在想更正一下,你可以查看Google网站站长工具中的反向链接。登录Google网站站长工具,点击指向您网站的链接 。如果你发现有来自日志或其他允许留言的平台的可疑链接,应查看它们的URL。如果发现是你创建的垃圾链接,请将其主动删除,不行的话请联系对方网管清除链接。一旦你清除掉了自己创建的垃圾导入链接,就可以创建一个请求重新审核您的网站的文档。
如果你想知道更多细节,或者愿意加入讨论,请访问网络管理员帮助论坛,不过千万不要乱发垃圾留言哟~)。
add to del.icio.us. look up in del.icio.us.
add to furl
具体方法,如下所示:
add to del.icio.us. look up in del.icio.us.
add to furl
下面是Silverlight制作的时钟演示:
代码简单介绍:
旋转一个对象的中心点是可以定义在这个对象之外的。这个演示中, 时针,分针,秒针,盘上的格子,就是把旋转的中心点定义在盘面的中心,然后定义旋转转换而实现的。
比如盘面的格子部分,我们在样式中有如下定义:
<Style x:Key="MarkersBig" TargetType="Rectangle"> <Setter Property="RenderTransformOrigin" Value="0.5,12.8"/> <Setter Property="Fill" Value="White"/> <Setter Property="StrokeThickness" Value="0"/> <Setter Property="Width" Value="4"/> <Setter Property="Height" Value="10"/> <Setter Property="Canvas.Left" Value="158"/> <Setter Property="Canvas.Top" Value="32"/> </Style>
在MainPage.xaml的定义就简单成下面方式:
<Rectangle x:Name="MarkersBig00" Style="{StaticResource MarkersBig}"/> <Rectangle x:Name="MarkersBig30" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="30"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersBig60" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="60"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> ............ <Rectangle x:Name="MarkersBig330" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="330"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle>
巧妙利用故事板的Seek来实现按需播放,以秒针为例,MainPage.xaml 中我们的定义如下:
<!-- 秒针 --> <Path x:Name="SecondHand" Data="M1.75,100 L-0.5,100 L-0.5,0 L2,0 z" Fill="Red" Canvas.Left="158.494" Stretch="Fill" RenderTransformOrigin="0.51,1.199" StrokeThickness="0" Canvas.Top="40.05" UseLayoutRounding="False" Height="100"
Width="2.888"> <Path.RenderTransform> <TransformGroup> <RotateTransform Angle="0"/> </TransformGroup> </Path.RenderTransform> <Path.Resources> <Storyboard x:Name="SecondsHandStoryboard" > <DoubleAnimation From="0" To="360" Duration="00:01:00" RepeatBehavior="Forever" Storyboard.TargetProperty="(Path.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)" Storyboard.TargetName="SecondHand"/> </Storyboard> </Path.Resources> </Path> 我们在 Canvas_Loaded 事件中只需要简单的如下代码就完成了秒针位置的播放。
SecondsHandStoryboard.Begin(); SecondsHandStoryboard.Seek(DateTime.Now.TimeOfDay); 原因何在呢?
SecondsHandStoryboard.Seek( 将故事板进行到指定的时间点位置,而我们 DoubleAnimation 故事板中,执行时间是1分钟,一直不间断执行。这样
DateTime.Now.TimeOfDay 的定位就是秒针正确的时间点。
代码:
App.xaml 中样式文件的定义:
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="SilverlightApp_Clock.App"> <Application.Resources> <Style x:Key="MarkersBig" TargetType="Rectangle"> <Setter Property="RenderTransformOrigin" Value="0.5,12.8"/> <Setter Property="Fill" Value="White"/> <Setter Property="StrokeThickness" Value="0"/> <Setter Property="Width" Value="4"/> <Setter Property="Height" Value="10"/> <Setter Property="Canvas.Left" Value="158"/> <Setter Property="Canvas.Top" Value="32"/> </Style> <Style x:Key="MarkersSmall" TargetType="Rectangle"> <Setter Property="Width" Value="2"/> <Setter Property="Height" Value="6"/> <Setter Property="RenderTransformOrigin" Value="0.5,21.5"/> <Setter Property="Fill" Value="White"/> <Setter Property="StrokeThickness" Value="0"/> <Setter Property="Canvas.Left" Value="159"/> <Setter Property="Canvas.Top" Value="31"/> </Style> <Style x:Key="TextBlockNum" TargetType="TextBlock"> <Setter Property="FontSize" Value="21.333"/> <Setter Property="FontWeight" Value="Bold"/> <Setter Property="TextAlignment" Value="Center"/> <Setter Property="Foreground" Value="White"/> </Style> </Application.Resources> </Application>
MainPage.xaml 文件内容 :
<UserControl x:Class="SilverlightApp_Clock.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="320" d:DesignWidth="320"> <Canvas Loaded="Canvas_Loaded"> <!-- 盘面 --> <Ellipse Fill="Black" Height="300" Canvas.Left="10" Stroke="#FF3F4462" Canvas.Top="10" Width="300" StrokeThickness="15" /> <!-- 盘面中心园° --> <Ellipse Fill="Black" Height="30" Canvas.Left="145" Stroke="#FF0024F9" StrokeThickness="3" Canvas.Top="145" Width="30"/> <!-- 盘面大格 --> <Rectangle x:Name="MarkersBig00" Style="{StaticResource MarkersBig}"/> <Rectangle x:Name="MarkersBig30" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="30"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersBig60" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="60"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersBig90" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="90"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersBig120" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="120"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersBig150" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="150"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersBig180" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="180"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersBig210" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="210"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersBig240" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="240"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersBig270" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="270"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersBig300" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="300"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersBig330" Style="{StaticResource MarkersBig}" > <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="330"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <!-- 盘面小格 --> <Rectangle x:Name="MarkersSmall06" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="6"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall12" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="12"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall18" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="18"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall24" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="24"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall36" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="36"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall42" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="42"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall48" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="48"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall54" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="54"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall66" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="66"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall72" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="72"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall78" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="78"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall84" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="84"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall96" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="96"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall102" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="102"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall108" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="108"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall114" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="114"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall126" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="126"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall132" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="132"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall138" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="138"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall144" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="144"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall156" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="156"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall162" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="162"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall168" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="168"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall174" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="174"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall186" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="186"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall192" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="192"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall198" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="198"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall204" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="204"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall210" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="210"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall216" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="216"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall222" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="222"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall228" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="228"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall234" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="234"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall246" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="246"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall252" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="252"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall258" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="258"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall264" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="264"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall276" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="276"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall282" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="282"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall288" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="288"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall294" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="294"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall306" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="306"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall312" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="312"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall318" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="318"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall324" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="324"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall336" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="336"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall342" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="342"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall348" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="348"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle x:Name="MarkersSmall354" Style="{StaticResource MarkersSmall}"> <Rectangle.RenderTransform> <TransformGroup> <RotateTransform Angle="354"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <!-- 盘面数字显示 --> <TextBlock x:Name="tb_1" Canvas.Left="207" Canvas.Top="64" Style="{StaticResource TextBlockNum}">1</TextBlock> <TextBlock x:Name="tb_2" Canvas.Left="241" Canvas.Top="102" Style="{StaticResource TextBlockNum}">2</TextBlock> <TextBlock x:Name="tb_3" Canvas.Left="253" Canvas.Top="145" Style="{StaticResource TextBlockNum}">3</TextBlock> <TextBlock x:Name="tb_4" Canvas.Left="240" Canvas.Top="194" Style="{StaticResource TextBlockNum}">4</TextBlock> <TextBlock x:Name="tb_5" Canvas.Left="205" Canvas.Top="227" Style="{StaticResource TextBlockNum}">5</TextBlock> <TextBlock x:Name="tb_6" Canvas.Left="153" Canvas.Top="238" Style="{StaticResource TextBlockNum}">6</TextBlock> <TextBlock x:Name="tb_7" Canvas.Left="106" Canvas.Top="227" Style="{StaticResource TextBlockNum}">7</TextBlock> <TextBlock x:Name="tb_8" Canvas.Left="71" Canvas.Top="194" Style="{StaticResource TextBlockNum}">8</TextBlock> <TextBlock x:Name="tb_9" Canvas.Left="54" Canvas.Top="145" Style="{StaticResource TextBlockNum}">9</TextBlock> <TextBlock x:Name="tb_10" Canvas.Left="64" Canvas.Top="96" Style="{StaticResource TextBlockNum}">10</TextBlock> <TextBlock x:Name="tb_11" Canvas.Left="99" Canvas.Top="63" Style="{StaticResource TextBlockNum}">11</TextBlock> <TextBlock x:Name="tb_12" Canvas.Left="145" Canvas.Top="50" Style="{StaticResource TextBlockNum}">12</TextBlock> <!-- 显示年月日 --> <TextBlock x:Name="tb_YearMonthDay" Height="19" Canvas.Left="123" TextWrapping="Wrap" Text="2010-01-20" Canvas.Top="204" Width="74" Style="{StaticResource TextBlockNum}" FontSize="10.667"/> <!-- 秒针 --> <Path x:Name="SecondHand" Data="M1.75,100 L-0.5,100 L-0.5,0 L2,0 z" Fill="Red" Canvas.Left="158.494" Stretch="Fill" RenderTransformOrigin="0.51,1.199" StrokeThickness="0" Canvas.Top="40.05" UseLayoutRounding="False" Height="100" Width="2.888"> <Path.RenderTransform> <TransformGroup> <RotateTransform Angle="0"/> </TransformGroup> </Path.RenderTransform> <Path.Resources> <Storyboard x:Name="SecondsHandStoryboard" > <DoubleAnimation From="0" To="360" Duration="00:01:00" RepeatBehavior="Forever" Storyboard.TargetProperty="(Path.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)" Storyboard.TargetName="SecondHand"/> </Storyboard> </Path.Resources> </Path> <!-- 分针 --> <Path x:Name="MinuteHand" Data="M3.8847754,100.055 L-1.3659742,100.055 L-0.5036189,40.723522 L2.4733605,40.545193 z" Fill="#FFB9DA12" Canvas.Left="157.238" RenderTransformOrigin="0.545,1.333" Stretch="Fill" StrokeThickness="0" Canvas.Top="80.486" UseLayoutRounding="False" Height="59.511" Width="5.25"> <Path.RenderTransform> <TransformGroup> <RotateTransform Angle="30"/> </TransformGroup> </Path.RenderTransform> <Path.Resources> <Storyboard x:Name="MinuteHandStoryboard" > <DoubleAnimation From="0" To="360" Duration="01:00:00" RepeatBehavior="Forever" Storyboard.TargetProperty="(Path.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)" Storyboard.TargetName="MinuteHand"/> </Storyboard> </Path.Resources> </Path> <!-- 时针 --> <Path x:Name="HourHand" Data="M3.8847754,100.055 L-1.3659742,100.055 L-0.62599868,41.085041 L2.9391243,41.085041 z" Fill="#FFB9DA12" Canvas.Left="156.393" RenderTransformOrigin="0.459,1.536" Stretch="Fill" StrokeThickness="0" Canvas.Top="102.667" UseLayoutRounding="False" Height="37.333" Width="7.855"> <Path.RenderTransform> <TransformGroup> <RotateTransform Angle="0"/> </TransformGroup> </Path.RenderTransform> <Path.Resources> <Storyboard x:Name="HourHandStoryboard" > <DoubleAnimation From="0" To="360" Duration="12:00:00" RepeatBehavior="Forever" Storyboard.TargetProperty="(Path.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)" Storyboard.TargetName="HourHand"/> </Storyboard> </Path.Resources> </Path> </Canvas> </UserControl>
MainPage.xaml.cs 文件内容:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; namespace SilverlightApp_Clock { public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); } private void Canvas_Loaded(object sender, RoutedEventArgs e) { DateTime dt = DateTime.Now; this.tb_YearMonthDay.Text = dt.ToString("yyyy-MM-dd"); SecondsHandStoryboard.Begin(); SecondsHandStoryboard.Seek(dt.TimeOfDay); MinuteHandStoryboard.Begin(); MinuteHandStoryboard.Seek(dt.TimeOfDay); HourHandStoryboard.Begin(); HourHandStoryboard.Seek(dt.TimeOfDay); } } }
参考资料:
Clock C#
http://silverlight.net/community/samples/details.aspx?__itemid=15784
Hybrid Clock for Microsoft Silverlight 2.0
http://www.alexanderbell.us/HybridClock.htm
Developing Silverlight Analog Clock - pattern oriented approach
http://www.silverlightshow.net/items/Developing-Silverlight-Analog-Clock-pattern-oriented-approach.aspx
Developing Silverlight AnalogClock – part 2 – Enhancing the view
http://www.silverlightshow.net/items/Developing-Silverlight-AnalogClock-part-2-Enhancing-the-view.aspx
博客堂源代码发布已经发布于Codeplex,邀请您来捉臭虫
add to del.icio.us. look up in del.icio.us.
add to furl
我们的All-In-One Code Framework项目里面的Sample工程已经增长到了300多个,于是就有了一个新问题:我是一个.NET开发者,我现在想知道怎么用VB.NET或者C#自动化操作Office,面对茫茫300多个文件夹,我迷茫了……
为了解决这个问题,我制作了一个小工具可以帮助大家快速地从300多个(还在不断增长中)工程中快速搜索到自己需要的Sample:All-In-One Code Framework Sample Browser。

现在,你只需要在这个Sample Browser里面输入关键字(如上图所示的“office”和“automation”),所有和这些关键字匹配的工程就会被列出来(你还可以进一步用开发语言过滤结果);选择列表中的某个项目,就可以看到这个项目的说明(界面下方黑底白字部分);双击项目图标,就会直接在Visual Studio中打开该项目。方便多了吧?
关于如何获得、安装(其实只是复制两个文件)这个工具,请参考:微软All-In-One Code Framework 代码示例浏览器 v1 beta版。哦,这篇Blog里面有一个错误:其实我WPF一点都不牛的。
博客堂源代码发布已经发布于Codeplex,邀请您来捉臭虫
add to del.icio.us. look up in del.icio.us.
add to furlWed 20 January, 2010

大东海是个挺热闹挺平民的地方,跟亚龙湾那些酒店private beach完全不一样感觉。不过跟三亚湾也不一样,这里不很市民化。没见到打羽毛球遛狗的。但是又不是像亚龙湾中心广场那个public beach的旅游景点感觉。这里没有旅行团,没有高音喇叭喧哗。
某种程度上这有点像是海南的省内旅游目的地,或者三亚市那些中学生情侣们周末爱来玩的地方。挺不错的。
有个唱K的地方。这位北京的姐姐台风非常赞,竟然唱李玟的歌,我听得快被她的热情融化了。唯一的问题就是唱歌跑调。不过话说这年头曾哥都能红,唱歌跑调算个P问题啊。

这里有椰林树影遮阳,不热;风也没有被山挡住,很舒服;视野也不错。是个歇脚的好地方。推荐一下,叫百乐酒吧。
我抬头看了半天,琢磨着我军啥时候装备了可以空中悬停以及进行小幅度矢量位移的战斗机,这远超过了美军F35的技术水准啊。
咦,还是超静音的诺。
然后意识到,这是一个风筝。
待到山花烂漫时,他在丛中笑
邓爷爷是好人,要顶的
山上看海,风很大,我怕帽子飞走



今天,你一见钟情了吗

这是一个灵感源自迪拜的人工岛,岛上的几个玉米笋建筑,据说这几天卖到了7万一平米。记得我抵达三亚的第5天去看楼盘的时候三亚湾的一线海景房还只要1W5一平米呢,不过已经感觉到满大街东北口音的炒房客了,只觉得吃海鲜的时候邻桌全都在说房子的事儿。
然后国务院就发了个文,说海南要建立国际旅游岛,要建赌场(好像原文说得没那么直接),要在海南特区对30多个国家的人免签证(反正靠近俄罗斯那块比较冷的那些个国家基本全都免签,可能阿富汗除外),然后海南省也发了个文,说国际旅游岛这个事儿在整停当之前暂停土地出让,然后的事情大家都看到了,三亚的房价1天1000的涨,据说个别楼盘1天涨5000。
大家来为海南会不会有第二次房地产泡沫以及啥时候破灭押注吧。
三亚市区全貌。其实三亚是个挺不错的城市,你会发自内心地觉得三亚房价确实应该比苏州贵一截的。曾经有过的1W5的三亚湾一线海景房是真的没有泡沫(或者就是苏州的那些1W5 1W8或者更贵的甚至都不靠湖的公寓楼泡沫实在太多了)。
这里的鸽子不怕人
最后来一张特写,猜猜这是啥?是不是看起来很好吃的样子?
答案:某个酒店的肥皂...
<to be continued>
add to del.icio.us. look up in del.icio.us.
add to furl
今天看了一则新闻,说北京为了加大对互联网的监管力度,将在2010年分步推行网络实名注册制,逐步实现全部用户以真实身份信息注册,推动文明办网、上网。
网络实名制以前曾经讨论过很多次,但一直都没有实际实施过,主要原因其实还是技术上的问题,但随着新技术的不断发展,网络实名制实施的难度将变得越来越低,不久的将来网络实名制的实现将不会存在技术上的障碍。
据CNNIC报道说,2012年现有的IP地址将分配完毕,解决网络地址的问题就要从目前的IPv4过渡到以IPv6为基础的下一代互联网,目前IPv6已经在国内一些省份试点,预计不久的将来即可使用。
IPv6在中国推广的话,一个重要的应用很可能会是“网络实名制”,目前基于IPv4的网络之所以难以实现“网络实名制”,一个重要原因就是因为IP资源的共用,因为IP资源不够,所以不同的人在不同的时间段共用一个IP,IP和上网用户无法实现一一对应。
在IPv4下,现在根据IP查人也比较麻烦,电信局要保留一段时间的上网日志才行,通常因为数据量很大,运营商只保留三个月左右的上网日志,比如查前年某个IP发帖子的用户就不能实现。
IPv6的出现可以从技术上一劳永逸地解决实名制这个问题,因为那时IP资源将不再紧张,运营商有足够多的IP资源,那时候,运营商在受理入网申请的时候,可以直接给该用户分配一个固定IP地址,这样实际就实现了实名制,也就是一个真实用户和一个IP地址的一一对应。
当一个上网用户的IP固定了之后,你任何时间做的任何事情都和一个唯一IP绑定,你在网络上做的任何事情在任何时间段内都有据可查,并且无法否认,想跑都跑不了。
到时候抓网民也方便和容易多了,你可能昨晚刚浏览过色情网站,第二天早上就会有人上门给你开罚款单。
未来真可怕啊。
名词解释:IPv6
IPv6是Internet Protocol Version 6的缩写,其中Internet Protocol译为“互联网协议”。IPv6是IETF(互联网工程任务组,Internet Engineering Task Force)设计的用于替代现行版本IP协议(IPv4)的下一代IP协议。目前IP协议的版本号是4(简称为IPv4),它的下一个版本就是IPv6。 现有标准IPv4只支持大概40亿(232)个网络地址,而IPv6支持2128(约3.4 ×1038)个。
相关文章:
关于我们: 地址 - www.williamlong.info 我的Google Reader - 我的Twitter - 我的Facebook - 月光博客Twitter月光博客投稿信箱:williamlong.info(at)gmail.com
add to del.icio.us. look up in del.icio.us.
add to furl
OpenSocial 可为跨多个网站的社交应用程序定义通用 API。最典型的就是获得用户的好友信息。Siverlight 在用户体验方面又可以做出很酷的应用。这两者一旦结合,做出来的应用应该很有市场。
下面就是一个简单的演示。
演示修改自 Michael S. Scherotter 写的例子:
主要修改是:
由于CSDN支持的 opensocial 是 0.9, 而 Michael S. Scherotter 写的例子写的列子是基于opensocial 是 0.5 的, 对应的获得好友信息等的代码修改成使用最新的API: osapi 。
Michael S. Scherotter 写的例子可以在下面地址获得:
http://hosting.gmodules.com/ig/gadgets/file/113009390747258006757/SilverlightOpenSocial.xml
我写的例子代码可以在下面地址获得:
http://ghj1976.blob.core.windows.net/silverlight/SilverlightOpenSocial.xml
程序执行的效果截图如下:
有 CSDN 帐号的,也可以通过CSDN 的个人空间安装这个应用。
参考资料:
MySpace supports Silverlight for OpenSocial apps
http://www.itworld.com/development/65364/myspace-supports-silverlight-opensocial-apps
Silverlight Kit for MySpace
http://myspacesilverlight.codeplex.com/
Microsoft Silverlight Jives With Google's OpenSocial
http://silverlight.sys-con.com/node/454864
Silverlight in MySpace via OpenSocial
http://blogs.msdn.com/synergist/archive/2008/02/06/silverlight-in-myspace-via-opensocial.aspx
博客堂源代码发布已经发布于Codeplex,邀请您来捉臭虫
add to del.icio.us. look up in del.icio.us.
add to furlTue 19 January, 2010

今天,谷歌官方博客发布了一则辟谣声明,首次对这次“退出门”事件做出了官方回应,表示谷歌中国并没有关闭,员工也没有离职,谷歌仍然一如既往地为用户和合作伙伴提供最好的产品和服务。虽然这个辟谣的声明来的有点晚,但也澄清了早前各种各样的不实谣言,让谷歌中国的客户群心里有了底。
效法一周前Google首席法务官大卫·德拉蒙德(David Drummond)通过博客表示考虑退出中国,谷歌中国分公司最高负责人刘允和杨文洛联名对外发布博客,表示日前某些中文媒体所报道的“Google已经关闭中国办公室”“部分员工已接通知将于近期离职”均为不实报道。而在一周前,Google总部的德拉蒙德在Google的官方博客发表了题为“对中国的新策略”的署名文章,表示Google要重新评估在中国商业运营的可行性,并决定不再继续审查中文网站Google.cn上的搜索结果。德拉蒙德称,“这很可能意味着公司将不得不关闭谷歌中文网站,甚至可能包括我们在中国的办事处”。
谷歌中国发布的辟谣声明全文如下:
澄清不实的传言
发表者:刘允 杨文洛
过去几天里,我们看到有很多关于谷歌中国以及谷歌员工的不真实的传言,一些报道称我们已经关闭了在中国的办公室,还有一些报道称我们在中国的员工已经接到通知将于近期离职。这些都是不真实的。目前,谷歌中国的员工同过去一样在办公室正常工作,讨论产品开发,与客户进行沟通。尽管谷歌总部管理层近期宣布他们将会在未来的几个星期与中国政府就一些事宜进行商讨,谷歌中国的员工们仍在一如既往地努力向我们的用户和合作伙伴提供最好的产品和服务,用户和合作伙伴对谷歌是非常重要的。
相关文章:
关于我们: 地址 - www.williamlong.info 我的Google Reader - 我的Twitter - 我的Facebook - 月光博客Twitter月光博客投稿信箱:williamlong.info(at)gmail.com
add to del.icio.us. look up in del.icio.us.
add to furl
Q:我在安装新版本的QuickPart,替换已有的旧版本时,总有问题,应该怎么办?
A:请到QuickPart项目网站,下载“How to Uninstall QuickPart”文档,然后按照文档中的说明,先从服务器上彻底删除旧版本QuickPart,然后再重新安装新版本QuickPart。
Q:我能修改QuickPart的源代码,添加自己想要的新功能吗?
A:请到QuickPart项目网站,下载“Source Code”包,然后修改源码自己编译即可。
Q:我下载了QuickPart源码项目,用Visual Studio打开后,会提示错误?
A:QuickPart 1.03版本的源码项目使用了Visual Studio 2008 + VSeWSS 1.3,请确认你使用了正确的Visual Studio版本,并安装了VSeWSS 1.3扩展包。
QuickPart 1.03下载页面
博客堂源代码发布已经发布于Codeplex,邀请您来捉臭虫
add to del.icio.us. look up in del.icio.us.
add to furl
之前我一直在使用 http://silverlight.live.com/ 作为我开发的 silverlight 免费存储空间, 最近这里看到将在2010年1月31号就将停止。在上述地址提供的推荐替代方案是使用 Windows Azure 。
Azure 的价格表,请参看: http://www.microsoft.com/windowsazure/offers/ 其中免费的可以提供500M的空间,如下图:
如何申请 Azure ,并部署 Silverlight Application ,有很多博客都写到的,下面就是一些这样的博客:
不用工具,10分钟内在Windows Azure部署Silverlight Application:
http://colinizer.com/2009/12/14/deploy-this-silverlight-application-on-windows-azure-in-10-minutes-no-tools-required/
Hosting Videos on Windows Azure
http://blogs.msdn.com/david_sayed/archive/2010/01/07/hosting-videos-on-windows-azure.aspx
Using Azure as a Silverlight Streaming replacement
http://timheuer.com/blog/archive/2009/11/30/using-windows-azure-to-replace-silverlight-streaming-howto.aspx
Creating and Publishing a Silverlight Video to Windows Azure
http://blogs.msdn.com/david_sayed/archive/2010/01/07/creating-and-publishing-a-silverlight-video-to-windows-azure.aspx
Setting up Windows Azure for video storage
http://blogs.msdn.com/david_sayed/archive/2010/01/07/setting-up-windows-azure-for-video-storage.aspx
为了更方便的使用 Azure, 下面有几个工具推荐,都是免费的:
Azure Storage Explorer , 协助我们在 Azure 上存取文件的工具,下载地址如下:
http://azurestorageexplorer.codeplex.com/
Windows Live Writer 嵌入Azure 的 Silverlight Application,并嵌入博客的插件:
http://gallery.live.com/liveItemDetail.aspx?li=f84d87b9-e284-4691-aa90-2d628e67af6f&pl=8&bt=9
对这个插件的介绍博客:
Azure Blob Storage plugin for Windows Live Writer now available
http://www.clarkezone.net/default.aspx?id=395b2013-40c3-4459-9824-43890bde7b2c
Embedding a Silverlight Video in a Blog
http://blogs.msdn.com/david_sayed/archive/2010/01/07/embedding-a-silverlight-video-in-a-blog.aspx
参考资料:
如何在Windows Azure上托管SilverLight应用
http://blogs.msdn.com/yananwu/archive/2009/04/04/windows-azure-silverlight.aspx
下面就是我嵌入的一个演示:使用的是 iframe
<iframe style="width: 500px; height: 180px"
src=http://ghj1976.blob.core.windows.net/silverlight/TwoWayBindingTestPage.html
frameborder="0" scrolling="no"></iframe>
博客堂源代码发布已经发布于Codeplex,邀请您来捉臭虫
add to del.icio.us. look up in del.icio.us.
add to furl
上周末,在SharePoint 2010 Day技术活动上,我做了一个纯演示的课程,演示了Visual Studio 2010中所包含的SharePoint 2010开发功能。
下载更高清晰度的WMV文件。
博客堂源代码发布已经发布于Codeplex,邀请您来捉臭虫
add to del.icio.us. look up in del.icio.us.
add to furlMon 18 January, 2010

最近一段时间在海外华人圈里流行着一则攻击Google的帖子,说Google发飙,利用搜索关键字报复中国,输入关键字“chinese people”就会出现“chinese people eat babies”这样的辱华建议。
据Google搜索建议的官方说明,Google Suggest(搜索建议)是利用是最近一段时间内相关搜索的相对热门的信息进行自动排名的,整个搜索建议的词语都是自动生成,通常情况下是按照搜索次数由大到小排列的,没有任何人工干预。
而根据Google Trends(谷歌趋势)上分析,“Chinese people eating babies”这个词在最近一个月的搜索量不断上升,搜索次数的增加使得这个词自动成为chinese people的搜索建议词。
这个词出现的原因,大概是因为几年前网上曾经流传过一组四川籍行为艺术家朱昱的吃死婴的行为艺术照片,有人曾经利用这组照片丑化日本人,说是日本人吃婴儿,因此导致美国人会误以为中国人或日本人有吃婴儿的行为。
而有人为了丑化Google,故意在最近一段时间传播这样的谣言,导致“Chinese people eat babies”的搜索量越来越大,而Google搜索引擎的Google Suggest如实反应了这样的事实,反而证明了Google搜索引擎的公正和客观。
另外,这个搜索建议并非仅仅只在Google中出现,如果说Google利用“Chinese people eat babies”来辱华,那么就请解释一下百度搜索“Chinese people”后出现的这张图是怎么回事。不要说百度也联合Google一起辱华哦。
英文Yahoo搜索引擎搜索“Chinese people”也会出现类似建议。
因此可以认为,Google的搜索结果是根据用户的搜索频率自动调整的,不存在Google故意利用搜索建议的关键词进行辱华的行为。
相关文章:
关于我们: 地址 - www.williamlong.info 我的Google Reader - 我的Twitter - 我的Facebook - 月光博客Twitter月光博客投稿信箱:williamlong.info(at)gmail.com
add to del.icio.us. look up in del.icio.us.
add to furlSun 17 January, 2010
22:35 ArticleS.UncleBob.GreenWristBand» FitNesse:
add to del.icio.us. look up in del.icio.us.
add to furl
最近,Google针对Gmail被攻击事件,全面默认启用了始终以https访问Gmail的方式了。但是,对于可以动用整个国家力量的黑客来说,从网络通讯数据中(在此不讨论对用户电脑种木马破解https的情况,只讨论在网络通讯数据中破解https的方法)破解https除了暴力破解(暴力破解https即使按照现在的集群计算能力仍旧需要几百至几万年不等)之外真的别无他法了吗?事实并非如此。
我们知道,https的安全性主要是由SSL证书中的公钥和私钥来保证的。浏览器与服务器经过https建立通讯的时候(不考虑SSL代理方式需要用户提交证书的情况,因为我们现在讨论的是浏览器访问网站,和SSL代理无关)会按照以下步骤保证通讯的安全性:
1、浏览器连接服务器,服务器把SSL证书的公钥发送给浏览器
2、浏览器验证此证书中的域是否和访问的域一致(比如用户访问https://mail.google.com/时,浏览器验证服务器发送过来的SSL证书的公钥中的域是否为mail.google.com或*.google.com)并没有过期
3、如果浏览器验证失败,浏览器通知用户证书有问题,让用户选择是否继续
4、如果浏览器验证成功,那么浏览器随机生成一个对称密钥并使用接收到的SSL证书的公钥进行加密并发送给服务器
5、服务器通过SSL证书的私钥对收到的信息进行解密并得到浏览器随机生成的对称密钥
6、最后服务器和浏览器都通过这个对称密钥进行通讯了(为什么不直接使用公钥和私钥进行通讯?因为非对称加密比对称加密效率低)
这个方案看似完美,却无法抵御中间人攻击,攻击者可以按以下步骤实施攻击截取https通讯中的所有数据:
1、攻击者伪造一个Gmail的SSL证书,使其中的域为mail.google.com或*.google.com,并设置合适的证书过期时间
2、攻击者等待访问者的浏览器访问Gmail时,通过DNS劫持或IP伪造(对于有路由器控制权限的黑客来说简直轻而易举)的方法使其访问到攻击者的服务器上
3、攻击者把伪造的SSL证书公钥发送给浏览器
4、浏览器验证SSL证书的域和过期时间都没错,认为访问到的就是Gmail本身,从而把对称密钥发送给黑客服务器
5、黑客服务器把伪造的Gmail网页通过收到的对称密钥加密后发送给浏览器
6、访问者通过浏览器输入Gmail帐户,发送给黑客服务器,黑客服务器通过收到的对称密钥解密后成功获得访问者的Gmail密码
为了抵御这种中间人攻击,SSL证书需要由可信的SSL证书颁发机构颁发,形成一个证书链(比如Gmail的证书链为:最底层为网域mail.google.com,上一层为Thawte SGC CA证书颁发机构,最顶层为很有名的VeriSign证书颁发机构)。那么,浏览器除了需要验证域和有效期外,还要检查证书链中的上级证书公钥是否有效,上级的上级证书公钥是否有效,直至根证书公钥为止。这样就可以有效避免中间人攻击了,因为根证书公钥都是预装在操作系统中的,黑客如果不是暴力破解,无法得到根证书的私钥,如果黑客自己生成一个私钥,浏览器验证根证书公钥的时候发现无法通过操作系统中预装的公钥加密数据后使用这个私钥进行解密,从而判定这个公钥是无效的。这个方案也是现在https通讯通常的方案。
那么,这个现在所有的浏览器正在使用的https通讯方案就无懈可击了吗?答案仍是否定的。我们可以看到,在后一个方案中,https的安全性需要在证书颁发机构公信力的强有力保障前提下才能发挥作用。如果证书颁发机构在没有验证黑客为mail.google.com的持游者的情况下,给黑客颁发了网域为mail.google.com的证书,那么黑客的中间人攻击又可以顺利实施:
1、攻击者从一家不验证mail.google.com持有者的SSL证书颁发机构WoSign那里得到了网域为mail.google.com的证书,此证书的证书链为:最底层为网域mail.google.com,上一层证书颁发机构为WoSign,顶层证书颁发机构为VeriSign
2/3、第二、第三个步骤同上一个方案的中间人攻击的第二、第三个步骤
4、浏览器验证SSL证书的域和过期时间都没错,继续验证证书链:
4.1、最底层的网域mail.google.com证书公钥不在操作系统中,无法验证其访问到的就是Gmail本身,继续验证上一层证书颁发机构
4.2、上一层证书颁发机构WoSign的公钥也不在操作系统中,仍旧无法验证其有效性,继续验证上一层证书颁发机构
4.3、浏览器看到顶层证书颁发机构VeriSign的公钥在操作系统中,认为证书链有效,从而把对称密钥发送给黑客服务器
5/6、第五、第六个步骤同上一个方案的中间人攻击的第五、第六个步骤。黑客成功获得访问者的Gmail密码
然而,不验证域名持有者就颁发证书的情况在国外几乎不会发生,但是在国内就不一定了。针对破解目标,国内证书颁发机构WoSign(在此只是举例国内比较有名的证书颁发机构WoSign,并不代表WoSign今后一定会这么做)很有可能为了上级要求颁发了证书给非域名持有者的黑客,从而使得破解目标的Gmail密码被黑客截取。
那么,国内的破解目标是不是使用https的Gmail也无法保证安全了呢?欢迎与我进行探讨。
来源:读者lehui99投稿,投稿人Email为:lehui99@gmail.com,Google Wave为:lehui99@googlewave.com。
评论《破解Google Gmail的https新思路》的内容...
相关文章:
关于我们: 地址 - www.williamlong.info 我的Google Reader - 我的Twitter - 我的Facebook - 月光博客Twitter月光博客投稿信箱:williamlong.info(at)gmail.com
add to del.icio.us. look up in del.icio.us.
add to furl
[Updated] 原始出处 http://www.wired.com/threatlevel/2010/01/operation-aurora/ 不过貌似已被GFW。
“百度一下,你就知道; Google一下,你就知道得太多了”
add to del.icio.us. look up in del.icio.us.
add to furl
add to del.icio.us. look up in del.icio.us.
add to furl
【原文地址】ASP.NET MVC 2: Strongly Typed Html Helpers
【原文发表日期】 Sunday, January 10, 2010 8:57 PM
【除了写博客外,我现在还使用Twitter发短贴和共享链接。请通过twitter.com/scottgu跟随我。】
这是我针对即将发布的ASP.NET MVC 2所撰写的贴子系列的第一篇,这个博客贴子将讨论 ASP.NET MVC 2中新加的强类型HTML辅助方法。
现有的HTML辅助方法
ASP.NET MVC 1中发布了一套HTML辅助方法,可以用来在视图模板中帮助生成HTML界面。例如,要输出一个文本框,你可以在你的.aspx视图模板中使用Html.TextBox()辅助方法编写下列代码:
上面辅助方法的第一个参数提供了文本框的名称及id,第二个参数指定了它该有的值,然后上面的辅助方法会显示象下面这样的HTML到浏览器:
新的强类型HTML辅助方法
大家对ASP.NET MVC 2要求的一个常用特性是,要我们支持强类型的HTML辅助方法,这样的辅助方法使用 lambda 表达式来引用传到视图模板中的模型或视图模型。这可以促成更好的编译时视图检查(可以在编译时发现缺陷,而不是在运行时),还可以促成视图模板中更好的代码intellisense支持。
新的强类型HTML辅助方法现在已经内置于ASP.NET MVC 2中了,这些方法使用"Html.HelperNameFor()”的命名规范。例如,Html.TextBoxFor(), Html.CheckBoxFor(), Html.TextAreaFor()等等。它们支持使用lambda表达式来指定元素的名称和id,以及要显示的值。
例如,除了上面的Html.TextBox()辅助方法外,使用ASP.NET MVC 2,我们现在还可以使用新的Html.TextBoxFor()辅助方法:
注意上面,我们不再需要指定 “ProductName” 字符串参数,lambda表达式是相当灵活的,除了值以外,我们还可以获取我们模型对象中的属性/字段的名称。
因为这些HTML辅助方法是强类型的,编写lambda表达式时我们还可以在Visual Studio中得到完整的intellisense支持:
显示的HTML跟前面的后期绑定的HTML辅助方法版本的输出是一样的:
内置于ASP.NET MVC 2中的强类型HTML辅助方法列表
ASP.NET MVC 2对下列强类型的HTML辅助方法提供内置支持:
HTML元素辅助方法:
- Html.TextBoxFor()
- Html.TextAreaFor()
- Html.DropDownListFor()
- Html.CheckboxFor()
- Html.RadioButtonFor()
- Html.ListBoxFor()
- Html.PasswordFor()
- Html.HiddenFor()
- Html.LabelFor()
其他辅助方法:
- Html.EditorFor()
- Html.DisplayFor()
- Html.DisplayTextFor()
- Html.ValidationMessageFor()
我会在本系列的后期贴子中讨论ASP.NET MVC 2中改进了的“自动脚手架(auto-scaffold)"功能时,对新的Html.EditorFor() 和 Html.DisplayFor()辅助方法做进一步介绍。在本系列的下一个博客贴子中讨论ASP.NET MVC 2中改进了的验证支持时,我们还将使用Html.ValidationMessageFor()辅助方法。
Scaffolding中的强类型HTML辅助方法
VS 2008 和 VS 2010两者在用“添加视图”命令“生成(scaffolding)”新的强类型视图模板时,现在都会默认使用新的强类型HTML辅助方法。
例如,假设我们有一个象下面这样的简单 “ProductsController” 类,有一个“Edit” action方法,会为“Product”模型类显示一个编辑表单:
我们可以使用Visual Studio在Edit action方法中右击,选择“添加视图”上下文菜单命令来创建一个视图模板,我们将选择创建一个“Edit”模板,该模板是使用Product对象来生成的(scaffolded):
在ASP.NET MVC 2中,默认生成的视图模板现在使用了新的强类型HTML辅助方法来引用Product模型对象:
结语
包含在ASP.NET MVC 2中的强类型HTML辅助方法提供了一个很好的方式来在视图模板中得到更好的类型安全。这促成了对你的视图的更好的编译时检查(允许你在编译时,而不是运行时发现错误),还在Visual Studio中编辑视图模板时支持更丰富的intellisense。
希望本文对你有所帮助,
Scott
博客堂源代码发布已经发布于Codeplex,邀请您来捉臭虫
add to del.icio.us. look up in del.icio.us.
add to furl
【原文地址】ASP.NET MVC 2
【原文发表日期】 Sunday, January 10, 2010 8:54 PM
【除了写博客外,我现在还使用Twitter发短贴和共享链接。请通过twitter.com/scottgu跟随我。】
过去的6个月里,ASP.NET开发团队一直不断地发布了ASP.NET MVC 2的预览版,然后是beta版,现在则是RC(最终版的候选版)。
鉴于最终版的发布也不太远了,我想该是开始一个含多个部分的ASP.NET MVC 2 新博客系列的时候了,该系列旨在讨论新的特性以及该如何充分利用它们。
ASP.NET MVC 2
去年三月份时,我们发布了ASP.NET MVC 1.0。自那以后,几乎有一百万开发人员下载和使用了1.0的最终版,它的人气(popularity)逐月递增。
ASP.NET MVC 2是ASP.NET MVC的下一个重大更新版本,它与ASP.NET MVC 1是兼容的,即,你拥有的有关ASP.NET MVC所有的知识,技能,代码,和扩展,之后可以继续发挥作用。就象第一个版本一样,我们依然会在与OSI相容的开源许可下发布ASP.NET MVC 2的源代码。
ASP.NET MVC 2 特性
ASP.NET MVC 2 添加了一堆新的功能和特性。我将在这个博客系列里对它们进行深入讨论,包括下面这些:
- 新的强类型HTML辅助方法 (发表于2010年1月10日)
- Enhanced Model Validation support across both server and client (不久就会发表)
- Auto-Scaffold UI Helpers with Template Customization (不久就会发表)
- Support for partitioning large applications into “Areas” (不久就会发表)
- Asynchronous Controllers support (不久就会发表)
- Support for rendering sub-sections of a page/site using Html.RenderAction (不久就会发表)
- Lots of new helper functions, utilities, and API enhancements (不久就会发表)
- Improved Visual Studio tooling support (不久就会发表)
如何下载ASP.NET MVC 2
ASP.NET MVC 2是设计来可在VS 2008 / .NET 3.5,以及VS 2010 / .NET 4下工作的。同时支持2个版本意味着你今天就可以开始使用,而不必等着升级到VS2010 / .NET 4才用。
点击这里下载针对 .NET 3.5 和 VS 2008的 ASP.NET MVC 2的RC版,可在同个机器上与ASP.NET MVC 1.0并列安装。
ASP.NET MVC 2是 VS 2010 / .NET 4的内置组件,这意味着,在你安装Visual Studio 2010的任意一个版本之后,你不用下载或安装别的就可以得到ASP.NET MVC 2。目前的公开VS 2010 Beta 2版包含了ASP.NET MVC 2 的第二个预览版。下个月将发布的VS 2010 RC版将会有一个比较新的ASP.NET MVC 2 RC版本。
ASP.NET Web Forms + ASP.NET MVC
我们总是很谨慎地表明, ASP.NET MVC是ASP.NET中的一个选项。 ASP.NET Web Forms将继续会是使用ASP.NET建造应用时最为广泛使用的方式,而且新的 ASP.NET 4 版本包含了针对 ASP.NET Web Forms开发的显著改进(干净的客户端ID和基于CSS的控件标识,更好的ViewState管理,新的数据和图表控件,URL导向,SEO改进等等),你可以在我撰写中的VS 2010 和 .NET 4 博客系列中了解这些改进的详情。
在将来的版本中,我们还将进一步改进和增强ASP.NET Web Forms 和 ASP.NET MVC这2个编程模型,开发人员可以,也应该选择对他们来说感觉最舒服和最自然的模型。不久我们会在 www.asp.net 上发布新的录像和导引,帮助提供每个模型的额外导引,以及如何选择对你来说感觉最舒服的那个模型。
希望本文对你有所帮助,
Scott
博客堂源代码发布已经发布于Codeplex,邀请您来捉臭虫
add to del.icio.us. look up in del.icio.us.
add to furlFri 15 January, 2010

[In addition to blogging, I am also now using Twitter for quick updates and to share links. Follow me at: twitter.com/scottgu]
This is the second in a series of blog posts I’m doing on the upcoming ASP.NET MVC 2 release. This blog post covers some of the validation improvements coming with ASP.NET MVC 2.
ASP.NET MVC 2 Validation
Validating user-input and enforcing business rules/logic is a core requirement of most web applications. ASP.NET MVC 2 includes a bunch of new features that make validating user input and enforcing validation logic on models/viewmodels significantly easier. These features are designed so that the validation logic is always enforced on the server, and can optionally also be enforced on the client via JavaScript. The validation infrastructure and features in ASP.NET MVC 2 are designed so that:
1) Developers can easily take advantage of the DataAnnotation validation support built-into the .NET Framework. DataAnnotations provide a really easy way to declaratively add validation rules to objects and properties with minimal code.2) Developers can optionally integrate either their own validation engine, or take advantage of existing validation frameworks like Castle Validator or the EntLib Validation Library. ASP.NET MVC 2’s validation features are designed to make it easy to plug-in any type of validation architecture – while still taking advantage of the new ASP.NET MVC 2 validation infrastructure (including client-side validation, model binding validation, etc).
This means that enabling validation is really easy for common application scenarios, while at the same time still remaining very flexible for more advanced ones.
Enabling Validation with ASP.NET MVC 2 and DataAnnotations
Let’s walkthrough a simple CRUD scenario with ASP.NET MVC 2 that takes advantage of the new built-in DataAnnotation validation support. Specifically, let’s implement a “Create” form that enables a user to enter friend data:
We want to ensure that the information entered is valid before saving it in a database – and display appropriate error messages if it isn’t:
We want to enable this validation to occur on both the server and on the client (via JavaScript). We also want to ensure that our code maintains the DRY principle (“don’t repeat yourself”) – meaning that we should only apply the validation rules in one place, and then have all our controllers, actions and views honor it.
Below I’m going to be using VS 2010 to implement the above scenario using ASP.NET MVC 2. You could also implement the exact same scenario using VS 2008 and ASP.NET MVC 2 as well.
Step 1: Implementing a FriendsController (with no validation to begin with)
We’ll begin by adding a simple “Person” class to a new ASP.NET MVC 2 project that looks like below:
It has four properties (implemented using C#’s automatic property support, which VB in VS 2010 now supports too – woot!).
We’ll then add a “FriendsController” controller class to our project that exposes two “Create” action methods. The first action method is called when an HTTP-GET request comes for the /Friends/Create URL. It will display a blank form for entering person data. The second action method is called when an HTTP-POST request comes for the /Friends/Create URL. It maps the posted form input to a Person object, verifies that no binding errors occurred, and if it is valid will eventually save it to a database (we’ll implement the DB work later in this tutorial). If the posted form input is invalid, the action method redisplays the form with errors:
After we’ve implemented our controller, we can right-click within one of its action methods and choose the “Add View” command within Visual Studio – which will bring up the “Add View” dialog. We’ll choose to scaffold a “Create” view that is passed a Person object:
Visual Studio will then generate a scaffolded Create.aspx view file for us under the \Views\Friends\ directory of our project. Notice below how it takes advantage of the new strongly-typed HTML helpers in ASP.NET MVC 2 (enabling better intellisense and compile time checking support):
And now when we run the application and hit the /Friends/Create URL we’ll get a blank form that we can enter data into:
Because we have not implemented any validation within the application, though, nothing prevents us from entering bogus input within the form and posting it to the server.
Step 2: Enabling Validation using DataAnnotations
Let’s now update our application to enforce some basic input validation rules. We’ll implement these rules on our Person model object – and not within our Controller or our View. The benefit of implementing the rules within our Person object is that this will ensure that the validation will be enforced via any scenario within our application that uses the Person object (for example: if we later added an edit scenario). This will help ensure that we keep our code DRY and avoid repeating rules in multiple places.
ASP.NET MVC 2 enables developers to easily add declarative validation attributes to model or viewmodel classes, and then have those validation rules automatically be enforced whenever ASP.NET MVC performs model binding operations within an application. To see this in action, let’s update our Person class to have a few validation attributes on it. To do this we’ll add a “using” statement for the “System.ComponentModel.DataAnnotations” namespace to the top of the file – and then decorate the Person properties with [Required], [StringLength], [Range], and [RegularExpression] validation attributes (which are all implemented within that namespace):
Note: Above we are explicitly specifying error messages as strings. Alternatively you can define them within resource files and optionally localize them depending on the language/culture of the incoming user. You can learn more about how to localize validation error messages here.
Now that we’ve added the validation attributes to our Person class, let’s re-run our application and see what happens when we enter bogus values and post them back to the server:
Notice above how our application now has a decent error experience. The text elements with the invalid input are highlighted in red, and the validation error messages we specified are displayed to the end user about them. The form is also preserving the input data the user originally entered – so that they don't have to refill anything. How though, you might ask, did this happen?
To understand this behavior, let’s look at the Create action method that handles the POST scenario for our form:
When our HTML form is posted back to the server, the above method will be called. Because the action method accepts a “Person” object as a parameter, ASP.NET MVC will create a Person object and automatically map the incoming form input values to it. As part of this process, it will also check to see whether the DataAnnotation validation attributes for the Person object are valid. If everything is valid, then the ModelState.IsValid check within our code will return true – in which case we will (eventually) save the Person to a database and then redirect back to the home-page.
If there are any validation errors on the Person object, though, our action method redisplays the form with the invalid Person. This is done via the last line of code in the code snippet above.
The error messages are then displayed within our view because our Create form has <%= Html.ValidationMessageFor() %> helper method calls next to each <%= Html.TextBoxFor() %> helper. The Html.ValidationMessageFor() helper will output the appropriate error message for any invalid model property passed to the view:
The nice thing about this pattern/approach is that it is pretty easy to setup – and it then allows us to easily add or change validation rules on our Person class without having to change any code within our controllers or views. This ability to specify the validation rules one place and have it be honored and respected everywhere allows us to rapidly evolve our application and rules with a minimum amount of effort and keep our code very DRY.
Step 3: Enabling Client-side Validation
Our application currently only performs server-side validation – which means that our end users will need to perform a form submit to the server before they’ll see any validation error messages.
One of the cool things about ASP.NET MVC 2’s validation architecture is that it supports both server-side and client-side validation. To enable this, all we need to do is to add two JavaScript references to our view, and write one line of code:
When we add these three lines, ASP.NET MVC 2 will use the validation meta-data we’ve added to our Person class and wire-up client-side JavaScript validation logic for us. This means that users will get immediate validation errors when they tab out of an input element that is invalid.
To see the client-side JavaScript support in action for our friends application, let’s rerun the application and fill in the first three textboxes with legal values – and then try and click “Create”. Notice how we’ll get an immediate error message for our missing value without having to hit the server:
If we enter some text that is not a legal email the error message will immediately change from “Email Required” to “Not a valid email” (which are the error messages we specified when we added the rules to our Person class):
When we enter a legal email the error message will immediately disappear and the textbox background color will go back to its normal state:
The nice thing is that we did not have to write any custom JavaScript of our own to enable the above validation logic. Our validation code is also still very DRY- we can specify the rules in one place and have them be enforced across all across the application – and on both the client and server.
Note that for security reasons the server-side validation rules always execute even if you have the client-side support enabled. This prevents hackers from trying to spoof your server and circumvent the client-side rules.
The client-side JavaScript validation support in ASP.NET MVC 2 can work with any validation framework/engine you use with ASP.NET MVC. It does not require that you use the DataAnnotation validation approach – all of the infrastructure works independent of DataAnnotations and can work with Castle Validator, the EntLib Validation Block, or any custom validation solution you choose to use.
If you don’t want to use our client-side JavaScript files, you can also substitute in the jQuery validation plugin and use that library instead. The ASP.NET MVC Futures download will include support for enable jQuery validation against the ASP.NET MVC 2 server-side validation framework as well.
Step 4: Creating a Custom [Email] Validation Attribute
The System.ComponentModel.DataAnnotations namespace within the .NET Framework includes a number of built-in validation attributes that you can use. We’ve used 4 different ones in the sample above - [Required], [StringLength], [Range], and [RegularExpression].
You can also optionally define your own custom validation attributes and use them as well. You can define completely custom attributes by deriving from the ValidationAttribute base class within the System.ComponentModel.DataAnnotations namespace. Alternatively, you can choose to derive from any of the existing validation attributes if you want to simply extend their base functionality.
For example, to help clean up the code within our Person class we might want to create a new [Email] validation attribute that encapsulates the regular expression to check for valid emails. To do this we can simply derive it from the RegularExpression base class like so, and call the RegularExpression’s base constructor with the appropriate email regex:
We can then update our Person class to use our new [Email] validation attribute in place of the previous regular expression we used before – which makes the code more clean and encapsulated:
When creating custom validation attributes you can specify validation logic that runs both on the server and on the client via JavaScript.
In addition to creating validation attributes that apply to individual properties on an object, you can also apply validation attributes at the class level – which allows you to perform validation logic across multiple properties within an object. For an example of this in action, you can review the “PropertiesMustMatchAttribute” custom attribute that is included in the AccountModels.cs/vb file within the default ASP.NET MVC 2 application project template (just do a File->New ASP.NET MVC 2 Web Project within VS 2010 and look for this class).
Step 5: Persisting to a Database
Let’s now implement the logic necessary to save our friends to a database.
Right now we are simply working against a plain-old C# class (sometimes referred to as a “POCO” class - “plain old CLR (or C#) object”). One approach we could use would be to write some separate persistence code that maps this existing class we’ve already written to a database. Object relational mapping (ORM) solutions like NHibernate support this POCO / PI style of mapping today very well. The ADO.NET Entity Framework (EF) that ships with .NET 4 will also support POCO / PI mapping, and like NHibernate will also optionally enable the ability to define persistence mappings in a “code only” way (no mapping file or designers required).
If our Person object was mapped to a database in this way then we wouldn’t need to make any changes to our Person class or to any of our validation rules – it would continue to work just fine.
But what if we are using a graphical tool for our ORM mappings?
Many developers using Visual Studio today don’t write their own ORM mapping/persistence logic – and instead use the built-in designers within Visual Studio to help manage this.
One question that often comes up when using DataAnnotations (or any other form of attribute based validation) is “how do you apply them when the model object you are working with is created/maintained by a GUI designer”. For example, what if instead of having a POCO style Person class like we’ve been using so far, we instead defined/maintained our Person class within Visual Studio via a GUI mapping tool like the LINQ to SQL or ADO.NET EF designer:
Above is a screen-shot that shows a Person class defined using the ADO.NET EF designer in VS 2010. The window at the top defines the Person class, the window at the bottom shows the mapping editor for how its properties map to/from a “People” table within a database. When you click save on the designer it automatically generates a Person class for you within your project. This is great, except that every time you make a change and hit save it will re-generate the Person class – which would cause any validation attribute declarations you make on it to be lost.
One way you can apply additional attribute-based meta-data (like validation attributes) to a class that is auto-generated/maintained by a VS designer is to employ a technique we call “buddy classes”. Basically you create a separate class that contains your validation attributes and meta-data, and then link it to the class generated by the designer by applying a “MetadataType” attribute to a partial class that is compiled with the tool-generated class. For example, if we wanted to apply the validation rules we used earlier to a Person class maintained by a LINQ to SQL or ADO.NET EF designer we could update our validation code to instead live in a separate “Person_Validation” class that is linked to the “Person” class created by VS using the code below:
The above approach is not as elegant as a pure POCO approach – but has the benefit of working with pretty much any tool or designer-generated code within Visual Studio.
Last Step – Saving the Friend to the Database
Our last step – regardless of whether we use a POCO or tool-generated Person class – will be to save our valid friends into the database.
Doing that simply requires us to replace the “Todo” placeholder statement within our FriendsController class with 3 lines of code that saves the new friend to a database. Below is the complete code for the entire FriendsController class - when using ADO.NET EF to do the database persistence for us:
And now when we visit the /Friends/Create URL we can easily add new People to our friends database:
Validation for all the data is enforced on both the client and server. We can easily add/modify/delete validation rules in one place, and have those rules be enforced by all controllers and views across our application.
Summary
ASP.NET MVC 2 makes it much easier to integrate validation into web applications. It promotes a model-based validation approach that enables you to keep your applications very DRY, and helps ensure that validation rules are enforced consistently throughout an application. The built-in DataAnnotations support within ASP.NET MVC 2 makes supporting common validation scenarios really easy out of the box. The extensibility support within the ASP.NET MVC 2 validation infrastructure then enables you to support a wide variety of more advanced validation scenarios – and plugin any existing or custom validation framework/engine.
Hope this helps,
Scott
add to del.icio.us. look up in del.icio.us.
add to furlThu 14 January, 2010

海港观鱼
沙滩观人
(这片沙滩是public的,于是人山人海,我就根本没下去。)
休息休息

这个岛一面是沙滩,一面是礁石滩。当在礁石滩那一面时,恍惚中我有行驶在加州1号公路上的错觉。
很多小贝的尸体和珊瑚的尸体。不过没看见海藻。
海水感觉比亚龙湾的要冷一些
吹MM的笛子
话说,让悠悠笛声随着海风飘远,是件多么浪漫的事情啊
背景里有个穿皮鞋打桃红色遮阳伞的一本正经男人,我怎么拍照的时候没留意到捏…
穿比基尼的MM

我在中国迄今看到的最美的海景是在这个海岛。(据说西沙群岛更美,不过去那儿要坐蛮久的船。)
这种美要亲自实地体会才好。照片里没有风的声音,没有海的味道,没有阳光的温暖,没有海水的冷冽,没有惊涛拍岸,没有笛声悠悠。
<to be continued>
add to del.icio.us. look up in del.icio.us.
add to furl


