
2008年12月11日
今天和同事一起把Plugins Manager放在了google code上面。大家可以从下面的地址获取所有的源码了。
http://code.google.com/p/dynamic-plugins-manager/
posted @
2008-12-11 12:29 Cooldog's .NET Space 阅读(48) |
评论 (3) |
编辑
这里提供了插件的Demo程序,和编译好的Plugin Manager下载。
Plugin Manager的代码待整理后放上来。 
开发运行环境:
Windows Vista Ultimate
.NET Framework 3.5
Microsoft Visual Studio 2008
http://www.xiaozhou.net/cooldog/attachments/month_0803/k200832916121.rar
posted @
2008-12-11 12:28 Cooldog's .NET Space 阅读(55) |
评论 (0) |
编辑
下面来演示一下Plugins Manager,先看看我们事先做好的Interface和两个具体实现的插件.
Interface,接口定义:

程序代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Component
{
public interface IComponent
{
string Invoke();
}
}
整个接口,只声明了一个Invoke方法,这也是具体的插件必须实现的一个方法。
第一个插件类 class1.cs

程序代码
namespace Component
{
public class Class1 : MarshalByRefObject,IComponent
{
string message = string.Empty;
int number;
float number2;
public Class1(string input, int num)
{
message = input;
number = num;
number2 = 0;
}
public Class1(int num1, float num2)
{
message = "none";
number = num1;
number2 = num2;
}
public string Invoke()
{
return "This is TestLibrary. member---message:" + message + "number:" + number.ToString() + "number2:" +number2.ToString();
}
}
}
第二个插件类 class2.cs

程序代码
namespace Component
{
public class Class2 : MarshalByRefObject,IComponent
{
public Class2()
{
}
public string Invoke()
{
return "This is TestLibrary2 without construct param";
}
}
}
这里我们看到区别,第一个插件,需要构造函数,第二个插件不需要构造函数,所以在配置文件中,第二个插件可以关闭掉ConstructParam这个节点。
下面我们将Class1.cs编译好,放到插件目录中,改名为TestLibrary.dll,将Class2.cs直接改名为TestLibrary2.cs,放到插件目录中。
演示的目的:插件1需要在加载时,构造初始化函数,而插件2不需要。
插件1,是已经编译好的dll,不需要实时编译,但是插件2需要。
下面是测试的截图
1.插件目录,一个放入编译好的dll,一个放入.cs源码
2.启动测试程序,看到2个插件都已经被加载
3.此时,再查看插件的目录,我们会看到Plugin Manager为我们编译,并生成的插件2的dll
4.查看插件的日志记录
5.插件的调用
选中TestLibrary,点击Invoke按钮:
选中TestLibrary2,点击Invoke按钮:
6.插件的动态卸载和发布
a.动态卸载,可以直接使用Plugins Manager的UnloadPluginByName直接通过程序调用卸载。也可以手动打开插件的配置文件,将EnablePlugin设置为False
b.动态发布,直接在插件目录中,建立一个新的子目录,然后将插件的dll和xml配置文件直接拷入即可
关于第6点,这些功能的演示,截图比较麻烦,大家可以直接使用测试程序测试即可。
posted @
2008-12-11 12:26 Cooldog's .NET Space 阅读(37) |
评论 (0) |
编辑
整个Plugins Manager是一个基于dll的程序集,开发平台,基于VS2008和.NET 3.5 Framework。为何要用.NET 3.5呢?没别的原因,只要是为了尝试一下Linq to XML
使用改组件,需要在应用程序中,添加引用。另外,还需要在应用程序中,建立相关的配置项:

程序代码
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="PluginsPath" value="D:\MyCode\C# Projects\DynamicPlugInsApp\DynamicPlugInsApp\bin\Debug\Plugins"/>
<add key="LogPath" value="D:\MyCode\C# Projects\DynamicPlugInsApp\DynamicPlugInsApp\bin\Debug\Log"/>
</appSettings>
</configuration>
PluginsPath是插件的主目录,里面通过子目录的方式,存放不同的插件。Plugins Manager在启动的时候,会遍历插件主目录,并搜寻可加载的插件。
LogPath是插件的日志目录,里面的日志文件,会详细记载插件管理器当前的运行情况。
另外,在Plugins Manager中,区别不同的插件,是根据不同的插件名称来决定的,这取决于插件dll的命名。
对于不同的插件,Plugins Manager也提供不同的特性配置文件。配置文件是.xml文件,命名需要和插件dll相一致。
下面是一个配置文件的示例:

