本文的更新时间为 2017 年 6 月,与互联网上许多其他 教程和 指南不同。
任何提到“project.json”的内容都已过时,VS 2017 需要 .csproj 文件。
介绍
我通过在 FluentFTP 库上的工作,被引入了 .NET Core/.NET Standard 的世界。我需要将其移植到 .NET Core,因此我决定学习这项技术。毕竟,它只是 .NET Framework 的一个简化版本,能有多难?事实证明这是一次艰难的经历,在找不到任何相关教程后,我决定自己写一篇。我不认为这是最好的方法,但这是对我有效的方法。
我的库是基于 .NET Framework 4.0 构建的,是移植的理想选择,因为它不使用 WindowsForms 或任何 UI 控件,而这些控件目前在 .NET Core 上不可用。您只能使用核心数据类型、集合、文件 I/O、XML、图像、计时器、进程等。几乎所有构建功能齐全的控制台应用或后端库所需的内容。
您可能知道 .NET Core 是 .NET Framework 的 开源版本,由 Microsoft 构建。它将整个 .NET Framework 分割成托管在 Nuget 上的“包”。每个包会拉入所需的依赖项包,使您的库保持精简。
移植这个相对简单的库是一次艰难的经历,我决定写下我的经验和技巧,以帮助开发人员将他们的技术移植到 .NET Core,从而为该平台做出贡献。您可以在 Awesome .NET Core 项目中浏览当前可用的 .NET Core 库。如果您已经将一个很棒的库移植到了 .NET Core,请不要忘记通过 fork 并 提交 PR 来将其添加到列表中!
本文以教程的形式编写,旨在帮助您完成自己的项目,但我将使用 FluentFTP
项目的屏幕截图和示例文件,因为它为每个步骤提供了实际示例。
回顾
简要回顾 .NET Core 及其与 .NET Framework 相比的优势。
- 它具有跨平台性,因此您的应用程序可以在 Windows、Unix、Mac 等上运行。我还没有看到合法的用途,因为如果您有一个服务器端应用程序,您就可以直接在具有完整 .NET Framework 的 Windows Server 上运行它。但是,使用 .NET Core,您现在可以在 Unix 上运行它,这是一个免费的操作系统。
- 它具有模块化,因为整个 .NET Framework 被切成小块并上传到 Nuget。因此,您的应用程序必须指定其依赖项包,这些包又拥有自己的依赖项。当您从 nuget 安装应用程序时,它只下载所需的包,不多不少。
- 它在线,这意味着最好让您的应用程序运行的每个系统都连接到互联网。如果您想要离线功能,可以在编译时将依赖项与您的应用程序一起打包,但这样您就无法获得基于 nuget 的更新。
要求
- 您需要 Visual Studio 2017 Community Edition 或更高版本来编译库的 .NET Core 版本。它必须安装在联网的 PC 上,并且在您登录后永久免费。您可以使用 VS 2015,但它已过时且不推荐。
- 您还需要Visual Studio 2012 或 2015 来编译库的 .NET Framework 版本。您可以像我一样在单台机器上同时安装 2012 和 2017。
- 您需要Windows 7 或更高版本,因为所有步骤都旨在在 Windows 机器上构建,使用批处理文件等。我仍然使用 Windows 7,所以我建议使用它。在 Unix 上构建不在本文讨论范围内,因为它需要 VS Code,这是 Microsoft 完全不同的开源 IDE,具有完全不同的项目配置(这是最困难的部分)。
- 您需要NuGet 位于C:\Tools\Nuget\nuget.exe 以创建您的 nuget 包
- 您将需要 NuGet Package Explorer 来查看最终的 nuget 包(.nupkg),并确保所有元数据和库都已成功拉取。如果您不打算发布到 nuget,请跳过此项。
目标
在本教程中,我们的目标是为库的 .NET Framework 和 .NET Core 版本使用单个代码库,使用 #if
指令有条件地为特定平台编译代码。由于在单个 VS 2017 项目中很难实现这一点,我 resort 于使用两个项目,一个用于 .NET Fx 的 VS 2012 项目,一个用于 .NET Core 的 VS 2017 项目。两个项目都引用相同的代码库,允许共享代码和共享修复。无需维护两个代码库并手动同步代码。
我们还将致力于使用相同的两个项目和单个代码库来定位多个 .NET Framework 版本。您始终可以使用相同的方法稍后添加更多版本。
平台 | Binaries 文件夹 | 解决方案 |
.NET 2.0 | net20 | FluentFTP_NET_VS2012.sln |
.NET 4.0 | net40 | FluentFTP_NET_VS2012.sln |
.NET 4.5 | net45 | FluentFTP_NET_VS2012.sln |
.NET Core 5.0 | dnxcore50 | FluentFTP_Core_VS2017.sln |
.NET Standard 1.6 | netstandard1.6 | FluentFTP_Core_VS2017.sln |
我们最终将创建一个NuGet 包,将我们的库发布到 NuGet,我将介绍创建有效的 .nuspec、构建 nuget 包,甚至在 NuGet 上发布库所需的步骤。您的库将使用 NuGet 安装在 VS 2010、VS 2012 和 VS 2017 中。这是使用优秀的 NuGet Package Explorer 查看时的结果 .nupkg。

