一、提供服务
有时候我们在利用WCF开发SOA服务时,可能需要使用编码的方式来提供服务,而没有使用配置文件的方式。这里我写了一个助手方法来方便实现这方面的需求,还是先来看一下代码:
{
public static void OpenPipeHost<S, C>(
ServiceHost serviceHost, string netPipeAddress, string timeout) where S : class, C, new()
{
Uri baseAddress = new Uri(netPipeAddress);
TimeSpan receiveTimeout = TimeSpan.Parse(timeout);
if (serviceHost != null && serviceHost.State != CommunicationState.Closed)
{
serviceHost.Close();
}
serviceHost = new ServiceHost(typeof(S), baseAddress);
//加载元数据结点
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
serviceHost.Description.Behaviors.Add(smb);
serviceHost.AddServiceEndpoint(typeof(IMetadataExchange),
MetadataExchangeBindings.CreateMexNamedPipeBinding(), "mex");
NetNamedPipeBinding binding = new NetNamedPipeBinding();
binding.Security.Mode = NetNamedPipeSecurityMode.None;
binding.ReceiveTimeout = receiveTimeout;
binding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
binding.MaxReceivedMessageSize = (long)int.MaxValue;
serviceHost.AddServiceEndpoint(typeof(C), binding, baseAddress);
//打开服务
serviceHost.Open();
}
public static void OpenTcpHost<S, C>(
ServiceHost serviceHost, string netTcpAddress, string timeout) where S : class, C, new()
{
Uri baseAddress = new Uri(netTcpAddress); //net.pipe://localhost/SpecialProvider
TimeSpan receiveTimeout = TimeSpan.Parse(timeout); //00:00:03
if (serviceHost != null && serviceHost.State != CommunicationState.Closed)
{
serviceHost.Close();
}
serviceHost = new ServiceHost(typeof(S), baseAddress);
//加载元数据结点
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
serviceHost.Description.Behaviors.Add(smb);
serviceHost.AddServiceEndpoint(typeof(IMetadataExchange),
MetadataExchangeBindings.CreateMexTcpBinding(), "mex");
//服务通过tcp通道结点加载
NetTcpBinding binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.None;
binding.ReceiveTimeout = receiveTimeout;
binding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
binding.MaxReceivedMessageSize = (long)int.MaxValue;
serviceHost.AddServiceEndpoint(typeof(C), binding, baseAddress);
//打开服务
serviceHost.Open();
}
public static void CloseHost(ServiceHost serviceHost)
{
if (serviceHost != null && serviceHost.State != CommunicationState.Closed)
{
serviceHost.Close();
serviceHost = null;
}
}
}
由代码可见,我们针对NetPipe和NetTcp分别提供了不同的泛型方法,泛型方法就像一个模板提供了一定程度的多态。
void OpenPipeHost<S, C>(ServiceHost serviceHost, string netPipeAddress, string timeout) where S : class, C, new()
方法都需要指定两个类型S和C,并且使用了泛型约束,规定S是继承于C的,在这里C表示Contract接口,S表示实现Contract的实现类。
方法还需要传入需依赖的ServiceHost对象,并且为了处理不同的服务需要传入服务对应的Address,当然这里的Address需要以"net.tcp://"开头的,同时为了保证服务在阻塞时能及时返回超时提示,我们还需要提供一个超时设定。
注意:这里的超时设定是字符串的形式并最终转换为TimeSpan对象,例如:00:00:03表示3秒钟,通过使用TimeSpan.Parse("00:00:03")来转为内置的类型,如果你要表示0.5秒就可以用"00:00:00.5"
另外:针对NetPipe和NetTcp都提供了mex元数据的服务终结点,这些就可以在VS中添加服务时发现服务的元数据,此时的URL可以去掉mex,生成代理会自动去添加的,例如:
net.tcp://localhost:8860/SpecialProvider
net.tcp://localhost:8860/SpecialProvider/mex
net.pipe://localhost/SpecialProvider
net.pipe://localhost/SpecialProvider/mex
Now, We have a helper class, and then how to use it? Let us to see follow codes:
{
private ServiceHost tcpHost = null;
private ServiceHost pipeHost = null;
public SpecialServiceSupport()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
//启动特定服务
ServiceHelper.OpenTcpHost<SpecialProvider, ISpecialProvider>(tcpHost,
ConfigurationManager.AppSettings["SpecialProvider_NetTcpAddress"],
ConfigurationManager.AppSettings["ReceiveTimeout"]);
ServiceHelper.OpenPipeHost<SpecialProvider, ISpecialProvider>(pipeHost,
ConfigurationManager.AppSettings["SpecialProvider_PipeAddress"],
ConfigurationManager.AppSettings["ReceiveTimeout"]);
}
protected override void OnStop()
{
ServiceHelper.CloseHost(tcpHost);
ServiceHelper.CloseHost(pipeHost);
}
}
You can see, we just simply invoke the methods of ServiceHelper, and we can start and stop our services.
We configure this application is smple as well, just set the appSettings like this:
<add key="SpecialProvider_NetTcpAddress" value="net.tcp://localhost:8860/SpecialProvider"/>
<add key="SpecialProvider_PipeAddress" value="net.pipe://localhost/SpecialProvider"/>
二、使用服务。
通常我们在应用WCF时在系统框架上就会把服务的接口和服务的实现分开,例如:SpecialService.Contract是一个契约,而SpecialService.Service是Contract的一个实现。如果我们不是能过引用服务的方式来使用WCF服务,这样我们就会需要写一点代码,此时在服务的使用方会直接引用SpecialService.Contract这个组件,如果我们组件更合理一些,应该有一个专门的Model组件,例如:SpecialService.ComponentModel 它包含了所有需要用到的业务Model定义。所以服务Invoker就需要引用两个组件Contract和ComponentModel。
当然,我们可以让客户自已处理调用的问题,但是为什么我们就不可以把这样的调用也封装一下,这样使得服务调用透明起来,并且可以很方便的在NetPipe和NetTcp之间进行切换。我们可以想是这样调用了:
ISpecialProvider iSpecialProvider = ClientFactory.Create<ISpecialProvider>();
ISpecialProvider iSpecialProvider = ClientFactory.Create<ISpecialProvider>(string implType, string sendTimeout, string address);
通过传入不同的实现方式、地址和超时时间就可以很方便的返回我们所要的服务接口。
Okay, 我们看一下这个ClientFactory是如何实现的:
{
public static T Create<T>() where T : class
{
string typeName = typeof(T).Name;
//ISpecialProvider.ImplType
string implType =
ConfigurationManager.AppSettings[string.Format("{0}.ImplType", typeName)];
//ISpecialProvider.SendTimeout
string sendTimeout =
ConfigurationManager.AppSettings[string.Format("{0}.SendTimeout", typeName)];
//ISpecialProvider.Address
string address =
ConfigurationManager.AppSettings[string.Format("{0}.Address", typeName)];
if (string.IsNullOrEmpty(implType))
throw new ConfigurationErrorsException(
string.Format("请必须提供有效的{0}.ImplType配置", typeName));
if (string.IsNullOrEmpty(sendTimeout))
throw new ConfigurationErrorsException(
string.Format("请必须提供有效的{0}.SendTimeout配置", typeName));
if (string.IsNullOrEmpty(address))
throw new ConfigurationErrorsException(
string.Format("请必须提供有效的{0}.Address配置", typeName));
return Create<T>(implType, sendTimeout, address);
}
public static T Create<T>(string implType, string sendTimeout, string address) where T : class
{
T t = default(T);
Type type = Type.GetType(implType);
if (type != null)
{
t = (T)Activator.CreateInstance(type, new object[] { sendTimeout, address });
}
if (t == default(T))
throw new Exception(
string.Format("未能成功调用{0}接口的实现,请检查配置文件或传入参数", typeof(T).Name));
return t;
}
}
也是两个泛型方法,可见泛型为我们提供了新的多态形式,很是实用。
static T Create<T>() where T : class 是一个通地配置文件来指定通道、地址、超时的默认方式,使用这种方式是最简单了,应该优先使用这种方式,这里的T指定为必须是一个引用类型。
static T Create<T>(string implType, string sendTimeout, string address) where T : class 这是一个具体方法,它需要传入多个参数:
T 表示调用服务后转换的接口类型,在这个例子就是ISpecialProvider接口。
implType 表示使用何种通道方式去调用服务,返回我们想用的Contract
sendTimeout 表示超时时间
address 表示调用服务的地址,它应该与implType有关联的。
另外,在代码中我们可以看到通过反射来调用 (T)Activator.CreateInstance(type, new object[] { sendTimeout, address }); 来动态调用implType指定的通道方式,并且会把sendTimeout和address参数传给对应的实现中。
Next step! 我们看一下这样的implType到底指的是什么?
为了提供一个统一的骗程模型,我们这里创建一个抽象类ServiceClient<T> 它定义了一个通用的调用服务所需的资源(属性和方法),代码如下:
{
public string SendTimeout { get; set; }
public string Address { get; set; }
public ServiceClient()
{
this.SendTimeout = "00:00:03";
this.Address = "";
}
public ServiceClient(string sendTimeout, string address)
{
if (string.IsNullOrEmpty(address))
throw new ArgumentNullException("必须提供address参数");
this.SendTimeout = sendTimeout;
this.Address = address;
}
protected abstract T CreateFromChannelFactory();
}
其中,SendTimeout和Address就是我们在ClientFactory中通过反射创建的实现时需要传递的参数,也就是说所有合符ClientFactory调用规则的实现都会传递这两个参数,无论是tcp、pipe、http等。
另外,定义了一个抽象方法protected abstract T CreateFromChannelFactory(); 它表示从ChannelFactory中创建出调用的目标接口,要注意的是从这个方法反回的T需要进行关闭,虽然有使用时它看上只是一个接口,但实质上它是一个与通道有关的对象,在通道调用完后需要关闭,可以使用如下方式进行关闭:
using (iSpecialProvider as IDisposable)
{
flag = iSpecialProvider.Ping();
}
Okay, 已经有了一个抽象类了,现在就需要为每种接口请况创建一个实现,这个实现是继承于ServiceClient<T>的,当然针对于不同的接口,T就会变成相应的接口名称了,例如:ServiceClient<ISpecialProvider> ,还得注意我们创建这个实现类又是一个抽象类,为什么呢?因为我们需要为不同的服务接口定义不同的通道实现,而不同的通道实现所调用的服务接口是相同的,所以我们把它定义为一个抽象就可以方便给不同的通道实现继承使用了。看一下针对ISpecialProvider这个接口的抽象类的代码:
一切都准备好了,我们来为接口的服务调用提供不同的通道实现吧,其实很简单,
针对TCP的实现就用:internal class SpecialProviderTcpClient : SpecialProviderClient
针对Pipe的实现就用:internal class SpecialProviderPipeClient : SpecialProviderClient
三、调用例子。
服务和客户端的调用封装都做好了,现在就剩下如何调用了,通过ClientFactory.Create<ISpecialProvider>方法就可以很容易的返回ISpecialProvider接口的调用了,这里就只给出例子:
最后补充一下:我们在WCF服务的调用中如果需要抛出异常,并且这种异常需要传道到调用方,这样需要做一定的处理,
可以参考<<WCF的错误处理>>。
这里只提及较为简单的方式是组装一下FaultException新异常重新抛出:
{
throw new ArgumentNullException("kkkkkkk");
}
catch (Exception ex)
{
ExceptionDetail detail = new ExceptionDetail(ex);
throw new FaultException<ExceptionDetail>(detail, ex.Message);
}