程序代码
<?xml version="1.0" encoding="utf-8" ?>
<Plugin>
<TypeName>Component.Class1</TypeName>
<EnablePlugin>True</EnablePlugin>
<ConstructParams>
<Param type="string" value="msadc" />
<Param type="int" value="12" />
</ConstructParams>
<ReferenceList>
<Reference>D:\MyCode\C# Projects\DynamicPlugInsApp\ITestInterface\bin\Debug\ITestInterface.dll</Reference>
</ReferenceList>
</Plugin>
TypeName:插件的类名,和具体的插件命名空间和类名有关,Plugins Manager根据配置文件提供此相关信息,通过.NET的反射技术,生成具体的插件Instance
EnablePlugin:插件开关,True表示当前插件可用,False表示阻止Plugins Manager加载此插件。该开关可以在Plugins Manager运行前设定,也可以在Plugins Manager运行时动态更改。Plugins Manager会检测配置文件变化,并留意EnablePlugin开关,以决定是否动态加载或者卸载该插件。
ConstructParams:此节点,及其子节点,用以读取插件是否需要在运行时初始化构造函数。子节点用以描述构造函数中的参数,以及类型,和初始化的值。构造初始化函数,也是在.NET的反射中实现。
这里要注意,节点顺序和构造函数参数顺序有关。如果插件不需要构造函数,可以关闭此节点。
ReferenceList:此节点用以需要动态编译的插件,如果插件代码中还引用了其他的程序集,需要在此进行描述。
此外,插件还提供了事件通知功能,供应用程序进行响应和处理,实现的事件类型有以下几种:

程序代码
public enum PluginEventType
{
PluginLoadOver, //所有插件加载完毕
PluginAdded, //插件新增完毕
PluginRemoved, //插件卸载完毕
PluginStopped, //插件管理器已停止
PluginStarted //插件管理器已启动
}
插件的事件参数定义如下,只包含简单的事件类型和文本描述:

程序代码
public class PluginEventArgs : EventArgs
{
#region Private Members
public PluginEventType EventType { get; set; }
public string EventMessage { get; set; }
#endregion
public PluginEventArgs(PluginEventType eventtype, string message)
{
EventType = eventtype;
EventMessage = message;
}
}
以上是使用该插件,需要配置的地方,下一篇日志,将演示该插件的Demo.
posted @
2008-12-11 11:44 Cooldog's .NET Space 阅读(49) |
评论 (0) |
编辑
Dynamic Plugins Manager是一个基于.NET的动态插件管理组件,也是我在业余时间做的一个小东东,代码在07年底就开始动工,因为一直没充裕时间完成,趁这个周末休息,加班加点的Coding,出来了一个原型,估计里面还有很多的显而易见的以及隐而未现的bug。
设计这个组件的初衷,是为了能方便的应用于今后的项目中。先进的企业级项目中,变化最为突出的,还是业务,业务的变化,直接导致编码中业务逻辑代码的变化。如果将这些业务逻辑混杂在一起,业务的变化,最终会导致代码的混乱,甚至难以维护。这对于一个长期迭代的项目来说,是相当痛苦的事。就像直接拿一个成千上万行的代码给开发人员,估计开发人员不是晕厥,就是吐血。而一个难以维护的业务逻辑代码,对于每一次迭代,都是一次冒险。很可能在某次的迭代中,会对以前的业务逻辑,产生影响或者风险。
对于复杂的业务逻辑,需要进行重构,并且将不断变化的业务逻辑,抽离出来。比较折中的方法,就是将这些变化的逻辑,做成独立的插件形式。这样一来,独立的业务,在物理上进行了分离。开发人员也不用担心修改某处业务逻辑,会影响其他的业务模块。而动态的插件管理形式,对后期维护也带来益处,独立的业务逻辑插件之间,不会相互影响。开发人员还可以在程序运行而又不影响业务的情况下,动态的发布、替换原有的业务逻辑模块。
Dynamic Plugins Manager(以下简称Plugins Manager),也就是按照这样的思路和需求来设计的,其实现的功能,具体有一下几个特点:
1.所有的插件,都以DLL程序集的方式发布,Plugins Manager在启动的时候,搜索加载所有可加载的插件并进行加载
2.Plugins Manager,支持插件的动态卸载功能。借助于AppDomain的特性,所有的组件,在动态加载后,可以动态从内存中卸载。插件的实现,需要继承自MarshalByRefObject。
3.与接口无依赖性,Plugins Manager负责插件的管理、动态加载、动态卸载、调用。但是和具体的插件接口无关,这样一来,开发人员可以定义自己的插件接口,并开发不同的插件实现接口。
4.插件可以动态发布和卸载,Plugins Manager会监视当前目录中,插件的变化,对于以xcopy方式,拷贝入插件目录的插件,可以进行动态加载。对于插件的卸载,可以Plugins Manager提供的接口进行动态卸载,也可以实时更改插件配置文件中的具体开关进行卸载。
5.动态编译功能:Plugins Manager允许直接发布插件代码。通过读取插件配置文件中的引用信息,可以直接从代码生成dll插件,并加载。
6.允许插件实现不同的构造函数重载,并将构造函数参数以及值信息,定义在插件配置文件中。Plugins Manager在加载插件的时候,采用配置文件中的值,调用相应的构造函数,并加载插件。
7.事件通知:Plugins Manager在不同的插件操作完成后,会触发相应的事件机制,通知客户端程序进行相应的处理
插件的结构一览:
PluginsManager:插件管理类,实现插件功能的主要代码逻辑,以及对插件容器的维护
AssemblyLoader:插件加载类,实现从AppDomain加载插件的逻辑
PluginEntity:插件实体类,包含插件的信息(插件实例、插件名称……)
AssemblyFactory:插件生成类,实现从插件代码生成dll的功能
下一篇日志中,将会介绍这个插件的具体配置,及简单分析其实现。
posted @
2008-12-11 11:43 Cooldog's .NET Space 阅读(139) |
评论 (0) |
编辑
在LA的软件开发者会议(PDC 2008)上,微软首席软件架构师Ray Ozzie宣布了微软的云计算战略以及云计算平台——Windows Azure Service Platform。押注于云计算,出售信息存储空间和云端计算能力,为全球用户提供数据中心,由此可见微软想在互联网平台和其竞争对手一搏的决心。
基于Windows Azure Service Platform,用户可以在不必搭建自己服务器群的情况下创建基于互联网的各种应用。或许从现在开始,我们的整个软件工业,都在面临一个巨大的转折点,从某种意义上说,是一场巨大的变革,从强大的桌面运算进化到在互联网上运行各种轻量级且便宜的各种应用,而用户的体验也不再仅仅局限于桌面应用,而是不断的延伸到互联网。Windows Azure以云计算为核心,提供了软件+服务的计算方法。它也是Azure服务平台的基础。Azure由微软主导开发,用于帮助开发者开发下一代应用程序。这些应用程序将可以跨越云端和专业数据中心,在PC、Web和手机间创造完美的,无缝的用户体验。
对于开发者而言,借助于Windows Azure的灵活性,我们能建立基于云计算的新应用,或者为已有的应用增加云计算的能力。更为方便的是,开发Azure Service应用,不需要开发人员进行其他前端知识的准备,通过充分利用现有的VS开发环境和.NET Framework的技术,我们就能搭建基于云计算的应用了。基于Azure Service 开放、灵活的架构设计,在不久的将来,还将针对更多的开发环境和开发语言提供支持。而Azure Service本身所提供的开放的,基于标准的通用性环境也提供了对众多互联网标准协议的支持,包括:HTTP, REST, SOAP, 和 XML.
一、Windows Azure Service 的服务结构
新的开发模式,新的技术,总是让人为之振奋,Windows Azure Service所提供的组件包括以下几种类型:
Windows Azure
Windows Azure 是一个提供云计算服务的操作系统,作为Azure Service Platform的一部分,为开发、部署云服务以及管理提供环境。
Live Services
为用户可以在PC、手机、PC应用程序和Web网站上存储、共享、同步文档、照片、文件以及任何信息提供服务。
Microsoft SQL Services
Microsoft SQL Service作为Azure Service的一部分,扩展了原有Microsoft SQL Server的功能,是一种基于Web的分布式关系数据库。它提供的基于Web的服务包括查询、搜索以及同步数据到移动用户,远程办公系统以及商业合作伙伴。提供结构化、半结构化以及非结构化的数据存取服务。
Microsoft .NET Services
Microsoft .NET Service 使开发松耦合结构的云端应用更为简单。所提供的功能与.NET Framework的工作流、访问控制等概念相似,但是提供了基于服务的实现。
Microsoft® SharePoint® Services & Dynamics® CRM Services
用于在云端提供针对业务内容、协作和快速开发的服务
二、Windows Azure Service开发流程
进行云端开发,首先需要下载微软所提供的相应的SDK。由于现在Azure Service还未推出正式版,所以我们能下载到的SDK是CTP版本。在Azure Services Developer Portal获取Windows Azure授权,还需要在Azure的官方站点申请邀请码并进行注册。
获取SDK和更多的信息,可以访问Azure的官方站点:
http://www.azure.com/
在前面的介绍中,我们也看到,Azure 是一个灵活的平台,我们能利用它所提供的所有服务或者仅仅是某一项服务进行开发。开发人员能通过现有的Microsoft .NET Framework和Microsoft Visual Studio进行开发,编写基于web或是基于Mobile的应用,或者编写Web Service。在不久的将来,Azure会支持更多微软的或者非微软的开发语言和平台。
开发的流程也相对简单,一旦你完成编码,只需要将的应用部署到Windows Azure的云服务器,终端用户就能够通过Internet访问到你的应用了。
大致了解了Windows Azure,也让我对Azure服务平台的强大能力感到兴奋不已。或许在未来的应用中,Windows Azure能未我们带来的不仅仅是惊喜,而是一场变革。
posted @
2008-12-11 11:41 Cooldog's .NET Space 阅读(96) |
评论 (3) |
编辑
WAS 是 Windows (Process) Activation Service 的缩写,是Windows Vista中所新增的一种进程宿主模型。WAS作为 IIS7.0 特有的新增功能,和以前IIS 6.0的功能相比更加强大,因为它提供并支持除HTTP之外的更多协议,比如TCP方式和Pipe(管道)方式。以下的文中,都把Windows (Process) Activation Service简称为WAS。利用WAS作为WCF(Windows Communication Foundation)的宿主,我们能充分利用WAS的很多优点,因为我们再也不用为非HTTP方式的WCF Service单独编写宿主程序了。而WAS本身的特点,也让我们的服务端程序能享受到只有以往的HTTP方式的WCF Service才能拥有的很多特性。本文也简单向大家分享一下怎样使用WAS来作为WCF Service的宿主。
以往的部署WCF Service的方式,大家一般会想到以下几种:
1. 使用WinForm或者控制台程序作为宿主
2. 使用Windows Service作为宿主
不管我们用怎样的方式来作为WCF Service的宿主,少不了的麻烦,都是需要单独编写Host程序,除此之外,就该轮到WAS了。WAS在Vista中,其实是由一个单独的Windows Service来实现的,如果我们仔细找找,就能找到,Windows Service的名称,就叫做"Windows Process Activation Service"。由此看来,WAS的进程和IIS进程在物理上是隔离开的,能为我们提供一个灵活、稳定的WCF Service宿主环境。WAS内部的工作机制,大致和ASMX WebService类似。
简单了解了WAS的特性,下面我们用一个netTcpBinding的WCF示例来演示怎样利用WAS来Host WCF Service:
首先,我们需要查看Vista的组件中,是否打开了WAS的功能,打开控制面板,打开“程序和功能”对话框,在"打开/关闭 Windows功能"对话框中,确保下面图中的功能项被打开:
其实在这一部操作之后,windows会自动帮我们在IIS中做好配置,不过为了放心,我们还是打开IIS确认一下设置是否正确。
首先是检查IIS站点中的绑定:
确保net.tcp方式,绑定到808端口
打开站点的高级设置,确认"已启用的协议"中,填有"net.tcp",没有的话,可以补上。
然后是建立应用程序目录,这个目录等下会用来部署WCF的Service端程序。建立好目录后,也在"高级设置--已启用的协议"中,填上net.tcp
到这里,基本的host环境我们已经设置好了。下面来实现我们的服务端和客户端。
建立Contract

