WCF 4.0/4.5 中的默认终结点和默认协议映射






4.91/5 (20投票s)
本文描述了 WCF 4.0/4.5 中的默认终结点和协议映射行为
目录
引言
WCF 4.0 带来了许多新功能,大大简化了开发人员的工作。其中之一就是简化配置。WCF 4.0 通过支持默认终结点、默认协议映射、默认绑定和行为配置,简化了配置部分。这些新功能使得托管无配置的服务成为可能。这对于开发人员的体验来说是最显著的改进。我将在本文中一步一步地解释默认终结点和默认协议映射的概念。
默认终结点
在 WCF 3.x 中,如果有人尝试在未配置任何终结点的情况下托管服务,ServiceHost
实例将抛出 System.InvalidOperationException
,提示至少需要配置一个终结点。

但是,在 WCF 4.0 中,运行时会自动添加一个或多个“默认终结点”,从而使服务无需任何配置即可使用。实际上,当服务宿主应用程序调用 ServiceHost
实例上的 Open 时,它会根据配置文件和服务程序集以及宿主应用程序的任何显式配置来构建内部服务描述。如果发现已配置的终结点数量为零,它将调用 AddDefaultEndpoints
方法(ServiceHost
类的一个新的公共方法),根据服务的基址配置为服务描述添加一个或多个终结点。在 IIS 场景下,这通常是 .svc 地址。规则如下:
AddDefaultEndpoints
方法为服务实现的每个服务协定,为每个基址添加一个默认终结点。
如果服务实现了一个服务协定(例如 IA),并且宿主配置了两个基址(例如一个用于 TCP,另一个用于 HTTP),则 AddDefaultEndpoints
方法将为服务添加两个默认终结点(每个基址一个,与服务协定组合,例如 TCP 与 IA,以及 HTTP 与 IA)。
如果服务实现了两个服务协定(例如 IA 和 IB),并且宿主配置了单个基址(例如在此情况下为 TCP),则 AddDefaultEndpoints
方法将为服务添加两个默认终结点(每个基址一个,与每个服务协定组合,例如 TCP 与 IA,以及 TCP 与 IB)。
如果服务实现了两个服务协定(例如 IA 和 IB),并且宿主配置了两个基址(例如一个用于 TCP,另一个用于 HTTP),则 AddDefaultEndpoints
方法将为服务添加四个默认终结点(每个基址一个,与每个服务协定组合,例如 TCP 与 IA、TCP 与 IB、HTTP 与 IA 和 HTTP 与 IB)。
因此,一般来说,对于零终结点配置的服务,如果已配置的基址总数为 n,实现的有效服务协定总数为 m,则 AddDefaultEndpoints
方法将为服务添加 n x m 个默认终结点。
让我们通过一个例子来理解这个概念。为了演示,我创建了一个简单的服务来演示默认终结点的创建。我定义了 IHusband
、IWife
和 IKids
服务协定,然后按照以下方式通过实现这些服务协定创建了一个 FamilyService
服务:
[ServiceContract]
public interface IHusband
{
[OperationContract]
void WelcomeHusband(string name);
}
[ServiceContract]
public interface IWife
{
[OperationContract]
void WelcomeWife(string name);
}
[ServiceContract]
public interface IKids
{
[OperationContract]
void WelcomeKids();
}
public class FamilyService : IHusband, IWife, IKids
{
public void WelcomeHusband(string name)
{
Console.WriteLine("Hello Mr. {0}", name);
}
public void WelcomeWife(string name)
{
Console.WriteLine("Hello Mrs. {0}", name);
}
public void WelcomeKids()
{
Console.WriteLine("Hi Kids!");
}
}
然后,我创建了一个控制台应用程序,使用 ServiceHost
类来托管服务,并通过编程方式为其添加了一个 HTTP 基址,而没有关联宿主应用程序的配置文件。请看下面的代码:
var host = new ServiceHost(typeof(FamilyService), new Uri("https://:8080/familyservice"));
try
{
host.Open();
PrintServiceInfo(host);
Console.ReadLine();
host.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
host.Abort();
}
现在运行宿主,您将在控制台窗口中看到默认情况下已添加到服务的三个终结点,每个服务协定对应一个 HTTP 基址。

现在,通过编程方式为其添加一个用于 TCP 的基址,如下所示:
var host = new ServiceHost(typeof(FamilyService),
new Uri("https://:8080/familyservice"),
new Uri("net.tcp://:8085/familyservice"));
再次运行宿主应用程序。现在您将在宿主控制台窗口中看到六个终结点,每个服务协定对应一个 TCP 基址,每个服务协定对应一个 HTTP 基址。

因此,您已经看到在这两种情况下,ServiceHost
实例如何在后台为提供的基址添加默认终结点。实际上,ServiceHost
实例隐式调用 AddDefaultEndpoints
方法来添加默认终结点。因此,使用 WCF 4,您可以使用 ServiceHost
类托管任何服务,而无需任何应用程序配置文件。
现在需要注意的是,此时如果服务已配置单个终结点,则此默认终结点行为将不再有效。要验证这一点,只需向 FamilyService
服务添加一个终结点,如下所示:
host.AddServiceEndpoint(typeof(IHusband),new NetTcpBinding(), "pipe");
然后运行宿主应用程序,您会发现控制台窗口中只显示一个终结点。没有默认终结点。

