以前的项目格式使用的是 csproj 的格式,但是 .net core 支持使用 project.json 格式的项目文件,后来还是决定不使用这个格式。 VS2017 的项目格式更好读、更简单而且减少了 git 冲突。 本文来告诉大家如何从 VS2015 和以前的项目格式修改为 VS2017 项目格式。当前对新项目格式的命名是 SDK Style 风格的 csproj 项目文件格式。而在 VS2015 和以前的项目格式是 Franken-proj 格式

在迁移之前,我需要告诉大家,现在是2018年1月15日,最新的项目格式只有对下面的项目支持 现在是 2020年12月 好像啥项目都支持了

  • class library projects 类库项目
  • console apps 控制项目
  • ASP.NET Core web apps asp 项目
  • .NET Core .NET Core
  • 还有其他所有的项目

自动化升级

因为好多小伙伴都在期望有自动化的工具去转换,因此 dotnet 基金会特别推出了转换工具,使用方法如下

这是一个 dotnet tool 工具,使用之前先使用下面命令进行安装。这是一个在 GitHub 上完全开源的仓库,请看 https://github.com/dotnet/try-convert

dotnet tool install -g try-convert

接着进入到需要转换的项目所在的文件夹

cd 需要转换的项目所在的文件夹

使用下面命令进行自动化转换

try-convert

默认能将 csproj 转换好,但是转换完成之后,也许会构建不通过,如提示 特性重复的编译出现 CS0579 重复,请看下面提示内容

    Error CS0579: “System.Reflection.AssemblyCompanyAttribute”特性重复 (1, 1)

解决方法是删除 AssemblyInfo.cs 文件的内容。特性文件不删除也可以,请使用解决从旧格式的 csproj 迁移到新格式的 csproj 格式 AssemblyInfo 文件值重复问题的方法

详细请看 dotnet 自动迁移 VS 2017 以前的 csproj 转为 dotnet core 的 SDK Style 风格工具

其他细节部分还请看手动修改

手动修改

对于 UWP 和 WPF ,有 xaml 的项目是没有很好支持 现在是 2020年12月 好像支持不错

现在很多项目,测试使用的项目都使用新格式,建议在自己用来测试的项目试试

建议从一个测试项目试试,先做好提交,如果失败可以回滚。 不用试试了,我都玩了三年了

如果创建是库项目,那么 csproj 文件只有下面的代码就可以完成了

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net46</TargetFramework>
  </PropertyGroup>
</Project>

如果创建的是控制项目,那么只有下面的代码

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net46</TargetFramework>
  </PropertyGroup>
</Project>

如果创建的是测试项目,那么只有下面的代码

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net46</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
    <PackageReference Include="xunit" Version="2.2.0" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
  </ItemGroup>
</Project>

如果是 WinForms 或 WPF 项目,那么只有下面的代码

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFrameworks>netcoreapp3.1;net5-windows</TargetFrameworks>
    <UseWPF>true</UseWPF>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>

</Project>

如果想知道新格式和之前的区别,如何从以前的格式迁到新的格式,请继续阅读本文

下面从项目的第一行开始来告诉大家如何一步步迁移。当前还没有一个可以做到自动化的迁移工具,但是有一个快捷的方式是重新新建项目,然后一点点将原有项目的内容通过在 VS 上可视化的步骤加回来。本文会告诉大家的是如何编辑 csproj 文件,而不是在 VS 上通过点一点完成,本文更适合高级的开发者

原来的第一行的代码大概内容如下

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

新的格式的第一行内容如下

<Project Sdk="Microsoft.NET.Sdk">

或者对于 WPF 来说是如下格式,关于 WPF 部分放在本文最后

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

从第一行可以看到新的格式的代码比较少,而实际上除了第一行之外,后面的内容有着更大的简化

其实建议大家重新创建一个项目,然后把文件放进去,安装 Nuget 不然需要修改比较多

必须删除的内容