程序代码
[ServiceContract]
public interface IService1
{
[OperationContract]
string SayHello(string value);
}
实现简单的Service类

程序代码
public class Service1 : IService1
{
public string SayHello(string value)
{
return string.Format("Hello,{0}", value);
}
}
建立svc文件

程序代码
<%@ ServiceHost Language="C#" Debug="true" Service="WCFLibrary.Service1" CodeBehind="./App_Data/Service1.cs" %>
服务端的WCF配置

程序代码
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding">
<security mode="None" />
</binding>
</netTcpBinding>
</bindings>
<services>
<service behaviorConfiguration="WCFLibrary.Service1Behavior" name="WCFLibrary.Service1">
<endpoint binding="netTcpBinding"
bindingConfiguration="NetTcpBinding" contract="WCFLibrary.IService1">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint contract="IMetadataExchange" binding="mexTcpBinding" address="mex" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://Timothy-T61/WCFService/HelloService.svc" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WCFLibrary.Service1Behavior">
<serviceMetadata httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
其实endpoint只需要一个即可,为了方便通过svcutil.exe生成客户端代理,需要通过另外一个endpoint的mexTcpBinding来暴露元数据。这样当服务在发布好的时候,我们可以通过
Svcutil.exe net.tcp://Timothy-T61/WCFService/HelloService.svc/mex 来生成客户端代理了。
完成服务端的编写,直接将服务端程序,部署到刚才在IIS中添加的应用程序目录中。
客户端的wcf配置如下:

