指定终结点地址

与 Windows Communication Foundation (WCF) 服务的所有通信都通过其终结点进行。 每个 ServiceEndpoint 属性都包含一个 Address、一个 Binding和一个 Contract。 合同规定哪些操作是可用的。 绑定指定如何与服务通信,地址指定查找服务的位置。 每个终结点必须具有唯一的地址。 终结点地址由 EndpointAddress 类表示,该类包含一个统一资源标识符(URI),表示服务的地址、一个 Identity表示服务的安全标识以及可选 Headers集合。 可选标头提供更详细的寻址信息,用于标识或与终结点交互。 例如,标头可以指示如何处理传入消息、终结点应发送回复消息的位置,或者当多个实例可用时用于处理来自特定用户的传入消息的服务实例。

终结点地址的定义

在 WCF 中,EndpointAddress 用于对 WS-Addressing 标准中定义的终端点引用(EPR)进行建模。

大多数传输的地址 URI 有四个部分。 例如,此 URI http://www.fabrikam.com:322/mathservice.svc/secureEndpoint 具有以下四个部分:

  • 方案:http:

  • 计算机:www.fabrikam.com

  • (可选)端口:322

  • 路径:/mathservice.svc/secureEndpoint

EPR 模型的一部分是,每个终结点引用可以携带一些引用参数来添加额外的标识信息。 在 WCF 中,这些引用参数建模为类的 AddressHeader 实例。

可以使用代码来直接指定服务的终结点地址,或者通过配置以声明方式指定。 在代码中定义终结点通常不可行,因为部署服务的绑定和地址通常不同于在开发服务时使用的终结点。 通常,使用配置而不是代码定义服务终结点更为实用。 将绑定和寻址信息从代码中保留允许更改,而无需重新编译和重新部署应用程序。 如果未在代码或配置中指定任何终结点,则运行时会在服务实现的每个协定的每个基址上添加一个默认终结点。

可通过两种方法在 WCF 中为服务指定终结点地址。 可以为与服务关联的每个终结点指定绝对地址,也可以为服务提供基址,然后为此服务关联的每个终结点指定一个地址,该地址是相对于此基址 ServiceHost 定义的。 可以使用上述每个过程在配置或代码中指定服务的终结点地址。 如果未指定相对地址,服务将使用基址。 还可以为服务提供多个基址,但每个服务只允许每个传输的一个基址。 如果有多个终结点,其中每个终结点都配置了不同的绑定,则其地址必须是唯一的。 使用相同绑定但使用不同协定的终结点可以使用相同的地址。

使用 IIS 托管时,你不会自行管理 ServiceHost 实例。 在 IIS 中托管时,基址始终是服务 .svc 文件中指定的地址。 因此,必须对 IIS 承载的服务终结点使用相对终结点地址。 提供完全限定的终结点地址可能会导致服务部署中出现错误。 有关详细信息,请参阅部署承载于 Internet Information Services 中的 WCF 服务

在配置中定义终结点地址

若要在配置文件中定义终结点,请使用 <终结点> 元素。

<configuration>
  <system.serviceModel>
    <services>
      <service name="UE.Samples.HelloService"
               behaviorConfiguration="HelloServiceBehavior">
        <endpoint address="/Address1"
                  binding="basicHttpBinding" 
                  contract="UE.Samples.IHello"/>

        <endpoint address="mex"
                  binding="mexHttpBinding"
                  contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="HelloServiceBehavior">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

当调用 Open 方法时(即宿主应用程序尝试启动该服务时),系统查找 <service> 元素,该元素具有用于指定“UE.Samples.HelloService”的名称特性。 <如果找到服务>元素,系统将加载指定的类,并使用配置文件中提供的终结点定义创建终结点。 通过此机制,可以使用两行代码加载和启动服务,同时将绑定信息和寻址信息从代码中分离出来。 此方法的优点是,无需重新编译或重新部署应用程序即可进行这些更改。

可选的标头在 <headers> 中声明。 以下是用于在配置文件中指定服务端点的元素示例,该配置文件区分两个标头:“Gold”客户端来自http://tempuri1.org/,而“Standard”客户端来自http://tempuri2.org/。 调用此服务的客户端在其配置文件中必须具有适当的 <标头>

<configuration>
  <system.serviceModel>
    <services>
      <service name="UE.Samples.HelloService"
               behaviorConfiguration="HelloServiceBehavior">
        <endpoint address="/Address1"
                  binding="basicHttpBinding" 
                  contract="UE.Samples.IHello">
          <headers>
            <Member xmlns="http://tempuri1.org/">Gold</Member>
          </headers>
        </endpoint>
        <endpoint address="/Address2"
          binding="basicHttpBinding" 
          contract="UE.Samples.IHello">
          <headers>
            <Member xmlns="http://tempuri2.org/">Silver</Member>
          </headers>
        </endpoint>

        <endpoint address="mex"
                  binding="mexHttpBinding"
                  contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="HelloServiceBehavior">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

还可以在单个消息上设置标头,而不是终结点上的所有消息(如前所示)。 这是通过使用 OperationContextScope 在客户端应用程序中创建新上下文将自定义标头添加到传出消息来完成的,如以下示例所示。

