Author:李会军
Date:2008年10月27日
概述
在WCF专题系列( 1):深入 WCF寻址 Part1 一文中,我们对Web 服务寻址规范做了一些认识,了解了终结点引用和消息信息报头两种结构,该规范在Web 服务中的地位举足轻重,后续我们会经常提到该规范。在本文中,我们将继续深入WCF 寻址的内容,包括元数据中的终结点地址,自定义寻址标头等相关信息。
终结点地址定义
了解了Web 服务寻址规范,再回到WCF ,在WCF 中,终结点地址是由EndpointAddress 类来表示的,它其中很重要的几个部分是:一个表示服务地址的统一资源定位符(URI) ,一个表示服务的安全标识的 Identity 和一个可选的 Headers 集合,其中Headers 用于标识终结点或与终结点交互的更多详细寻址信息。如图1 所示:
图1
记的我在WCF专题系列( 1):深入 WCF寻址 Part1 一文提到过,每个终结点引用都可以包含一些添加额外标识信息的引用参数,即寻址标头,在WCF 中,将这些引用参数建模为 AddressHeader 类的实例,这里的Headers 属性就是这些实例的集合,可以通过AddressHeader 类提供的静态方法CreateAddressHeader 来创建一个AddressHeader 实例,如下代码所示:
AddressHeader header = AddressHeader.CreateAddressHeader("basic",
"http://www.cnblogs.com/terrylee", "Terrylee");
指定终结点地址
在WCF 中提供了基址技术,这使的我们在指定终结点地址时可以酌情选用相对地址或者绝对地址,指定绝对地址的方法是在终结点定义中提供完全限定的地址,如下代码所示:
<service name="TerryLee.WCFAddressing.Service.CalculatorService"
behaviorConfiguration="calculatorBehavior">
<endpoint address="https://localhost:8887/CalculatorService"
binding ="basicHttpBinding"
contract="TerryLee.WCFAddressing.Contract.ICalculator">
</endpoint>
</service>
使用绝对地址固然简单,但是如果我们的服务需要公开多个终结点,而这些终结点地址又具有相同的基地址时,也许相对地址是更好的选择。在创建服务宿主对象时,提供一个基地址,如下代码所示:
using (ServiceHost calculatorServiceHost =
new ServiceHost(typeof(CalculatorService),
new Uri("https://localhost:8887/CalculatorService")))
{
calculatorServiceHost.Opened += delegate
{
Console.WriteLine("Service begin to listen via the Address:{0}",
calculatorServiceHost.BaseAddresses[0].ToString());
};
calculatorServiceHost.Open();
Console.Read();
}
又或者同时在配置文件中指定基地址,这样就无须在每个终结点中指定绝对地址了,如下代码所示:
<service name="TerryLee.WCFAddressing.Service.CalculatorService"
behaviorConfiguration="calculatorBehavior">
<host>
<baseAddresses>
<add baseAddress="https://localhost:8887/Calculator"/>
</baseAddresses>
</host>
<endpoint address="myservice1"
binding ="basicHttpBinding"
contract="TerryLee.WCFAddressing.Contract.ICalculator">
</endpoint>
<endpoint address="myservice2"
binding ="wsHttpBinding"
contract="TerryLee.WCFAddressing.Contract.ICalculator">
</endpoint>
</service>
但请注意,基址技术是为我们在配置终结点时提供了方便,客户端对它是毫无所知的,客户端看到的仍然是绝对地址,在打开服务宿主时,它会匹配所有的相对地址,从而为每个终结点提供相应的绝对地址,如上面的示例,可以在WSDL 中看到:
<wsdl:service name="CalculatorService">
<wsdl:port name="BasicHttpBinding_ICalculator" binding="tns:BasicHttpBinding_ICalculator">
<soap:address location="https://localhost:8887/Calculator/myservice1" />
</wsdl:port>
<wsdl:port name="WSHttpBinding_ICalculator" binding="tns:WSHttpBinding_ICalculator">
<soap12:address location="https://localhost:8887/Calculator/myservice2" />
<wsa10:EndpointReference>
<wsa10:Address>https://localhost:8887/Calculator/myservice2</wsa10:Address>
<Identity xmlns="https://schemas.xmlsoap.org/ws/2006/02/addressingidentity">
<Upn>TerryLee-PC\TerryLee</Upn>
</Identity>
</wsa10:EndpointReference>
</wsdl:port>
</wsdl:service>
如果在指定了基地址的情况下,有以下几种情况:指定相对地址为空,终结点地址与基地址相同;指定相对地址不为空,追加相对地址到基地址上;指定一个绝对地址,基地址不起作用,终结点地址仍然为指定的绝对地址;指定一个绝对地址和一个与基地址不同的绑定,基地址不起作用。现在有这样一段配置信息:
<service name="TerryLee.WCFAddressing.Service.CalculatorService"
behaviorConfiguration="calculatorBehavior">
<host>
<baseAddresses>
<add baseAddress="https://localhost:8887/Calculator"/>
</baseAddresses>
</host>
<endpoint address=""
binding ="wsHttpBinding"
contract="TerryLee.WCFAddressing.Contract.ICalculator">
</endpoint>
<endpoint address="myservice2"
binding ="wsHttpBinding"
contract="TerryLee.WCFAddressing.Contract.ICalculator">
</endpoint>
<endpoint address="https://localhost:8886/CalculatorService"
binding ="wsHttpBinding"
contract="TerryLee.WCFAddressing.Contract.ICalculator">
</endpoint>
<endpoint address="net.tcp://localhost:8885/Calculator"
binding ="netTcpBinding"
contract="TerryLee.WCFAddressing.Contract.ICalculator">
</endpoint>
</service>
可以在ServiceHost 启动后,输出所有的终结点地址和绑定信息,如下代码:
ServiceDescription desc = calculatorServiceHost.Description;
foreach (ServiceEndpoint endpoint in desc.Endpoints)
{
Console.WriteLine("Endpoint - address: {0}", endpoint.Address);
Console.WriteLine(" binding: {0}", endpoint.Binding.Name);
Console.WriteLine(" contract: {0}", endpoint.Contract.Name);
}
输出结果如图2 所示:
图2
元数据中终结点地址
终结点地址在WSDL 中表示为对应终结点的 wsdl:port 元素内的终结点引用(EndpointReference )元素。终结点引用包含终结点的地址以及所有的地址属性,如下示例代码所示:
<wsdl:service name="CalculatorService">
<wsdl:port name="WSHttpBinding_ICalculator" binding="tns:WSHttpBinding_ICalculator">
<soap12:address location="https://localhost:8887/Calculator" />
<wsa10:EndpointReference>
<wsa10:Address>https://localhost:8887/Calculator</wsa10:Address>
<Identity xmlns="https://schemas.xmlsoap.org/ws/2006/02/addressingidentity">
<Upn>TerryLee-PC\TerryLee</Upn>
</Identity>
</wsa10:EndpointReference>
</wsdl:port>
</wsdl:service>
自定义寻址报头
在本文的终结点定义一节,我们提到了寻址报头,在某些情况下,我们可能希望通过自定义寻址报头来解决一些复杂的问题,如根据根据传入的寻址报头中是否包含某些信息,将其转发到不同的终结点,通过自定义寻址报头,可以实现SOAP 消息的无限扩展,放置任何希望的控制信息到SOAP 消息。如下面的代码:
using (ServiceHost calculatorServiceHost =
new ServiceHost(typeof(CalculatorService),
new Uri("https://localhost:8887/CalculatorService")))
{
calculatorServiceHost.Opened += delegate
{
Console.WriteLine("Service begin to listen via the Address:{0}",
calculatorServiceHost.BaseAddresses[0].ToString());
};
AddressHeader header =
AddressHeader.CreateAddressHeader("basic",
"http://www.cnblogs.com/terrylee", "Terrylee");
EndpointAddress ea = new EndpointAddress(
new Uri("https://localhost:8887/CalculatorService"), header);
calculatorServiceHost.Description.Endpoints.Add(
new ServiceEndpoint(
ContractDescription.GetContract(typeof(ICalculator)),
new WSHttpBinding(),
ea));
ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
behavior.HttpGetEnabled = true;
calculatorServiceHost.Description.Behaviors.Add(behavior);
calculatorServiceHost.Open();
Console.Read();
}
我们在WSDL 中可以看到该自定义的报头,它作为终结点引用的引用参数:
<wsdl:service name="CalculatorService">
<wsdl:port name="WSHttpBinding_ICalculator" binding="tns:WSHttpBinding_ICalculator">
<soap12:address location="https://localhost:8887/CalculatorService" />
<wsa10:EndpointReference>
<wsa10:Address>https://localhost:8887/CalculatorService</wsa10:Address>
<wsa10:ReferenceParameters>
<basic xmlns="http://www.cnblogs.com/terrylee">Terrylee</basic>
</wsa10:ReferenceParameters>
<Identity xmlns="https://schemas.xmlsoap.org/ws/2006/02/addressingidentity">
<Upn>TerryLee-PC\TerryLee</Upn>
</Identity>
</wsa10:EndpointReference>
</wsdl:port>
</wsdl:service>
截获到SOAP 消息可以看到,在消息报头中添加了basic 这样的信息,如下代码所示:
<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<basic xmlns="http://www.cnblogs.com/terrylee">Terrylee</basic>
<To s:mustUnderstand="1">https://localhost:8887/CalculatorService</To>
<Action s:mustUnderstand="1">http://tempuri.org/ICalculator/Add</Action>
</s:Header>
<s:Body>
<Add xmlns="http://tempuri.org/">
<x>1</x>
<y>2</y>
</Add>
</s:Body>
</s:Envelope>
当然我们也可以通过配置的方式来自定义寻址报头,如下代码所示:
<service name="TerryLee.WCFAddressing.Service.CalculatorService"
behaviorConfiguration="calculatorBehavior">
<host>
<baseAddresses>
<add baseAddress="https://localhost:8887/Calculator"/>
</baseAddresses>
</host>
<endpoint address=""
binding ="wsHttpBinding"
contract="TerryLee.WCFAddressing.Contract.ICalculator">
<headers>
<basic xmlns="http://www.cnblogs.com/terrylee">Terrylee</basic>
</headers>
</endpoint>
</service>
结束语
本文相对于WCF专题系列( 1):深入 WCF寻址 Part1 来说,注重于实际的使用,介绍了指定终结点地址、元数据中的终结点地址、自定义消寻址报头等,在下一篇中,我们将继续深入WCF 寻址,探讨消息筛选器等问题。WCF 寻址相关文章:
WCF专题系列( 5):深入 WCF寻址 Part 5—逻辑地址和物理地址
WCF专题系列( 4):深入 WCF寻址 Part 4—自定义消息筛选器