程序代码
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="ClientBinding">
<security mode="None" />
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://timothy-t61/WCFService/HelloService.svc" binding="netTcpBinding"
bindingConfiguration="ClientBinding" contract="WCFLibrary.IService1" />
</client>
</system.serviceModel>
客户端拖放一个按钮,添加如下代码:

程序代码
private void button1_Click(object sender, EventArgs e)
{
ClientProxy client = new ClientProxy();
MessageBox.Show(client.SayHello("Timothy!"));
}
一切就绪,运行程序,点击按钮:
没有单独编写host程序,通过WAS,我们的服务端正常运行了。
posted @
2008-12-11 11:35 Cooldog's .NET Space 阅读(88) |
评论 (0) |
编辑
前段时间工作中的一个新需求,有机会用到了Linq to SQL。使用后的第一感觉,就是方便很多,也为整个项目节约了一大把的开发时间,甚至代码量也少了很多。不过在程序的实际运行中,始终会遇到一些莫名其妙的异常,最令人不解的,就是“System.Data.Linq.ChangeConflictException: Row not found or changed.” 。当初凭自己和同事的判断,可能是数据库的数据异常所导致,后来发觉这个异常出现得越来越频繁,于是上MSDN查了查,原来是Linq中一个常见的问题:更新冲突。
这个词说起来比较玄乎,其实再平常不过了。下面可以通过一个简单的例子,来重现这个异常。
建立一个普通的测试表:LinqTest(如图)
在测试表中,插入一条测试数据(如图)
测试代码如下:

程序代码
namespace LinqTest
{
class Program
{
static void Main(string[] args)
{
TestDataContext db = new TestDataContext();
db.Log = Console.Out;
var result = from p in db.LinqTests
where p.ID == 1
select p;
var info = result.FirstOrDefault();
if(info != null) //插入断点
{
info.Age = 25;
db.SubmitChanges();
}
Console.ReadLine();
}
}
}
在测试代码中,将DataContext的日志定向到Console的输出部分,这样方便我们观察Linq实际执行的SQL语句是什么。重现的时候,我们需要在注释的地方,插入断点进行测试。对于示例中的代码,在正常情况下,是不会有错误的。执行过后,我们可以在Console的输出中,看到实际执行的SQL语句(如图)
再进行第二次调试,首先,恢复Age的数据到以前的样子。下面我们运行到断点处,然后偷偷去SQL Server Management Studio中,手动修改数据,将原始数据中的Age,由24,改为22。然后回到VS2008的IDE,按F5继续运行程序,这个时候,你会发现异常出现了(如图)
再回到Console的输出,查看,执行的SQL语句和刚才的一样。这就是问题的所在,在正常运行状态下,Linq在运行时,会把数据库的数据缓存到实体对象中,这是一种理想化的情况,并且在更新时,Linq会默认把除更新字段外的所有字段,作为Update语句中的Where条件。但是,如果此时有另外的程序,在访问数据库,并修改数据库数据的时候,比如刚才把Age改为22。此时Linq缓存起来的数据和实际数据库中的数据产生了不一致的情况。Linq此时仍然把被修改过的字段,作为Update的Where条件,但是数据库中Age早就被我们改过了,不再是25,Where条件始终匹配不到原有的数据。这时,就会抛出所谓的:“System.Data.Linq.ChangeConflictException: Row not found or changed.”异常。
产生此异常,主要是Linq缓存数据和实际数据库数据不一致的情况造成。解决次问题的情况,主要有几种:
1.比较简单的方法,不使用Linq提供的SubmitChanges()方式提交更改,而直接执行SQL语句,例如:
db.ExecuteCommand("Update [dbo].[LinqTest] SET Age=25 Where ID = @p0", 1);
这样虽然比较方便,但是感觉又回到了直接写SQL的时代,毕竟Linq to SQL的目的,就是为了让我们看不见SQL,避免写复杂的SQL语句,而直接操作实体对象,这样也可以避免程序可读性差、不便于维护。所以除非万不得已,还是不太推荐使用此方法。
2.参考MSDN的资料,采用Linq提供的解决更新冲突的方法,在异常中捕获冲突,然后手动解决冲突:

程序代码
try
{
db.SubmitChanges(System.Data.Linq.ConflictMode.ContinueOnConflict);
}
catch (System.Data.Linq.ChangeConflictException ex)
{
foreach (System.Data.Linq.ObjectChangeConflict occ in db.ChangeConflicts)
{
//以下是解决冲突的三种方法,选一种即可
// 使用当前数据库中的值,覆盖Linq缓存中实体对象的值
occ.Resolve(System.Data.Linq.RefreshMode.OverwriteCurrentValues);
// 使用Linq缓存中实体对象的值,覆盖当前数据库中的值
occ.Resolve(System.Data.Linq.RefreshMode.KeepCurrentValues);
// 只更新实体对象中改变的字段的值,其他的保留不变
occ.Resolve(System.Data.Linq.RefreshMode.KeepChanges);
}
// 这个地方要注意,Catch方法中,我们前面只是指明了怎样来解决冲突,这个地方还需要再次提交更新,这样的话,值 //才会提交到数据库。
db.SubmitChanges();
}
3. 这个方法也比较简单,也即MSDN中所说的Pessimistic Concurrency Control 。 我们可以来设定哪些字段需要放入Where条件,哪些字段不需要,这样就可以控制更新时候的条件匹配尺度。具体做法,就是在Linq to SQL Designer中,把一些字段的UpdateCheck属性设置为Never,这样,这些字段在更新的时候,就不会再出现在Where条件中了。其实比较推荐的做法,就是在表中设立主键,因为更新的时候,只要把主键作为Where条件,就可以单独的确立一行数据了。把除主键外的字段属性中UpdateCheck设置为Never即可。
关于Linq to SQL中如何管理更改冲突的更多资料,可以在MSDN找到
http://msdn.microsoft.com/zh-cn/library/bb399389.aspx
posted @
2008-12-11 11:33 Cooldog's .NET Space 阅读(69) |
评论 (0) |
编辑
摘要:
阅读全文
posted @
2008-12-11 11:31 Cooldog's .NET Space 阅读(30) |
评论 (0) |
编辑