但是,如果您仍然希望在服务中拥有默认终结点以及您自己的终结点,只需显式调用 AddDefaultEndpoints
方法,如下所示:
host.AddServiceEndpoint(typeof(IHusband),new NetTcpBinding(), "pipe");
host.AddDefaultEndpoints();
再次运行宿主应用程序,您现在将看到所有可能的默认终结点以及您自己的终结点都显示在控制台宿主窗口中。

如果通过配置文件配置基址,则默认终结点行为也将有效。但是,在这种情况下,您需要通过配置文件为服务配置所需的基址。要查看这一点,只需创建一个 ServiceHost
实例,如下所示:
var host = new ServiceHost(typeof(FamilyService));
并为控制台宿主应用程序添加配置文件(在此情况下为 app.config
),并在其中配置服务,使其具有单个基址,如下所示:
<system.serviceModel>
<services>
<service name="FamilyServiceLibrary.FamilyService">
<host>
<baseAddresses>
<add baseAddress="net.pipe:///familyservice" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
然后运行控制台宿主应用程序,您将在控制台窗口中看到所有默认终结点。

IIS/WAS 托管中的默认终结点行为
当服务托管在 IIS 中时,也可以观察到默认终结点行为。使用 WCF 4.0,如果要在 IIS 中使用默认配置托管服务,则 WCF 激活逻辑将在后台创建 ServiceHost
实例,并为服务配置默认的 HTTP 终结点。如果服务仅实现一个服务协定,则会添加一个默认的 HTTP 终结点;如果服务实现多个服务协定,则生成的 ServiceHost 实例将配置一个针对每个协定的 HTTP 终结点。
让我们在 IIS 中托管我们的 FamilyService
来理解这个概念。为了演示,我创建了一个 WCF 服务网站,并将我们 FamilyService
项目(FamilyServiceLibrary)的引用添加进去,并将 service.svc
文件重命名为 Family.svc
(非必需)。然后打开 FamilyService.svc
并按如下方式更新 ServiceHost
指令:
<%@ ServiceHost Service="FamilyServiceLibrary.FamilyService" %>
我们知道,当服务托管在 IIS/WAS 中时,基址由托管服务的 IIS 虚拟目录以及 .svc 文件名决定,因此无需担心基址配置。现在,我仅通过 web.config
文件来确保服务 metadata
是否已启用,如下所示:
<serviceMetadata httpGetEnabled="true" />
最后,我在 IIS 中托管了我们的 FamilyService
。现在,当有人首次访问 Family.svc
时,WCF 激活逻辑将创建 ServiceHost
实例,并为 FamilyService
类型添加三个默认的 HTTP 终结点(每个服务协定一个)。您可以通过访问 WSDL 定义来验证这些默认终结点,您将在 <service>
元素内找到三个 <port>
元素。

让我们在 IIS (版本 > 7.0)/Windows Process Activation Services (WAS) 托管的情况下,为其他协议实现此行为。我们知道 WAS 允许在 IIS (版本 > 7.0) 中通过任何传输(TCP/MSMQ/NamedPipe 等)激活 WCF 服务。因此,我通过Internet Information Services (IIS)管理器为我们的 FamilyService
启用了 TCP 和 NamedPipe 协议,如下所示:

现在,您也可以通过访问 TCP 和 NamePipe 协议的 WSDL 定义来验证默认终结点。只需查看 <service>
元素内的九个 <port>
元素。在这里,当 Family.svc
首次被访问时,WCF 激活逻辑已经创建了 ServiceHost
实例,并为 FamilyService
类型添加了九个默认终结点(每个服务协定对应 HTTP 三个,TCP 三个,NamedPipe 三个)。

如果您不希望在服务中使用默认终结点行为,而希望配置自己的终结点,只需在 web.config
文件中为您的服务配置一个相对地址:
<services>
<service name="FamilyServiceLibrary.FamilyService">
<endpoint address="ws" binding="wsHttpBinding" bindingConfiguration=""
contract="FamilyServiceLibrary.IHusband" />
</service>
</services>
然后访问 Family.svc
;您将在 WSDL 定义的 <service>
元素内找到一个 <port>
元素。

