Posted in:

New .csproj format

One really nice thing about the .NET Core tooling is the new csproj file format, which is a lot less verbose.

For example, if you create a brand new C# library project, targeting .NET Standard 2.0, then here's the entirety of the .csproj file:

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

It doesn't even require you to specify all the C# files to be compiled - it just assumes that all of them are wanted, which cuts down on a lot of noise.

Creating a NuGet package on build

If you want to create a NuGet package for your library, there's no need for a separate .nuspec file - you can just enable GeneratePackageOnBuild and define any metadata. The Visual Studio project properties dialog simplifies setting this up.

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <Authors>Mark Heath</Authors>
    <Version>1.0.1</Version>
    <PackageId>MyNugetLibrary</PackageId>
  </PropertyGroup>

Multi-Targeting

Another thing you can do, is multi-target more than one framework, which can be useful when you're creating a NuGet package intended to be used on several different versions of .NET. Here's how a project can target both .NET Standard 2.0 and .NET 4.6.2:

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

If we build this now, we'll get a NuGet package containing separately built DLLs for both targets. By the way, I'm using the excellent NuGet Package Explorer utility here to look inside my .nupkg file.

NuGet Multi-Target

Conditional References

One issue you might run into is that you need to reference different assemblies or NuGet packages depending on which target you're building for.

For example, it we add the following method to our library, it will successfully compile the .NET Standard 2.0 target, but the .NET 4.6.2 target will fail because it can't find the definition of HttpUtility.

public string JavaScriptEncode(string input)
{
    return HttpUtility.JavaScriptStringEncode(input);
}

To fix this we need to add a reference to the System.Web assembly, but it's only needed for the .NET 4.6.2 target. To do that, we simply add a conditional reference to our .csproj file using the following syntax:

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

Unfortunately, there doesn't seem to be any way of getting Visual Studio to auto-generate this for you, and the conditional syntax can be hard to remember, which is why I've documented it here so I can find it again in the future next time I forget it!

If it was a NuGet package you wanted to conditionally reference, that's done in the same way, just using PackageReference instead. I'm also showing here how you can use multiple conditions, so we'll reference this whether we're targeting .NET 4.6.2 or 3.5:

<ItemGroup Condition=" '$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'net35'">
    <PackageReference Include="NAudio" Version="1.8.5" />
</ItemGroup>

Conditional Compile

Sometimes you might want to exclude some C# files from being compiled for a certain target. For example, here's how in NAudio for the .NET 3.5 target, I'm referencing the System.Windows.Forms assembly, and excluding three specific files from being compiled:

<ItemGroup Condition=" '$(TargetFramework)' == 'net35' ">
<Reference Include="System.Windows.Forms" />
<Compile Remove="Wave\WaveOutputs\WasapiOutRT.cs" />
<Compile Remove="Wave\WaveInputs\WasapiCaptureRT.cs" />
<Compile Remove="Wave\WaveOutputs\WaveFileWriterRT.cs" />
</ItemGroup>

Summary

The new .csproj file format makes it really easy to develop NuGet packages that multi-target several frameworks, but you will likely occasionally need to use the conditional syntax to make this work.

Comments

Comment by Mark Heath

thanks for the heads up

Mark Heath