.NET Core vs .NET Standard
.NET Core 和 .NET Standard 之间的区别如下:
- .NET Standard 是一个“标准化”的 .NET 类集合,可在所有 .NET 平台(无论是 .NET Framework、.NET Core、Xamarin、PCL 还是其他)上使用。所谓的“一个库,统治所有” (参见下面 Microsoft 的图)。关于版本,.NET Standard 1.4 已足以满足我们的库需求,但是,我们实现 FTPS 需要
SslStream
类,因此我们不得不采用 .NET Standard 1.6。 - .NET Core 只是 .NET Standard 的一种实现,并包含一些额外功能,但这些都超出了本文的范围。以“
dnxcore50
”标识符发布到 nuget。

引用 Jon Skeet 的话:
引用
.NET Core 是 .NET Standard 的一种实现。它可以在多个操作系统上使用,但这并不相同——还有其他 .NET Standard 的实现。
因此,如果您创建一个 .NET Core 库,它将可以访问 .NET Core 中实现的但不属于 .NET Standard 的内容,并且您的库将不兼容 .NET Standard 的其他实现,例如 Xamarin、Tizen、完整的 .NET 桌面框架等。
简而言之:为了实现最大的可移植性,请让您的库目标为 .NET Standard。
下载
如果您想下载完成的项目,可以获取 FluentFTP 源文件 ZIP 17.4.2(截至 2017 年 6 月 5 日的最新版本),或者您可以从 github 仓库 下载/fork 源文件。
包含在 ZIP 和 Github 仓库中
- 适用于 VS 2012 和 VS 2017 的解决方案和项目文件
- 适用于 VS 2012 和 .NET Framework:FluentFTP_NET_VS2012.sln 和 FluentFTP.csproj
- 适用于 VS 2017 和 .NET Core:FluentFTP_Core_VS2017.sln 和 FluentFTP_Core.csproj
- 共享代码库(FluentFTP 文件夹)
- 用于更新 .NET Core 包(restore.bat)和构建 nuget 包(build_nuget.bat)的批处理文件
- 用于构建 nuget 包的 Nuspec 文件(FluentFTP.nuspec)
- 用于强命名签名的密钥文件(sn.snk)
仅包含在 ZIP 中
- 预先构建的二进制文件,用于显示文件夹结构(FluentFTP\bin)
- 预先构建的 nupkg 文件,用于显示最终的 nuget 包(FluentFTP\nuget\FluentFTP.17.4.2.nupkg)
入门
为 VS 2012 和 VS 2017 创建项目
使用任何方法为 VS 2012 和 VS 2017 创建 C# 库项目。您可以创建新项目,或使用我提供的项目作为模板。
接下来,为多个 .NET Framework 版本添加构建配置;打开 VS 2012 项目并通过 UI 添加构建配置,或手动修改文件(我就是这样做的)。下面详细列出了相关文件和所需的更改。
FluentFTP_NET_VS2012.sln
这里我们有 3 组构建配置
Debug / Release
- 构建 .NET 4.5 版本 DebugNET4 / ReleaseNET4
- 构建 .NET 4.0 版本 DebugNET2 / ReleaseNET2
- 构建 .NET 2.0 版本