那么,如果您仍然希望在服务中拥有默认终结点以及您自己的终结点呢?嗯,这在这种情况下有点棘手。您需要使用 ServiceHostFactory
类。您可以通过 ServiceHostFactory
来拦截 ServiceHost
的创建。因此,为了实现这一点,我首先创建了一个 FamilyServiceHost
类,该类继承自 ServiceHost
类,并重写了其 OnOpening
方法,以使用所需的(相对)终结点以及默认终结点配置服务,如下所示:
public class FamilyServiceHost : ServiceHost
{
public FamilyServiceHost(Type serviceType, params Uri[] baseAddresses) :
base(serviceType, baseAddresses) { }
protected override void OnOpening()
{
base.OnOpening();
this.AddServiceEndpoint(typeof(IHusband),
new WSHttpBinding(), "ws");
this.AddDefaultEndpoints();
}
}
然后,我创建了一个 FamilyServiceHostFactory
类,该类继承自 ServiceHostFactory
类,并重写了其 CreateServiceHost
方法,以便创建我们自定义服务宿主类 FamilyServiceHost
的实例,如下所示:
public class FamilyServiceHostFactory : ServiceHostFactory
{
protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType,
Uri[] baseAddresses)
{
return new FamilyServiceHost(serviceType, baseAddresses);
}
}
最后,我在 Family.svc
文件中使用 Factory
属性指定了我们自定义的宿主工厂类,如下所示:
<%@ ServiceHost Service="FamilyServiceLibrary.FamilyService"
Factory="FamilyServiceLibrary.FamilyServiceHostFactory" %>
现在,当您第一次访问 Family.svc
时,WCF 激活逻辑将创建自定义的 ServiceHost
实例,它将为 FamilyService
类型添加您自己的终结点以及默认的 HTTP 终结点(每个服务协定一个)。您可以通过访问 WSDL 定义来验证这些默认终结点以及您自己的终结点,您将看到 <service>
元素内有四个 <port>
元素。

WCF 4.5 带来了一个不错的 HTTPS 默认终结点功能。因此,如果您的 IIS 已配置 SSL,并且您将在 IIS 中使用默认配置托管 WCF 4.5 服务,那么 WCF 4.5 激活逻辑将在后台创建 ServiceHost 实例,并为默认的 HTTP 和 HTTPS 终结点配置服务。
因此,如果我们的 FamilyService
将在配置了 SSL 的 IIS 中托管,我们将获得六个默认终结点:三个用于 HTTP,三个用于 HTTPS。请看下面的屏幕截图:

如果您配置了 SSL 的 IIS,但不想使用 HTTPS 默认终结点,您可以通过配置文件中的 HTTPS 协议映射来摆脱它,如下所示:
<system.serviceModel>
<protocolMapping>
<remove scheme="https"/>
</protocolMapping>
</system.serviceModel>
默认协议映射
您是否注意到在上面的示例中,WCF 如何为默认的 HTTP 终结点选择 BasicHttpBinding
,为默认的 TCP 终结点选择 NetTcpBinding
,以及为默认的 NamedPipe 终结点选择 NetNamedPipeBinding
?WCF 是如何决定为特定的基址使用哪种绑定的?这个问题的答案很简单。WCF 定义了传输协议方案(例如 http, net.tcp, net.pipe 等)与内置 WCF 绑定之间的默认协议映射。默认协议映射在 .NET 4 的 machine.config.comments 文件中定义,如下所示:
<system.serviceModel>
<protocolMapping>
<add scheme="http" binding="basicHttpBinding" bindingConfiguration="" />
<add scheme="net.tcp" binding="netTcpBinding" bindingConfiguration=""/>
<add scheme="net.pipe" binding="netNamedPipeBinding" bindingConfiguration=""/>
<add scheme="net.msmq" binding="netMsmqBinding" bindingConfiguration=""/>
</protocolMapping>
...
</system.serviceModel>
可以通过修改每个协议方案的映射来在计算机级别覆盖这些映射。您也可以通过在应用程序/Web 配置文件中覆盖此部分来覆盖应用程序范围内的映射。
让我们来实现这个行为。我在 app.config
中为我们的控制台服务宿主应用程序覆盖了 HTTP 协议方案的默认绑定映射,将其从 BasicHttpBinding
更改为 WSHttpBinding
,如下所示:
<protocolMapping>
<add scheme="http" binding="wsHttpBinding" bindingConfiguration="" />
</protocolMapping>
然后运行宿主应用程序。您现在会发现在控制台窗口中显示了三个默认终结点(每个协定一个),它们使用的是 WSHttpBinding
,而不是 HTTP 协议方案的 BasicHttpBinding
。

同样,您可以通过在 web.config
中覆盖 HTTP 协议方案的默认绑定映射,从 BasicHttpBinding
更改为 WSHttpBinding
,来为我们托管在 IIS 中的服务实现此行为。您可以通过访问 <service>
元素内的 <port>
元素的 WSDL 定义来验证 HTTP 默认终结点现在是否在使用 WSHttpBinding
而不是 BasicHttpBinding
。

一旦 WCF 通过协议映射表确定了使用哪种绑定,它将使用默认绑定配置来配置默认终结点。
如果您想覆盖协议方案的默认绑定映射,您可以通过配置文件为特定绑定覆盖它。
结论
因此,我在本文中探讨了默认终结点行为和默认协议映射行为。我将在接下来的文章中探讨更多功能。希望您现在对这些概念有了很好的理解。祝您编码愉快!