XBAP = XAML Browser Application
作为Windows Vista的重要feature,XAML在浏览器中的应用让我心动不已。不过这东西比HTML复杂,以前ASP.NET是浏览器端HTML/JavaScript+服务器端C#,HTML都是服务器端生成的;现在是浏览器端XAML/C#,服务器端可以没有,也可以是Web Service或者别的远程服务。JavaScript在浏览器里面能做的事情是很有限的,比如正常情况下不能访问文件系统;同样,运行在客户端的C#代码虽然能被自动加载,但也受安全沙箱模型限制。
假如你在运行在客户端的C#代码里面调用了个SqlCommand,那你的XBAP程序需要的安全级别就一下子变成了Full Trust。这会导致用户不能像访问普通网页一样访问你的XBAP程序,而需要通过MSI安装包安装,或者先下载个证书(就像下载ActiveX控件一样)。如果是一个普通的Internet应用,这显然不是理想的情况。
我想99%的Web应用都是需要访问数据库的。这样最佳设计应该是这样子:
XBAP (UI Layer) <-> Web Service (Business Layer) <-> Database
Business Layer里面可以根据需要分出数据层等等。
刚开始玩,写的这些东西可能不对,仅供参考。
参考:
http://scorbs.com/2007/01/10/xbap-trust-levels/,http://scorbs.com/2006/11/09/wpf-internet-sandbox-feature-list-xbaps-loose-xaml/
这个post主要用来share我的IdentityScope类
在Windows Vista运行Visual Studio 2005(without SP1)时,最好使用Vista的Run as administrator的功能,否则有些功能就不能正常工作。而在我们开发的.NET程序内部,有时也会碰到需要临时提高权限的情形。ASP.NET程序员经常问的一个问题是,我的代码为什么没有权限创建一个文件?基本的解决方案有三个:
- 提升ASPNET帐户(在Windows 2003则是Network Service)的权限(不推荐)
- 为目标文件或文件夹设置ASPNET帐户的读写权限(如果需要访问的文件路径是固定的)
- 在web.config设置impersonate,以另一个帐户的身份运行程序,比如Administrator...
论坛和新闻组里面常出现的另一个问题是,如何访问网上邻居或者映射的网络驱动器?常见的答案是使用Process.Start方法调用cmd.exe执行一个net use命令,这个方案是可行的但是显然不科学... 这里同样是权限问题。
.NET类库自带了个WindowsImpersonationContext类,可以用来进行用户身份模拟。不过创建这个对象的唯一办法调用WindowsIdentity.Impersonate(IntPtr userToken)方法,而userToken却只有通过Windows API调用而来(根本就是没打算让人用的)... MSDN上语焉不详,而实际上Windows SDK解释的更清楚,在LogonUser页有完整的说明和示例。于是使用P/Invoke封装了LogonUser、ImpersonateLoggedOnUser、RevertToSelf这几个API以及相关的一些枚举类,实现了一个IdentityScope类。演示代码:
Console.WriteLine(WindowsIdentity.GetCurrent().Name);
using (new IdentityScope("SUMA-LP", "Administrator", "********"))
{
Console.WriteLine(WindowsIdentity.GetCurrent().Name);
}
Console.WriteLine(WindowsIdentity.GetCurrent().Name);
Console将输出:
Redmond\V-Wexia
SUMA-LP\Administrator
Redmond\V-Wexia
你可以看到在这个using代码块里面执行者身份成功的扮演了本机的Administrator。在访问网上共享资源的时候这个类同样有效,比如:
using (new IdentityScope("Domain", "User", "Password", LogonType.NewCredentials, LogonProvider.WinNT50))
{
File.Copy("file.ext", "\\shared\folder\file.ext");
}
这里使用LogonType.NewCredentials登陆类型意味着本地身份不变,访问网络资源时使用扮演的身份。
IdentityScope类的源代码见:http://blog.joycode.com/sunmast/articles/identityscope_class.aspx
原来只是打算实现一个进程互斥的功能,而.NET似乎没有内置,所以决定调Windows API自己写。原来简单的几句代码,加上了Exception Handling和Dispose Pattern后,居然搞出了近一百行代码,还没怎么写注释。
/// <summary>
/// Process level simple mutex class. By Sunmast.
/// </summary>
/// <remarks>
/// Always avoid duplicate mutex names.
/// </remarks>
class ProcessMutex : IDisposable
{
[DllImport("Kernel32.dll")]
static extern IntPtr CreateMutex(IntPtr lpMutexAttributes, bool bInitialOwner, string lpName);
[DllImport("Kernel32.dll")]
static extern bool ReleaseMutex(IntPtr hMutex);
[DllImport("Kernel32.dll")]
static extern uint WaitForSingleObject(IntPtr hHandle, int dwMilliseconds);
const uint WAIT_ABANDONED = 0x00000080;
const uint WAIT_OBJECT_0 = 0x00000000;
const uint WAIT_TIMEOUT = 0x00000102;
const uint WAIT_FAILED = 0xFFFFFFFF;
const int MAX_PATH = 260;
IntPtr hMutex;
bool disposed;
public ProcessMutex(string mutexName)
: this(mutexName, int.MaxValue)
{
}
public ProcessMutex(string mutexName, int timeout)
{
if (mutexName == null)
{
throw new ArgumentNullException("mutexName");
}
if (mutexName.Length > MAX_PATH)
{
throw new PathTooLongException("The name is limited to MAX_PATH characters.");
}
hMutex = CreateMutex(IntPtr.Zero, false, mutexName);
if (hMutex == IntPtr.Zero)
{
throw new InvalidOperationException("Mutex creation failed.");
}
uint waitResult = WaitForSingleObject(hMutex, timeout);
switch (waitResult)
{
case WAIT_TIMEOUT:
throw new TimeoutException("Cannot get mutex ownership due to time-out.");
case WAIT_OBJECT_0:
break;
case WAIT_ABANDONED:
throw new InvalidOperationException(
"The specified object is a mutex object that was not released by the " +
"thread that owned the mutex object before the owning thread terminated.");
case WAIT_FAILED:
throw new InvalidOperationException("Function failue.");
default:
break;
}
}
~ProcessMutex()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Nothing to do.
}
ReleaseMutex(hMutex);
disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
其用法有点类似C#的lock关键字,可以定义一个scope,例:
using (new ProcessMutex("MUTEX_TEST"))
{
Console.WriteLine("Wait for ENTER key pressed.");
Console.ReadLine();
}
看来白忙了
,.NET已经内置了这个功能了。原来一直以为所有的WaitHandle都是线程级别的...
Windows Vista系统内置了CD/DVD光盘刻录功能,当然,这个功能在Windows Media Player里面早已提供,但它现在和文件系统已经集成,功能上算不上强大,但使用上很傻瓜。对于普通用户的简单刻录需求,可以说简单够用。
有两个途径,一是右键点击要刻录的文件或者文件夹,选择Send To,再选择DVD RW Drive (E:),就可以了(机器不同则菜单上有细微差别,我的是DELL 640m)。
或者在资源管理器里面直接打开DVD RW Drive,此时在工具栏上会出现一些按钮,比如Burn to disc和Erase this disc。这时候可以直接CTRL+A选中所有文件并删除(如果你不想续刻),把要刻录的文件或文件夹直接拖进去... 就像操作一个Flash Drive一样(有了这个功能还买什么DVD-RAM刻录机?)。
实在是太简单了,不想详细描述。但下面的问题是光盘镜像文件怎么刻录?我没找到(谁找到了请在下面留言)。问题不大,以前记得Windows Server 2003的一个Resource Kit里面有这个功能,下载地址请点这里。这是个12MB的一个自解压包,不过如果你只关心刻录工具,那大部分都是没用的... 下载之后用7-Zip(或者WinZIP、WinRAR等)打开,在rktools.msi里面看到cdburn.exe和dvdburn.exe这两个文件。把它们解压缩到C:\DVRW(或者其它路径)。
然后就可以刻录镜像文件了,比如我打算把刚下载的en_office_business_contact_manager_2007_X13-05848.iso刻录到我的一张DVDRW光盘里面,那么打开命令行界面输入:
C:\DVRW>dvdburn E: D:\Software\en_office_business_contact_manager_2007_X13-05848.iso
如果光盘里已经有东西则自动帮你擦除光盘(假如是RW)。
这两个工具的用法如下:
C:\DVRW>cdburn -help
Usage:
cdburn <drive> -erase [image [options]]
cdburn <drive> image [options]
Options:
-erase Erases the disk before burning (valid for R/W only)
-sao Writes the image out in "session at once", or cue sheet, mode (default is "track at once")
-speed Speed of burn, or 'max' for maximum speed
-imagehaspostgap Use if your image already contains a 150 sector postgap
The [image] must be provided unless the -erase flag is set.
If both an image and -erase are provided, the media will be erased prior to burning the image to the disc.
C:\DVRW>dvdburn -help
Usage: dvdburn <drive> <image> [/Erase]
最后,除了给普通用户提供的刻录功能,系统还公开了刻录API — 微软一贯风格。这个不是Windows Vista的新功能了,事实上它是Windows Media Player API的一部分。参考:Windows Media Player 11 SDK IWMPCdromBurn Interface。如果没有记错的话,WMP 9就已经有这个接口了。
----- Update on 7/30/2007
今天翻UDF文件格式的资料的时候才发现IMAPI才是用来管光盘刻录的,WMP的那个只是专门用来把mp3刻成CD。
看这个:http://msdn2.microsoft.com/en-us/library/aa364817.aspx
里头有个例子:
' This script burns data files to disc in a single session
' using files from a single directory tree.
' Copyright (C) Microsoft Corp. 2006
Option Explicit
' *** CD/DVD disc file system types
Const FsiFileSystemISO9660 = 1
Const FsiFileSystemJoliet = 2
Const FsiFileSystemUDF102 = 4
WScript.Quit(Main)
Function Main
Dim Index ' Index to recording drive.
Dim Recorder ' Recorder object
Dim Path ' Directory of files to burn
Dim Stream ' Data stream for burning device
Index = 1 ' Second drive on the system
Path = "g:\BurnDir" ' Files to transfer to disc
' Create a DiscMaster2 object to connect to optical drives.
Dim g_DiscMaster
Set g_DiscMaster = WScript.CreateObject("IMAPI2.MsftDiscMaster2")
' Create a DiscRecorder object for the specified burning device.
Dim uniqueId
set recorder = WScript.CreateObject("IMAPI2.MsftDiscRecorder2")
uniqueId = g_DiscMaster.Item(index)
recorder.InitializeDiscRecorder( uniqueId )
' Create an image stream for a specified directory.
Dim FSI ' Disc file system
Dim Dir ' Root directory of the disc file system
Dim dataWriter
' Create a new file system image and retrieve root directory
Set FSI = CreateObject("IMAPI2FS.CFileSystemImage")
Set Dir = FSI.Root
'Create the new disc format and set the recorder
Set dataWriter = CreateObject ("IMAPI2.MsftDiscFormat2Data")
dataWriter.recorder = Recorder
FSI.FreeMediaBlocks = datawriter.FreeSectorsOnMedia
FSI.FileSystemsToCreate = FsiFileSystemISO9660
' Add the directory and its contents to the file system
Dir.AddTree Path, false
' Create an image from the file system
Dim result
Set result = FSI.CreateResultImage()
Stream = result.ImageStream
' Write stream to disc using the specified recorder.
WScript.Echo "Writing content to disc..."
dataWriter.write(Stream)
WScript.Echo "----- Finished writing content -----"
Main = 0
End Function你甚至可以用这套API制作ISO镜像文件。同时支持ISO9660、Joliet和UDF。
这个post要表达的东西是非常基本而且简单的,但我已经反复考虑了数月之久。这里的程序指的是个人/家用桌面程序,非所谓的“企业级应用”。
几周前我在整理我家里电脑的相册时,PC和Laptop之间的相册不同步,COPY来COPY去实在是太繁琐了。而我在公司的另一台Laptop也带回家后,这变成了一个更麻烦的问题。解决这个问题的办法是找个文件夹同步软件。
在另外一个方面,假如我在父母住处也想看看我的相册怎么办呢?答案是我最好创建一个网上相册,比如Windows Live的Gallery服务,Google的Picasa相册,或者是其他网上大量的网络相册服务。而这里的问题是,网络太慢了,桌面程序点一下Page Dn就能翻一个相片,网上有时候要一分钟。
我的电子邮件也有着类似的问题。如果使用Outlook收邮件的话,收下来后服务器上就没了,这意味着我不能在另外一台电脑上再去查看这个邮件。对于工作邮件这可能不是个问题,但个人邮箱却没有办法。于是很多人选择了全部基于Web界面处理个人邮件,比如Gmail和Live Mail。
但更为理想的状况是,我可以用Outlook收发邮件,用Word编辑邮件,同时邮件在服务器上总是保存着副本(这可以设置Outlook下载邮件后不删除服务端邮件,但邮箱容量往往都很小,不能长期这么做,现有邮件系统也不把这种需求当作典型应用)。邮件服务器上不但保存一个一个邮件,还有我设置的邮件分类,还有已读/未读状态值,甚至Outlook的Flag和Task等。这样无论我在哪里打开Outlook,都是一样的体验。
我现在使用Google的Picasa相册已经有了部分的这种能力,它同时有桌面和Web版本,我曾对它寄予完全的希望,用来管理我所有的相片。但如果不是我孤陋寡闻,显然Google并没有考虑到这个方面,因为Picasa桌面端只能向Web端上传图片却不能下载。。。而更进一步的设计则应该是具有本地文件和Web数据库同步的功能。
Windows Live的Favorites似乎有着一模一样的问题,它允许你从IE的Favorites导入到Web端,但却没有反向同步的能力。。。但它相比Picasa的优点是,客观上它是支持导出的,但是只是导出到一个HTM文件让你下载而已。。。而不是自动“装”到IE收藏夹所在的文件夹内。
微软的Share Point Service加上Office则是个更接近理想的组合:
- 数据保存在服务端的数据库里,你在任意网络可连通的地方都可打开
- Office一系列桌面软件安装于客户端,可以用这些软件来打开修改文档
这个组合在我看来已经接近完美了,但唯一的问题是在离线状态文档就不可访问了。我们需要文件在硬盘上的Cache。当然,SPS/WSS主要还是针对企业应用的,这个问题不大,网络速度一般也不会成为问题,数M的Word文件在数秒内就能传输完成。但如果Office System在客户端再加个“Share Point Client”维护硬盘Cache的话,那就更棒了。
这里的Model是相当简单的:
数据(Online,公共服务器维护,受安全措施保护)
↑↓
缓存(Offline,程序自动维护)
↑↓
程序(Offline,自维护或者IT管理员维护)
作为可选项,公共服务器上可以支持Web版的数据查看和一些简单操作 — 如果有必要的话。
至于Google的在线版本的Word和Excel,则根本是误入歧途(我不是作为一个MS MVP下这个结论的)。Google的选择是把程序和数据统统放在服务端,这大概是大部分对Google这些服务心存怀疑态度的人的最根本出发点。Web程序最终是不可打败桌面程序的,就算有下一代的DHTML可以做到和现有桌面程序一样的用户体验(比如Word般丰富的文本编辑器),他仍然需要硬盘的Cache。且不说这是个相当大的假设,更不用说微软的WPF加Expression套件在Rich Client方面已经再次走在了前面。也许五年后网络速度和连通性又有了非常大(速度*=100,连通性100%,网络延迟100ms内)的提高,硬盘Cache无足轻重,那基于浏览器的应用还面临着最后一个问题,那就是它们必定受限于浏览器安全沙箱模型,可触及的数据和可做的动作都是受限的。
Google的Picasa软件离这个目标则只有一步之遥。但令人匪夷所思的是,他们为什么不在Gmail这样的产品上也应用类似的策略呢?Gmail Notifier都做出来了,再进一步吧。Gmail的Web界面编辑器确实“还算不错”,但也只是相对于其它Web编辑器而言而已,距离Outlook等桌面软件差太远了。
除了这些用户关注的数据,其它程序本身使用的数据也可放在网上。比如说配置文件。极端一点,想象一下把整个Windows注册表都同步到网上的情形(当然硬件相关的配置信息就不用了),至少你能节约大量重装系统的时间(装系统很快,但装软件配置软件很慢)。Q3ACN(一个游戏网站)曾提供过保存Quake3游戏CFG文件的服务,大受欢迎,因为这样我们Quaker随便去哪个网吧都能以自己的CFG文件配置游戏系统,提高作战水平。要是id Soft直接把这个功能植入游戏,再在公司的Server上提供数据库接口,那就更完美了。(这是个坏例子,不打Quake游戏的朋友可能很难理解这一点。。。)
最后,关于SaaS(Software as a Service),这也是一个很好的切入点,虽然这个和SaaS只有一丁点关系。。。程序除了使用更好的加密算法防止盗版(比如微软的SPP),还可结合Internet。让程序的一部分功能依赖于Internet上的Server,可以非常轻松的获得非常好的保护。破解的Xbox360能玩单机游戏,却不能玩联网游戏,这就是个良证。把程序所需的配置数据放在互联网上,也是实现这个目的的途径之一。是不是合适,这是另外一个问题,这里的假设是你希望保护好你的软件并为你带来利润,而不是一个FOSS偏执狂。
刚才Kaneboy提示,Office 2007的Groove已经具备了我所需要的"Share Point Client"的功能,当然它的feature还不止这些,呵呵。
updating
--- 写在Vista RTM之日的流水帐
今年7月我转到了新公司,新部门,从头开始建设新的开发团队。作为一个理想主义者,原来打算直接上TFS(Team Foundation Server)的。。。不过对于这么小的Team,TFS太贵了点,有浪费之嫌。而现有的正版资源是,Windows 2003 STD,Office 2003 PRO,和Visual Studio 2005 STD。于是乎,我打算用WSS(Windows Sharepoint Service,Windows 2003的一个免费组件)做项目门户、共享文档、文档(with版本)管理等,用VSS 8.0做配置管理(有了VS或者Office的License后VSS就是免费的),再开一个File Server来放工具、软件等等。
但Bug管理的问题却一直没想好怎么办。原来在Infosys用的是Excel + RADAR,显然不能用;再之前是Clear Quest,还是太贵。Test Director和Silk Test之类的我也不想买。。。于是乎,在网上看各种免费Bug管理工具的评价和测试报告,最终圈定在了这三个(分先后):BugFree、Mantis,和BugZilla。BugFree是一个微软员工写的,据说贯彻了微软Bug管理的核心思想。。。不过我觉得它的数据统计和报表的功能不尽如人意。而Mantis和BugZilla都是老牌的开源Bug管理工具了,但对我来说他们的安装和配置实在是太恼人了,在我还没有开始尝试使用他们之前,就放弃了(准确的说是,我只能用用online的demo)。而BugFree的代码更不能让人放心,似乎有绝对路径/相对路径的错误,安装过程还得自己去改它代码,搞死人啊。此外,它们似乎也都是使用内建的帐户管理系统,而我希望它能和AD集成。
在以前的公司里,我们常常用Bug管理工具管Task,而其实反过来也问题不大。想想我们究竟关注Bug管理工具的什么功能?除了基本的纪录每个Bug的各种属性信息以外,还需要支持方便的查询,邮件通知,还有统计报表,等等。对于开发/测试者来说,要能方便的纪录Bug信息,管理Bug生命周期;而对管理者来说,需要准确的统计数据以支持决策。Bug管理和Task管理在某种程度上是相当类似的,WSS的Task List的自定义能力又正好非常的强(没装过WSS的赶快装一个看看吧,WSS 3.0都快出来了),那么为什么一定要找一个专门为Bug管理定制的软件呢?我打算开始在项目里面尝试使用Task List管理Bug。就算还没能100%满足要求,但自定义的Task List可以作为一个起点,要知道WSS是支持二次开发的。如果你愿意,基于WSS搞一个集成了需求管理、缺陷管理等等的Product Studio出来也不是不可能。。。
-----
看到Alex的回复,重新考虑了下,可能Discussion List确实比Task List更适于做Bug Tracker。。。
温昱(0,1,2,3,4,5)的《软件架构设计》共 26 章,分为 3 篇:
- 软件架构概念与思想篇
- 软件架构设计方法与过程篇
- 程序员成长篇
详细目录现在还不能公布(尚未得到出版社许可)。
下面是作者的一些文章和演讲稿,供朋友们管窥本书:
所有对国产书籍嗤之以鼻的人,不妨读一读这本,它也许能改变你的看法。
经过数个CTP后,Visual Studio 2005 SDK的3.0版本终于release了。最值得注意的大概是其中的Domain-specific Language Tools v1.0 — 这个应该是DSL的第一个正式版本。毫无疑问,DSL是微软在软件架构领域的重要作品。
DSL不同于UML,它也不是用来和UML竞争的,也许可以把它理解为meta-UML更合适,也就是UML的UML。此外,DSL是可编译的,如同编译器可以把代码编译为机器码一样,DSL模型可以被编译为代码;而UML只能用来写设计文档(并且UML 2.0似乎依然没有精确到足够支持MDA的地步)。
下载
Using Query Notifications:http://msdn2.microsoft.com/en-us/library/t9x04ed2.aspx
- Query Notification不同于Notification Service,最简单的理由是我压根就没装Notification Service也能用它。看BOL文档,Notification Service似乎不只是应用程序级别的服务。
- SqlDependency和SqlCacheDependency的实现是不同的。SqlCacheDependency是基于poll模式定时轮询实现的(默认情况),而SqlDependency的是push模型(这意味着SqlDependency性能更高且更可靠)。不过,SqlCacheDependency可以基于SqlDependency实现,但MSDN只是提到“The SqlCacheDependency class also supports integration with the System.Data.SqlClient.SqlDependency class when using a SQL Server 2005 database. ”,但没有给出例子。我Goo了半天才发现Quickstart里面有:
http://x/QuickStartv20/aspnet/doc/caching/SQLInvalidation.aspx
- SQL Server 2005 EXPRESS也支持Query Notification,但默认似乎是关闭的,并且我也没能在配制工具找到配制界面。事实上不管怎么说只要能开启Broker服务就OK。这可以通过ALTER DATABASE [Your DB Name] SET ENABLE_BROKER命令实现。(后来找到了个相关的,在Management Studio的Object Explorer,Databases|Your DB Name|Properties|Permissions|Subscrib query notifications)
- SqlDependency.OnChange Event会报告订阅过程中出现的所有事件,比如订阅失败,而不只是Server端数据改变的情形。假如你在这个事件绑定函数中又重新注册了Query Notification,小心产生死循环。
- 使用Query Notification是有限制的,并且不是所有的查询都支持(否则产生订阅失败事件),具体规则是:
http://msdn2.microsoft.com/en-us/library/aewzkxxh.aspx
The projected columns in the SELECT statement must be explicitly stated, and table names must be qualified with two-part names. Notice that this means that all tables referenced in the statement must be in the same database.
The statement may not use the asterisk (*) or table_name.* syntax to specify columns.
The statement may not use unnamed columns or duplicate column names.
The statement must reference a base table.
The projected columns in the SELECT statement may not contain aggregate expressions unless the statement uses a GROUP BY expression. When a GROUP BY expression is provided, the select list may contain the aggregate functions COUNT_BIG() or SUM(). However, SUM() may not be specified for a nullable column. The statement may not specify HAVING, CUBE, or ROLLUP.
A projected column in the SELECT statement that is used as a simple expression must not appear more than once.
The statement must not include PIVOT or UNPIVOT operators.
The statement must not include the INTERSECT or EXCEPT operators.
The statement must not reference a view.
The statement must not contain any of the following: DISTINCT, COMPUTE or COMPUTE BY, or INTO.
The statement must not reference server global variables (@@variable_name).
The statement must not reference derived tables, temporary tables, or table variables.
The statement must not reference tables or views from other databases or servers.
The statement must not contain subqueries, outer joins, or self-joins.
The statement must not reference the large object types: text, ntext, and image.
The statement must not use the CONTAINS or FREETEXT full-text predicates.
The statement must not use rowset functions, including OPENROWSET and OPENQUERY.
The statement must not use any of the following aggregate functions: AVG, COUNT(*), MAX, MIN, STDEV, STDEVP, VAR, or VARP.
The statement must not use any nondeterministic functions, including ranking and windowing functions.
The statement must not contain user-defined aggregates.
The statement must not reference system tables or views, including catalog views and dynamic management views.
The statement must not include FOR BROWSE information.
The statement must not reference a queue.
The statement must not contain conditional statements that cannot change and cannot return results (for example, WHERE 1=0).
- 订阅成功后,SQL Server会自动创建一个dbo.SqlQueryNotificationStoredProcedure-[GUID]这样存储过程。订阅结束后(应用程序退出或者SqlDependency.Stop方法被调用),这个存储过程会被自动删除。
- 关于行级别的缓存失效功能(子曰“Row level cache invalidation”),看上去目前还没有实现(至少我还没找到办法访问这个功能)。但世事无绝对,现在你可以用托管代码编写触发器,再建个临时表,间接的实现(也许你已经有主意了)
- 有时候Query Notification会在没有任何征兆的情况下突然不工作(真要命~),原因可能是你没有安装这个补丁:http://support.microsoft.com/kb/913364/EN-US/ 。实际情况是SQL Server已经发出了消息,客户端也已经接收到,但是OnChange事件没有触发。。。所以这是个.NET 2.0框架的补丁。
- 测试过程中,如果SQL Server在数据改变时报告了其它错误(红色的X!),需要特别注意,可能是配置错误(比如权限不足),Query Notification也会停止工作,但SQL Server会不断地重试(时间间隔大约是一分钟左右),导致EventLog里面出现了长长的错误列表(比较醒目~)。使用SELECT * FROM sys.transmission_queue命令查看队列中所有待发的消息通知,注意最后一列[transmission_status]的值。
- 数据库迁移到另一台机器后(Attach/Detach,Backup/Restore),可能会碰到这个错误:“An exception occurred while enqueueing a message in the target queue. Error: 15517, State: 1. Cannot execute as the database principal because the principal "dbo" does not exist, this type of principal cannot be impersonated, or you do not have permission.”,这样Query Notification也不会工作。这个原因是原先创建数据库的用户在新的机器上不再有效,可以使用这个命令搞定之:ALTER AUTHORIZATION ON DATABASE::[Your DB Name] TO [sa]
- 如果你的程序在调用了SqlDependency.Start后却忘了调用SqlDependency.Stop就退出了,那么程序再次启动时,在EventLog你可能会看到这样的提示:The query notification dialog on conversation handle '{GUID}.' closed due to the following error: Cannot find the remote service 'SqlQueryNotificationService-GUID' because it does not exist.
这是因为SQL Server找不到原先注册的Subscriber所致,所以SQL Server会自动注销这个Subscriber,不会影响程序功能。但你却得注意SqlDependency.Stop没有被调用,程序逻辑可能有问题。而且SQL Server端的Broker队列会积压所有待发的消息(因为发不出去),可能会给Server端造成无谓的压力。
在基于Visual Studio 2005的Web应用程序开发中,如果你使用Visual Studio内置的Web Server,那么当这个Server关闭时,不会等Application_End函数执行完毕就有可能退出(这个应该算是个小小的Bug)。假如你把SqlDependency.Stop放在了这个函数里面,那每次启动调试你大概都能收到SQL Server的这个提示信息。IIS不存在这个问题。
- 如果Server端在很短的时间内发生了大量的改动(比如用了一个循环Update了好多行),OnChanged必须能迅速处理事件,否则它只会被触发一次。这个不是缺陷,因为一般OnChanged事件处理函数内都要执行类似刷新缓存的操作,它只触发一次,不会影响程序逻辑,却能提高程序性能。
- SqlDependency构造函数有个options选项,MSDN的文档给链接到了错误的地方。我原来以为可以通过这个选项来过滤掉一些不必要的事件通知(比如我不想监视所有删除操作的事件),但事实上它是用来定义Service Broker实例名等参数的,就像一个SQL连接字符串。
// updating
这些天我在家里用C#实现一个资源管理器控件,功能和Windows自己的那个类似,我想把它实现的和Windows的尽量接近 — 当然是越接近越好啦。
问题马上就来了,.NET目前似乎并不是很擅长搞这个,或者说那些相关的Windows API还没被收入进FCL(WinFX/.NET 3.0应该已经收录了吧),这下可麻烦了:
- 地址栏
MFC里面有个CComboBoxEx类,可以用来做带层次缩进和图标的ComboBox,实现资源管理器的地址栏。但是.NET没有,不过还好可以继承System.Windows.Forms.ComboBox类,然后重写OnDrawItem方法,搞定
- 文件图标
.NET 2.0终于开始支持这个功能了,就是Icon.ExtractAssociatedIcon方法,不过它返回的总是文件的大图标.... 而Windows XP里面一个文件可以包含大中小三种尺寸的图标(每种还有三等色深所以一共是九种),所以还是得调用SHGetFileInfo这个API;
还有些图标是在shell32.dll里头的(比如文件夹的图标),那样还得把它们枚举出来... 这个得用ExtractIconEx;
此外还要维护一个ImageList,不然占用太多Icon句柄,会发生GDI Failue的... 这个还好,FCL已经有这个类支持了
- 文件类型信息
每种文件类型都有一个Description,比如说.doc就是Microsoft Word Document,也记录在注册表里头。这个问题我在CSDN已经回答了N遍,不想再打一遍了
- 文件夹背景图
这个暂时还没功夫去研究,并且我认为这个功能相对不是那么的重要。还有文件夹缩略图之类的... 不是一般的麻烦,日后再说
- 左边的树形视图
.NET自带了TreeView控件,只要有Icon,剩下的工作就很容易了
- 右键菜单
菜单里有很多项目都是记录在注册表里面的。这个又是个很大的话题... 总而言之就是麻烦得要死。不过那些Cut、Copy、Delete、Properties什么的操作的实现倒是简单,可以直接用ShellExecuteEx这个API
- 快捷方式
解析.lnk文件到目标文件,可以直接打开文件流读取里面的字符,在某个偏移量的某个位置是可以找到的,不过这样不“安全”,还是用API吧。MSDN说可以使用IShellLink和IPersistFile这两个COM接口,代码虽然很ugly但是不算很长。问题又来了,IShellLink没有继承IDispatch,只是继承了IUnknown,这意味着tlbimp.exe也帮不了忙(貌似),得自己声明那些接口和组件原型... 阿门,我今天搞了一晚上,还是不大对头,想睡觉了...
- 主文件视图
这个可以用.NET自带的ListView控件应付,和Windows一样它也带了5种浏览模式(Title、List、Detail等等),不用自己实现
- 文件/文件夹的Tooltip
Tooltip的内容并不是固定的... 比如说doc文件的Tooltip还会包括Author等信息,mp3文件则会有Duration之类的,这个要用Structure Storage
相关的COM组件,超级麻烦,暂时不想管了~
- .....
.....
不过今天也不是没有收获,在Google上找到了这么个网站:
http://www.vbaccelerator.com/home/NET/Code/Libraries/Shell_Projects/index.asp
他们把很多Shell相关的东西给封装了一下,比如我一直郁闷的文件快捷方式的问题,他们已经提供了解决方案。早知道有这个,我就不用那么费劲了。
最近老夫心情很好,上海天气也前所未有的暖和,所以这篇超级流水账纯属YY。
想必是被公司里的数据库高手惯坏了,这两年来自我感觉离数据库距离越来越远,以至于SQL Server 2005发布这么久 — 连SP1都出来了 — 我都还没把它装到硬盘上;如此严重的轻视MSDN Subscriber帐号的存在,在还有三天MVP帐号就要到期之前(美丽的Sisley阿姨快救救我吧),我在想这世上还有做.NET的人不会数据库吗?自己到底还算不算一个.NETer or MSer?出于这个严重问题的考虑,我决定,为了支持微软(以增加连任MVP的可能性),我决定,去MSDN当一个先,再晚就来不及了!
Initially我看上了SQL Server 2005 Developer Edition,原因是我的机器跑的是Windows XP SP2,而Windows XP没有Enterprise Edition,并且SQL Server 2005没有Professional Edition,作为一个“出于学习和研究目的”的Developer,当然选择Developer Edition啦。在当当当当当当当当当当了一天两夜之后,把ISO载入DAEMON(此时突然想起微软寄来的DVD里应该已经带了,我浪费了全人类36小时的宝贵的电力能源),安装,点开开始菜单,晕,SQL Server 2000的Enterprise Manager去哪儿了我说?MSN上随便抓个人一问,子曰现在那个玩意的名字叫SQL Server 2005 Management Studio,啊,变高级了,不过可惜我找不到,开始菜单里没有啊,卖糕的,没有这个东西该怎么管理数据库啊,搜索硬盘也没找到那个EXE(子曾经曰过是SSMS.EXE),算了,归为RPWT,重装。结果我成功的证明了我没有RPWT,因为我还是没有找到那个Management Studio。此时突然想起我以前一个Manager的口头禅:
“微软的东西就是烂!”
阿弥陀佛,迫于人家管理层的淫威,我不能说“实际上是你自己脑子有病”或者是“晚上吃药忘了开灯吧”,等等,虽然那些从小到大学会的脏话在脑海里已经翻腾了好多年;只能说“没有吧,你的机器又出啥问题了我帮你看看”。
受CSDN论坛影响,我的潜意识也变得越来越浮躁,再加上我不得不边看球边装机器的客观原因,所以我心安理得的没有按照正规流程(看文档 -> 查资料 -> 自个儿琢磨 -> 再看文档,循环到Stack Overflow为止)研究问题原因,直接在MSN上又抓了个人问。不巧的是这位大哥似乎也在看球(和我不同的是我可以边看电视边看电脑,他不行),好不容易等到中场的时候那位大哥才来了一句:
“啊?你居然还在用Windows XP?我不用XP已经好多年了都。”
言下之意是我这个火星来的也应该学学地球人怎么用Windows Server 2003了,但是我认为我64-bit的Windows XP SP2号称使用了Windows Server 2003的内核,理论上一样的;况且以前SQL Server 2000都没问题,根据寡人这些年来观察猪奔跑的经验,新版本应该也可以的。算了,人家还有一个意思就是“我不对使用Windows XP的人提供技术支持”。搞不懂了,这年头装个OS都能让人产生优越感(特别是那帮装了Macintosh的,老觉得我的XP就是盗版了他们Apple买来的界面技术)。
由于已经确定这个不是我的RPWT,再加上那位大哥的误导,我开始怀疑我的OS起来。原因是我的XP是经过nLite RIP的,万一那天脑袋不清醒弄丢了什么组件,造成一些超自然现象也在情理之中。但是为了这种破问题就重装系统值得吗,太无能了吧?为了免受江湖高手的耻笑,我打算坚持在PC上使用现有系统,在另一台刚买的DELL本本上再试一次(sigh,由于买的是DELL的机器,我还是没能逃脱来自黑社会人士的鄙视,地球真的是一个很奇怪的星球)。这个时候终于注意到MSDN 上赫然写着:
New in Subscriber Downloads - SQL Server 2005 Express Edition with Advanced Services
SQL Server Express with Advanced Services has all of the features in SQL Server 2005 Express Edition, plus you can:
- Easily manage and administer SQL Server Express with a new easy-to-use graphical management tool -- SQL Server 2005 Management Studio Express (SSMSE).
- Issue full-text queries against plain character-based data in SQL Server tables. Full-text queries can include words and phrases, or multiple forms of a word or phrase.
- Run SQL Server Reporting Services reports on local relational data.
并且还集成了SP1,大小是235MB。下载了安装,YOXI,我终于把它搞定了。
BTW,非MSDN订阅用户可以在这里下载:
http://msdn.microsoft.com/vstudio/express/sql/download/
----- Updated on 7/1/2006
MVP RENEWED 
----- Updated on 12/2/2006
打算回复这个post的这个评论:
“技术含量不高的文章,谁叫你是mvp呢,严格来讲,mvp最好少写这样的文章,mvp就象明星,哪一天当了明星,你是高手也好,不是高手也好,你就得认真些,冒办法.”
不知道是越活越糊涂还是越活越明白,现在我最喜欢的是“郑渊洁”式的风格。参考:http://blog.sina.com.cn/u/482646730100019v
“多年以来,我们的社会里都有一个被普遍认可的观念,那就是认为公众人物应该保持良好的形象。这种想法今天仍然非常流行,这在某种程度上说明了我们这个社会整体上仍然欠缺理性的思考能力。我们只能说我们希望公众人物保持良好形象,不该说公众人物应该保持良好形象。当公众人物做了让你讨厌的事情(但不涉及是非对错)的时候,你可以批评他、谴责他、讽刺他、甚至骂他,但不能说他应该怎么怎么样。这种蛮横的公众想法的本质其实是:“你不应该做让我讨厌的事情,我不喜欢你这样。”这不是莫名其妙吗?很多有精神疾病的人容易这样一厢情愿。”
正则表达式是设计为用来匹配文本的,可是论坛里头老是有这种问题:
“谁能帮我写个正则表达式,验证从0到180的数字。”
我说,正则表达式不是用来干这个的,匹配和验证是不一样的概念,数据验证过程可能包含很复杂的逻辑,而这个时候要用文本匹配来模拟,就变得很不科学。虽然,这个问题对于正则表达式并不是无解,可以写成这样:
^([0-9]{1,2}|1[0-7][0-9]|180)$
但用编程语言来判断显然方便直观的多,(x >= 0 && x <= 180)就可以了。你大概觉得上头那个正则表达式还不算复杂,那么看看这个:
“匹配从874到142857的数字。”
那正则表达式就得写成:
^(87[4-9]|8[8-9][0-9]|9[0-9]{2}|[1-9][0-9]{3,4}|1[0-3][0-9]{4}|14[0-1][0-9]{3}|142[0-7][0-9]{2}|1428[0-4][0-9]|14285[0-7])$
尽管还是可以做到,不过这个就有点走火入魔了。
不过呢,如果你非得这么做(比如客户逼你,我就是...),那我写了个小工具来干这个事情(写这种正则表达式,毕竟还是有规律可循)。下面是截图:


刚才那两个正则表达式,也是用这个工具生成的。代码在这里。
P.S.
这个工具目前尚不支持小数和负数,也许你可以基于我提供的代码写个更完整的版本出来。(不过,真的有必要吗?)
此外,这个工具尚未经过严格测试。发现问题了请给我发邮件或者在这里留言,多谢。
Duet是微软和SAP合作开发的一个产品。在其官方网站的Technical Blogs里面,有人这样写道:
Duet: Enterprise Applications and Desktop Productivity: "Duet" composed by SAP and Microsoft
言下之意是,Duet连接了SAP和Office,可以用它来开发基于SAP ERP的、使用Office(以及用.NET+VSTO开发的Office插件)作为前端的企业应用程序。下面是官方网站提供的一个架构示意图:
Duet,中文可以译为“二重奏”,意即微软和SAP之合作。微软显然又做了一件正确的事情,Duet正好可以让两个公司更好的发挥各自所长。我一直觉得.NET@Office可能会比.NET@IIS(ASP.NET)更有潜力,现在这种想法更加强烈了。
啊,今天真的很闲,这是今天写的第三篇了,哈哈,超过了前面每月的平均水平。
VCKBASE的翻译团队刚刚把MSDN的一篇关于ManagedSpy的文章翻译了过来,我在这里直接引用一下,就不多说废话介绍了:
ManagedSpy是一个诊断工具,和Spy++类似。它显示受托管的属性,允许你记录事件,并且是一个使用ManagedSpyLib的很好例子。ManagedSpyLib引入了一个称为ControlProxy的类。一个ControlProxy是一个System.Windows.Forms.Control的代表,允许你获得或设置属性并预定事件好似你正在目标进程中运行着一样。使用ManagedSpyLib来做自动化测试,为兼容性做事件记录、进程交互通讯,或白盒测试。
译文:http://www.vckbase.com/document/viewdoc/?id=1625
原文:http://msdn.microsoft.com/msdnmag/issues/06/04/ManagedSpy/default.aspx
下载:http://download.microsoft.com/download/f/2/7/f279e71e-efb0-4155-873d-5554a0608523/ManagedSpy.exe
截图:

很酷吧~ 
Managed Spy是开源的,它用C++/CLI封装了一个基础类ControlProxy(小小的展现了一下C++/CLI的NB之处),外加一个用C#写的UI(就是你看到的这个截图)。你只可以用Managed Spy来spy基于.NET 2.0的Windows窗体应用程序。
它的ControlProxy类还能用来做别的事情,除了察看别的进程里控件的属性和事件之外,我在想是不是可以利用它写一个记录UI操作的Recorder,这样连测试脚本都不用写了,Beyondsoft这样的公司有福了。
在前面提到,.NET 1.x提供的自定义序列化的solution很不方便。不过,IMPOSSIBLE IS NOTHING,我们可以写两个辅助方法来绕过这个问题。
class SerializationHelper

...{
public static void Serialize(object obj, SerializationInfo info, StreamingContext context)

...{
MemberInfo[] members = FormatterServices.GetSerializableMembers(obj.GetType(), context);
foreach(FieldInfo field in members)

...{
info.AddValue(field.Name, field.GetValue(obj), field.FieldType);
}
}
public static void Deserialize(object obj, SerializationInfo info, StreamingContext context)

...{
MemberInfo[] members = FormatterServices.GetSerializableMembers(obj.GetType(), context);
foreach(FieldInfo field in members)

...{
field.SetValue(obj, info.GetValue(field.Name, field.FieldType));
}
}
}
如果你已经看明白了我的意图,那就不必往下看了。
下面是利用了这个SerializationHelper的自定义序列化方案:
[Serializable]
class Person : ISerializable

...{
// public field
public string Name = "Vista Xia";
// private field
private int age = 23;
// public property (no use in serialization)
public int Age

...{

get ...{ return age; }

set ...{ age = value; }
}
[NonSerialized]
public string Award;
// event/delegate
public event EventHandler Birthday;
public Person()

...{
Award = "MVP";
}
protected Person(SerializationInfo info, StreamingContext context)

...{
SerializationHelper.Deserialize(this, info, context);
Award = "MVP";
}
public void GetObjectData(SerializationInfo info, StreamingContext context)

...{
SerializationHelper.Serialize(this, info, context);
}
}

加上前面提到的background,就很容易理解了嗯。
注意:
这个不是微软官方提供的方案,我也不提供技术支持。我只能说这个应该是可行的,我自己做了不少测试也证明了这一点,但是,万一出了问题可不要怪俺。