FluentFTP.csproj
这里,我们有与上述相同的构建配置,每个配置有一个 PropertyGroup
。相关选项是:
DefineConstants
- 根据需要添加条件编译常量(例如,NETFX, NET2, NETFX45
) OutputPath
- 遵循 nuget 指定的标准命名系统(.NET 2 为 net20
,依此类推) TargetFrameworkVersion
- 您需要的 .NET 版本

FluentFTP_Core.csproj
解决方案文件无需更改 VS 2017,但是您必须在 Notepad++ 中打开 .csproj 文件,并确保已填充某些关键变量。
TargetFrameworks
- 非常重要。在这里,我使用了 netstandard1.6
和 dnxcore50
(.NET Core 5.0),因为我想同时支持这两个框架。如果您需要 .NET Standard 1.4,则需要在此处添加它,或者如果您只想支持 .NET Standard,则删除 dnxcore50
标识符。 DefineConstants
- 添加 CORE 常量以进行共享代码库的条件编译。 NetStandardImplicitPackageVersion
- 我们使用 .NET Standard 1.6。 TargetFrameworkIdentifier
- 我们使用 .NET Standard。 TargetFrameworkVersion
- 我们使用 .NET Standard 1.6。 AssemblyOriginatorKeyFile
- 如果您需要强命名签名,请在此处指定您的密钥文件。 PackageReference(s)
- 非常重要。您的库所需的 .NET Core 框架中的哪些程序集。
- 从
System.IO
开始,稍后根据需要添加更多。 - 要了解应添加哪些引用,请参阅“引用 .NET 类”部分。
- 要了解要填写的版本号,请访问程序集的 nuget 页面,并使用提供的最新版本。
- 一些程序集是隐式包含的,例如
System.Runtime
和 System.Collections
。但是,您仍然需要在 .nuspec 文件中列出它们。

创建 NUSPEC 文件
如果您不打算发布到 nuget,请跳过此项。我建议使用 Notepad++ 手动创建 .nuspec 文件,并以 我的 作为模板。关键参数如下所示并已详细说明:
<frameworkAssemblies>
包含您需要的任何额外 .NET Framework 引用。您需要为要为其发布 .NET Framework 版本的每个版本添加一个条目,因此在本例中是 net20
、net40
和 net45
。 <dependencies>
是较新 nuget 版本支持的一个较新标签,用于指定每一个 .NET Core 依赖项,即使是像 System.Runtime
和 System.Collections
这样的显式依赖项。
- 每次添加 .NET Core 依赖项时,都必须在此处添加(请参阅“引用 .NET 类”部分)。
- 虽然我为 .NET Framework 版本也放置了
<group>
条目,但您不必填充它们,但请添加它们,以免较新版本的 nuget 失败。

