Stop using the old *.csproj SDK format
Migrate your legacy csproj files to the new SDK format in 8 steps
My last article, “Using glob patterns in *.csproj files” helps make your legacy *.csproj files leaner, cleaner and more reliable, but let’s take it even further.
Why be stuck with the legacy *.csproj file format? You see, if your *.csproj
file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
That is the old csproj format, and there is just no good reason to continue using it.
Why?
- You have to reference all
*.cs
files you intend to publish, and while you can use glob patterns (and you should, if you’re stuck with the old csproj format)
The drawbacks of the old *.csproj format
- Manual File References: In the old *.csproj format, you have to reference all *.cs files you intend to publish as well as each file you want to copy to the output directory. This means listing each file individually, leading to a cluttered and error-prone project file.
- Package Management Sucks: You have to reference DLLs directly. If the location changes, the error messages are not helpful. Granted, Visual Studio handles a lot of this for you, but if you haven’t used the new csproj format, you won’t even know what you’re missing.
- Overly Verbose: With workflows like Git where you have to review changes to the
*.csproj
files, having a compact file with information that is mostly useful to you at a glance, helps. - Can’t use the Dotnet CLI: Imagine you could make a change, and instantly have access to
dotnet build
,dotnet run
,dotnet publish
, instead of relying on Visual Studio to usemsbuild
in the background for everything because the syntax formsbuild
is too verbose and you never bothered to learn it.
Making the Transition
Let’s migrate to the new *.csproj file.
1. Create a new *.csproj file
First, we begin by creating your new *.csproj
file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo><!--important for ASP.NET projects -->
<LangVersion>preview</LangVersion>
<Version>1.0.0</Version>
</PropertyGroup>
<PropertyGroup>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
</PropertyGroup>
</Project>
2. Include References
Do you have a bunch of <Reference Include="..." />
in your old *.csproj
file? Add them:
<ItemGroup>
<Reference Include="System" />
<Reference Include="..." />
</ItemGroup>
Do you have <Reference />
statements that point to DLL files, such as:
<ItemGroup>
<Reference Include="Azure.Core, Version=1.35.0.0, Culture=neutral, PublicKeyToken=92742159e12e44c8, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Azure.Core.1.35.0\lib\net472\Azure.Core.dll</HintPath>
</Reference>
</ItemGroup>
Let’s add them, but let’s use Nuget packages instead where we can:
<ItemGroup>
<PackageReference Include="Azure.Core" Version="1.35.0" />
</ItemGroup>
3. Ignore Compile statements
You’ll likely have statements like:
<ItemGroup>
<Compile Include="App_State/Foo.cs" />
</ItemGroup>
Ignore them!
4. Move Content statements
When you see statements like:
<ItemGroup>
<Content Include="packages.config">
<SubType>Designer</SubType>
</Content>
<Content Include="Global.asax" CopyToOutputDirectory="PreserveNewest" />
<Content Include="Areas\HelpPage\Views\Web.config" />
<Content Include="Areas\HelpPage\Views\Shared\_Layout.cshtml" />
<Content Include="Areas\HelpPage\Views\Help\ResourceModel.cshtml" />
</ItemGroup>
Add them, using glob patterns where you can:
<ItemGroup>
<Content Include="packages.config">
<SubType>Designer</SubType>
</Content>
<Content Include="Global.asax" CopyToOutputDirectory="PreserveNewest" />
<Content Include="Areas\**\Web.config" />
<Content Include="Areas\**\*.cshtml" />
</ItemGroup>
5. Move your Project References
When you see statements like:
<ItemGroup>
<ProjectReference Include="..\Foo\Foo.csproj">
<Project>{bf10a583-4d81-4794-a9cc-8083a296a8ad}</Project>
<Name>Foo</Name>
</ProjectReference>
</ItemGroup>
Rejoice! Because they get even simpler when we move them. Let’s move them:
<ItemGroup>
<ProjectReference Include="..\Foo\Foo.csproj" />
</ItemGroup>
6. Move all Target statements
All statements looking like:
<Target Name="...">
...
</Target>
should be migrated.
7. Ignore Import statements
I haven’t missed any of the <Import ... />
statements that were in my legacy *.csproj
in my new project files since migrating, so I’ll say feel free to ignore statements like:
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
8. Now, for ASP.NET projects
If migrating an ASP.NET project, I’ve found it very useful to add the following:
<PropertyGroup>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath Condition="$(UseWPP_CopyWebApplication) != 'true'">bin\</OutputPath>
<OutputPath Condition="$(UseWPP_CopyWebApplication) == 'true'">bin\bin\</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="ConfigTransformationTool" Version="1.5.1" />
</ItemGroup>
<Target Name="CopyWebsiteFilesOnPublish" AfterTargets="Build" Condition="$(UseWPP_CopyWebApplication) == 'true'">
<Exec Command="powershell.exe -WindowStyle Hidden -NonInteractive -Command "Move-Item $(OutDir)Areas $(OutDir)../Areas" -Force" IgnoreExitCode="true" />
<Exec Command="powershell.exe -WindowStyle Hidden -NonInteractive -Command "Move-Item $(OutDir)App_Data $(OutDir)../App_Data" -Force" IgnoreExitCode="true" />
<Exec Command="powershell.exe -WindowStyle Hidden -NonInteractive -Command "Move-Item $(OutDir)Global.asax $(OutDir)../Global.asax" -Force" IgnoreExitCode="true" />
<Exec Command="$(NuGetPackageRoot)ConfigTransformationTool\1.5.1\tools\ctt.exe source:Web.config transform:Web.$(Configuration).config destination:$(OutDir)../Web.config" />
</Target>
This ensures that the output is built to the bin
directory, and if using the -p:UseWPP_CopyWebApplication=true
compiler option, then it goes to bin/bin
, so that your Web.config
and Global.asax
can live in the bin
.
Now, go ahead and build!
Note: If your project references other projects that use the old csproj format, they will also have to be migrated using the above steps.
Did it work? Let me know in the comments.