| 歲月輕品's profile蚂蚁的窝PhotosBlogLists | Help |
|
|
26 June Smart Client Software Factory的启动过程应网友要求,结合参考实现(BankBranchWorkbench)写一篇关于 SCSF 内部工作原理的文章,需要读者有 SCSF 基础。基本概念和基本理念后面相关文章介绍。 SCSF 自动为我们建立了 Shell 项目。该项目的 ShellApplication 是SCSF 应用的入口程序,该类继承自 SmartClientApplication<TWorkItem, TShell> ,TWorkItem 是要指定的 root workitem ,TShell 是主窗体。
该类的 Main 方法通过 new ShellApplication().Run(); 启动应用。Run() 在父类 CabApplication 中实现,定义了 SCSF 的启动流程:1 public void Run() 其中核心流程有: 1. 首先创建 Builder builder = CreateBuilder(); CreaterBuilder()方法注册了 RootWorkItemInitializationStrategy ,EventBrokerStrategy,CommandStrategy,ObjectBuiltNotificationStrategy 总共四个策略,同时还添加了三个 Policy :SingletonPolicy,BuilderTraceSourcePolicy,ObjectBuiltNotificationPolicy 。 CreaterBuilder() 一般使用 builder.Strategies.AddNew 方法利用 ObjectBuilder 构建策略对象 (例如:builder.Strategies.AddNew<EventBrokerStrategy>(BuilderStage.Initialization))。 2. 子类可以通过重写 protected virtual void AddBuilderStrategies(Builder builder) 来给 ObjectBuilder 添加其他构建策略(在构建对象或者销毁对象时执行的操作)。 3. 初始化 RootWorkItem 这些准备工作做完后,第一件事是创建 RootWorkItem 。RootWorkItem 是通过 CreateRootWorkItem(builder) 完成的,以前面创建的 builder 作为参数:protected internal void InitializeRootWorkItem(Builder builder)。 InitializeRootWorkItem 中首先初始化 RootWorkItem 相关的 Builder 和 Locator (这两个都是 ObjectBuilder 的组件,用与对象创建和依赖注入): this.builder = builder; 其次对 rootWorkItem 进行初始化,主要执行以下三个方法: InitializeFields():设置或初始化 ObjectBuilder 相关的对象: Builder,Locator,ObjectBuiltNotificationPolicy,还有 workItem 的状态。 InitializeState():通过 Guid 生成本 workItem 实例的 ID (ID = Guid.NewGuid().ToString();); InitializeCollectionFacades():初始化管理 SCSF 核心组件的对象管理集合: ServiceCollection,commandCollection,workItemCollection,workspaceCollection,itemsCollection,smartPartCollection,eventTopicCollection,uiExtensionSiteCollection 。 RootWorkItem 构建完成后有一个可选的过程是创建 IVisualizer (用于在运行时查看 WorkItem 的状态):1 IVisualizer visualizer = CreateVisualizer(); 4. 添加服务: AddRequiredServices() 方法添加的服务有:TraceSourceCatalogService,WorkItemExtensionService,WorkItemTypeCatalogService,SimpleWorkItemActivationService,WindowsPrincipalAuthenticationService,ModuleLoaderService,FileCatalogModuleEnumerator,DataProtectionCryptographyService,CommandAdapterMapService,UIElementAdapterFactoryCatalog 。 AddConfiguredServices() 添加在配置文件中配置的服务,也就是运行我们通过配置的方式决定在 SCSF 框架启动时加载额外服务(Services)。 AddServices() 在 CabApplication 中是一个空的虚拟方法,允许我们创建 CabApplication 的子类来重写该方法以添加需要的 Services 。 5. 验证用户 通过获取在启动过程中注册的 IAuthenticationService 服务来进行用户验证(ObjectBuilder 的具体应用): 1 private void AuthenticateUser() 6. 处理 Shell 程序集(可执行的 Shell Assembly) 1 private void ProcessShellAssembly() 通过 ObjectBuilder 获取已注册的 IModuleLoaderService (默认的是 Microsoft.Practices.CompositeUI.Services.ModuleLoaderService),调用 ModuleLoaderService 的 Load 方法来通过SCSF Attribute(反射和特性)结合 ObjectBuilder 来加载 SCSF 核心组件,包括 Services、 Command 、 SmartParts、UIElement、workspace、Event Broker、State 等,这些属性(Attribute)主要有:ServiceAttribute,ServiceDependencyAttribute,SmartPartAttribute,CommandHandlerAttribute,EventPublicationAttribute,EventSubscriptionAttribute,ModuleDependencyAttribute,StateChangedAttribute,RootWorkItemExtensionAttribute,OptionalDependencyAttribute,TraceSourceAttribute,ComponentDependencyAttribute,WorkItemExtensionAttribute ,以后有时间会专门介绍这些属性的使用。 7. 构建 RootWorkItem (rootWorkItem.BuildUp()) RootWorkItem 通过 builder.BuildUp(locator, type, temporaryID, this, policies) 方法使用 ObjectBuilder 进行构建,前面介绍过 CabApplication 在 CreaterBuilder() 中注册了构建策略 RootWorkItemInitializationStrategy: builder.Strategies.Add(new RootWorkItemInitializationStrategy(this.OnRootWorkItemInitialized), BuilderStage.Initialization),该语句表明在创建 RootWorkItem 时 ObjectBuilder 会执行该策略并调用本类(CabApplication 或者重载的子类)的 OnRootWorkItemInitialized 方法:protected virtual void OnRootWorkItemInitialized()。CabShellApplication (CabApplication 的子类) 重写了该方法: 1 protected sealed override void OnRootWorkItemInitialized() 其中 RootWorkItem.Items.AddNew<TShell>() 语句创建了新的 TShell 主窗体对象(通过 ObjectBuilder 构建)并加入到 RootWorkItem 的 Items 集合中。 8. 通过配置加载模块(LoadModules()) RootWorkItem 构建完成后,SCSF 会根据 IModuleEnumerator 服务来枚举可以加载的模块(确定需要加载哪些模块),SCSF 中提供了两个 IModuleEnumerator 服务:FileCatalogModuleEnumerator(在配置文件中指明要加载哪些模块,默认的配置文件是 ProfileCatalog.xml )和 ReflectionModuleEnumerator(利用反射和 ModuleAttribute 来确定需要加载哪些模块)。同时 SCSF 的Package Guidance 为我们提供了一个 XmlStreamDependentModuleEnumerator ,用于从 Xml Stream 中确定需要加载哪些模块,这个可以用在通过 Web Service 将服务器上的配置发送到客户端的情况。 典型的配置文件示例 ProfileCatalog.xml :<SolutionProfile xmlns="http://schemas.microsoft.com/pag/cab-profile/2.0%22>
9. 完成对 RootWorkItem 的创建(rootWorkItem.FinishInitialization()) 同时 WorkItem 的 public void InitializeWorkItem() 是一个注入方法,有[InjectionMethod]属性标记,该方法执行时除了执行上面 rootworkItem中的初始化过程外,还会调用 InitializeServices 方法,该方法在 CabApplication 中是空方法,CabApplication 子类可以重写 protected virtual void InitializeServices() 方法,用于在 WorkItem 初始化时加载其他需要的服务。 10. 执行 rootWorkItem 的 run 方法 rootWorkItem.Run(); WorkItem 的 Run 方法直接调用 protected virtual void OnRunStarted() 方法:1 public void Run() OnRunStarted 方法触发 RunStarted 事件(public event EventHandler RunStarted) 1 protected virtual void OnRunStarted() 因此我们可以通过注册 RunStarted 事件或者在 WorkItem 子类中重写 OnRunStarted() 虚方法以便在 SCSF 启动过程中执行自己的操作,这是 SCSF 的又一扩展点。 11. 启动应用 Start() CabApplication 中的 Start 是一个抽象方法:protected abstract void Start(); 子类 FormShellApplication(public abstract class FormShellApplication<TWorkItem, TShell> : WindowsFormsApplication<TWorkItem, TShell>) 重写了Start : 1 protected override void Start() 25 June VSTS作Unit Test的总结Visual Studio 2005 集成了单元测试框架(Team Test),正好公司规范开发流程,需要提交Unit Test Log,以前都是用Nunit作单元测试,现在既然VS有了自己的UT类,就打算尝试一下,感觉和Nunit用起来差不多,没怎么深入,只是简单总结一下。 在弹出的“创建单元测试”对话框中的“输出项目”下拉框中选择“创建新的Visual C# 测试项目”,单击“确定”按钮,并在“新建测试项目”对话框中输入测试项目的名称(如:MySchoolTest),单击“创建”按钮后,就看见在原有的解决方案中生成了一个新的项目“MySchoolTest”。 AuthoringTest.txt 提供创建测试的说明,包括向项目增加其他测试的说明; 单元测试中,几个变量的简单介绍: Assert.AreEqual() 测试指定的值是否相等,如果相等,则测试通过;
Assert.Inconclusive() 表示一个未验证的测试;
Assert.IsTrue() 测试指定的条件是否为True,如果为True,则测试通过;
Assert.IsFalse() 测试指定的条件是否为False,如果为False,则测试通过;
Assert.IsNull() 测试指定的对象是否为空引用,如果为空,则测试通过;
Assert.IsNotNull() 测试指定的对象是否为非空,如果不为空,则测试通过;
我们通过对示例1 添加测试所需的初始值,并对断言进行简单的修改后,便得到一个正式的单元测试。
如示例2:
这样,便得到了一个正式的单元测试。用断言Assert.AreEqual()比较expected、actual是否相等。 如示例3:
测试项目的运行方式有两种: 代码覆盖是单元测试的一个关键指标。 注意: VSTS 在生成单元测试框架时,默认没有启用“代码覆盖”功能。 11 June Format时间显示最近改一个VB的老项目,大量的用到了不同格式的时间显示,比较讨厌,虽然没什么技术含量,
但要用的时候还真是急死人,所以顺便把format的东西总结一下:
Format(Now, "...")
年: yy/yyyy 两位年/四位年 输出: 08/2008 月: m/mm 一位月两位月 输出:1/01 (1-12)/(01-12) mmm/mmmm 英文简写月/全称月 输出: Jan/January ooo ¦oooo 中文显示月 输出为:一月 日: dd/d ¦y 一位日/两位日 输出: 8/08 (1-31)/(01-31) 时: h/hh 一位时/两位时 输出: 9/09 (0-23)/(00-23) 分: n/nn 一位分/两位分 输出: 30/30 (0-59)/(00-59) 秒: s/ss 一位秒/两位秒 输出: 40/40 (0-59)/(00-59) 星期: aaa ¦aaaa 中文星期 输出:星期六 ddd/dddd 英文星期简称/全称 输出: Sat/Satday w ¦ww 用数字表示星期 输出:7 表示星期六,星期日为1 综合显示: c 以短时间格式显示时间 输出:2008-1-8 9:30:40 ddddd 以短时间格式显示年月日 输出:2008-1-8 dddddd 中文显示年月日 输出:2008年1月8日 ttttt 以长时间格式显示时间 输出:9:30:40 ampm 显示上午/下午 Format(Now,'ampm') 输出为:上午 差不多就这些了吧! 25 September C#异步编程2基于事件的异步模式是比 IAsyncResult 模式更高级的一种异步编程模式,也被用在更多的场合。对于相对简单的应用程序可以直接用 .Net 2.0 新增的 BackgroundWorker 组件来很方便的实现,对于更复杂的异步应用程序则需要自己实现一个符合基于事件的异步模式的类。这两者对我都是新东西,先从简单的入手,下一篇里我再去尝试复杂类模型的实现
模式概述 支持基于事件的异步模式的类会有若干个 MethodNameAsync 方法表示开始异步操作,并有对应的 MethodNameCompleted 事件。类里面还可能会有 CancelAsync或 MethodNameAsyncCancel 方法用于取消异步操作,并可以有 ProgressChanged 或 MethodNameProgressChanged事件来跟踪执行进度。下面分别作一下解释 MethodNameAsync 方法可以有两个重载:单调用和多调用,多调用有一个额外的状态对象参数 userState。userState 参数用来区分各次异步操作,使得我们可以多次调用多调用形式的方法而不需要等待任何异步操作的完成(在学习 IAsyncResult 模式时我把状态对象仅仅当成传给回调方法的一个条件来用,可能在使用模式时这么做并没有什么关系,但在实现模式时不把状态对象用作异常调用的唯一标识而另作他用就值得商榷了)。而单调用形式的方法如果在前一个调用尚未完成时调用将会抛出 InvalidOperationException 异常 如果有多个异步方法,则应使用 CancelAsync 方法来取消挂起的操作,并可使用 userState 来取消指定的挂起任务。如果只有一个异步方法则可以使用 MethodNameAsyncCancel 方法 另外 MSDN 上说:一次只支持一个挂起的操作的方法(如 Method1Async(string param))是不可取消的。这句话我还没有理解,不可能说是单调用的异步方法就不能取消吧,BackgroundWorker 上都是这样做的 先不管了,接着看ProgressChanged 事件。它有一个 ProgressChangedEventArgs 参数,事件处理程序通过检查该参数的 ProgressPercentage属性来获取任务完成的百分比。如果有多个异步操作挂起,也可以通过检查参数的UserState 属性来分辨操作。如果需要用 ProgressChanged 事件来报告增量结果,则可以把结果保存在派生自 ProgressChangedEventArgs 的类中,并在事件处理程序中使用 BackgroundWorker BackgroundWorker 很好的符合了事件异步操作模式。它有两个重载版本的 RunWorkerAsync 方法(均为单调用形式)和 RunWorkerCompleted 事件,并有 CancelAsync 方法以及 ProcessChanged 事件。不同的是 BackgroundWorker增加了 DoWork 事件,在 RunWorkerAsync 方法调用时发生,以达到将实际执行的开始方法与 BackgroundWorker 分离的目的。还需要提一下的是 WorkerReportsProcess 属性和ReportProcess 方法,前者指示能否报告进度更新,后者引发 ProcessChanged 事件,它们会在接下来的 Demo 里用到 尝试 因为平时经常要处理几十兆的文本文件,这个 Demo 就做一个读取文件并显示进度的控制台程序。先看类名和字段
构造函数接收文件路径为参数,设置文件路径并初始化 BackgroundWorker
接下来看这三个事件的处理程序。每一个事件都有各自的 EventArgs 参数类型,都很简单就不多说了 第一个 BackgroundWorker_DoWork 方法写得我有些郁闷。我在方法里取文件长度,先是直接取 StreamReader.BaseStream.Length 或 FileInfo.Length ,结果却导致很多文件读不到 100% 就结束了,不得已改成先把整个文件读一次得到字符串的长度。这样的方法当然性能不好了,主要是因为自己对 IO 一直就不够清楚,等下一个主题重新认识下 IO 再回头过来改吧。也望有经验的朋友赐教,感激不尽 BackgroundWorker_RunWorkerCompleted 方法,输出结果。这里要注意如果 RunWorkerCompletedEventArgs 参数的 Error 属性不为空则读取其他属性会产生异常,然后如果 Cancelled 属性为 true 则读取 Result 属性也会产生异常,因此必须依次判断各属性的值 /**//// <summary> 向外提供一个入口方法
最后是 Main 方法,比昨天有了小小的改变,用 Console.ReadLine 代替了 Thread.Sleep 来达到阻止主线程退出的目的
其他 回顾一下我用委托实现 IAsyncResult 模式的 Demo ,与用 BackgroundWorker 实现的基于事件的异步模式很相似吧。而且应用程序可以通过委托的 BeginInvoke 和 EndInvoke 方法来异步执行现有的同步方法而不需要作额外的修改,BackgroundWorker 也差不多是一样。我把这两者看成实现对应异步操作模式的范本,在性能要求不是很高的一些异步操作场合,用好委托和 BackgroundWorker 就可以简单有效的完成开发了 C#异步编程1。NET Framework 为异步操作提供了两种设计模式:使用 IAsyncResult 对象的异步操作与使用事件的异步操作。先来学习前者
概述 IAsyncResult 异步设计模式通过名为 BeginOperationName 和 EndOperationName 的两个方法来实现原同步方法的异步调用,如 FileStream 类提供了 BeginRead 和 EndRead 方法来从文件异步读取字节,它们是 Read 方法的异步版本 Begin 方法包含同步方法签名中的任何参数,此外还包含另外两个参数:一个AsyncCallback 委托和一个用户定义的状态对象。委托用来调用回调方法,状态对象是用来向回调方法传递状态信息。该方法返回一个实现 IAsyncResult 接口的对象 End 方法用于结束异步操作并返回结果,因此包含同步方法签名中的 ref 和 out 参数,返回值类型也与同步方法相同。该方法还包括一个 IAsyncResult 参数,用于获取异步操作是否完成的信息,当然在使用时就必须传入对应的 Begin 方法返回的对象实例 开始异步操作后如果要阻止应用程序,可以直接调用 End 方法,这会阻止应用程序直到异步操作完成后再继续执行。也可以使用 IAsyncResult 的 AsyncWaitHandle 属性,调用其中的WaitOne等方法来阻塞线程。这两种方法的区别不大,只是前者必须一直等待而后者可以设置等待超时 如果不阻止应用程序,则可以通过轮循 IAsyncResult 的 IsCompleted 状态来判断操作是否完成,或使用 AsyncCallback 委托来结束异步操作。AsyncCallback 委托包含一个 IAsyncResult 的签名,回调方法内部再调用 End 方法来获取操作执行结果 尝试 先来熟悉一下今天的主角,IAsyncResult 接口
我用一个 AsyncDemo 类作为异步方法的提供者,后面的程序都会调用它。内部很简单,构造函数接收一个字符串作为 name ,Run 方法输出 "My name is " + name ,而异步方法直接用委托的 BeginInvoke 和 EndInvoke 方法实现
首先是 Begin 之后直接调用 End 方法,当然中间也可以做其他的操作
也可以用 IAsyncResult 的 AsyncWaitHandle 属性,我在这里设置为1秒超时
不中断的轮循,每次循环输出一个 "."
最后是使用回调方法并加上状态对象,状态对象被作为 IAsyncResult 参数的 AsyncState 属性被传给回调方法。回调方法执行前不能让主线程退出,我这里只是简单的让其休眠了1秒。另一个与之前不同的地方是 AsyncDemo 对象被定义成了类的静态字段,以便回调方法使用
其他 对于一个已经实现了 BeginOperationName 和 EndOperationName 方法的对象,我们可以直接用上述方式调用,但对于只有同步方法的对象,我们要对其进行异步调用也不需要增加对应的异步方法,而只需定义一个委托并使用其 BeginInvoke 和 EndInvoke 方法就可以了 21 September 窗口弹出形式项目里多处用到弹出窗口,现在小小地总结了一下三种open的形式,顺便把写title的方法也写下来: window.open('http://singlepine.cnblogs.com','title','height=100,width=200,top=0,left=0,toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,status=no') window.showModalDialog('http://singlepine.cnblogs.com','title','scrollbars=yes;resizable=no;help=no;status=no;dialogTop=25; window.showModelessDialog('http://singlepine.cnblogs.com','title','scrollbars=yes;resizable=no;help=no;status=no;dialogTop=25; 'http://singlepine.cnblogs.com' 弹出窗口的目标文件名 <script language="JavaScript1.2">
13 September C#的四个基本技巧1.如果可能尽量使用接口来编程 .NET框架包括类和接口,在编写程序的时候,你可能知道正在用.NET的哪个类。然而,在这种情况下如果你用.NET支持的接口而不是它的类来编程时,代码会变得更加稳定、可用性会更高。请分析下面的代码: private void LoadList (object [] items, ListBox l) 这个函数从一个可为任何对象的数组中加载ListBox,这段代码被限定为只能使用数组。假想过些时候你发现那些对象存在数据库中,或别的集合中。那么你需要修改程序来使用不同的集合类型。如果你用ICollection接口来写那段程序,你就不用修改那段程序了,对于任何实现ICollection接口的类型它都能很好的工作: private void LoadList (ICollection items,ListBox l) ICollection被数组和所有System.Collection中的集合实现。此外,多维数组也支持ICollection接口。如果那还不够的话,数据库.NET类同样支持ICollection接口。用接口写的这个函数不用需改就可以才许多中情况下使用。 2. 使用属性代替原始数据 因为属性已经成为语言本身的元素,所以声明数据元素时它的作用域等级没有必要大于private。因为代码本身会把属性看成数据元素,你并没有失去使用简单数据类型的便利性 。相反它会使你的代码更加灵活功能更加强大。属性使你的数据元素封装性更好。属性可以让你使用lazy evaluation来返回数据。lazy evaluation的意思是当用户请求时才计算它的值,而不是一直保留着它。 最后,属性可以是virtual也可以是abstract。你也可以在接口中定义属性。 这里还有维护方面的因素应当注意:尽管操作两者的方法是一样的,但是你把一个数据元素变成属性,那么原先客户端的程序便不能访问服务端的新版本程序了。实际上对于在Web service中你想实现序列化的值你可以把它们变成属性来使用: private int TheMonth = 0; [XmlAttribute ("Month")] 简单通过属性就可以使你的所有数据元素私有化。 3. 在Producer/Consumer 的Idiom中使用Delegate 当你生成一个实现producer idiom类的时候,使用deletate来通知consumer。这种方法相对于用接口更加灵活。Delegate是多点传送的,所以不用加额外的代码你就何以支持多用户。相对于用接口这样做可使类之间的耦合性降低。 下面的类处理键盘输入并把它传给所有的registered listeners: public class KeyboardProcessor public OnGetLine OnGetLineCallback { public void Run (){ 任何数目的listeners都可注册到producer,它们所要做的只是提供一个特定的函数:deletate。 4. 注意初始化顺序 C#中对于一些变量声明加入了initializer的概念。它们在构造函数之前被执行,实际上变量在基类的构造函数执行前之前被初始化。 所以,在初始化变量的时候不要用基类中的数据,因为它们还没有被构造。 10 September 采用C#泛型实现数据库之间的切换 泛型变量太牛B了,哈哈 public class DataProvider<ConnType, CmdType> where ConnType : IDbConnection, new() where CmdType : IDbCommand,new () { ![]() } 提供数据库表的Insert,Update,Select,Delete操作。 因为 IDbCommand能够由 IDbConnection 获取,其实只需要有一个泛型参数ConnType 的,不过这样以来,具体的代码改动比较大,偶就采用了两个泛型参数。 (2)ConnectionPool 泛型类 public class ConnectionPool<T> where T : IDbConnection,new () { ![]() } 最开始没在MySql 5.0 中找到对数据库连接池的支持,于是自己写了一个简单的数据库连接池。 下面,就是采用 C# 的NB语法――using: (1)如果使用 MySql 数据库: using DataProvider = DataProvider<MySql.Data.MySqlClient.MySqlConnection, MySql.Data.MySqlClient.MySqlCommand>; (2)如果使用 SQLServer 数据库: using DataProvider = DataProvider<System.Data.SqlClient.SqlConnection, System.Data.SqlClient.SqlCommand>; 这样一来,其它地方的代码一句都不用动了。 懒惰是程序员的美德。我的一台电脑上装的是 SQLServer 2000数据库,一台电脑上装的是 MySql数据库,我经常一会在这台电脑上干活,一会在另外一台电脑上干活,两个电脑上的代码应该一致啊。因此,采用预编译指令: #if MSSQLSERVER using System.Data.SqlClient; using DataProvider = DataProvider<System.Data.SqlClient.SqlConnection, System.Data.SqlClient.SqlCommand>; #elif MYSQL using MySql.Data; using DataProvider = DataProvider<MySql.Data.MySqlClient.MySqlConnection, MySql.Data.MySqlClient.MySqlCommand>; #endif 这样在编译时指定条件编译符号 " MYSQL " 得到的就是MySql版本的代码,指定条件编译符号" MSSQLSERVER ",得到的就是 SQLServer 版本的代码。 其它: (1)理论上来说,采用反射得到的解决方案更完美,不过那样工期会更长,没必要啦啦啦啦啦啦。。。。。。。。。。。。。 (2)数据库设计,偶采用的是免费软件 Toad Data Modeler 免费版,里面提供了数据库切换功能,切换过去,稍微改动改动。 (3)这次开发工具采用的全是开源软件或者免费软件,都是超级好用的东东,感觉开发速度并不比庞大的收费软件慢。用到的工具如下: IDE:VS 2005 Express (C++版,C#版,Web开发版),.Net的aspnet命令行编译工具 版本管理:SVN,小乌龟SVN Client Shell:Windows Power Shell(这玩意既然出来了,就要充分利用) UML建模:StarUML(功能强大的开源UML,比偶以前用过的JUDE,ArgoUML强大很多,支持C#) 数据库建模:Toad Data Modeler 免费版 数据库管理工具:EMS SQL Manager 2005 lite for MySQL 06 September USING NOCOUNTSET NOCOUNT 使返回的结果中不包含有关受 Transact-SQL 语句影响的行数的信息。 语法 SET NOCOUNT { ON | OFF } 注释 当 SET NOCOUNT 为 ON 时,不返回计数(表示受 Transact-SQL 语句影响的行数)。当 SET NOCOUNT 为 OFF 时,返回计数。 即使当 SET NOCOUNT 为 ON 时,也更新 @@ROWCOUNT 函数。 当 SET NOCOUNT 为 ON 时,将不给客户端发送存储过程中的每个语句的 DONE_IN_PROC 信息。当使用 Microsoft® SQL Server™ 提供的实用工具执行查询时,在 Transact-SQL 语句(如 SELECT、INSERT、UPDATE 和 DELETE)结束时将不会在查询结果中显示"nn rows affected"。 如果存储过程中包含的一些语句并不返回许多实际的数据,则该设置由于大量减少了网络流量,因此可显著提高性能。 SET NOCOUNT 设置是在执行或运行时设置,而不是在分析时设置。 权限 SET NOCOUNT 权限默认授予所有用户。 示例 下例在 osql 实用工具或 SQL Server 查询分析器中执行时,可防止显示有关受影响的行数的信息。 USE pubs GO -- Display the count message. SELECT au_lname FROM authors GO USE pubs GO -- SET NOCOUNT to ON and no longer display the count message. SET NOCOUNT ON GO SELECT au_lname FROM authors GO -- Reset SET NOCOUNT to OFF. SET NOCOUNT OFF GO 04 September c#泛型变量C#泛型类与结构
泛型是什么
一种类型占位符,或称之为类型参数。我们知道在一个方法中,一个变量的值可以作为参数,但其实这个变量的类型本身也可以作为参数。泛型允许我们在调用的时候再指定这个类型参数是什么。在.net中,泛型能够给我们带来的两个明显好处是——类型安全和减少装箱、拆箱。 03 September 三个关键字-ref,out ,params的区别C#中有三个关键字-ref,out ,params,虽然本人不喜欢这三个关键字,因为它们疑似破坏面向对象特性。但是既然ms把其融入在c#体系中,那么我们就来认识一下参数修饰符ref,out ,params吧,还有它们的区别。
NO.1 params
一个可以让方法(函数)的拥有可变参数的关键字。
原则:在方法声明中的 params 关键字之后不允许任何其他参数,并且在方法声明中只允许一个 params 关键字。 示例(拷贝到vs2005中即可用,下面不再说明) public partial class Form1 : Form { public static void UseParams(params int[] list) { string temp = ""; for (int i = 0; i < list.Length; i++) temp = temp +" " +list[i].ToString(); MessageBox.Show(temp); } public static void UseParams2(params object[] list) { string temp = ""; for (int i = 0; i < list.Length; i++) temp = temp + " " + list[i].ToString(); MessageBox.Show(temp); } public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { UseParams(1, 2, 3);//看参数是3个 UseParams(1, 2); //看参数是2个,可变吧 UseParams2(1, 'a', "test"); int[] myarray = new int[3] { 10, 11, 12 }; UseParams(myarray); //看也可以是容器类,可变吧:) } }NO.2 out
这是一个引用传递L。 原则一:当一个方法(函数)在使用out作为参数时,在方法中(函数)对out参数所做的任何更改都将反映在该变量中。
原则二:当希望方法返回多个值时,声明 out 方法非常有用。使用 out 参数的方法仍然可以返回一个值。一个方法可以有一个以上的 out 参数。 原则三:若要使用 out 参数,必须将参数作为 out 参数显式传递到方法。out 参数的值不会传递到 out 参数。 原则四:不必初始化作为 out 参数传递的变量,因为out 参数在进入方法(函数)时后清空自己,使自己变成一个干净的参数,也因为这个原因必须在方法返回之前为 out 参数赋值(只有地址没有值的参数是不能被.net接受的)。 原则五:属性不是变量,不能作为 out 参数传递。 原则六:如果两个方法的声明仅在 out 的使用方面不同,则会发生重载。不过,无法定义仅在 ref 和 out 方面不同的重载。例如,以下重载声明是有效的: class MyClass { public void MyMethod(int i) {i = 10;} public void MyMethod(out int i) {i = 10;} } 而以下重载声明是无效的: class MyClass { public void MyMethod(out int i) {i = 10;} public void MyMethod(ref int i) {i = 10;} } 有关传递数组的信息,请参见使用 ref 和 out 传递数组。 示例附后 NO.2 ref ref仅仅是一个地址!!! 原则一:当一个方法(函数)在使用ref作为参数时,在方法中(函数)对ref参数所做的任何更改都将反映在该变量中。 原则二:调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。
原则三:若要使用 ref 参数,必须将参数作为 ref 参数显式传递到方法。ref 参数的值可以被传递到 ref 参数。 原则四:ref参数传递的变量必须初始化,因为ref参数在进入方法(函数)时后还是它自己,它这个地址指向的还是原来的值,也因为这个原因ref参数也可以在使用它的方法内部不操作。 原则六:如果两种方法的声明仅在它们对 ref 的使用方面不同,则将出现重载。但是,无法定义仅在 ref 和 out 方面不同的重载。例如,以下重载声明是有效的: class MyClass { public void MyMethod(int i) {i = 10;} public void MyMethod(ref int i) {i = 10;} } 但以下重载声明是无效的: class MyClass { public void MyMethod(out int i) {i = 10;} public void MyMethod(ref int i) {i = 10;} } 有关传递数组的信息,请参见使用 ref 和 out 传递数组。 示例 public static string TestOut(out string i) { i = "out b"; return "return value"; } public static void TestRef(ref string i) { //改变参数 i = "ref b"; } public static void TestNoRef(string refi) { // 不用改变任何东西,这个太明显了 refi = "on c"; } public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { string outi;//不需要初始化 MessageBox.Show(TestOut(out outi));//返回值 //输出"out b";
MessageBox.Show(outi);//调用后的out参数 //输出"return value";
string refi = "a"; // 必须初始化 TestRef(ref refi); // 调用参数 MessageBox.Show(refi); //输出"ref b";
TestNoRef(refi);//不使用ref MessageBox.Show(refi); //输出"ref b"; }18 April ASP.NET中异步调用WebServiceWebService方法是不需要作任何修改的,只是在调用时采用异步的方式,这样在循环中,速度会显得快一点。 原来的方式: HotelMagWeb.com.china_sms.www.MainServices sms=new HotelMagWeb.com.china_sms.www.MainServices(); //实例化一个对象 string sSendRes=sms.massSend(); //直接调用方法现在可以改为异步调用方式: HotelMagWeb.com.china_sms.www.MainServices sms=new HotelMagWeb.com.china_sms.www.MainServices(); //同样的实例化对象 sms.BeginmassSend(new AsyncCallback(MySMS),sms); //使用带Begin开头那个方法(这个方法就不用我说了吧),要传一个方法进去(我这里叫MySMS) //这个方法中实现真正的调用及结果! public static void MySMS(IAsyncResult iar){ HotelMagWeb.com.china_sms.www.MainServices sms=(HotelMagWeb.com.china_sms.www.MainServices)iar.AsyncState; string sSendRes=sms.EndmassSend(iar);//使用带end开头那个方法,呵呵 } 好了,这样就可以了! 24 January MCSD考完,证书已收到!一张纸,加一张卡片。靠,真不知道有什么用,如果不是要移民,鬼才考这玩意。感觉一点意义都没有,看看书,考一下就过了。一点悬念都没有。都找不到考试的感觉了。郁闷!!! 15 January socket套接字Microsoft.Net Framework为应用程序访问Internet提供了分层的、可扩展的以及受管辖的网络服务,其名字空间System.Net和System.Net.Sockets包含丰富的类可以开发多种网络应用程序。.Net类采用的分层结构允许应用程序在不同的控制级别上访问网络,开发人员可以根据需要选择针对不同的级别编制程序,这些级别几乎囊括了Internet的所有需要--从socket套接字到普通的请求/响应,更重要的是,这种分层是可以扩展的,能够适应Internet不断扩展的需要。
抛开ISO/OSI模型的7层构架,单从TCP/IP模型上的逻辑层面上看,.Net类可以视为包含3个层次:请求/响应层、应用协议层、传输层。WebReqeust和WebResponse 代表了请求/响应层,支持Http、Tcp和Udp的类组成了应用协议层,而Socket类处于传输层。 传输层位于这个结构的最底层,当其上面的应用协议层和请求/响应层不能满足应用程序的特殊需要时,就需要使用这一层进行Socket套接字编程。 而在.Net中,System.Net.Sockets 命名空间为需要严密控制网络访问的开发人员提供了 Windows Sockets (Winsock) 接口的托管实现。System.Net 命名空间中的所有其他网络访问类都建立在该套接字Socket实现之上,如TCPClient、TCPListener 和 UDPClient 类封装有关创建到 Internet 的 TCP 和 UDP 连接的详细信息;NetworkStream类则提供用于网络访问的基础数据流等,常见的许多Internet服务都可以见到Socket的踪影,如Telnet、Http、Email、Echo等,这些服务尽管通讯协议Protocol的定义不同,但是其基础的传输都是采用的Socket。 其实,Socket可以象流Stream一样被视为一个数据通道,这个通道架设在应用程序端(客户端)和远程服务器端之间,而后,数据的读取(接收)和写入(发送)均针对这个通道来进行。 可见,在应用程序端或者服务器端创建了Socket对象之后,就可以使用Send/SentTo方法将数据发送到连接的Socket,或者使用Receive/ReceiveFrom方法接收来自连接Socket的数据; 针对Socket编程,.NET 框架的 Socket 类是 Winsock32 API 提供的套接字服务的托管代码版本。其中为实现网络编程提供了大量的方法,大多数情况下,Socket 类方法只是将数据封送到它们的本机 Win32 副本中并处理任何必要的安全检查。如果你熟悉Winsock API函数,那么用Socket类编写网络程序会非常容易,当然,如果你不曾接触过,也不会太困难,跟随下面的解说,你会发觉使用Socket类开发windows 网络应用程序原来有规可寻,它们在大多数情况下遵循大致相同的步骤。 在使用之前,你需要首先创建Socket对象的实例,这可以通过Socket类的构造方法来实现: public Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolType); 其中,addressFamily 参数指定 Socket 使用的寻址方案,socketType 参数指定 Socket 的类型,protocolType 参数指定 Socket 使用的协议。 下面的示例语句创建一个 Socket,它可用于在基于 TCP/IP 的网络(如 Internet)上通讯。 Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 若要使用 UDP 而不是 TCP,需要更改协议类型,如下面的示例所示: Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 一旦创建 Socket,在客户端,你将可以通过Connect方法连接到指定的服务器,并通过Send/SendTo方法向远程服务器发送数据,而后可以通过Receive/ReceiveFrom从服务端接收数据;而在服务器端,你需要使用Bind方法绑定所指定的接口使Socket与一个本地终结点相联,并通过Listen方法侦听该接口上的请求,当侦听到用户端的连接时,调用Accept完成连接的操作,创建新的Socket以处理传入的连接请求。使用完 Socket 后,记住使用 Shutdown 方法禁用 Socket,并使用 Close 方法关闭 Socket。其间用到的方法/函数有: Socket.Connect方法:建立到远程设备的连接 public void Connect(EndPoint remoteEP)(有重载方法) Socket.Send 方法:从数据中的指示位置开始将数据发送到连接的 Socket。 public int Send(byte[], int, SocketFlags);(有重载方法) Socket.SendTo 方法 将数据发送到特定终结点。 public int SendTo(byte[], EndPoint);(有重载方法) Socket.Receive方法:将数据从连接的 Socket 接收到接收缓冲区的特定位置。 public int Receive(byte[],int,SocketFlags); Socket.ReceiveFrom方法:接收数据缓冲区中特定位置的数据并存储终结点。 public int ReceiveFrom(byte[], int, SocketFlags, ref EndPoint); Socket.Bind 方法:使 Socket 与一个本地终结点相关联: public void Bind( EndPoint localEP ); Socket.Listen方法:将 Socket 置于侦听状态。 public void Listen( int backlog ); Socket.Accept方法:创建新的 Socket 以处理传入的连接请求。 public Socket Accept(); Socket.Shutdown方法:禁用某 Socket 上的发送和接收 public void Shutdown( SocketShutdown how ); Socket.Close方法:强制 Socket 连接关闭 public void Close(); 可以看出,以上许多方法包含EndPoint类型的参数,在Internet中,TCP/IP 使用一个网络地址和一个服务端口号来唯一标识设备。网络地址标识网络上的特定设备;端口号标识要连接到的该设备上的特定服务。网络地址和服务端口的组合称为终结点,在 .NET 框架中正是由 EndPoint 类表示这个终结点,它提供表示网络资源或服务的抽象,用以标志网络地址等信息。.Net同时也为每个受支持的地址族定义了 EndPoint 的子代;对于 IP 地址族,该类为 IPEndPoint。IPEndPoint 类包含应用程序连接到主机上的服务所需的主机和端口信息,通过组合服务的主机IP地址和端口号,IPEndPoint 类形成到服务的连接点。 用到IPEndPoint类的时候就不可避免地涉及到计算机IP地址,.Net中有两种类可以得到IP地址实例: IPAddress类:IPAddress 类包含计算机在 IP 网络上的地址。其Parse方法可将 IP 地址字符串转换为 IPAddress 实例。下面的语句创建一个 IPAddress 实例: IPAddress myIP = IPAddress.Parse("192.168.1.2"); Dns 类:向使用 TCP/IP Internet 服务的应用程序提供域名服务。其Resolve 方法查询 DNS 服务器以将用户友好的域名(如"host.contoso.com")映射到数字形式的 Internet 地址(如 192.168.1.1)。Resolve方法 返回一个 IPHostEnty 实例,该实例包含所请求名称的地址和别名的列表。大多数情况下,可以使用 AddressList 数组中返回的第一个地址。下面的代码获取一个 IPAddress 实例,该实例包含服务器 host.contoso.com 的 IP 地址。 IPHostEntry ipHostInfo = Dns.Resolve("host.contoso.com"); IPAddress ipAddress = ipHostInfo.AddressList[0]; 你也可以使用GetHostName方法得到IPHostEntry实例: IPHosntEntry hostInfo=Dns.GetHostByName("host.contoso.com") 在使用以上方法时,你将可能需要处理以下几种异常: SocketException异常:访问Socket时操作系统发生错误引发 ArgumentNullException异常:参数为空引用引发 ObjectDisposedException异常:Socket已经关闭引发 在掌握上面得知识后,下面的代码将该服务器主机( host.contoso.com的 IP 地址与端口号组合,以便为连接创建远程终结点: IPEndPoint ipe = new IPEndPoint(ipAddress,11000); 确定了远程设备的地址并选择了用于连接的端口后,应用程序可以尝试建立与远程设备的连接。下面的示例使用现有的 IPEndPoint 实例与远程设备连接,并捕获可能引发的异常: try { s.Connect(ipe);//尝试连接 } //处理参数为空引用异常 catch(ArgumentNullException ae) { Console.WriteLine("ArgumentNullException : {0}", ae.ToString()); } //处理操作系统异常 catch(SocketException se) { Console.WriteLine("SocketException : {0}", se.ToString()); } catch(Exception e) { Console.WriteLine("Unexpected exception : {0}", e.ToString()); } 需要知道的是:Socket 类支持两种基本模式:同步和异步。其区别在于:在同步模式中,对执行网络操作的函数(如 Send 和 Receive)的调用一直等到操作完成后才将控制返回给调用程序。在异步模式中,这些调用立即返回。 另外,很多时候,Socket编程视情况不同需要在客户端和服务器端分别予以实现,在客户端编制应用程序向服务端指定端口发送请求,同时编制服务端应用程序处理该请求,这个过程在上面的阐述中已经提及;当然,并非所有的Socket编程都需要你严格编写这两端程序;视应用情况不同,你可以在客户端构造出请求字符串,服务器相应端口捕获这个请求,交由其公用服务程序进行处理。以下事例语句中的字符串就向远程主机提出页面请求: string Get = "GET / HTTP/1.1\r\nHost: " + server + "\r\nConnection: Close\r\n\r\n"; 远程主机指定端口接受到这一请求后,就可利用其公用服务程序进行处理而不需要另行编制服务器端应用程序。 综合运用以上阐述的使用Visual C#进行Socket网络程序开发的知识,下面的程序段完整地实现了Web页面下载功能。用户只需在窗体上输入远程主机名(Dns 主机名或以点分隔的四部分表示法格式的 IP 地址)和预保存的本地文件名,并利用专门提供Http服务的80端口,就可以获取远程主机页面并保存在本地机指定文件中。如果保存格式是.htm格式,你就可以在Internet浏览器中打开该页面。适当添加代码,你甚至可以实现一个简单的浏览器程序。 实现此功能的主要源代码如下: //"开始"按钮事件 private void button1_Click(object sender, System.EventArgs e) { //取得预保存的文件名 string fileName=textBox3.Text.Trim(); //远程主机 string hostName=textBox1.Text.Trim(); //端口 int port=Int32.Parse(textBox2.Text.Trim()); //得到主机信息 IPHostEntry ipInfo=Dns.GetHostByName(hostName); //取得IPAddress[] IPAddress[] ipAddr=ipInfo.AddressList; //得到ip IPAddress ip=ipAddr[0]; //组合出远程终结点 IPEndPoint hostEP=new IPEndPoint(ip,port); //创建Socket 实例 Socket socket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); try { //尝试连接 socket.Connect(hostEP); } catch(Exception se) { MessageBox.Show("连接错误"+se.Message,"提示信息 ,MessageBoxButtons.RetryCancel,MessageBoxIcon.Information); } //发送给远程主机的请求内容串 string sendStr="GET / HTTP/1.1\r\nHost: " + hostName + "\r\nConnection: Close\r\n\r\n"; //创建bytes字节数组以转换发送串 byte[] bytesSendStr=new byte[1024]; //将发送内容字符串转换成字节byte数组 bytesSendStr=Encoding.ASCII.GetBytes(sendStr); try { //向主机发送请求 socket.Send(bytesSendStr,bytesSendStr.Length,0); } catch(Exception ce) { MessageBox.Show("发送错误:"+ce.Message,"提示信息 ,MessageBoxButtons.RetryCancel,MessageBoxIcon.Information); } //声明接收返回内容的字符串 string recvStr=""; //声明字节数组,一次接收数据的长度为1024字节 byte[] recvBytes=new byte[1024]; //返回实际接收内容的字节数 int bytes=0; //循环读取,直到接收完所有数据 while(true) { bytes=socket.Receive(recvBytes,recvBytes.Length,0); //读取完成后退出循环 if(bytes〈=0) break; //将读取的字节数转换为字符串 recvStr+=Encoding.ASCII.GetString(recvBytes,0,bytes); } //将所读取的字符串转换为字节数组 byte[] content=Encoding.ASCII.GetBytes(recvStr); try { //创建文件流对象实例 FileStream fs=new FileStream(fileName,FileMode.OpenOrCreate,FileAccess.ReadWrite); //写入文件 fs.Write(content,0,content.Length); } catch(Exception fe) { MessageBox.Show("文件创建/写入错误:"+fe.Message,"提示信息",MessageBoxButtons.RetryCancel,MessageBoxIcon.Information); } //禁用Socket socket.Shutdown(SocketShutdown.Both); //关闭Socket socket.Close(); } } 程序在WindowsXP中文版、.Net Frameworkd 中文正式版、Visual Studio.Net中文正式版下调试通过 关Visual C#.Net网络程序开发-Tcp篇 前一篇《Visual C#.Net网络程序开发-Socket篇》中说到:支持Http、Tcp和Udp的类组成了TCP/IP三层模型(请求响应层、应用协议层、传输层)的中间层-应用协议层,该层的类比位于最底层的Socket类提供了更高层次的抽象,它们封装 TCP 和 UDP 套接字的创建,不需要处理连接的细节,这使得我们在编写套接字级别的协议时,可以更多地尝试使用 TCPClient 、 UDPClient和TcpListener,而不是直接向 Socket 中写。它们之间的这种层次关系示意如下: 可见, TcpClient 类基于 Socket 类构建,这是它能够以更高的抽象程度提供 TCP 服务的基础。正因为这样,许多应用层上的通讯协议,比如FTP(File Transfers Protocol)文件传输协议、HTTP(Hypertext Transfers Protocol)超文本传输协议等都直接创建在TcpClient等类之上。 TCPClient 类使用 TCP 从 Internet 资源请求数据。TCP 协议建立与远程终结点的连接,然后使用此连接发送和接收数据包。TCP 负责确保将数据包发送到终结点并在数据包到达时以正确的顺序对其进行组合。 从名字上就可以看出,TcpClient类专为客户端设计,它为 TCP 网络服务提供客户端连接。TcpClient 提供了通过网络连接、发送和接收数据的简单方法。 若要建立 TCP 连接,必须知道承载所需服务的网络设备的地址(IPAddress)以及该服务用于通讯的 TCP 端口 (Port)。Internet 分配号码机构 (Internet Assigned Numbers Authority, IANA) 定义公共服务的端口号(你可以访问 http://www.iana.org/assignments/port-numbers获得这方面更详细的资料)。IANA 列表中所没有的服务可使用 1,024 到 65,535 这一范围中的端口号。要创建这种连接,你可以选用TcpClient类的三种构造函数之一: 1、public TcpClient()当使用这种不带任何参数的构造函数时,将使用本机默认的ip地址并将使用默认的通信端口号0。这样情况下,如果本机不止一个ip地址,将无法选择使用。以下语句示例了如何使用默认构造函数来创建新的 TcpClient: TcpClient tcpClientC = new TcpClient(); 2、public TcpClient(IPEndPoint)使用本机IPEndPoint创建TcpClient的实例对象。上一篇介绍过了,IPEndPoint将网络端点表示为IP地址和端口号,在这里它用于指定在建立远程主机连接时所使用的本地网络接口(IP 地址)和端口号,这个构造方法为使用本机IPAddress和Port提供了选择余地。下面的语句示例了如何使用本地终结点创建 TcpClient 类的实例: IPHostEntry ipInfo=Dns.GetHostByName("www.tuha.net");//主机信息 IPAddressList[] ipList=ipInfo.AddressList;//IP地址数组 IPAddress ip=ipList[0];//多IP地址时一般用第一个 IPEndPoint ipEP=new IPEndPoint(ip,4088);//得到网络终结点 try{ TcpClient tcpClientA = new TcpClient(ipLocalEndPoint); } catch (Exception e ) { Console.WriteLine(e.ToString()); } 到这里,你可能会感到困惑,客户端要和服务端创建连接,所指定的IP地址及通信端口号应该是远程服务器的呀!事实上的确如此,使用以上两种构造函数,你所实现的只是TcpClient实例对象与IP地址和Port端口的绑定,要完成连接,你还需要显式指定与远程主机的连接,这可以通过TcpClient类的Connect方法来实现, Connet方法使用指定的主机名和端口号将客户端连接到 远程主机: 1)、public void Connect(IPEndPoint); 使用指定的远程网络终结点将客户端连接到远程 TCP 主机。 public void Connect(IPAddress, int); 使用指定的 IP 地址和端口号将客户端连接到 TCP 主机。 public void Connect(string, int); 将客户端连接到指定主机上的指定端口。 需要指出的是,Connect方法的所有重载形式中的参数IPEndPoint网络终 结点、IPAddress以及表现为string的Dns主机名和int指出的Port端口均指的是远程服务器。 以下示例语句使用主机默认IP和Port端口号0与远程主机建立连接: TcpClient tcpClient = new TcpClient();//创建TcpClient对象实例 try{ tcpClient.Connect("www.contoso.com",11002);//建立连接 } catch (Exception e ){ Console.WriteLine(e.ToString()); } 3、public TcpClient(string, int);初始化 TcpClient 类的新实例并连接到指定主机上的指定端口。与前两个构造函数不一样,这个构造函数将自动建立连接,你不再需要额外调用Connect方法,其中string类型的参数表示远程主机的Dns名,如:www.tuha.net。 以下示例语句调用这一方法实现与指定主机名和端口号的主机相连: try{ TcpClient tcpClientB = new TcpClient("www.tuha.net", 4088); } catch (Exception e ) { Console.WriteLine(e.ToString()); } 前面我们说,TcpClient类创建在Socket之上,在Tcp服务方面提供了更高层次的抽象,体现在网络数据的发送和接受方面,是TcpClient使用标准的Stream流处理技术,使得它读写数据更加方便直观,同时,.Net框架负责提供更丰富的结构来处理流,贯穿于整个.Net框架中的流具有更广泛的兼容性,构建在更一般化的流操作上的通用方法使我们不再需要困惑于文件的实际内容(HTML、XML 或其他任何内容),应用程序都将使用一致的方法(Stream.Write、Stream.Read) 发送和接收数据。另外,流在数据从 Internet 下载的过程中提供对数据的即时访问,可以在部分数据到达时立即开始处理,而不需要等待应用程序下载完整个数据集。.Net中通过NetworkStream类实现了这些处理技术。 NetworkStream 类包含在.Net框架的System.Net.Sockets 命名空间里,该类专门提供用于网络访问的基础数据流。NetworkStream 实现通过网络套接字发送和接收数据的标准.Net 框架流机制。NetworkStream 支持对网络数据流的同步和异步访问。NetworkStream 从 Stream 继承,后者提供了一组丰富的用于方便网络通讯的方法和属性。 同其它继承自抽象基类Stream的所有流一样,NetworkStream网络流也可以被视为一个数据通道,架设在数据来源端(客户Client)和接收端(服务Server)之间,而后的数据读取及写入均针对这个通道来进行。 .Net框架中,NetworkStream流支持两方面的操作: 1、 写入流。写入是从数据结构到流的数据传输。 2、读取流。读取是从流到数据结构(如字节数组)的数据传输。 与普通流Stream不同的是,网络流没有当前位置的统一概念,因此不支持查找和对数据流的随机访问。相应属性CanSeek 始终返回 false,而 Seek 和 Position 方法也将引发 NotSupportedException。 基于Socket上的应用协议方面,你可以通过以下两种方式获取NetworkStream网络数据流: 1、使用NetworkStream构造函数:public NetworkStream(Socket, FileAccess, bool);(有重载方法),它用指定的访问权限和指定的 Socket 所属权为指定的 Socket 创建 NetworkStream 类的新实例,使用前你需要创建Socket对象实例,并通过Socket.Connect方法建立与远程服务端的连接,而后才可以使用该方法得到网络传输流。示例如下: Socket s=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//创建客户端Socket对象实例 try{ s.Connect("www.tuha.net",4088);//建立与远程主机的连接 } catch(Exception e){ MessageBox.show("连接错误:" +e.Message); } try{ NetworkStream stream=new NetworkStream(s,FileAccess.ReadWrite,false);//取得网络传输流 } 2、通过TcpClient.GetStream方法:public NetworkStream etStream();它返回用于发送和接收数据的基础网络流NetworkStream。GetStream 通过将基础 Socket 用作它的构造函数参数来创建 NetworkStream 类的实例。使用前你需要先创TcpClient对象实例并建立与远程主机的连接,示例如下: TcpClient tcpClient = new TcpClient();//创建TcpClient对象实例 Try{ tcpClient.Connect("www.tuha.net",4088);//尝试与远程主机相连 } catch(Exception e){ MessageBox.Show("连接错误:"+e.Message); } try{ NetworkStream stream=tcpClient.GetStream();//获取网络传输流 } catch(Exception e) { MessageBox.Show("TcpClient错误:"+e.Message); } 通过以上方法得到NetworkStream网络流之后,你就可以使用标准流读写方法Write和Read来发送和接受数据了。 以上是.Net下使用TcpClient类实现客户端编程的技术资料,为了向客户端提供这些服务,我们还需要编制相应的服务端程序,前一篇《Visual C#.Net网络程序开发-Socket篇》上曾经提到, Socket作为其他网络协议的基础,既可以面向客户端开发,也可以面向服务端开发,在传输层面上使用较多,而在应用协议层面上,客户端我们采用构建于Socket类之上的TcpClient取代Socket;相应地,构建于Socket之上的TcpListener提供了更高理念级别的 TCP 服务,使得我们能更方便地编写服务端应用程序。正是因为这样的原因,像FTP 和 HTTP 这样的应用层协议都是在 TcpListener 类的基础上建立的。 .Net中的TCPListener 用于监视TCP 端口上的传入请求,通过绑定本机IP地址和相应端口(这两者应与客户端的请求一致)创建TcpListener对象实例,并由Start方法启动侦听;当TcpListener侦听到用户端的连接后,视客户端的不同请求方式,通过AcceptTcpClient 方法接受传入的连接请求并创建 TcpClient 以处理请求,或者通过AcceptSocket 方法接受传入的连接请求并创建 Socket 以处理请求。最后,你需要使用 Stop 关闭用于侦听传入连接的 Socket,你必须也关闭从 AcceptSocket 或 AcceptTcpClient 返回的任何实例。这个过程详细解说如下: 首先,创建TcpListener对象实例,这通过TcpListener类的构造方法来实现: public TcpListener(port);//指定本机端口 public TcpListener(IPEndPoint)//指定本机终结点 public TcpListener(IPAddress,port)//指定本机IP地址及端口 以上方法中的参数在前面多次提到,这里不再细述,唯一需要提醒的是,这些参数均针对服务端主机。下面的示例演示创建 TcpListener 类的实例: IPHostEntry ipInfo=Dns.Resolve("127.0.0.1");//主机信息 IPAddressList[] ipList=ipInfo.IPAddressList;//IP数组 IPAddress ip=ipList[0];//IP try{ TcpListener tcpListener = new TcpListener(ipAddress, 4088);//创建TcpListener对象实例以侦听用户端连接 } catch ( Exception e){ MessageBox.Show("TcpListener错误:"+e.Message); } 随后,你需要调用Start方法启动侦听: public void Start(); 其次,当侦听到有用户端连接时,需要接受挂起的连接请求,这通过调用以下两方法之一来完成连接: public Socket AcceptSocket(); public TcpClient AcceptTcpClient(); 前一个方法返回代表客户端的Socket对象,随后可以通过Socket 类的 Send 和 Receive 方法与远程计算机通讯;后一个方法返回代表客户端的TcpClient对象,随后使用上面介绍的 TcpClient.GetStream 方法获取 TcpClient 的基础网络流 NetworkStream,并使用流读写Read/Write方法与远程计算机通讯。 最后,请记住关闭侦听器:public void Stop(); 同时关闭其他连接实例:public void Close(); 下面的示例完整体现了上面的过程: bool done = false; TcpListener listener = new TcpListener(13);// 创建TcpListener对象实例(13号端口提供时间服务) listener.Start();//启动侦听 while (!done) {//进入无限循环以侦听用户连接 TcpClient client = listener.AcceptTcpClient();//侦听到连接后创建客户端连接TcpClient NetworkStream ns = client.GetStream();//得到网络传输流 byte[] byteTime = Encoding.ASCII.GetBytes(DateTime.Now.ToString());//预发送的内容(此为服务端时间)转换为字节数组以便写入流 try { ns.Write(byteTime, 0, byteTime.Length);//写入流 ns.Close();//关闭流 client.Close();//关闭客户端连接 } catch (Exception e) { MessageBox.Show("流错误:"+e.Message) } 综合运用上面的知识,下面的实例实现了简单的网络通讯-双机互连,针对客户端和服务端分别编制了应用程序。客户端创建到服务端的连接,向远程主机发送连接请求连接信号,并发送交谈内容;远程主机端接收来自客户的连接,向客户端发回确认连接的信号,同时接收并显示客户端的交谈内容。在这个基础上,发挥你的创造力,你完全可以开发出一个基于程序语言(C#)级的聊天室! 客户端主要源代码: public void SendMeg()//发送信息 { try { int port=Int32.Parse(textBox3.Text.ToString());//远程主机端口 try { tcpClient=new TcpClient(textBox1.Text,port);//创建TcpClient对象实例 } catch(Exception le) { MessageBox.Show("TcpClient Error:"+le.Message); } string strDateLine=DateTime.Now.ToShortDateString()+" "+DateTime.Now.ToLongTimeString();//得到发送时客户端时间 netStream=tcpClient.GetStream();//得到网络流 sw=new StreamWriter(netStream);//创建TextWriter,向流中写字符 string words=textBox4.Text;//待发送的话 string content=strDateLine+words;//待发送内容 sw.Write(content);//写入流 sw.Close();//关闭流写入器 netStream.Close();//关闭网络流 tcpClient.Close();//关闭客户端连接 } catch(Exception ex) { MessageBox.Show("Sending Message Failed!"+ex.Message); } textBox4.Text="";//清空 } 服务器端主要源代码: public void StartListen()//侦听特定端口的用户请求 { //ReceiveMeg(); isLinked=false; //连接标志 try { int port=Int32.Parse(textBox1.Text.ToString());//本地待侦听端口 serverListener=new TcpListener(port);//创建TcpListener对象实例 serverListener.Start(); //启动侦听 } catch(Exception ex) { MessageBox.Show("Can‘t Start Server"+ex.Message); return; } isLinked=true; while(true)//进入无限循环等待用户端连接 { try { tcpClient=serverListener.AcceptTcpClient();//创建客户端连接对象 netStream=tcpClient.GetStream();//得到网络流 sr=new StreamReader(netStream);//流读写器 } catch(Exception re) { MessageBox.Show(re.Message); } string buffer=""; string received=""; received+=sr.ReadLine();//读流中一行 while(received.Length!=0) { buffer+=received; buffer+="\r\n"; //received=""; received=sr.ReadLine(); } listBox1.Items.Add(buffer);//显示 //关闭 sr.Close(); netStream.Close(); tcpClient.Close(); } } 20 November 70-300刚好及格,看来还是要多研究一下设计!700分,刚好及格,本来没看多长时间的书,平时对设计方面也涉略很少,本以为可以考800分以上的,没想到自己在设计方面这么弱,郁闷! 03 November 使用企业库在某些站点会报试图执行安全策略不允许的操作异常的解决方法作.net的开发很多时候会用到微软企业库,有时候会有如下的异常产生:
安全性异常 异常详细信息: System.Security.SecurityException: 不允许所请求的注册表访问权。 堆栈跟踪: [SecurityException: 不允许所请求的注册表访问权。] Microsoft.Practices.EnterpriseLibrary.Common.Instrumentation.PerformanceCounterInstances..ctor(String categoryName, String counterName, Boolean createNewInstance) 造成这个的原因是企业库的公共项目中:Instrumentation 目录下有操作日志、性能的代码。而这些代码需要一定的权限才可以执行。 解决方案: 我们推荐的做法:重新编译 Microsoft.Practices.EnterpriseLibrary.Common 项目,编译时把其中 Conditional Compilation Constants 设置中除 DEBUG;TRACE 外的其他项都去掉。 具体步骤请参看: 另外:有时候也可以用这种方法 这种方法据说有人成功过,不过很可惜昨天我没有成功!大家可以试一下! 23 October SQL Server日志文件总结及日志满的处理 最近浙江电信的数据库的temp交易日志隔几天就满了,造成数据库无法访问,查了很久,总算解决了问题。对Transaction Logs也有了一些认识。
交易日志(Transaction logs)是数据库结构中非常重要但又经常被忽略的部分。由于它并不像数据库中的schema那样活跃,因此很少有人关注交易日志。
交易日志是针对数据库改变所做的记录,它可以记录针对数据库的任何操作,并将记录结果保存在独立的文件中。对于任何每一个交易过程,交易日志都有非常全面的记录,根据这些记录可以将数据文件恢复成交易前的状态。从交易动作开始,交易日志就处于记录状态,交易过程中对数据库的任何操作都在记录范围,直到用户点击提交或后退后才结束记录。每个数据库都拥有至少一个交易日志以及一个数据文件。 出于性能上的考虑,SQL Server将用户的改动存入缓存中,这些改变会立即写入交易日志,但不会立即写入数据文件。交易日志会通过一个标记点来确定某个交易是否已将缓存中的数据写入数据文件。当SQL Server重启后,它会查看日志中最新的标记点,并将这个标记点后面的交易记录抹去,因为这些交易记录并没有真正的将缓存中的数据写入数据文件。这可以防止那些中断的交易修改数据文件。 维护交易日志 因为很多人经常遗忘交易日志,因此它也会给系统带来一些问题。随着系统的不断运行,日志记录的内容会越来越多,日志文件的体积也会越来越大,最终导致可用磁盘空间不足。除非日常工作中经常对日志进行清理,否则日志文件最终会侵占分区内的全部可用空间。日志的默认配置为不限容量,如果以这种配置工作,它就会不断膨胀,最终也会占据全部可用空间。这两种情况都会导致数据库停止工作。 对交易日志的日常备份工作可以有效的防止日志文件过分消耗磁盘空间。备份过程会将日志中不再需要的部分截除。截除的方法是首先把旧记录标记为非活动状态,然后将新日志覆盖到旧日志的位置上,这样就可以防止交易日志的体积不断膨胀。如果无法对日志进行经常性的备份工作,最好将数据库设置为"简单恢复模式"。在这种模式下,系统会强制交易日志在每次记录标记点时,自动进行截除操作,以新日志覆盖旧日志。 截除过程发生在备份或将旧标记点标为非活动状态时,它使得旧的交易记录可以被覆盖,但这并不会减少交易日志实际占用的磁盘空间。就算不再使用日志,它依然会占据一定的空间。因此在维护时,还需要对交易日志进行压缩。压缩交易日志的方法是删除非活动记录,从而减少日志文件所占用的物理硬盘空间。 通过使用DBCC SHRINKDATABASE语句可以压缩当前数据库的交易日志文件,DBCC SHRINKFILE语句用来压缩指定的交易日志文件,另外也可以在数据库中激活自动压缩操作。当压缩日志时,首先会将旧记录标记为非活动状态,然后将带有非活动标记的记录彻底删除。根据所使用的压缩方式的不同,你可能不会立即看到结果。在理想情况下,压缩工作应该选在系统不是非常繁忙的时段进行,否则有可能影响数据库性能。 恢复数据库 交易记录备份可以用来将数据库恢复到某一指定状态,但交易记录备份本身不足以完成恢复数据库的任务,还需要备份的数据文件参与恢复工作。恢复数据库时,首先进行的是数据文件的恢复工作。在整个数据文件恢复完成前,不要将其设为完成状态,否则交易日志就不会被恢复。当数据文件恢复完成,系统会通过交易日志的备份将数据库恢复成用户希望的状态。如果在数据库最后一次备份后,存在多个日志文件的备份,备份程序会按照它们建立的时间依次将其恢复。 另一种被称为log shipping的过程可以提供更强的数据库备份能力。当log shipping配置好后,它可以将数据库整个复制到另一台服务器上。在这种情况下,交易日志也会定期发送到备份服务器上供恢复数据使用。这使得服务器一直处于热备份状态,当数据发生改变时它也随之更新。另一个服务器被称作监视(monitor)服务器,可以用来监视按规定时间间隔发送的shipping信号。如果在规定时间内没有收到信号,监视服务器会将这一事件记录到事件日志。这种机制使得log shipping经常成为灾难恢复计划中使用的方案。 性能优化 交易日志对数据库有重要作用,同时它对系统的整体性能也有一定影响。通过几个选项,我们可以对交易日志的性能进行优化。由于交易日志是一个连续的磁盘写入过程,在这当中不会发生读取动作。因此将日志文件放在一个独立的磁盘,对优化性能有一定作用。 另一项优化措施与日志文件的体积有关。我们可以设置日志文件的体积不超过硬盘空间的百分之几,或者确定它的大小。如果将其设置的过大会浪费磁盘空间,而如果设置的过小则会强制记录文件不断尝试扩展,导致数据库性能下降。 事务日志文件Transaction Log File是用来记录数据库更新情况的文件,扩展名为ldf。 在 SQL Server 7.0 和 SQL Server 2000 中,如果设置了自动增长功能,事务日志文件将会自动扩展。 一般情况下,在能够容纳两次事务日志截断之间发生的最大数量的事务时,事务日志的大小是稳定的,事务日志截断由检查点或者事务日志备份触发。 然而,在某些情况下,事务日志可能会变得非常大,以致用尽空间或变满。通常,在事务日志文件占尽可用磁盘空间且不能再扩展时,您将收到如下错误消息: Error:9002, Severity:17, State:2 The log file for database '%.*ls' is full. 除了出现此错误消息之外,SQL Server 还可能因为缺少事务日志扩展空间而将数据库标记为 SUSPECT。有关如何从此情形中恢复的其他信息,请参见 SQL Server 联机帮助中的“磁盘空间不足”主题。 另外,事务日志扩展可能导致下列情形: · 非常大的事务日志文件。 · 事务可能会失败并可能开始回滚。 · 事务可能会用很长时间才能完成。 · 可能发生性能问题。 · 可能发生阻塞现象。 原因 事务日志扩展可能由于以下原因或情形而发生: · 未提交的事务 · 非常大的事务 · 操作:DBCC DBREINDEX 和 CREATE INDEX · 在从事务日志备份还原时 · 客户端应用程序不处理所有结果 · 查询在事务日志完成扩展之前超时,您收到假的“Log Full”错误消息 · 未复制的事务 解决方法 日志文件满而造成SQL数据库无法写入文件时,可用两种方法: 一种方法:清空日志。 1.打开查询分析器,输入命令 DUMP TRANSACTION 数据库名 WITH NO_LOG 2.再打开企业管理器--右键你要压缩的数据库--所有任务--收缩数据库--收缩文件--选择日志文件--在收缩方式里选择收缩至XXM,这里会给出一个允许收缩到的最小M数,直接输入这个数,确定就可以了。 另一种方法有一定的风险性,因为SQL SERVER的日志文件不是即时写入数据库主文件的,如处理不当,会造成数据的损失。 1: 删除LOG 分离数据库 企业管理器->服务器->数据库->右键->分离数据库 2:删除LOG文件 附加数据库 企业管理器->服务器->数据库->右键->附加数据库 此法生成新的LOG,大小只有500多K。 注意:建议使用第一种方法。 如果以后,不想要它变大。 SQL2000下使用: 在数据库上点右键->属性->选项->故障恢复-模型-选择-简单模型。 或用SQL语句: alter database 数据库名 set recovery simple 另外,如上图中数据库属性有两个选项,与事务日志的增长有关: Truncate log on checkpoint (此选项用于SQL7.0,SQL 2000中即故障恢复模型选择为简单模型) 当执行CHECKPOINT 命令时如果事务日志文件超过其大小的70% 则将其内容清除在开发数据库时时常将此选项设置为True Auto shrink定期对数据库进行检查当数据库文件或日志文件的未用空间超过其大小的25%时,系统将会自动缩减文件使其未用空间等于25% 当文件大小没有超过其建立时的初始大小时不会缩减文件缩减后的文件也必须大于或等于其初始大小对事务日志文件的缩减只有在对其作备份时或将Truncate log on checkpoint 选项设为True 时才能进行。 注意:一般立成建立的数据库默认属性已设好,但碰到意外情况使数据库属性被更改,请用户清空日志后,检查数据库的以上属性,以防事务日志再次充满。 21 September Build a Managed BHO and Plug into the BrowserIntroduction
With the amount of time we spend touring the Internet for information, enhancing the browsing experience has never been more important. Internet Explorer has an extensibility model supporting a variety of browser plug-ins. You have probably seen and used them yourself: the Google toolbar, the Babelfish Translator, or the ever-popular variety of popup blockers. Plug-ins such as these may come in the form of basic Browser Helper Objects (BHOs) or other browser extensions such as Explorer Bars and docking Tool Bands. In any case, they enrich the browsing experience in some way by integrating otherwise cumbersome or elusive functionality. They do this by implementing a required set of predefined COM interfaces and by communicating with interfaces exposed by IE's Web browser object model.
Many a BHO has been implemented using lightweight ATL components in C++, but for those who have fully embraced C# and spend the majority of time writing managed code, the thought of revisiting COM and ATL probably seems a lousy idea. Fortunately, the COM interoperability support provided by the .NET Framework gives us a way to build 100 percent managed components and to control how they are exposed to COM. In this article I will show you how to create a managed BHO that responds to browser events and discuss the interoperability features that make it possible. Here We Are...Talking COM Again Prior to .NET I fancied myself a pretty good COM developer, with years of experience developing components with C++, ATL, MFC and VB. For that reason, COM interoperability was a subject near and dear to me in the early days of .NET. But, as time passed, more and more of my .NET development became pure, and COM became a distant memory. But, every once in a great while, for example, while I was porting a legacy ATL BHO to C#, I find myself summoning from past COM experiences. In the case of the BHO, I had to gather interface identifiers to import existing COM interfaces and review the rules of reference counting ...yet again. The simplest form of BHO is a component that, once registered, is loaded by IE and can both communicate with IE's object model and hook into its useful browsing events. A BHO does not need to expose a visible user interface to be effective. IE loads any registered BHOs and provides them with reference to its container site (the Web browser class in this case), and from there BHOs decide what events to subscribe to and what parts of the IE object model to communicate with. This requires two-way communication between managed and unmanaged code. In the sample application the managed component, Observer.dll, is registered so COM clients can consume it, and the .NET runtime dynamically generates a COM Callable Wrapper (CCW) that exposes COM interfaces to those clients. When you set the project properties option to register the assembly for COM interoperability, the IDE invokes regasm.exe to insert registry settings for the managed component, but it also invokes tlbexp.exe to generate a type library for the assembly. In fact the CCW is generated at runtime, but we can inspect the type library to see how assembly types will be marshaled to COM clients. The CCW exposes IUnknown and IDispatch interfaces for the assembly, in addition to any coclass and interface definitions it encapsulates (see Figure 1). Figure 1: The sample application includes a BHO that runs within each IE process and communicates with a remote Web service. The Web service is also invoked by a singleton Windows client, loaded through the IE toolbar, to display a list of active session browsing statistics. Since the Observer assembly will be using the IE object model, a reference to the COM component SHDocVw.dll is added to the project. This generates a Runtime Callable Wrapper (RCW) that gives us access from managed code to IE's Web browser class and related object and event interfaces (also shown in Figure 1). In the Observer project the generated interop assembly (RCW) is located in Interop.SHDocVw.dll. For this article, I'll now move past the basics of COM and .NET wrapper classes and focus on the specific implementation requirements of a BHO along with some extended features of COM interoperability that are leveraged to implement its functionality. You're Browsing...Who's Listening? BHOs, like other browser extensions, must be registered in a particular section of the Windows registry. If you double-click on the observerbho.reg file supplied with the sample Observer project, you will see the following entry was added to the registry (to see the registry, run regedit.exe): HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{0CD00297-9A19-4698-AEF1-682FBE9FE88D} Each subkey beneath the Browser Helper Objects key is identified by a GUID that matches a registered BHO component class. Of course, that means that the component must also be registered for COM, and you can test this by compiling the Observer project, which creates the following registry entry: HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{0CD00297-9A19-4698-AEF1-682FBE9FE88D} This CLSID key provides information about the programmatic identifier and class loader required to load the BHO component when a client invokes it: Observer.BrowserMonitor and mscoree.dll, respectively. Once loaded, IE will look for the component to have implemented the IObjectWithSite COM interface. This interface is a lightweight alternative to IOleObject, for non-embedded objects to communicate with their host container. But, to expose this predefined COM interface from managed code, we must define the interface as shown in Figure 2. [ComImport(), Guid("fc4801a3-2ba9-11cf-a229-00aa003d7352")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IObjectWithSite { void SetSite([In, MarshalAs(UnmanagedType.IUnknown)] object pUnkSite); void GetSite(ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppvSite); } Figure 2: Importing a COM interface means providing an exact definition with matching GUID. IObjectWithSite is an externally defined COM interface shown here defined in C#. As with COM objects, COM interfaces are also identified in the Windows registry by a GUID. In order for IE to gain access to the observer BHO through its IObjectWithSite interface, the interface must be defined with this GUID. For managed components, rather than including a header as we would in a C++ application, we can define the interface in C# and use the ComImportAttribute to mark the interface as externally defined. The interface must define interface members exactly as they appear in the actual interface definition, in the same order. When the ComImportAttribute is applied, the compiler also expects a GuidAttribute to be present to specify a matching GUID. In this case, the GUID for IObjectWithSite as it is registered. NOTE: When ComImportAttribute is applied to a class definition, the class need not include members since those will be supplied by the CCW. In this respect, ComImportAttribute allows us to provide friendly names to externally defined COM objects. By default, objects are marshaled as COM variants, but the methods exposed by IObjectWithSite define an IUnknown interface pointer that is passed to SetSite and an interface pointer returned by GetSite in the second parameter: HRESULT SetSite(IUnknown* pUnkSite); HRESULT GetSite(REFIID riid, void** ppvSite); The MarshalAsAttribute can be used to tell the .NET Framework how to marshal types between the CCW and the COM client that invokes it. As shown in Figure 2 the UnmanagedType enumeration includes an IUnknown element that is applied for this purpose. A BHO COM Wrapper - Made to Order Now that we have the interface well defined, let's take a look at the BHO class that implements it, BrowserMonitor. A partial view of BrowserMonitor is shown in Figure 3. [GuidAttribute("181C179B-7CC9-4457-8C1D-4B45E7C8589D")] [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual)] public interface IObserver { } [ClassInterfaceAttribute(ClassInterfaceType.None)] [GuidAttribute("0CD00297-9A19-4698-AEF1-682FBE9FE88D")] [ProgIdAttribute("Observer.BrowserMonitor")] public class BrowserMonitor: IObserver, IObjectWithSite { public void SetSite(object pUnkSite) { if (m_pIWebBrowser2!=null) Release(); if (pUnkSite==null) return; m_pIWebBrowser2 = pUnkSite as SHDocVw.IWebBrowser2; string sHostName = m_pIWebBrowser2.FullName; if (!(sHostName.ToUpper().EndsWith("IEXPLORE.EXE"))) { Release(); return; } m_hwndBrowser = m_pIWebBrowser2.HWND; SHDocVw.DWebBrowserEvents2_Event?ev = m_pIWebBrowser2 as SHDocVw.DWebBrowserEvents2_Event ; if (ev != null) { ev.BeforeNavigate2 += new SHDocVw.DWebBrowserEvents2_BeforeNavigate2EventHandler( BeforeNavigate2); ev.DocumentComplete += new SHDocVw.DWebBrowserEvents2_DocumentCompleteEventHandler( DocumentComplete); ev.OnQuit += new SHDocVw.DWebBrowserEvents2_OnQuitEventHandler(OnQuit); ev.NavigateComplete2 += new SHDocVw.DWebBrowserEvents2_NavigateComplete2EventHandler( NavigateComplete2); } else { Release(); return; } } public void GetSite(ref System.Guid riid, out object ppvSite) { ppvSite=null; if (m_pIWebBrowser2 != null) { IntPtr pSite = IntPtr.Zero; IntPtr pUnk = Marshal.GetIUnknownForObject(m_pIWebBrowser2); Marshal.QueryInterface(pUnk, ref riid, out pSite); Marshal.Release(pUnk);? Marshal.Release(pUnk); if (!pSite.Equals(IntPtr.Zero)) { ppvSite = pSite; } else { Release(); Marshal.ThrowExceptionForHR(E_NOINTERFACE); } } else { Release(); Marshal.ThrowExceptionForHR(E_FAIL); } } // more Observer methods?/span> } Figure 3: A partial listing of the BrowserMonitor BHO object (defined in the Observer assembly) showing the implementation of IObjectWithSite members. The BrowserMonitor will be registered as a coclass with a GUID and programmatic identifier (ProgID), so a few attributes have been applied to the class tailoring how the runtime exposes it to COM. By default, a new GUID will be generated each time we compile and register the Observer assembly's types, so the GuidAttribute is applied to ensure that the same GUID is always used. Following COM versioning rules, this GUID need only be altered when the object's interface is altered or implementation is drastically altered. The GUID for BrowserMonitor matches the BHO registry setting discussed earlier. By default, the runtime generates a CCW with a default class interface for each publicly exposed class. This behavior is consistent with how Visual Basic 6.0 (and earlier) exposed types to COM; however, this means that all public methods are aggregated and exposed in a single default interface. It is traditionally preferred to create well-defined interfaces for each coclass and group members in the appropriate interface that can be separately versioned and accessed through QueryInterface. To suppress the automatic generation of a class interface the ClassInterfaceAttribute is applied with enum parameter ClassInterfaceType.None. In addition, although symbolic in this case, I created an empty custom dual interface for the class called IObserver and apply this interface to the BrowserMonitor in lieu of the class interface. The InterfaceTypeAttribute makes it possible to create IDispatch, IUnknown, or dual interfaces, the latter of which is preferred. The ProgIdAttribute is used to override the default generated progid which would normally be generated assemblyname.classname. In the case of this assembly, the default actually matches the attribute setting, but is kept for good measure. So, now that the Observer class is registered as a BHO, and the class properly implements the IObjectWithSite interface, IE will load the BHO and invoke SetSite to initiate communications. Next let's see what interesting things we can do with the BHO. Hooking the Web Browser After a BHO is loaded you have a golden opportunity during SetSite() to store a reference to the browser instance that loaded it and subscribe to useful events. When we implement SetSite and GetSite, we must also consider the rules we're asked to follow, per the interface definition. The implementation of SetSite should store a reference to the browser site, unless a null reference is passed, which means we are being asked to release our hold on a previously stored reference. The first few lines of the SetSite method shown in Figure 2 demonstrates this. Once we have established there is a valid reference to the Web browser, the following code casts that reference to the IWebBrowser2 interface of the object: m_pIWebBrowser2 = pUnkSite as SHDocVw.IWebBrowser2; Remember that when we hold a reference to a COM object, we are supposed to increment the reference count? Well, the CCW handles this when we assign the reference as shown. In fact, under the hood, this safe cast to IWebBrowser2 using the as keyword invokes the appropriate calls to QueryInterface to find the IWebBrowser2 interface (if present) exposed by the pUnkSite parameter. SetSite requires us to return an S_OK HRESULT in all cases (which is why we don't throw any exceptions in this method), but GetSite requires additional logic. GetSite is called to retrieve the last site passed to SetSite, which we should return if we have stored a valid reference. However, if the first parameter to GetSite requests an interface identifier that is not supported on the site reference, an E_NOINTERFACE HRESULT should be returned. If we are holding a null site reference, GetSite should return E_FAIL. System.Runtime.InteropServices.Marshal is a utility class supplying methods to help us with COM interoperability. Throwing a ComException will return E_FAIL to a COM client, however ThrowExceptionForHR supports the return of any valid HRESULT. I copied the definitions for two predefined HRESULT constants in the BrowserMonitor class for this purpose: const int E_FAIL = unchecked((int)0x80004005); const int E_NOINTERFACE = unchecked((int)0x80004002); The Marshal class also supplies a QueryInterface method that is used in GetSite to retrieve the requested interface reference. In order to invoke QueryInterface, the site object (IWebBrowser2 type) had to be cast to IUnknown, as shown in Figure 3. Be warned, you must know your utility functions well. In this case, since GetIUnknownForObject adds a reference to the site object, as does QueryInterface, I decrement the reference count twice to release those additional references before the method call is over.
If you take a look at the interop assembly, Interop.SHDocVw.dll, you will see many interesting types and event interfaces that could be useful to communicate with. IWebBrowser2 is the primary interface implemented by the Web browser control contained in SHDocVw.dll (see Figure 1). It exposes properties and methods for browser navigation to interact with UI components such as the address bar and to instruct the browser to close. The same Web browser control also exposes a core connection point interface, DWebBrowserEvents2, that includes events related to navigation, UI activities, file downloads and printing. As shown in Figure 3, SetSite checks the FullName property of the Web browser control to see if the calling container is Internet Explorer (iexplore.exe). This check ensures that we suppress BHO functionality for Windows Explorer, for example, which also loads registered BHOs into each instance. This BHO is otherwise more interested in navigation events, so before SetSite completes we grab the event interface by casting the site object to DWebBrowserEvents2. The connection point mechanism exposed by COM objects is exposed via delegates in managed code through the RCW. Figure 3 shows how to use the DWebBrowserEvents2_Event class to subscribe to a few key navigation events (BeforeNavigate2, DocumentComplete and NavigateComplete2) and the Quit event to detect when the browser session is closing. The NavigateComplete2 event is issued once navigation to a requested URL has been complete; however, the page may take time to load, so I hook the DocumentComplete event that notifies listeners that the current page has completed loading. BeforeNavigate2 is issued prior to navigation when a user has requested a new page. The sample BHO uses these events to track navigation URLs and time spent on each URL. This information is sent to the Web service for storage (see Figure 1). One interesting point to note is that the Web service proxy that was generated for the BHO project when I added a Web reference to BrowsingServices becomes part of the CCW when the BHO is exposed to COM. This is because the proxy is a public class. To hide this object from the CCW I applied the ComVisibleAttribute to the proxy class. The drawback of this, of course, is that each time I generate a new proxy I have to re-apply the attribute. Similarly, this ComVisibleAttribute can be applied to any public type of member that you want omitted from the runtime-generated COM wrapper. A BHO and Then Some As Figure 1 demonstrates, the sample application is more than just a BHO, although that is the heart of it all and the focus of this article. Rather than keep things simple, I created a solution around the BHO that includes Web services and a singleton Windows client. The BHO uses Web services to remotely store active session browsing data. The Windows client (really, a smart client of sorts) leverages the same Web services to display a list of active session browsing data. In fact, the installation will also add a toolbar to the browser so that you can launch the Windows client from any open browser. Now you should have a deeper understanding of how BHOs operate, and the power they are able to harness through the container site's object model. In addition, with our short and sweet COM review, you now know how to import COM class and interfaces, control marshalling, and customize the way your components are exposed to COM. 28 July 三层结构“三层结构”的开发模式不仅仅可以应用于Web应用程序,在其他应用领域也是可以发挥其巨大作用的。而本文主旨是阐明三层结构的原理与用意,并说明Bincess的三层结构的特点。 “三层结构”指的是什么? “三层结构”一词中的“三层”是指:“外观层”、“中间层”、“数据库层”。其中: ¨ 外观层:位于最外层,直接呈现在用户面前。用于显示数据,并为用户提供一种交互式的界面。 ¨ 中间层:负责处理用户输入的信息,或者是将这些信息发送给数据库层进行保存,或者是调用数据库层中的函数再次读出这些数据。 ¨ 数据库层:仅实现对数据的保存和读取操作。 为什么需要 “三层结构” 在一个软件系统中,如果不分以层次,那么在将来的升级维护中会遇到很大的麻烦。就像一个ASP.NET网页访问数据库一样。例如在ASP.NET后台程序文件aspx.cs中,使用OleDbConnection和OleDbCommand来处理Access后台数据库。而当数据库服务器从Access2000升迁到SQLServer2000的时候,我们就必须修改原来的OleDbConnection为新的SqlConnection,OleDbCommand为新的SqlCommand来适应新的数据库服务器。但问题是对于一个大型的商业网站,要进行数据库操作的并不只有一两个页面。访问数据库的代码会散落各个页面中,就像夜空中的星星一样。这样的维护,难度可想而知。有一个比较好的解决办法,那就是将访问数据库的代码全部都放在一个cs文件里,这样数据库服务器一旦变换,那么只需要集中修改一个cs文件就可以了。 namespace Bincess // ListBoard.aspx.cs 文件 { public class ListBoard { private void BoardDataBind() { OleDbConnection dbConn=new OleDbConnection(); OleDbCommand dbCmd=new OleDbCommand(); ... } } 注意,这两个文件都进行了数据库操作。那么在数据库服务器改换时,两个文件就都必须修改并重新编译… } namespace Bincess // ListTopic.aspx.cs 文件 { public class ListTopic { private void TopicDataBind() { OleDbConnection dbConn=new OleDbConnection(); OleDbCommand dbCmd=new OleDbCommand(); ... } } }
将原来的访问数据库的代码全部都放在DBTask.cs程序文件中,这样只要修改这一个文件就可以适应新的数据库 namespace Bincess // DBTask.cs { public class DBTask { public void BoardDataBind() { OleDbConnection dbConn=new OleDbConnection(); OleDbCommand dbCmd=new OleDbCommand(); 定义一个DBTask类,让它来完成所有的数据库操作。那么当数据库服务器改换时,只要集中修改这一个文件并重新编译即可… ... } public void TopicDataBind() { OleDbConnection dbConn=new OleDbConnection(); OleDbCommand dbCmd=new OleDbCommand(); ... } } } namespace Bincess // ListBoard.aspx.cs 文件 { public class ListBoard { private void BoardDataBind() { (new DBTask()).BoardDataBind(); } } } namespace Bincess // ListTopic.aspx.cs 文件 { public class ListTopic { private void TopicDataBind() { (new DBTask()).TopicDataBind(); } } } 当然这是一个简单的“门面模式”的应用,恐怕也是“三层结构”的最原始模型… 如果数据库访问代码太多,令DBTask.cs文件过大的话,可以将函数功能分组,存储到其它文件里。 在一个ASP.NET Web应用程序解决方案中,并不是说有aspx文件、有dll文件、还有数据库,就是“三层结构”的Web应用程序,这样的说法是不对的。也并不是说没有对数据库进行操作,即没有“数据库层”,就不是“三层结构”的。其实三层结构是功能实现上的三层: ¨ 外观层,用于显示,并为用户提供交互式操作的可能… ¨ 中间层,服务于外观层并调用数据库层的函数。 ¨ 数据库层,实现数据库的存储和读出。存储目标不一定是数据库服务器,也可以是文本文档或XML文档。 在微软的ASP.NET示范实例Duwamish7中,外观层被放置在Web项目中,中间层是放置在BusinessFacade项目中,而数据库层则是放置在DataAccess项目中。而微软的另一个ASP.NET示范实例PetShop中,外观层被放置在Web项目中,中间层是放置在BLL项目中,而数据库层则是放置在SQLServerDAL和OracleDAL两个项目中。在我的彬月论坛中,外观层是被放置在Web项目中,中间层是被放置在InterService项目中,而数据库层是被放置在AccessTask项目中。 27 July .NET简单三层结构Web 层 Web 层为客户端提供对应用程序的访问。这一层是作为 Duwamish.sln 解决方案文件中的 Web 项目实现的。Web 层由 ASP.NET Web 窗体和代码隐藏文件组成。Web 窗体只是用 HTML 提供用户操作,而代码隐藏文件实现各种控件的事件处理。 业务外观层 业务外观层为 Web 层提供处理帐户、类别浏览和购书的界面。这一层是作为 Duwamish.sln 解决方案文件中的 BusinessFacade 项目实现的。业务外观层用作隔离层,它将用户界面与各种业务功能的实现隔离开来。除了低级系统和支持功能之外,对数据库服务器的所有调用都是通过此程序集进行的。 业务规则层 业务规则层是作为 Duwamish.sln 解决方案文件中的 BusinessRules 项目实现的,它包含各种业务规则和逻辑的实现。业务规则完成如客户帐户和书籍订单的验证这样的任务。 数据访问层 数据访问层为业务规则层提供数据服务。这一层是作为 Duwamish.sln 解决方案文件中的 DataAccess 项目实现的。 代码示例: 以下是两种不同处理路径的代码示例: 获取商品目录 表示层调用业务外观层: productSystem = new ProductSystem(); categorySet = productSystem.GetCategories(categoryID); 业务外观层直接调用数据层: public CategoryData GetCategories(int categoryId) 添加定单 表示层调用业务外观层: public void AddOrder() 业务外观层调用业务规则层: public OrderData AddOrder(OrderData order) (new BusinessRules.Order()).InsertOrder(order); 业务规则层调用数据层: public bool InsertOrder(OrderData order) |
|
|