移植您的代码
一旦设置好所有项目,就可以开始编译并将库移植到 .NET Core 了。启动 VS 2017 并打开您的 .NET Core 解决方案。如果您使用的是我的模板,那么您需要的文件是 FluentFTP_Core_VS2017.sln。点击编译。如果您使用自己的库,您应该会看到数百个错误。别担心!大多数错误都可以轻松解决。
有用技巧
使用 #if CORE
标记 .NET Core 所需的替代代码。一种常见模式是:
#if CORE
socket.Close();
#else
socket.Dispose();
#endif
以及“禁用”代码以编译到您的 .NET Core 版本中:
#if !CORE
// advanced code that only works on .NET framework
#endif
如果您也要为 .NET 2.0 编译,那么您需要用以下方式保护您的导入:
#if (CORE || NETFX)
using System.Threading;
#endif
#if (CORE || NETFX45)
using System.Threading.Tasks;
#endif
常见错误
- 找不到类型 X - 您需要找到所需的类属于哪个 .NET Core 程序集,并在您的 .NET Core .csproj 和 .nuspec 文件(如果发布到 nuget)中添加引用。请记住,每次添加引用后都要运行 restore.bat,否则编译错误将不会消失!有关更多信息,请参阅“引用 .NET 类”部分。
- 类型 X 中未提供方法 X - 它可能已被重命名或删除。要进行检查,只需打开相关类的 .NET Core API 参考,并手动查看其成员。
一些关键点
- 反射在 .NET Core 中可用,但您需要引用以下内容:
System.Reflection
System.Reflection.Primitives
System.Reflection.Extensions
System.Reflection.TypeExtensions
- 如果您需要 IL 生成,则添加
System.Reflection.Emit
和 System.Reflection.Emit.ILGeneration
- 任务和线程以及 async/await 可用,但您需要引用以下内容:
System.Threading.Thread
System.Threading.Tasks
- 套接字可用,但您需要引用以下内容:
System.Net.Sockets
. System.Net.Security
如果您想要 SslStream
。 - 此外,
socket.Close()
现在是 socket.Dispose()
- 异步受支持(请参阅上面的要点),但旧的
IAsyncResult
基于的异步不支持。您必须使用 #if
标签禁用这些部分,或升级到 async/await
。 - 序列化(通过将数据转换为二进制)不受支持,但 XML 和 JSON 序列化支持。(请参阅
System.Runtime.Serialization.Xml
和 System.Runtime.Serialization.Json
) - 位图不可用(
System.Drawing.Image
),但对于 Point
和 Rect
等几何图元,请参阅 System.Drawing.Primitives
。 - 加密可用,但许多类已重命名和重构,例如
new SHA1CryptoServiceProvider()
现在是 SHA256.Create()
。 - StackTrace 可用,但您需要额外的
System.Diagnostics.StackTrace
,因此如果它不是必需的,您可能希望从代码中删除它,而不是添加额外的依赖项。 - DataTable 和 DataSet 在
System.Data
命名空间中不可用,但提供者模型和 SQL 客户端等其他功能可用。 - XAML 不受支持,但如果您以 UWP 为目标,则必须使用 Windows RT XAML API。
一些关键的缺失组件(来源)
System.AppDomain
- 应用域 System.Drawing.Image
- 图形、位图图像 System.DirectoryServices
- LDAP、Active Directory System.Transactions
- 事务上下文、分布式事务 System.Xml.Xsl
- XSLT System.Xml.Schema
- XSD System.Net.Mail
- 发送电子邮件 System.Runtime.Remoting
- 远程处理、RPC System.Runtime.Serialization.Xml
- 二进制序列化 System.IO.Ports
- 串行端口 System.Workflow
- Windows Workflow Foundation
引用 .NET 类
许多类和方法被重命名或删除,以清理或现代化 API。即使是未重命名的类,也需要以特定方式引用,.NET Core 编译器才能将其包含在您的项目中。因此,当您想使用 .NET 类时,流程如下:
- 在在线 .NET Core 网站上查找类(例如,Directory),或使用以下 其他工具
- 在标题中,您会找到程序集:System.IO.FileSystem.dll - 这告诉您该类所需的 nuget 包(请参见下图)
- 将该 nuget 包添加到您的 .NET Core “.csproj” 和 “.nuspec” 文件中。(请参见下图)
- 运行“
dotnet restore
”命令以下载新的程序集(运行 restore.bat) - 添加一个
import
并在您的代码中使用该类。 - 希望此时它能编译,如果不行,尝试使用最新版本的 .NET Standard(当前为 1.6)。
.NET Core 文档

将引用添加到您的 .NET Core .csproj 中

将引用添加到您的 .nuspec 中

创建您的 NuGet 包
一旦您的库能够编译和构建,您可能希望将其发布到 NuGet。如果您还没有了解,NuGet 是一种很酷的方式,可以即时自动地将库(及其所有依赖项)拉入 .NET 项目。它由 Microsoft 收购,并在 Visual Studio 中得到官方支持。
要测试创建 nuget 包,请运行 build_nuget.bat。任何与 nuget 相关的错误都会显示在控制台窗口中。修复它们。一切正常后,您应该会在控制台中看到“成功创建包 XYZ”。
常见错误
- 参数缺失。 Nuspec 格式一直在变化,如果您缺少参数,在尝试构建时 nuget.exe 会通知您。使用 Google 查找新参数的默认值。
- 程序集引用不正确。 Nuget 有时会警告您
<dependencies>
中指定的程序集在 Nuget 上不存在。打开 nuget 网站(或 这些工具中的一个)并搜索您需要的 .NET Core 程序集的准确名称。
打开您的 nuget 目录(在我的模板中位于 FluentFTP\nuget),然后双击新创建的 .nupkg 文件,在 NuGet Package Explorer 中打开您的 NuGet 包。
常见错误
如果一切顺利,您应该会看到类似这样的内容:

发布到 NuGet
我确信有更自动化的方法可以实现这一点,但以下是我每次需要将库的更新推送到 NuGet 时使用的步骤。
编译您的库
- 在 AssemblyInfo.cs 中增加版本号
- 在 FluentFTP.nuspec 中增加版本号
- 在 FluentFTP_Core.csproj 中增加版本号
- 打开 VS 2012 - 编译“NET Release”、“Release_NET2”、“Release_NET4”
- 打开 VS 2017 - 编译“Release”
构建并发布 Nuget 包
- 运行“build_nuget.bat”脚本以创建 .nupkg
- 访问 nuget 上的此链接以下载包
- 登录到您的 nuget 帐户(如果没有,请创建一个)
- 浏览到您的 nuget 包(.nupkg)
- 添加“发布说明”(支持有限的 markdown,例如项目符号列表)
- 上传包
- 您将在包“发布”时收到一封电子邮件。
总结
虽然 .NET Core 生态系统正在被 Microsoft 积极改进,但它仍然需要大量工作,尤其是在文档方面,才能使其易于使用。移植一个简单的库花费了一个多星期的辛勤工作,尽管我的教程应该有助于项目配置和编译,但在移植和测试阶段,开发人员仍然需要付出很多努力。应该有一个“一键式”应用程序移植工具,可以将您的项目重构为与 .NET Core 兼容。
最困难的部分是将其编译成 nuget 兼容的格式(发现 dnxcore50
标识符)以及为库的 .NET Framework 和 .NET Core 版本创建有效的“合并”nuspec。
我希望我的教程能帮助您将 .NET 库移植到 .NET Core,如果它有帮助,请在下方评论!
谢谢。
有用链接
在移植过程中,您将需要这些工具来帮助查找 .NET Core 类与 .NET Framework 类之间的对应关系。有时一个类已被重命名,有时它根本不存在。
- .NET API 浏览器 - Microsoft 的出色工具,可帮助您查找包含在 .NET Core / .NET Standard 中的类。您可以输入一个类的名称,例如“
stream
”,以查看包含哪些同名类。 - 反向包搜索 - 与上述类似,但您可以搜索类和成员!输入一个类的名称然后按 Enter(它没有“边输入边查找”功能)。它会列出所有同名的类以及该类位于哪个 .NET Core / .NET Standard 版本的信息。
- NuGet 搜索 - 一旦您知道所需的 .NET Core 程序集,就可以在 nuget 上搜索以获取确切的包名称,并获取最新版本,然后将其添加到您的 .nuspec 和 .csproj 中。
- .NET API 可移植性分析器 - Microsoft 的一个工具,用于分析您的项目并突出显示 .NET Core 中不可用的类。我从未使用过它,所以没有评论。
- 移植 FluentFTP - 将 FluentFTP 从 .NET Framework 4.0 移植到 .NET Core 5.0 和 .NET Standard 1.6 所需的更改。链接指向我关于此的 github 提交历史记录。
历史
- 2017 年 6 月 5 日 - 文章的第一个版本,包含简介、回顾、要求、目标、.NET Core vs .NET Standard、下载、入门、引用 .NET 类、创建 NuGet 包、发布到 NuGet、有用链接和摘要。
- 2017 年 6 月 6 日 - 在顶部添加了“最新”注释。将 NuGet 添加到要求列表中。将下载内容分为仅在 ZIP 中可用和通用内容。
- 2017 年 6 月 9 日 - 修正了关于 .NET Core“仅在线”的说明。提及离线也可行。