WCF 代理生成选项
概述了多种生成 WCF 服务代理的方法。
引言
本项目旨在演示客户端通过代理使用 WCF 服务的三种不同方式。即:
- VS 生成的代理
- svcutil 代理
- 手动代理
我假设您已经设置了至少一个测试 WCF 服务,并且您了解其基本原理。
背景
在我工作的公司,我们使用 WCF 已经一年多了,我们发现其中一件事是系统生成的代理代码经常非常糟糕,有时无法构建,而且它生成的配置文件也非常糟糕。我们最终采取了类似于我下面介绍的第三种方案。
基本项目
为了创建一个简单易用的 WCF 服务来演示生成代理的不同方式,我使用了 VS 2008。在 VS 2008 中,有一个选项可以创建类型为“WCF 服务库”的新项目,它几乎生成了我需要的一切。如果不是使用 VS 2008,只需将以下代码复制到一个新项目中并解决引用,您就拥有了一个(非常)基本的 WCF 服务。删除大部分无关内容,只保留一个方法 HelloWorld()
,我们得到以下服务和配置文件:
服务实现
public class ProxyServ : IProxyServ
{
public void HelloWorld() { }
}
服务合同
[ServiceContract]
public interface IProxyServ
{
[OperationContract]
void HelloWorld();
}
配置
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="ProxyGeneration.ProxyServ"
behaviorConfiguration="ProxyGeneration.Service1Behavior">
<endpoint address ="" binding="wsHttpBinding"
contract="ProxyGeneration.IProxyServ"/>
<endpoint address="mex" binding="mexHttpBinding"
contract="ProxyGeneration.IProxyServ"/>
<host>
<baseAddresses>
<add baseAddress="https://:8080/ProxyServ"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ProxyGeneration.Service1Behavior">
<serviceMetadata httpGetEnabled="True"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
您可以在 VS 2008 中直接运行它,它会即时为您生成一个主机并允许您使用它,但我更喜欢自己创建主机。
宿主
我在解决方案中添加了一个控制台项目,并将以下代码片段粘贴到其中。我总是使用相同的代码片段,并对其进行调整以使其适用于每个解决方案。
static void Main(string[] args)
{
ServiceProc();
}
private static void ServiceProc()
{
ServiceHost host = new ServiceHost(typeof(ProxyServ));
try
{
Console.WriteLine("WCF Service: Starting Up");
host.Opened += new EventHandler(host_Opened);
host.Open();
Console.WriteLine("Please press any key to close...");
Console.ReadLine();
}
finally
{
if (host.State != System.ServiceModel.CommunicationState.Closed)
{
host.Close();
}
}
}
private static void host_Opened(object sender, EventArgs e)
{
Console.WriteLine("WCF Service: Running");
System.ServiceModel.ServiceHost host = (System.ServiceModel.ServiceHost)sender;
StringBuilder detailBuilder = new StringBuilder();
foreach (Uri baseAddress in host.BaseAddresses)
{
detailBuilder.AppendFormat("\r\nBase Address: {0}", baseAddress.ToString());
}
foreach (ServiceEndpoint endPoint in host.Description.Endpoints)
{
string address = endPoint.Address.ToString();
string bindingName = endPoint.Binding.Name;
detailBuilder.AppendFormat("\r\nEndpoint({0}): {1}", bindingName, address);
}
Console.WriteLine(detailBuilder.ToString());
}
解决引用,然后将 app.config 从服务移动到主机。现在,我们可以运行控制台主机,它将为我们托管 WCF 服务,这意味着我们不再需要从 VS 2008 运行服务,我们只需运行控制台应用程序的 exe 文件。
客户端
我将为每种类型的代理创建一个客户端项目。我正在使用 Windows Forms 客户端。您实际上可以使用任何您想要的东西。此外,您会注意到 HelloWorld()
方法不返回任何内容,这没关系。只要它不抛出错误,我们就知道操作成功了。
生成代理
添加服务引用
在 VS 2005 中,您可以运行服务主机,并且仍然可以将服务引用添加到解决方案中的另一个项目。令人恼火的是,我的 VS 2008 版本似乎不起作用。因此,您必须转到控制台主机的 bin 目录并从那里运行它。一旦它运行起来,您就可以在客户端中添加服务引用。VS 2008 比 VS 2005 具有一个更漂亮的表单,它允许您预览服务及其方法,并为其命名。
我**总是**尝试将代理命名为与服务不同的名称,这有助于我在脑海中将两者分开。无论如何,单击“确定”,您将得到一个名为“*服务引用*”的文件夹和一个小小的万维网图标。您可以通过 Windows 资源管理器深入查看 VS 2008 隐藏的实际代码文件,您会找到一个名为“*Reference.cs*”的文件。*Reference.cs* 是您刚刚创建的实际代理,但如果您只想让服务正常工作,请不要担心,这并不重要。使用它非常简单。这些代码行运行得很好:
private void btnTest_Click(object sender, EventArgs e)
{
ProxyServClient client = new ProxyServClient("WSHttpBinding_IProxyServ");
client.HelloWorld();
}
据我所知,通过 VS 2008 添加服务引用所做的只是将 svcutil 的命令行功能包装在一个设置各种参数的表单后面。它很漂亮,但在 app.config 和代理中生成了大量的额外内容(svcutil 也是如此)。
svcutil
我只将此方法包含在内以求完整性,VS 2005 有扩展允许您添加服务引用,并且它随 VS 2008 本身提供。
编辑:好的,Chris Richner 在评论中指出,并非所有人都能访问 VS 的专业版,并且会使用 VS 的 Express 版本。这些版本几乎保证您会使用 svcutil。
svcutil 本身用起来真是噩梦。好吧,其实也不是,但如果你喜欢 GUI 并且讨厌命令行,那就跳到下一节。但是,如果你真的想知道如何使用它,请继续阅读。
我已将客户端项目重命名为 ReferenceClient,并创建了一个名为 svcutilClient 的类似项目。要访问 svcutil,请打开 VS 命令提示符,键入“svcutil”,然后按 Enter 键,您将看到一页又一页的开关和参数。它们非常多,我不会一一介绍。请查看 MSDN 文章此处,假设您能成功加载该网站。运行以下命令(在运行主机之后):
svcutil.exe "https://:8080/ProxyServ"
/out:"C:\Projects\Testing\ProxyGeneration\svcutilClient\ProxySVC"
/config:"C:\Projects\Testing\ProxyGeneration\svcutilClient\app.config"
我告诉工具要查看哪个服务(您也可以使用主机 exe 和配置来生成此服务,但这种方式键入更少),我希望生成的代理放在哪里,以及如何命名它,配置也是如此。(奇怪的是:默认的配置名称是“output.config”,这很糟糕,因为为您生成的代理显然只能“看到”名为“app.config”的配置。或者至少,我遇到了这种情况。)
您应该会看到一些输出,类似于这样:
Microsoft (R) Service Model Metadata Tool
[Microsoft (R) Windows (R) Communication Foundation, Version 3.0.4506.648]
Copyright (c) Microsoft Corporation. All rights reserved.
Attempting to download metadata from 'https://:8080/ProxyServ' using WS-
Metadata Exchange or DISCO.
Generating files...
C:\Projects\Testing\ProxyGeneration\svcutilClient\ProxySVC.cs
C:\Projects\Testing\ProxyGeneration\svcutilClient\app.config
如果您看到这个,那太棒了!现在,我们转到 VS,为生成的 CS 文件和配置“添加现有项”。和之前在我们漂亮的表单中一样,使用相同的测试代码。
private void btnTest_Click(object sender, EventArgs e)
{
ProxyServClient client = new ProxyServClient("WSHttpBinding_IProxyServ");
client.HelloWorld();
}
构建它,我们就完成了!
手工代理
好的,现在是选项 3。手动完成。
是的,它确实比使用服务引用甚至 svcutil 方法花费的时间要长得多;但是,您最终得到的代码更简洁,并且配置**大大**更清晰易读,您很快就会看到。这部分比它本来可以的要长一些,我添加了一个额外的项目,名为 Proxy,它编译成一个 DLL,然后我从另一个名为 ProxyClient
的新类中引用它。这对于这个项目来说并非严格必要,但它展示了这种方法的扩展性。
这个想法是展示在构建 WCF 服务时,您可以将代理作为一个项目构建在服务中(将代理保留在服务中有助于良好的维护),然后将代理作为 DLL 引用到使用它的项目中。配置也经过了大量修改,只包含我需要运行它的内容,这使得它看起来更容易理解。
所以,首先,我创建了一个名为 Proxy 的项目。它有一个接口和一个类。接口 IBob
是服务接口中定义的方法的子集。这允许您拥有一个**只**包含您将要使用的方法的代理,而不是全部,无论您是否需要它们。如果您的服务非常大,那么多个代理允许您分发所需的功能。然后,类 ProxyServiceClient
继承自 ClientBase<IProxyServ>
和我们的自定义接口。我们最终得到一个如下所示的接口(我将其命名为 IBob
以便*真正*清楚哪个是哪个接口):
public interface IBob
{
void HelloWorld();
}
代理本身看起来像这样:
public class ProxyServiceClient : ClientBase<IProxyServ>, IBob
{
public ProxyServiceClient()
: base()
{ }
public ProxyServiceClient(string endpoint)
: base(endpoint)
{ }
public void HelloWorld()
{
try
{
base.Channel.HelloWorld();
}
catch (Exception exc)
{
//FANTASTIC error handling here. No, really. It must be awesome.
throw new ApplicationException(exc.Message, exc);
}
}
}
如果您查看了生成的代理,您会注意到它非常相似,只是稍微精简了一些。我没有使用任何属性,并且只使用了四个可能的构造函数中的两个。
配置文件在客户端,如下所示:
<configuration>
<system.serviceModel>
<client>
<endpoint address="https://:8080/ProxyServ"
binding="wsHttpBinding"
contract="ProxyGeneration.IProxyServ"
name="ProxyServHTTP"/>
</client>
</system.serviceModel>
</configuration>
您会注意到**只有** ABC,没有其他内容。一切都是默认值,所以我们不需要明确提及它。然后我们添加对代理的引用,并像上面一样创建一个测试按钮。
就是这样。祝您编码愉快,易于维护!