下面的代码必须删除,因为新的 SDK 风格的项目格式不再需要了

<!-- usually at the top of the file -->
<!-- 下面的代码一般在文件的最前 -->
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />

<!-- usually at the bottom -->
<!-- 一般在文件的最后面 -->
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />

还需要删除 AssemblyInfo.cs 文件,不然编译出现 CS0579 重复,请看下面提示内容

    Error CS0579: “System.Reflection.AssemblyCompanyAttribute”特性重复 (1, 1)

特性文件不删除也可以,请使用解决从旧格式的 csproj 迁移到新格式的 csproj 格式 AssemblyInfo 文件值重复问题的方法

必须修改

需要修改平台框架的代码

这是以前的代码

<PropertyGroup>
  <!-- ... -->
  <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>

需要修改为下面的代码

<PropertyGroup>
  <TargetFramework>net452</TargetFramework>
</PropertyGroup>

如果是 v4.5.2 ,请修改为 net452 ,如果是 v4.6 就修改为 net46 详细请看 dotnet 新项目格式与对应框架预定义的宏

添加文件

现在新的格式可以使用通配添加文件,例如在文件夹的所有的代码都需要添加,可以使用这个方式

<Compile Include="lindexi\*.cs" />

如果啥都不做,默认的 SDK 风格的项目格式已经包含了如下的引用

<!-- the defaults -->
<Compile Include="**\*.cs" />
<EmbeddedResource Include="**\*.resx" />

所以添加的 cs 文件都会添加到编译,需要删除这个代码才可以不编译一些文件,也就是原先写的添加文件都需要删除,如果不想删除可以使用 EnableDefaultCompileItems 设置不添加 cs 文件

    <EnableDefaultCompileItems>false</EnableDefaultCompileItems>

对 WPF 项目需要额外设置 EnableDefaultPageItems 不包含 Page 解决

    <EnableDefaultPageItems>false</EnableDefaultPageItems>

更多默认的行为请看 Roslyn 禁止 sdk style csproj 默认引用 Compile 代码文件

项目引用

之前的方式需要添加很多代码,如引用 ClassLibrary1 的项目,需要写下面的代码

<ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj">
  <Project>{2C7DF870-5B35-49EF-963D-EE1E72C3177E}</Project>
  <Name>ClassLibrary1</Name>
</ProjectReference>

Project 这个可以表示这是一个类库或一个其他的项目,因为新的项目不需要这个,所以在新建文件的时候就不知道给哪个项目,这是比较差的

新的格式就需要下面的代码,不需要使用 Project 和 Name 这两个元素

<ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj" />

如果引用的项目有依赖,以前的格式需要把引用写在文件,现在不需要添加引用

假如有 A 引用 B ,B 引用 C ,那么之前的A项目文件就是这样

<ProjectReference Include="..\ProjectB\ProjectB.csproj">
  <Project>{A900C843-8340-421B-B4F0-6C65A0D093C4}</Project>
  <Name>ProjectB</Name>
</ProjectReference>
<ProjectReference Include="..\ProjectC\ProjectC.csproj">
  <Project>{871AC142-FC46-49F5-A5E0-90436648B9C5}</Project>
  <Name>ProjectB</Name>
</ProjectReference>

现在的文件格式不需要写引用的依赖,只需要写入最顶层的依赖就可以了

<ProjectReference Include="..\ProjectB\ProjectB.csproj" />

而原先存在的一些引用,如 System.Net.Http 等可以使用 dotnet 新 sdk style 项目格式的一些命名空间和引用 的方法引用

引用包

之前的 Nuget 引用需要添加 packages.config 和 csproj 才可以使用,现在的 Nuget 4 可以直接在 csproj 添加引用

这是之前的格式

<Import Project="..\..\packages\xunit.runner.visualstudio.2.2.0\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\..\packages\xunit.runner.visualstudio.2.2.0\build\net20\xunit.runner.visualstudio.props')" />

