翻译:使用 CoreWCF 升级 WCF 服务到 .NET 6
翻译:使用 CoreWCF 升级 WCF 服务到 .NET 6
原文地址:https://devblogs.microsoft.com/dotnet/upgrading-a-wcf-service-to-dotnet-6/
大约在 3 年之前,我发布过一篇将一个 WPF 应用迁移到 .NET Core 3 演练过程的博客。这是一个被称为 Bean Trader 的简单商用交易示例程序。当时,我只能迁移示例解决方案的一部分。这个 Bean Trader 解决方案包括一个 WPF 客户端应用和一个客户端用来发布和接受交易的服务器端应用。客户端与服务器端的通讯使用 WCF。因为 .NET Core 3 ( 以及后继版本如 .NET 5 和 .NET 6 ) 支持客户端的 WCF API,但是不支持服务器端,我只能迁移 Bear Trader 的客户端部分,而遗留下服务器端继续运行在 .NET Framework 上。
由于近期 CoreWCF 1.0 发布了,我期待完成将 Bean Trader 升级到 .NET 6 上!
1. 关于 CoreWCF
CoreWCF 是一个社区驱动的项目,使得 WCF 可以运行在现代的 .NET 版本上。尽管 CoreWCF 不是微软拥有的项目,微软已经宣布它将提供对 CoreWCF 的产品支持。对于新的开发工作推荐采用最新的技术,像 gRPC 和 ASP.NET WebAPI,但是对于需要迁移到 .NET 6 的现存的重度依赖于 WCF 技术的项目来说, CoreWCF 对于提供了巨大的帮助。
尽管 CoreWCF 支持众多的 WCF 常见使用场景,它并不支持全部的 WCF 功能。你的迁移过程体验非常依赖于你对 WCF 的使用与包含在 CoreWCF 中的功能的交集。如果你使用的功能还没有包含在 CoreWCF 中,请在 Feature Roadmap 中提供反馈,以便 CoreWCF 项目的维护者可以基于社区的需求优先安排工作。
2. 关于示例项目
示例项目 Bean Trader ( GitHub 地址 ) 是多年以前,我在演示如何迁移到 .NET Core 的时候创建的示例。因为该示例本来是仅仅用来展示如何迁移客户端,Bean Trader 服务就非常简单。它包含一个含有模型和接口的类库,以及一个用来托管 WCF 服务的控制台应用程序 ( 使用支持证书验证的 Net.Tcp 传输 )。支持客户端发出或者接受交易不同颜色的豆子。尽管该示例应用很小,我想它对于展示迁移到 .NET 6 的过程还是有用的。
3. 迁移
3.1 运行升级助理
为了使得迁移更快捷,我将使用 .NET 升级助理。升级助理是用来帮助用户从 .NET Framework 升级到 .NET Standard 和 .NET 6 的命令行程序,使用交互方式。升级助理还不能自动从 WCF 迁移到 CoreWCF ( 尽管以及安排在计划中 ),运行该工具仍然是有帮助的,因为项目文件可以迁移,NuGet 包的引用可以被升级,以及其它处理等等。在该工具执行之后,我将手工完成从 WCF 迁移到 CoreWCF 的需要的变更处理。
为了安装升级助理,我运行如下的 .NET SDK 命令:
dotnet tool install -g upgrade-assistant
升级助理安装之后,我可以在 BeanTrader 解决方案上运行它来开始迁移过程。通过在解决方案 ( 而不是特定项目上 ) 运行,我可以通过执行一次该工具,来升级类库和服务器控制台应用两者。
升级解决方案的命令:
upgrade-assistant upgrade BeanTrader.sln
该工具随即指导我进行一系列的升级步骤 ( 这会通过升级助理自动处理 ),如下所示:
我通过升级助理的完整步骤如下:
-
选择入口点。这支持我选择希望运行在 .NET 6 上的项目。基于该选择,升级助理将决定要升级的项目以及升级的次序。我选择
BeanTraderServer
-
选择一个项目进行升级。这过渡到该工具开始升级指定项目。它建议我先升级
BeanTraderCommon
项目,然后升级beanTraderServer
项目更加合理,所以我选择该升级顺序。 -
备份
BeanTraderCommon
-
将
BeanTraderCommon
项目转换为 SDK 风格的项目 -
升级
BeanTraderCommon
的 NuGet 包。该步骤替换对 System.ServiceModel 程序集的引用,使用 NuGet 包,例如 System.ServiceModel.NetTcp 来代替。 -
升级
BeanTraderCommon
的目标架构 ( TFM )。该工具建议使用 .NET Standard 2.0,因为该项目是一个纯类库项目,没有任何 .NET 6 特定依赖。 -
此时,对辅助项目的升级已经完成,升级助理切换到升级
BeanTraderServer
-
备份
BeanTraderServer
-
转换
BeanTraderServer
项目到 SDK 风格 -
将 System.ServiceMoel 引用替换为等价的 NuGet 包,如在
BeanTraderCommon
项目中一样 -
升级
BeanTraderServer
项目的目标框架。工具建议为 .NET 6,因为该项目是控制台应用 -
禁用不再支持的配置节。升级助理检测到
BeanTraderServer
在它的 app.config 文件中有一个system.ServiceModel
配置节,它不再被 .NET 6 所支持 ( 会导致运行时错误 ),所以它为我注释掉了。最后,我们将会在其它文件中重用这段注释掉的配置节,来配置我们的 CoreWCF 服务。 -
在检查 C# 源是否有任何必要的更改时,升级助手会发出有关 WCF 使用情况的警告。警告消息提醒我,BeanTraderServer 使用服务器端 WCF API,这些 API 在 .NET 6 上不受支持,并且该工具未进行升级。它告诉我,我需要手动进行更改,并建议升级到 CoreWCF、gRPC 或 ASP.NET Core。
-
清理升级。此刻,升级助理完成工作,所以它删除一些临时文件并退出。
3.2 CoreWCF 迁移
现在升级助理已经完成了升级过程,是时候升级 CoreWCF 了。在 Visual Studio 中打开 Bean Trader 解决方案,我发现 BeanTraderCommon 类库可以成功构建。项目升级到 .NET Standard 已经完成了。而 BeanTraderServer
项目有一些错误,可以想到,关联到不能找到某些 WCF 类型。
为了开始升级到 CoreWCF,我添加 CoreWCF.NetTcp NuGet 包的 1.0 版本引用。我还替换了 using System.ServiceModel;
,在 BeanTrader.cs 中导入 using CoreWCF;
。除了在 program.cs 中我创建 ServiceHost 的错误之后,这解决了其它所有错误。
CoreWCF 构建于 ASP.NET Core 之上,所以我需要升级该项目来启动一个 ASP.NET Core 宿主。BeanTrader 示例是一个自托管的服务项目,所以我只需要做一小点修改来设置一个 ASP.NET Core 宿主来运行我的服务,而不是直接使用 ServiceHost 完成。为了完成这一点,我将项目的 SDK 升级为 Microsoft.NET.Sdk.Web
( 因为项目使用了 ASP.NET Core ),将应用的 Main() 方法改为 async,并使用下面代码替换了对 ServiceHost 的设置。
存在不同类型的 WCF 项目 ( 不都是直接创建并运行 ServiceHost ),但所有的 CoreWCF 应用都是运行在 ASP.NET Core 的端点上。这里展示的代码使用新的 .NET 6 的 minimal API 语法,来使用最少的代码启动宿主并运行,它也可以微调为使用 ASP.NET Core 语法 ( 例如有独立的 Startup.cs ),如果你愿意的话,CoreWCF 示例中演示了这两种方式。
注意证书的配置是从原来的示例项目中复制过来的,只用于演示目的。真实的场景下应该使用来自机器的证书存储来使用证书,或者使用安全的位置,比如 Azure 的 Key Vault。另外,这也很好地演示了在使用 CoreWCF 的时候,宿主的属性如何修改,但是设置服务器证书是特别针对 NetTcp 场景的。对于 HTTPS 端点,SSL 通过 ASP.NET Core API 设置,与其它的 ASP.NET Core 应用一样。
var builder = WebApplication.CreateBuilder();
// Add CoreWCF services to the ASP.NET Core app's DI container
builder.Services.AddServiceModelServices();
var app = builder.Build();
// Configure CoreWCF endpoints in the ASP.NET Core host
app.UseServiceModel(serviceBuilder =>
{
serviceBuilder.ConfigureServiceHostBase<BeanTrader>(beanTraderServiceHost =>
{
// This code is copied from the old ServiceHost setup and configures
// the local cert used for authentication.
// For demo purposes, this just loads the certificate from disk
// so that no one needs to install an untrusted self-signed cert
// or load from KeyVault (which would complicate the sample)
var certPath = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), "BeanTrader.pfx");
beanTraderServiceHost.Credentials.ServiceCertificate.Certificate = new X509Certificate2(certPath, "password");
beanTraderServiceHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
});
});
await app.StartAsync();
我还使用 await app.StopAsync()
替换了原来应用中的 host.Close()
调用。
3.3 升级配置信息
如前所提及,.NET 6 默认没有包含 system.serviceModel
配置节。但是众多现存的 WCF 应用程序使用 app.config 和 web.config 来设置绑定配置。为了更容易迁移,CoreWCF 包含了可以从 xml 配置文件中显示加载配置信息的 API。
为了使用 Bean Trader 服务器的 WCF 配置,我从添加 CoreWCF.ConfigurationManager
NuGet 包开始。然后,我把原来应用的 app.config 配置文件中的 system.serviceModel
配置节 ( 它被升级助理注释掉了 ),复制到一个新的配置文件中。该配置文件可以使用任意名称,不过我命名它为 wcf.config
。
在 WCF 与 CoreWCF 之间支持哪些 WCF 配置存在一些不同,所以我需要对 wcf.config
做一些如下的修改:
-
IMetadataExchange
还不被 CoreWCF 支持,所以删除 mex 端点。我可以仍然使得 WSDL 可以被下载,记住,我随后将展示如何做到这一点。 -
在服务模型配置中,元素
\<host>
不被支持。相反,端点的监听端口在代码中配置。所以,我需要从 wcf.config 中删除\<host>
元素,然后在应用的 Main() 方法中添加如下的代码行:builder.WebHost.UseNetTcp(8090);.
这应该在调用
builder.Build()
之前。
最后,我升级应用的 Main() 方法,添加配置到 ASP.NET Core 应用的依赖注入容器中。
builder.Services.AddServiceModelConfigurationManagerFile("wcf.config");
此时,应用将可以正常工作,客户端也可以成功连接到它。我还希望使 WSDL 也可以使用,所以,我继续对项目做一些修改。首先,添加如下代码到 Main() 方法中,来使得 ASP.NET Core 应用监听到端口 8080 ( 因为以前的应用从该端口下载 WSDL ):
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(8080);
});
然后,当注册服务的时候,我添加对 builder.Services.AddServiceModelMetadata()
的调用,来确保元数据服务可用,这样我将获得该 ServiceMetadataBehavior
对象实例,它以单例模式注册,通过修改它来使得 WSDL 可以下载。这些代码需要在构建 app 之后,但是在启动之前。
// Enable getting metadata/wsdl
var serviceMetadataBehavior = app.Services.GetRequiredService<ServiceMetadataBehavior>();
serviceMetadataBehavior.HttpGetEnabled = true;
serviceMetadataBehavior.HttpGetUrl = new Uri("http://localhost:8080/metadata");
通过这些修改,该 Bean Trader 服务现在完全迁移到了 .NET 6! 我可以运行该服务应用,并使用现在的客户端连接到它。并且 WSDL 也可以通过 localhost:8080/metadata 来下载。想要看到完整的本文中所有的修改,你可以 查看该 PR,它这样修改了 Bean Trader 示例应用。最后,示例项目的 NetCore 文件夹中包含了只有 .NET Core 和面向 .NET 6 的项目!
总结
Bean Trader 示例项目只是一个小项目,但是希望该演练过程展示了在 .NET 6 平台上,使用 CoreWCF 来利用 WCF 服务继续工作做需要的修改。除了引用不同的命名空间之外,WCF 的服务实现几乎不需要修改,多数的 xml 配置也得以重用。我做了使得服务宿主创建的修改 ( 现在服务通过 ASP.NET Core 托管 ),但是我仍然能够重用以前用来定制服务宿主行为的代码。