将 WCF 客户端配置集中到类库中





5.00/5 (7投票s)
本文介绍如何将 WCF 客户端配置放在类库的配置文件中,
引言
这是一个常见的场景:我有一个客户端应用程序,它使用在不同类库项目中编写的业务逻辑。该类库通过 Visual Studio 生成的代理类或 ChannelFactory 引用 WCF 服务。当服务引用添加到类库时,Visual Studio 会在类库项目的配置文件中的 system.serviceModel 部分下生成大量配置。然而,在运行时,应用程序将不会从该文件获取配置。相反,它会尝试从客户端应用程序的配置文件(例如 app.config 或 web.config)获取配置。显然,由于该文件中缺少配置,这将引发错误。一种解决方法是将 system.serviceModel 配置从类库的配置文件复制到客户端应用程序的配置文件中。虽然这可能是一个即时解决方案,但如果您有多个客户端应用程序使用相同的业务逻辑库,并且您最终需要在每个客户端应用程序中复制相同的配置,这可能会成为维护的麻烦。
背景
在我最近不得不处理的一个项目中,我有 9 个客户端应用程序,它们都使用了相同的业务逻辑层。这个业务逻辑层又调用了一个 WCF 服务。最初的想法是在每个客户端应用程序中复制 WCF 客户端配置,但显然这被认为是维护噩梦,所以我不得不找到一种方法将配置集中在一个类库(BLL)中,并在客户端应用程序中使用该类库。我搜索了一下,找到了一些资源,但大多数都只是部分讨论了概念,部分提供了代码示例,当我坐下来实现时,它们总是抛出某种错误。所以,我决定写一个易于理解的逐步指南,详细描述一切,以便其他人可能会觉得有帮助。
解决方案
幸运的是,有一种方法可以解决这个问题,让 WCF 代理从类库的配置文件中获取配置。这样我们就可以将服务配置和代理生成集中在一个地方。我将在这里演示两种实现方法
1. 通过 Visual Studio 向导自动生成的代理类。
2. 实现自己的代理类。
我将在页面下方适当的位置将这些部分分为第一部分和第二部分。
无论哪种情况,我都会采用逐步方法来指导您完成整个过程。那么,让我们从创建一个新的解决方案开始,我们将创建一个图书服务。该服务只返回一个硬编码的图书名称列表。在实际应用中,这可能来自数据库等,但为了演示概念,一个简单的列表就足够了。
首先,在 Visual Studio 中创建一个新的 WCF 服务库项目,并将项目命名为 MyService,解决方案命名为 CentralizeWCFConfigDemo
从解决方案资源管理器中删除 IService1.cs 和 Service1.cs 文件。我们将从头开始创建新类。我们将构建一个图书服务,该服务返回一个包含图书名称和 ID 的图书列表。
添加一个新的解决方案文件夹并将其命名为“Server”。向项目添加一个新的接口文件并将其命名为 IBookService。使用 [ServiceContract] 修饰接口。添加一个名为 GetAllBooks() 的方法并使用 [OperationContract] 修饰该方法。此外,添加一个新类并将其命名为 Book。这将作为数据传输对象,用于保存图书信息。接口定义如下
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
namespace MyService
{
[ServiceContract]
public interface IBookService
{
[OperationContract]
IList<Book> GetAllBooks();
}
/// <summary>
/// Book DTO
/// </summary>
[DataContract]
public class Book
{
[DataMember]
public int BookId { get; set; }
[DataMember]
public string BookName { get; set; }
}
}
向项目添加一个新的类文件并将其命名为 BookService。该类将实现我们上面创建的 IBookService。实现如下所示
using System.Collections.Generic;
namespace MyService
{
public class BookService : IBookService
{
public IList<Book> GetAllBooks()
{
return new List<Book>
{
new Book { BookId = 1, BookName = "Gold of Small Things"},
new Book { BookId = 2, BookName = "The Guide"},
new Book { BookId = 3, BookName = "Midnight's Children"},
new Book { BookId = 4, BookName = "Wings of Fire"},
new Book { BookId = 5, BookName = "My Experiements with Truth"},
};
}
}
}
删除 MyService 项目的 app.config 中的所有内容,并粘贴以下内容。在这里,我们更改了服务名称、baseAddress 和 endpoint,使其指向我们上面创建的 BookService,而不是 Visual Studio 自动生成的默认 Service1。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service name="MyService.BookService">
<host>
<baseAddresses>
<add baseAddress = "https://:8733/Design_Time_Addresses/MyService/BookService/" />
</baseAddresses>
</host>
<!-- Service Endpoints -->
<!-- Unless fully qualified, address is relative to base address supplied above -->
<endpoint address="" binding="basicHttpBinding" contract="MyService.IBookService">
<!--
Upon deployment, the following identity element should be removed or replaced to reflect the
identity under which the deployed service runs. If removed, WCF will infer an appropriate identity
automatically.
-->
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<!-- Metadata Endpoints -->
<!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
<!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information,
set the value below to false before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
到目前为止,我们已经创建了一个 WCF 服务,它将在服务器端运行。我们在其中创建了一个名为 BookService 的小型新服务。现在,我们将从客户端的类库中调用此服务,具体描述如下
现在添加一个新的解决方案文件夹并将其命名为“Client”。我们将把客户端代码放在此文件夹中。右键单击此文件夹并添加一个新的类库项目。将项目命名为 BusinessLogicLayer。该类将作为我们应用程序的业务逻辑层。我们将在此处添加对上一节中创建的 WCF 服务的引用。
删除自动创建的 Class1.cs。添加一个新的类文件并将其命名为 BookManager.cs。
注意:如上所述,我在这里演示了两种在类库中消费 WCF 服务的方法。第一种是通过 Visual Studio 向导添加服务引用,它将自动生成代理类。第二种是手动创建代理类。我将本文的其余部分分成两部分,正如我之前在本文中提到的那样。
第一部分 - 使用 Visual Studio 生成的代理
添加对我们刚刚创建的服务的服务引用。
在“添加引用向导”中,单击“发现”,它将列出解决方案中所有可用的服务,将名称命名为“MyService”并单击“确定”。
这将在 BusinessLogicLayer 项目中添加一个新的服务引用。它还将向项目添加一个新的 app.config 文件,该文件将保存必要的 WCF 客户端配置。
现在我们将在我们之前编写的 BookManager 类中添加一个新方法。添加一个名为 GetMyBooks 的新方法。还要添加一个名为 Book 的类。定义如下所示。请注意,我们稍后将更改 GetMyBooks 方法的定义。
using System.Collections.Generic;
using System.Linq;
namespace BusinessLogicLayer
{
public class BookManager
{
public List<Book> GetMyBooks()
{
var client = new MyService.BookServiceClient();
var apiResult = client.GetAllBooks();
return apiResult.Select(x => new Book { BookId = x.BookId, BookName = x.BookName }).ToList();
}
}
}
//Book class
public class Book
{
public int BookId { get; set; }
public string BookName { get; set; }
}
现在,我们必须创建一个消费我们业务逻辑层的客户端应用程序。为此,右键单击 Client 解决方案文件夹并添加一个新的控制台应用程序。我们将其命名为 TestApp。添加对 BusinessLogicLayer 项目的引用。在 Main 方法中,编写代码以调用业务逻辑层中的 GetMyBooks 方法。代码如下所示
using System.Collections.Generic;
using System.Linq;
namespace BusinessLogicLayer
{
public class BookManager
{
public List<Book> GetMyBooks()
{
var client = new MyService.BookServiceClient();
var apiResult = client.GetAllBooks();
return apiResult.Select(x => new Book { BookId = x.BookId, BookName = x.BookName }).ToList();
}
}
}
现在,将 TestApp 设置为启动项目并尝试运行解决方案。糟糕,它抛出以下错误
错误消息称未找到终结点配置。发生这种情况是因为 TestApp 控制台应用程序尝试在 TestApp 的 app.config 文件中查找 WCF 配置。事实上,目前,配置位于类库的配置文件中。现在,我们将让应用程序从类库的配置文件中读取配置,以便多个客户端应用程序可以将业务逻辑和服务器配置集中在一个地方——类库中。
为了实现这一点,我们将对类库进行一些添加和修改。首先,让我们将 app.config 文件重命名为 BusinessLogicLayer.dll.config。在解决方案资源管理器中,右键单击 BusinessLogicLayer 类库中的配置文件,并将其重命名为 BusinessLogicLayer.dll.config。在属性窗口中,将“生成操作”设置更改为“内容”,将“复制到输出目录”设置更改为“始终复制”。这将确保配置文件被复制到客户端应用程序的输出目录(通常是 bin 文件夹),并在运行时使配置文件可用。
添加对 System.Configuration 程序集的引用,以便我们能够从类库中读取配置文件。然后,向同一项目添加一个新的类文件并将其命名为 ServiceManger。向其中添加以下代码
using System.Configuration;
using System.Reflection;
using System.ServiceModel.Configuration;
namespace BusinessLogicLayer
{
public class ServiceManager
{
public static T CreateServiceClient<T>(string configName)
{
string _assemblyLocation = Assembly.GetExecutingAssembly().Location;
var PluginConfig = ConfigurationManager.OpenExeConfiguration(_assemblyLocation);
ConfigurationChannelFactory<T> channelFactory = new ConfigurationChannelFactory<T>(configName, PluginConfig, null);
var client = channelFactory.CreateChannel();
return client;
}
}
}
上面的代码用于创建一个动态 WCF 服务代理,它将从类库的配置文件而不是客户端应用程序的配置文件中读取配置。CreateServiceClient 方法将返回新创建的客户端对象。请注意,我们不再直接使用我们之前通过 Visual Studio 服务引用向导创建的 MyService 代理对象。添加服务引用只是为了使所有支持的 WCF 服务操作和 IBookServiceChannel 接口可用于客户端应用程序,并在编码时利用智能感知。
现在让我们重写 GetMyBooks 方法,使其使用新的动态代理。转到 BookManager 类并将以下行从
var client = new MyService.BookServiceClient();
改为
var client = ServiceManager.CreateServiceClient<MyService.IBookServiceChannel>("BasicHttpBinding_IBookService");
另请注意,字符串“BasicHttpBinding_IBooService”是 BusinessLogicLayer.dll.config 文件中指定的配置名称。完整代码如下所示
using System.Collections.Generic;
using System.Linq;
namespace BusinessLogicLayer
{
public class BookManager
{
public List<Book> GetMyBooks()
{
var client = ServiceManager.CreateServiceClient<MyService.IBookServiceChannel>("BasicHttpBinding_IBookService");
var apiResult = client.GetAllBooks();
return apiResult.Select(x => new Book { BookId = x.BookId, BookName = x.BookName }).ToList();
}
}
}
现在,运行应用程序。您可以看到应用程序从类库的配置文件中获取配置并正确执行。
第二部分 - 实现自己的代理类
现在让我们看看消费 WCF 服务的第二种方法——实现自己的代理类,但仍然使用集中式配置。在这种方法中,我们根本不会通过解决方案资源管理器中的 Visual Studio 向导向项目添加服务引用。所以我们将删除 BusinessLogicLayer 类库项目“服务引用”文件夹下的 MyService 引用,但在删除之前,将 BusinessLogicLayer.dll.config 文件中的所有内容复制到剪贴板,因为删除服务引用也可能会清除此文件中的 serviceModel 部分。一旦服务引用被删除,检查这些设置是否因删除服务引用而从 BusinessLogicLayer.dll.config 中清除,如果清除了,则将已复制的内容粘贴到 BusinessLogicLayer.dll.config 中。
此时,由于我们已经从类库中删除了服务的引用,我们不再享受 WCF 服务支持的智能感知的便利。如果您查看客户端应用程序中 BookManager 类中的 GetMyBooks 方法,您会发现 VS 抱怨找不到 MyService 命名空间。因此,我们需要创建一个类作为代理来调用 WCF 方法。这个类应该引用 WCF 项目中的服务库。为了实现这一点,右键单击 BusinessLogicLayer 项目并“添加引用…”到 MyService WCF 项目(请注意,这次这不是服务引用,而是类库引用)。完成此操作后,向项目添加一个新的类文件并将其命名为“MyServiceProxy”。使此类继承自 ClientBase<MyService.IBookService>,MyService.IBookService。此外,右键单击 MyService.IBookService 并从菜单中选择“实现接口”命令。修改生成的代码,使其如下所示
using System.Collections.Generic;
using System.ServiceModel;
namespace BusinessLogicLayer
{
public class MyServiceProxy : ClientBase<MyService.IBookService>, MyService.IBookService
{
public IList<MyService.Book> GetAllBooks()
{
return this.Channel.GetAllBooks();
}
}
}
这段代码的作用是创建一个代理类,它继承自 IBookService 类型的 ClientBase。我们还实现了接口,以便它在内部调用服务通道提供的公开服务方法。
作为下一步,向项目添加一个接口,其定义如下
namespace BusinessLogicLayer
{
public interface IBookServiceChannel : MyService.IBookService, System.ServiceModel.IClientChannel
{
}
}
这是我们将传递给 CreateServiceClient 方法的接口。为此,回到 BookManager 类并修改 GetMyBooks 方法,从
var client = ServiceManager.CreateServiceClient<MyService.IBookServiceChannel>("BasicHttpBinding_IBookService");
改为
var client = ServiceManager.CreateServiceClient<IBookServiceChannel>("BasicHttpBinding_IBookService");
现在,尝试运行代码,如果您按照描述完成了所有操作,您应该能够成功调用 GetAllBooks 服务方法。
使用代码
本文附带了两种消费 WCF 服务方法的实际示例(当然可能有更多方法,但我只关注两种),可作为附件下载。
历史
文章创建于 2015 年 12 月 1 日。