<ItemGroup>
  <None Include="packages.config" />

  <Reference Include="MySql.Data, Version=6.9.9.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d, processorArchitecture=MSIL">
  <HintPath>..\..\packages\MySql.Data.6.9.9\lib\net45\MySql.Data.dll</HintPath>
  <Private>True</Private>
</Reference>
</ItemGroup>

新的格式写引用,不需要 packages.config 文件,这样减少了找不到nuget的坑,下面代码就是新的格式,可以看到代码减少1/2

<ItemGroup>
  <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
  <PackageReference Include="MySql.Data" Version="6.9.9" />
</ItemGroup>

这个格式可以在git冲突比较容易看到哪里需要修改,所以解决冲突很简单

在 nuget 2 的引用,如果引用了包A,他引用了 B 库,那么就需要在 packages.config 引用写了这几个项目

<?xml version="1.0" encoding="utf-8"?>
<!-- in packages.config -->
<packages>
  <package id="A" version="2.2.0" targetFramework="net452" />
  <package id="B" version="2.0.1" targetFramework="net452" />
</packages>

现在 Nuget 4 需要写引用的库,不需要写他的引用,所以可以减少代码,添加了id和版本就好

<ItemGroup>
  <PackageReference Include="A" Version="2.2.0" />
</ItemGroup>

如果在迁移过程遇到诡异的问题,请看将 WPF、UWP 以及其他各种类型的旧样式的 csproj 文件迁移成新样式的 csproj 文件 - walterlv

删除多余文件

现在 VisualStudio 2017 项目格式不需要使用 AssemblyInfo 包含一些值,所以现在编译 VisualStudio 会发现重复定义了一些值,需要删除 Properties/AssemblyInfo.cs 文件。

如果只有需要定义一些全局的特性,那么直接新建一个类写就好。

如果不想删除,请设置 GenerateAssemblyInfo 属性

    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>

详细请看 解决从旧格式的 csproj 迁移到新格式的 csproj 格式 AssemblyInfo 文件值重复问题

删除的时候小心属于 WPF 的 ThemeInfo 特性和 COM 不能删除

[assembly: ThemeInfo(
    ResourceDictionaryLocation.None, //主题特定资源词典所处位置
    //(在页面或应用程序资源词典中 
    // 未找到某个资源的情况下使用)
    ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
    //(在页面、应用程序或任何主题特定资源词典中
    // 未找到某个资源的情况下使用)
)]

输出注释文档

如果需要输出注释文档文件,也就是 xx.xml 注释文件,在以前的代码是在属性页面,点击生成xml选项就可以生成的注释文档,现在新的格式和之前有些不一样,可以通过添加下面的代码生成xml注释文档文件,请看代码

  <PropertyGroup>
    <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>

这里的$(Configuration)就是用到了宏,他会替换当前编译的是 Debug 还是 release ,所以对于所有的项目都可以使用这个来生成 xml 注释文档文件

或者使用 OutputPath 相同的文件夹,因为可能是自己定义了 OutputPath 下面代码就自己修改了不在当前的文件夹

  <PropertyGroup>
    <OutputPath>..\..\bin\$(Configuration)\</OutputPath>
    <DocumentationFile>$(OutputPath)$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>

注意这时需要让 OutputPath 在 DocumentationFile 之前,否则拿到的值是默认的值

更简易的方法,可以无视路径的方法是使用 GenerateDocumentationFile 属性

  <PropertyGroup>
      <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>

将 XML 输出到 NuGet 包的方法请看 Roslyn 在 NuGet 包中放注释 xml 文件的方法

如果还有其他的文件也需要放入 NuGet 包,请看 Roslyn 打包自定义的文件到 NuGet 包

多个框架

如果需要同时打包出 dotnet standard 和 dotnet framework 的包,就需要使用下面的方法。