SampleServiceClient wcfClient = new SampleServiceClient(new InstanceContext(this));
try
{
  using (OperationContextScope scope = new OperationContextScope(wcfClient.InnerChannel))
  {
    MessageHeader header
      = MessageHeader.CreateHeader(
      "Service-Bound-CustomHeader",
      "http://Microsoft.WCF.Documentation",
      "Custom Happy Value."
      );
    OperationContext.Current.OutgoingMessageHeaders.Add(header);

    // Making calls.
    Console.WriteLine("Enter the greeting to send: ");
    string greeting = Console.ReadLine();

    //Console.ReadLine();
    header = MessageHeader.CreateHeader(
        "Service-Bound-OneWayHeader",
        "http://Microsoft.WCF.Documentation",
        "Different Happy Value."
      );
    OperationContext.Current.OutgoingMessageHeaders.Add(header);

    // One-way
    wcfClient.Push(greeting);
    this.wait.WaitOne();

    // Done with service.
    wcfClient.Close();
    Console.WriteLine("Done!");
    Console.ReadLine();
  }
}
catch (TimeoutException timeProblem)
{
  Console.WriteLine("The service operation timed out. " + timeProblem.Message);
  Console.ReadLine();
  wcfClient.Abort();
}
catch (CommunicationException commProblem)
{
  Console.WriteLine("There was a communication problem. " + commProblem.Message);
  Console.ReadLine();
  wcfClient.Abort();
}
Dim wcfClient As New SampleServiceClient(New InstanceContext(Me))
Try
    Using scope As New OperationContextScope(wcfClient.InnerChannel)
        Dim header As MessageHeader = MessageHeader.CreateHeader("Service-Bound-CustomHeader", _
                            "http://Microsoft.WCF.Documentation", "Custom Happy Value.")
        OperationContext.Current.OutgoingMessageHeaders.Add(header)

        ' Making calls.
        Console.WriteLine("Enter the greeting to send: ")
        Dim greeting As String = Console.ReadLine()

        'Console.ReadLine();
        header = MessageHeader.CreateHeader("Service-Bound-OneWayHeader", _
                                            "http://Microsoft.WCF.Documentation", "Different Happy Value.")
        OperationContext.Current.OutgoingMessageHeaders.Add(header)

        ' One-way
        wcfClient.Push(greeting)
        Me.wait.WaitOne()

        ' Done with service. 
        wcfClient.Close()
        Console.WriteLine("Done!")
        Console.ReadLine()
    End Using
Catch timeProblem As TimeoutException
    Console.WriteLine("The service operation timed out. " & timeProblem.Message)
    Console.ReadLine()
    wcfClient.Abort()
Catch commProblem As CommunicationException
    Console.WriteLine("There was a communication problem. " & commProblem.Message)
    Console.ReadLine()
    wcfClient.Abort()
End Try

元数据中的终结点地址

终结点地址以 Web 服务描述语言(WSDL)表示为相应终结点EndpointReference元素内的 WS-Addressing wsdl:port (EPR)元素。 EPR 包含终结点的地址以及任何地址属性。 请注意,如下例所示,内部的 EPR wsdl:portsoap:Address 替换。

在代码中定义终结点地址

可以使用类在代码 EndpointAddress 中创建终结点地址。 为终结点地址指定的 URI 可以是完全限定的路径,也可以是相对于服务基址的路径。 以下代码演示如何创建类的 EndpointAddress 实例并将其添加到 ServiceHost 承载服务的实例。

以下示例演示如何在代码中指定完整的终结点地址。

Uri baseAddress = new Uri("http://localhost:8000/HelloService");
string address = "http://localhost:8000/HelloService/MyService";

using (ServiceHost serviceHost = new ServiceHost(typeof(HelloService), baseAddress))
{
    serviceHost.AddServiceEndpoint(typeof(IHello), new BasicHttpBinding(), address);
    serviceHost.Open();
    Console.WriteLine("Press <enter> to terminate service");
    Console.ReadLine();
    serviceHost.Close();
}

以下示例演示如何将相对地址(“MyService”)添加到服务主机的基址。

Uri baseAddress = new Uri("http://localhost:8000/HelloService");

using (ServiceHost serviceHost = new ServiceHost(typeof(HelloService), baseAddress))
{
    serviceHost.AddServiceEndpoint(typeof(IHello), new BasicHttpBinding(), "MyService");
    serviceHost.Open();
    Console.WriteLine("Press <enter> to terminate service");
    Console.ReadLine();
    serviceHost.Close();
}

注释

服务应用程序中 ServiceDescription 的属性不能在 OnOpening 上的 ServiceHostBase 方法之后进行修改。 某些成员(如属性CredentialsAddServiceEndpoint方法)在ServiceHostBaseServiceHost该点后修改时引发异常。 某些允许你修改它们,但结果是不确定的。

同样,在调用 ServiceEndpoint 上的 OnOpening 之后不能在客户端上修改 ChannelFactory 值。 如果该点后修改,该 Credentials 属性将引发异常。 可以修改其他客户端说明值而不出错,但结果未定义。

无论是对于服务还是客户端,建议在调用 Open前修改说明。

使用默认终结点

如果未在代码或配置中指定终结点,则运行时通过在服务实现的每个服务协定的每个基址上添加一个默认终结点来提供默认终结点。 可以在代码或配置中指定基地址,并在Open调用ServiceHost时添加默认终结点。

如果显式提供终结点,则仍可在调用AddDefaultEndpoints之前通过在ServiceHost上调用Open来添加默认终结点。 有关默认终结点、绑定和行为的详细信息,请参阅 WCF 服务的 简化配置简化配置

另请参阅