code4fun: one service,one config

WCF的承载既可以通过编码(Code)实现,也能够通过配置(Config)实现.而且使用配置,更有利于日后的维护和扩展.WCF缺省的是将全部的服务配置都写到一个config文件中去.这种方式更有利于专业的IT人员对服务进行集中式管理.在很多项目中,这种方式也能解决分工问题.但是它也有一定的弊端,比如: 一个无关紧要的service变动,就需要变动config,而对应用程序config的任何修改非常有可能导致应用程序的重启或者异常.比如在网站中,如果更改web.config会导致Session,Application等的丢失.这些情况就会影响用户的体验.本文使用自定义ServiceHost的方式,实现一个Service对应一个config文件,这样当有service发生修改的时候,管理人员就只需要更改对应的config文件。更好的实现了服务与服务之间的隔离。

1)自定义配置文件的实现思路和关键点

在ServiceHost的父类ServiceHostBase中,有一个和配置文件的加载密切相关的方法,它为:

protected virtual void ApplyConfiguration();

这个方法用于将应用程序配置文件中<system.serviceModel>节点下的配置信息,转换成WCF的具体服务设置。那么重写这个方法,应该就能实现上述目的。

2)自定义配置文件的策略

缺省的策略是服务与配置是多对一的关系,一个配置中包含全部的服务配置。那配置文件也就是默认的应用程序的配置文件了,在网站中是web.config,而在windows应用程序中,配置文件是应用程序名.config。而本文中要实现的自定义配置的策略是一个服务就对应一个配置文件,配置文件的名称就是服务的全名。比如有一个服务名字为:Robin.Wcf.OnetoOne.Services.Calculator,那与其对应的服务配置文件名称就是:Robin.Wcf.OnetoOne.Services.Calculator.config。同时还需要考虑这个文件的位置。我们制定一个策略:自定义的服务配置文件和缺省应用程序配置文件存放到同一个目录。

实现过程:

第一步:创建一个解决方案,名称为:Robin.Wcf.OnetoOne,并在其中添加三个项目,分别为:

Robin.Wcf.OnetoOne.ServiceLib

Robin.Wcf.OnetoOne.Services

Robin.Wcf.OnetoOne.Services.Web

这三个项目的作用分别为:

Robin.Wcf.OnetoOne.ServiceLib中用于创建契约和实现服务,并且创建自定义的ServiceHost和自定义的ServiceHostFactory

Robin.Wcf.OnetoOne.Services是对Robin.Wcf.OnetoOne.ServiceLib实现服务的一种自托管(self hosting),它最终是一个Console Application

Robin.Wcf.OnetoOne.Services.Web是对Robin.Wcf.OnetoOne.ServiceLib实现服务的iis托管。

第二步:实现服务

在Robin.Wcf.OnetoOne.ServiceLib中创建两个文件:ICalculator.cs和Calculator.cs,它们分别用于服务契约和服务实现,具体代码如下:

ICalculator.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace Robin.Wcf.OnetoOne.Services
{
[ServiceContract]
public interface ICalculator
{
[OperationContract]
int Add(int a, int b);
}
}

Calculator.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Robin.Wcf.OnetoOne.Services
{
public class Calculator:ICalculator
{
public int Add(int a, int b)
{
return a + b;
}
}
}

实现的服务非常的简单,因为我们的目的也不是要实现非常复杂的业务逻辑,而关键在于修改框架的使用方式上面。

第三步:实现自定义的ServiceHost,名称为:MyServiceHost。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Configuration;
using System.Configuration;
namespace Robin.Wcf.OnetoOne.Services
{
public class MyServiceHost:ServiceHost
{
public MyServiceHost(object singletonInstance, params Uri[] baseAddresses)
: base(singletonInstance, baseAddresses)
{
}
public MyServiceHost(Type serviceType, params Uri[] baseAddresses):base(serviceType,baseAddresses)
{
}
/// <summary>
/// override ApplyConfiguration to load config from custom file
/// </summary>
protected override void ApplyConfiguration()
{
//get custom config file name by our rule: config file name = ServiceType.Name
var myConfigFileName = this.Description.ServiceType.FullName;
//get config file path
string dir = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
string myConfigFilePath = System.IO.Path.Combine(dir, myConfigFileName + ".config");
if (!System.IO.File.Exists(myConfigFilePath))
{
base.ApplyConfiguration();
return;
}
var configFileMap = new System.Configuration.ExeConfigurationFileMap();
configFileMap.ExeConfigFilename = myConfigFilePath;
var config = System.Configuration.ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None);
var serviceModel = System.ServiceModel.Configuration.ServiceModelSectionGroup.GetSectionGroup(config);
if (serviceModel == null)
{
base.ApplyConfiguration();
return;
}
foreach (ServiceElement serviceElement in serviceModel.Services.Services)
{
if (serviceElement.Name == this.Description.ServiceType.FullName)
{
LoadConfigurationSection(serviceElement);
return;
}
}
throw new Exception("there is no service element match the description!");
}
}
}

在自定义的ServiceHost中,主要是通过重写(override)ApplyConfiguration方法来实现,将服务的全名,也就是this.Description.ServiceType.FullName作为配置文件的文件名,然后仍然使用.config作为后缀。使用缺省应用程序配置文件的路径作为自定义配置文件路径,缺省的配置文件路径的获取方法是:

string dir = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
下面涉及到一个难题,如何将自定义文件作为系统配置文件,下面的代码段解决了这个问题:
var configFileMap = new System.Configuration.ExeConfigurationFileMap();
configFileMap.ExeConfigFilename = myConfigFilePath;
var config = System.Configuration.ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None);
最后得到的config是System.Configuration.Configuration类别的一个实例。
将自定义的文件作为配置并生成配置对象后,接下来要做的就是在此Configuration对象中寻找ServiceModeSectionGroup,然后通过在ServiceModeSectionGroup中获取
和当前要承载的服务名称相等的ServiceElement,方法如下面的代码所示:
var serviceModel = System.ServiceModel.Configuration.ServiceModelSectionGroup.GetSectionGroup(config);
if (serviceModel == null)
{
base.ApplyConfiguration();
return;
}
foreach (ServiceElement serviceElement in serviceModel.Services.Services)
{
if (serviceElement.Name == this.Description.ServiceType.FullName)
{
LoadConfigurationSection(serviceElement);
return;
}
}
throw new Exception("there is no service element match the description!");

最后,如果在配置中找不到与承载服务匹配的节点,就抛出异常。

第四步:实现自定义的ServcieHostFactory:MyServiceHostFactory

如果承载单纯是Consol Application或者其他非Web应用程序,实现了第三步就可以了。但在Web的.svc中,没有显示的指定ServiceHost的Factory,那它在默认情况下是使用ServiceHostFactory的,而ServiceHostFactory产生的是ServiceHost对象,想要使用我们上一步中自定义的MyServiceHost,需要实现一个自定义的ServcieHostFactory,实现代码非常简单:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Activation;
using System.ServiceModel;
using System.Reflection;
namespace Robin.Wcf.OnetoOne.Services
{
public class MyServiceHostFactory:ServiceHostFactory
{
protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return  new MyServiceHost(serviceType, baseAddresses);
}
}
}

其实,就是在CreateServiceHost方法中返回一个MyServiceHost就可以了。

第五步:实现Console的承载

在Robin.Wcf.OnetoOne.Services中,新建一个config文件,名称为:Robin.Wcf.OnetoOne.Services.Calculator.config,注意名称和已经实现的服务的全名称一致。将其内容设置为:

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="Robin.Wcf.OnetoOne.Services.Calculator">
<host>
<baseAddresses>
<add baseAddress="http://127.0.0.1:5698"/>
</baseAddresses>
</host>
<endpoint address="Calculator" binding="wsHttpBinding" contract="Robin.Wcf.OnetoOne.Services.ICalculator"></endpoint>
</service>
</services>
</system.serviceModel>
</configuration>

注意了,自定义的配置文件也是配置文件,它也需要和缺省应用程序的配置文件格式一致,首先要有<configuration> 节点。

然后,设置Programe.cs的代码为:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Robin.Wcf.OnetoOne.Services
{
public class Programe
{
static void Main(string[] args)
{
using(MyServiceHost host = new MyServiceHost(typeof(Calculator)))
{
host.Opened+=new EventHandler(delegate(object sender,EventArgs e){
Console.WriteLine("Service is Opened!");
foreach (var channelDiapacher in host.ChannelDispatchers)
{
Console.WriteLine("listen url:" + channelDiapacher.Listener.Uri.ToString());
}
});
host.Open();
}
Console.Read();
}
}
}

第六步:实现IIS承载

在Robin.Wcf.OnetoOne.Services.Web中,首先引用Robin.Wcf.OnetoOne.ServiceLib,然后同样添加一个自定义配置文件:Robin.Wcf.OnetoOne.Services.Calculator.config。代码为:

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="Robin.Wcf.OnetoOne.Services.Calculator" behaviorConfiguration="Robin.Wcf.OnetoOne.WebHost.Service1Behavior">
<endpoint binding="wsHttpBinding" contract="Robin.Wcf.OnetoOne.Services.ICalculator"></endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="Robin.Wcf.OnetoOne.WebHost.Service1Behavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>

将Service.svc的代码更改为:

<%@ ServiceHost Language="C#" Debug="true" Service="Robin.Wcf.OnetoOne.Services.Calculator"
Factory="Robin.Wcf.OnetoOne.Services.MyServiceHostFactory" %>

注意Service与Factory的配置。

 

接下来的一步就是验证我们的实现是否可行了,启动Robin.Wcf.OnetoOne.Services,得到如下的程序输出:

image

结合上面的代码我们可以发现,配置在Robin.Wcf.OnetoOne.Services.Calculator.config中的配置已经起到了预期的效果。说明在Console中可行

 

下面启动Robin.Wcf.OnetoOne.Services.Web中的Service.svc,能得到如下的输出结果:

image

表明,在iis的承载中也工作正常。

 

参考资料:

Custom Config file for a WCF Service hosted in IIS   

 

示例代码:http://files.cnblogs.com/jillzhang/Robin.Wcf.OnetoOne.rar

 

Next Topic : One Host,Many Service!

posted @ 2008-11-02 22:42  Robin Zhang  阅读(4042)  评论(9编辑  收藏