因为现在存在一些项目是使用多个开发框架,这时就需要修改TargetFrameworkTargetFrameworks也就是写为复数的TargetFrameworks,把里面的一个框架修改为多个,请看下面

  <PropertyGroup>
    <TargetFrameworks>net45;netstandard2.0</TargetFrameworks>
  </PropertyGroup>

如果想知道每个平台的缩写,请看Target frameworks 或我的博客 dotnet 新项目格式与对应框架预定义的宏

.NET Standard

  • netstandard1.0
  • netstandard1.1
  • netstandard1.2
  • netstandard1.3
  • netstandard1.4
  • netstandard1.5
  • netstandard1.6
  • netstandard2.0

.NET Core

  • netcoreapp1.0
  • netcoreapp1.1
  • netcoreapp2.0
  • netcoreapp2.1

.NET Framework

  • net11
  • net20
  • net35
  • net40
  • net403
  • net45
  • net451
  • net452
  • net46
  • net461
  • net462
  • net47
  • net471
  • net472

Universal Windows Platform

  • uap [uap10.0]
  • uap10.0 [win10] [netcore50]

使用条件判断

因为在多个框架,存在一些框架不能引用的库,而在一些框架需要这些库,如 ValueTuple ,就需要在引用的时候添加条件

添加条件可以使用这样的代码Condition=" '$(TargetFramework)' == 'net40' 把 net40 修改为你希望的框架就在指定的框架引用库。

  <ItemGroup Condition=" '$(TargetFramework)' == 'net40' ">
    <Reference Include="System.Net" />
  </ItemGroup>

如果通过条件还可以在某些框架引用或不引用某些文件。

在代码可以通过默认的宏来判断当前是哪个框架,默认的宏就是上面的缩写的大写

    static void Main()
    {
#if NET40
        Console.WriteLine("Target framework: .NET Framework 4.0");
#elif NET45  
        Console.WriteLine("Target framework: .NET Framework 4.5");
#endif
    }

不同框架的宏请看下面

.NET Framework

  • NET20
  • NET35
  • NET40
  • NET45
  • NET451
  • NET452
  • NET46
  • NET461
  • NET462
  • NET47
  • NET471
  • NET472

.NET Standard

  • NETSTANDARD1_0
  • NETSTANDARD1_1
  • NETSTANDARD1_2
  • NETSTANDARD1_3
  • NETSTANDARD1_4
  • NETSTANDARD1_5
  • NETSTANDARD1_6
  • NETSTANDARD2_0

.NET Core

  • NETCOREAPP1_0
  • NETCOREAPP1_1
  • NETCOREAPP2_0
  • NETCOREAPP2_1

更多宏细节请看 dotnet 新项目格式与对应框架预定义的宏

参见:让一个 csproj 项目指定多个开发框架 - walterlv

迁移 WPF 项目

如果需要迁移 WPF 项目,那么先记下自己的引用,最好是截图,然后卸载项目。编辑项目文件,使用下面的代码代替

<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
  <PropertyGroup>
    <LanguageTargets>$(MSBuildToolsPath)\Microsoft.CSharp.targets</LanguageTargets>
    <TargetFrameworks>net45;</TargetFrameworks>
    <OutputType>WinExe</OutputType>
  </PropertyGroup>
 
  <ItemGroup>
    <Compile Update="**\*.xaml.cs">
      <DependentUpon>%(Filename)</DependentUpon>
    </Compile>
    <Page Include="**\*.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>

    <Resource Include="**\*.png" />
    <Resource Include="**\*.jpg" />
    <Resource Include="**\*.cur" />
    <Resource Include="**\*.ps" />
    <None Include="**\*.fx" />
    <None Include="**\*.md" />
    <None Include="**\*.ruleset" />
  </ItemGroup>
 
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Data" />
    <Reference Include="System.Xml" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Xaml"/>
    <Reference Include="WindowsBase" />
    <Reference Include="PresentationCore" />
    <Reference Include="PresentationFramework" />
  </ItemGroup>
