Old csproj to new csproj: Visual Studio 2017 upgrade guide
The leaner csproj in VS 2017 can save you hundreds of lines of code. What to cut, keep, and change to upgrade to VS 2017
You may have heard the buzz: .NET Core went from the project.json to csproj file format, and the new csproj format is leaner, easier to read, and adds new features. But what about your .NET Framework VS 2015 (or 2013) project? How can you participate in the VS 2017 goodness? Keep reading: I’ll show you some of the major changes, and how to upgrade to VS 2017.
Disclaimer: this only works for a small set of project types.
- class library projects
- console apps
- ASP.NET Core web apps
- .NET Core
If you are building ASP.NET 4 (i.e not ASP.NET Core), WPF, Universal Windows, or Xamarin projects, you’ll have to stick with the old format (for now… follow https://github.com/dotnet/sdk/issues/491 and https://github.com/dotnet/project-system/issues/2670 for updates).
Approach one: start over
Delete everything and start over. Seriously consider this. It might be the easiest way to upgrade. (Make a backup copy first, obviously.)
Replace the contents of your csproj with the following, based on your project type:
Class library
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net46</TargetFramework>
</PropertyGroup>
</Project>
Console app
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net46</TargetFramework>
</PropertyGroup>
</Project>
Test 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>
Approach two: manually convert
Trim the fat manually.
Unfortunately, there is no magic button or command line tool to upgrade from VS 2015 to VS 2017 formats.
Keep your code in source control.
First line
Old
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
New
<Project Sdk="Microsoft.NET.Sdk">
Must delete
These lines must be removed to make the new format work:
<!-- 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')" />
Test projects usually have one or many blocks named <Choose>
. These must be deleted too:
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise />
</Choose>
Must change
These lines must be changed:
Old:
<PropertyGroup>
<!-- ... -->
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
New:
<PropertyGroup>
<!-- v4.5.2 => net452. v4.6 => net46, etc.-->
<TargetFramework>net452</TargetFramework>
</PropertyGroup>
That massive list of files
You can can delete any lines nested in an <ItemGroup>
that match with <Compile>
or <EmbeddedResource>
. This gets special mention, however, because their are excpetions.
By default, the new SDK will pick up default globbing patterns.
<!-- the defaults -->
<Compile Include="**\*.cs" />
<EmbeddedResource Include="**\*.resx" />
If you have anything outside of the project folder, you’ll need to keep those lines so VS and the compiler know where to find your code.
What to keep
These are common, non-default settings:
<PropertyGroup>
<Optimize>true</Optimize>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<OutputType>Exe</OutputType>
<!-- when RootNamespace != the csproj file name -->
<RootNamespace />ClassLibrary1</RootNamespace>
<!-- when AssemblyName != the csproj file name -->
<AssemblyName>ClassLibrary1</AssemblyName>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>$(DefineConstants);MY_CUSTOM_CONSTANT</DefineConstants>
<Platform>x64 or x86</Platform>
</PropertyGroup>
<ItemGroup>
<!-- keep references unless they are to package files. See the section about the NuGet upgrade below. -->
<Reference Include="System.Configuration" />
</ItemGroup>
Can remove
Heads up: these are suggestions for things you don’t need in most cases, but not all.
Project references
Old
<ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj">
<Project>{2C7DF870-5B35-49EF-963D-EE1E72C3177E}</Project>
<Name>ClassLibrary1</Name>
</ProjectReference>
New
<ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj" />
Also, MSBuild now does transitive project-to-project references automatically.
Example: Project A => Project B => Project C.
Old
Both B and C had to be listed.
<!-- in projectA.csproj -->
<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>
New
Only B needs to be listed. Project C will automatically be referenced as long as Project B references it.
<!-- in projectA.csproj -->
<ProjectReference Include="..\ProjectB\ProjectB.csproj" />
PropertyGroup
Bring out the red ink
Now the fun part, start deleting tons of stuff. The following elements under <PropertyGroup>
can be deleted.
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace />ClassLibrary1</RootNamespace>
<AssemblyName>ClassLibrary1</AssemblyName>
<Optimize>false</Optimize>
<ProjectGuid>xyz</ProjectGuid>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<FileAlignment>512</FileAlignment>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<NuGetPackageImportStamp></NuGetPackageImportStamp>
<TargetFrameworkProfile />
<TestProjectType>UnitTest</TestProjectType>
<IsCodedUITest>False</IsCodedUITest>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
You likely also have duplicates of these properties under a PropertyGroup that looks like this:
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
That whole block can usually be deleted too.
NuGet: imports and references
This gets special mention.
Old
In NuGet 2, you had a packages.config
file AND you had <Import>
and <Reference>
items in the csproj file.
<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>
New
Delete the packages.config file.
Delete any <Reference>
and <Import>
items that refer to files from packages.
Replace them with <PackageReference>
.
<ItemGroup>
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Include="MySql.Data" Version="6.9.9" />
</ItemGroup>
About transitive references
In NuGet 2, you listed every single package to be included. In NuGet 4 (VS 2017), you only need to list top-level dependencies. Everything else is that those top-level dependencies require will be imported automatically.
Old
<?xml version="1.0" encoding="utf-8"?>
<!-- in packages.config -->
<packages>
<package id="xunit" version="2.2.0" targetFramework="net452" />
<package id="xunit.abstractions" version="2.0.1" targetFramework="net452" />
<package id="xunit.assert" version="2.2.0" targetFramework="net452" />
<package id="xunit.core" version="2.2.0" targetFramework="net452" />
<package id="xunit.extensibility.core" version="2.2.0" targetFramework="net452" />
<package id="xunit.extensibility.execution" version="2.2.0" targetFramework="net452" />
<package id="xunit.runner.visualstudio" version="2.2.0" targetFramework="net452" developmentDependency="true" />
</packages>
Figuring this out will be trial and error. Best wishes if you have a long list.
New
<!-- in csproj -->
<ItemGroup>
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<!-- Notice that xunit.assert, xunit.core, etc. are imported automatically.-->
</ItemGroup>
Summary
Did I miss something? Not sure if something can be removed? Comment below and I’ll try to help out.