WCF 服务中元数据的地址问题
初用WCF的朋友可能会遇到这样的问题,就是在使用svcutil.exe生成proxy和config的时候,或者利用add service reference添加引用的时候,部署的WCF服务到底它的metadata是什么。或者换句话说,svcutil的URL参数,以及添加服务引用时候的那个Address,到底应该填什么。
在这里我用两个最常用的Binding方式,WSHttpBinding和NetTcpBinding,分别以实际的例子来进行说明。
建立服务契约
在这里就从MSDN上抄一个四则运算的服务来作为我们的素材。建立一个控制台程序,添加System.ServiceModel引用,然后添加下面两个文件:
ICalculatorService.cs :
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace Nocturne.Learning.WcfAddressDemo
{
[ServiceContract]
public interface ICalculatorService
{
[OperationContract]
double Add(double n1, double n2);
[OperationContract]
double Subtract(double n1, double n2);
[OperationContract]
double Multiply(double n1, double n2);
[OperationContract]
double Divide(double n1, double n2);
}
}
CalculatorService.cs:
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace Nocturne.Learning.WcfAddressDemo
{
public class CalculatorService : ICalculatorService
{
public double Add(double n1, double n2)
{
double result = n1 + n2;
Console.WriteLine("Received Add({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Subtract(double n1, double n2)
{
double result = n1 - n2;
Console.WriteLine("Received Subtract({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Multiply(double n1, double n2)
{
double result = n1 * n2;
Console.WriteLine("Received Multiply({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Divide(double n1, double n2)
{
double result = n1 / n2;
Console.WriteLine("Received Divide({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
}
}
下面我们就分别来看看怎样HOST这个服务,怎样获取它的元数据。
WSHttpBind方式
这种方式下,元数据可以直接从http地址中获得。先看看下面这段启动服务的代码(注意,我只使用code方式启动,如果存在app.config,请将其删除)。
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Description;
namespace Nocturne.Learning.WcfAddressDemo
{
class Program
{
static void Main(string[] args)
{
Uri baseAddress=new Uri("http://localhost:8009/MyService");
ServiceHost host = new ServiceHost(typeof(CalculatorService), baseAddress);
ServiceMetadataBehavior smb = new ServiceMetadataBehavior()
{
HttpGetEnabled = true
};
host.Description.Behaviors.Add(smb);
host.AddServiceEndpoint(typeof(ICalculatorService), new WSHttpBinding(), baseAddress);
host.Open();
Console.WriteLine("The service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
}
}
}
启动该工程,这时候就可以访问这个服务了。注意这里的baseAddress,决定了该host是采用基地址访问,而这个“访问”,仅仅是针对endpoint的位置来说的,理解这个概念非常重要。如果我们在将host.AddServiceEndpoint()的最后一个参数改为一个string,比如"CalculatorService“,影响的只是该服务的endpoint地址,对元数据没有影响。
这个时候,我们就可以通过svcutil来生成辅助文件了,命令如下:
D:\Program\temp>svcutil http://localhost:8009/MyService /language:cs /out:Proxy.cs /config:app.config
Microsoft (R) Service Model Metadata Tool
[Microsoft (R) Windows (R) Communication Foundation, Version 3.0.4506.2152]
Copyright (c) Microsoft Corporation. All rights reserved.
Attempting to download metadata from 'http://localhost:8009/MyService' using WS-Metadata Exchange or DISCO.
Generating files...
D:\Program\temp\Proxy.cs
D:\Program\temp\app.config
其中的URL参数,就是代码中写到的baseAddress,在后面添加endpoint的时候不管最后那个参数写的是啥,这个命令都这么写,因为metadata是属于一个host的,并不属于一个endpoint。
如果是通过给工程添加Service Reference,也是在Address里填入这个baseAddress。如图所示:
之后该怎么玩就悉听尊便了。
NetTcpBinding方式
由于在这种方式下,服务本身是通过NetTcp方式来与客户端应答的,元数据就得另开一个mex的endpoint,来专门提供。看下面的代码:
using System.ServiceModel;
using System.ServiceModel.Description;
namespace Nocturne.Learning.WcfAddressDemo
{
class Program
{
static void Main(string[] args)
{
Uri baseAddress = new Uri("net.tcp://localhost:8009/MyService");
ServiceHost host = new ServiceHost(typeof(CalculatorService), baseAddress);
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
host.Description.Behaviors.Add(smb);
host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexTcpBinding(), "mex");
host.AddServiceEndpoint(typeof(ICalculatorService), new NetTcpBinding(), baseAddress);
host.Open();
Console.WriteLine("The service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
}
}
}
与前面建立WSHttpBinding的代码有两个不同的地方。首先是smb里取消了HttpGetEnabled=true的属性设置,这是由于我们的基地址不是HTTP地址,会引发异常,错误信息是:The HttpGetEnabled property of ServiceMetadataBehavior is set to true and the HttpGetUrl property is a relative address, but there is no http base address. Either supply an http base address or set HttpGetUrl to an absolute address。第二点就是最关键的元数据地址,添加了一个MexTcpBinding绑定类型的endpoint,它就承担为整个service对外提供元数据的任务。元数据的地址就是在基址后面加了“mex”的形式,在这里就是net.tcp://localhost:8009/MyService/mex。在实际应用中,这个“mex”可以省略,即只用net.tcp://localhost:8009/MyService,系统会自动去寻找mex,而如果在前面建立endpoint的时候,用的是其它的名称(比如mex1),那就不能省略了。建议使用完整路径。
D:\Program\temp>svcutil net.tcp://localhost:8009/MyService/mex /language:cs /out:Proxy.cs /config:app.config
Microsoft (R) Service Model Metadata Tool
[Microsoft (R) Windows (R) Communication Foundation, Version 3.0.4506.2152]
Copyright (c) Microsoft Corporation. All rights reserved.
Attempting to download metadata from 'net.tcp://localhost:8009/MyService' using WS-Metadata Exchange. This URL does not support DISCO.
Generating files...
D:\Program\temp\Proxy.cs
D:\Program\temp\app.config
Add Service Reference的操作类同。
小结
Address和元数据,是部署WCF服务时最最基本的概念,有必要非常熟练地掌握,笔者也只是对常用的两种绑定方式做了一个讨论,更多的内容还有待发掘。