</Project>

保存重新加载,可能需要修改平台,我这里使用 net45 ,大家按照原来的需要修改。

看一下缺少了哪些引用再自己添加。

稍微解释一下上面的代码,在一开始使用的 OutputType 是告诉 VisualStudio 生成一个窗口应用程序。因为默认生成的是 dll ,而在本文上面也告诉大家设置控制台输出是使用下面代码

    <OutputType>Exe</OutputType>

对于 WPF 是窗口程序,如果使用了上面的设置,就会在启动的过程先出现控制台窗口,然后在显示主界面。为了让 WPF 窗口直接显示而不是先显示控制台窗口,需要把上面代码修改为

    <OutputType>WinExe</OutputType>

如果此时提示找不到 App.xaml 请在 csproj 添加下面代码

    <ApplicationDefinition Include="App.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </ApplicationDefinition>

在 WPF 还有很多窗口页面,这些代码使用 xaml 来写,需要把 xaml 和 xaml.cs 对应,所以需要使用下面代码

    <Compile Update="**\*.xaml.cs">
      <DependentUpon>%(Filename)</DependentUpon>
    </Compile>
    <Page Include="**\*.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>

上面这个代码的意思是对于 xaml 文件的编译和折叠,如折叠 Foo.xaml 和 Foo.xaml.cs 文件。现在对于 UWP 项目,使用上面的方法是编译不通过。如果需要支持 UWP 的 VisualStudio 2017 项目格式,请看 新 csproj 对 WPF/UWP 支持不太好?有第三方 SDK 可以用!MSBuild.Sdk.Extras - walterlv。如果不想点击网站,那么就请看代码

<Project Sdk="MSBuild.Sdk.Extras/1.5.4">
  <PropertyGroup>
    <TargetFrameworks>net47;uap10.0</TargetFrameworks>
  </PropertyGroup>
</Project>

现在官方支持使用下面代码迁移 WPF 项目

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

</Project>

核心是 Sdk="Microsoft.NET.Sdk.WindowsDesktop"<UseWPF>true</UseWPF> 两句话

如果需要引用 WinForms 项目,请添加 UseWindowsForms 属性

    <UseWindowsForms>true</UseWindowsForms>

把项目文件修改为上面的代码就可以支持 dotnet 4.7 和 UWP 项目

在新 SDK Style 风格 csproj 项目格式文件使用 WPF 和 WinForms 引用

如上面描述,在添加了 .NET Core 3.1 和以下版本或 .NET Framework 版本,可以使用 Microsoft.NET.Sdk.WindowsDesktop 构建

如果有 .NET 5 的引用支持,请使用 net5.0-windows 框架,于是一个多框架支持的项目格式,同时引用 WPF 和 WinForms 的新 SDK Style 风格的项目格式文件内容可以是如下

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFrameworks>netcoreapp3.1;net5-windows</TargetFrameworks>
    <UseWPF>true</UseWPF>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>

</Project>

参见:Old csproj to new csproj: Visual Studio 2017 upgrade guide

将 WPF、UWP 以及其他各种类型的旧样式的 csproj 文件迁移成新样式的 csproj 文件 - walterlv


本文会经常更新,请阅读原文: https://blog.lindexi.com/post/%E4%BB%8E%E4%BB%A5%E5%89%8D%E7%9A%84%E9%A1%B9%E7%9B%AE%E6%A0%BC%E5%BC%8F%E8%BF%81%E7%A7%BB%E5%88%B0-VS2017-%E6%96%B0%E9%A1%B9%E7%9B%AE%E6%A0%BC%E5%BC%8F.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者前往 CSDN 关注我的主页

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系

无盈利,不卖课,做纯粹的技术博客

以下是广告时间

推荐关注 Edi.Wang 的公众号

欢迎进入 Eleven 老师组建的 .NET 社区

以上广告全是友情推广,无盈利