A mapping between project.json and csproj properties

By Nate McMaster

During the development of the .NET Core tooling, an important design change was made to no longer support project.json files and instead move the .NET Core projects to the MSBuild/csproj format.

This article shows how the settings in project.json are represented in the MSBuild/csproj format so you can learn how to use the new format and understand the changes made by the migration tools when you're upgrading your project to the latest version of the tooling.

The csproj format

The new format, *.csproj, is an XML-based format. The following example shows the root node of a .NET Core project using the Microsoft.NET.Sdk. For web projects, the SDK used is Microsoft.NET.Sdk.Web.

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

Common top-level properties

name

{
  "name": "MyProjectName"
}

No longer supported. In csproj, this is determined by the project filename, which is defined by the directory name. For example, MyProjectName.csproj.

By default, the project filename also specifies the value of the <AssemblyName> and <PackageId> properties.

<PropertyGroup>
  <AssemblyName>MyProjectName</AssemblyName>
  <PackageId>MyProjectName</PackageId>
</PropertyGroup>

The <AssemblyName> will have a different value then <PackageId> if buildOptions\outputName property was defined in project.json. For more information, see Other common build options.

version

{
  "version": "1.0.0-alpha-*"
}

Use the VersionPrefix and VersionSuffix properties:

<PropertyGroup>
  <VersionPrefix>1.0.0</VersionPrefix>
  <VersionSuffix>alpha</VersionSuffix>
</PropertyGroup>

You can also use the Version property, but this may override version settings during packaging:

<PropertyGroup>
  <Version>1.0.0-alpha</Version>
</PropertyGroup>

Other common root-level options

{
  "authors": [ "Anne", "Bob" ],
  "company": "Contoso",
  "language": "en-US",
  "title": "My library",
  "description": "This is my library.\r\nAnd it's really great!",
  "copyright": "Nugetizer 3000",
  "userSecretsId": "xyz123"
}
<PropertyGroup>
  <Authors>Anne;Bob</Authors>
  <Company>Contoso</Company>
  <NeutralLanguage>en-US</NeutralLanguage>
  <AssemblyTitle>My library</AssemblyTitle>
  <Description>This is my library.
And it's really great!</Description>
  <Copyright>Nugetizer 3000</Copyright>
  <UserSecretsId>xyz123</UserSecretsId>
</PropertyGroup>

frameworks

One target framework

{
  "frameworks": {
    "netcoreapp1.0": {}
  }
}
<PropertyGroup>
  <TargetFramework>netcoreapp1.0</TargetFramework>
</PropertyGroup>

Multiple target frameworks

{
  "frameworks": {
    "netcoreapp1.0": {},
    "net451": {}
  }
}

Use the TargetFrameworks property to define your list of target frameworks. Use semi-colon to separate multiple framework values.

<PropertyGroup>
  <TargetFrameworks>netcoreapp1.0;net451</TargetFrameworks>
</PropertyGroup>

dependencies

Important

If the dependency is a project and not a package, the format is different. For more information, see the dependency type section.

NETStandard.Library metapackage

{
  "dependencies": {
    "NETStandard.Library": "1.6.0"
  }
}
<PropertyGroup>
  <NetStandardImplicitPackageVersion>1.6.0</NetStandardImplicitPackageVersion>
</PropertyGroup>

Microsoft.NETCore.App metapackage

{
  "dependencies": {
    "Microsoft.NETCore.App": "1.0.0"
  }
}
<PropertyGroup>
  <RuntimeFrameworkVersion>1.0.3</RuntimeFrameworkVersion>
</PropertyGroup>

Note that the <RuntimeFrameworkVersion> value in the migrated project is determined by the version of the SDK you have installed.

Top-level dependencies

{
  "dependencies": {
    "Microsoft.AspNetCore": "1.1.0"
  }
}
<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore" Version="1.1.0" />
</ItemGroup>

Per-framework dependencies

{
  "framework": {
    "net451": {
      "dependencies": {
        "System.Collections.Immutable": "1.3.1"
      }
    },
    "netstandard1.5": {
      "dependencies": {
        "Newtonsoft.Json": "9.0.1"
      }
    }
  }
}
<ItemGroup Condition="'$(TargetFramework)'=='net451'">
  <PackageReference Include="System.Collections.Immutable" Version="1.3.1" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.5'">
  <PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
</ItemGroup>

imports

{
  "dependencies": {
    "YamlDotNet": "4.0.1-pre309"
  },
  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dnxcore50",
        "dotnet"
      ]
    }
  }
}
<PropertyGroup>
  <PackageTargetFallback>dnxcore50;dotnet</PackageTargetFallback>
</PropertyGroup>
<ItemGroup>
  <PackageReference Include="YamlDotNet" Version="4.0.1-pre309" />
</ItemGroup>

dependency type

type: project

{
  "dependencies": {
    "MyOtherProject": "1.0.0-*",
    "AnotherProject": {
      "type": "project"
    }
  }
}
<ItemGroup>
  <ProjectReference Include="..\MyOtherProject\MyOtherProject.csproj" />
  <ProjectReference Include="..\AnotherProject\AnotherProject.csproj" />
</ItemGroup>
Note

This will break the way that dotnet pack --version-suffix $suffix determines the dependency version of a project reference.

type: build

{
  "dependencies": {
    "Microsoft.EntityFrameworkCore.Design": {
      "version": "1.1.0",
      "type": "build"
    }
  }
}
<ItemGroup>
  <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.0" PrivateAssets="All" />
</ItemGroup>

type: platform

{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.1.0",
      "type": "platform"
    }
  }
}

There is no equivalent in csproj.

runtimes

{
  "runtimes": {
    "win7-x64": {},
    "osx.10.11-x64": {},
    "ubuntu.16.04-x64": {}
  }
}
<PropertyGroup>
  <RuntimeIdentifiers>win7-x64;osx.10-11-x64;ubuntu.16.04-x64</RuntimeIdentifiers>
</PropertyGroup>

Standalone apps (self-contained deployment)

In project.json, defining a runtimes section means the app was standalone during build and publish. In MSBuild, all projects are portable during build, but can be published as standalone.

dotnet publish --framework netcoreapp1.0 --runtime osx.10.11-x64

For more information, see Self-contained deployments (SCD).

tools

{
  "tools": {
    "Microsoft.EntityFrameworkCore.Tools.DotNet": "1.0.0-*"
  }
}
<ItemGroup>
  <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
</ItemGroup>
Note

imports on tools are not supported in csproj. Tools that need imports will not work with the new Microsoft.NET.Sdk.

buildOptions

See also Files.

emitEntryPoint

{
  "buildOptions": {
    "emitEntryPoint": true
  }
}
<PropertyGroup>
  <OutputType>Exe</OutputType>
</PropertyGroup>

If emitEntryPoint was false, the value of OutputType is converted to Library, which is the default value:

{
  "buildOptions": {
    "emitEntryPoint": false
  }
}
<PropertyGroup>
  <OutputType>Library</OutputType>
  <!-- or, omit altogether. It defaults to 'Library' -->
</PropertyGroup>

keyFile

{
  "buildOptions": {
    "keyFile": "MyKey.snk"
  }
}

The keyFile element expands to three properties in MSBuild:

<PropertyGroup>
  <AssemblyOriginatorKeyFile>MyKey.snk</AssemblyOriginatorKeyFile>
  <SignAssembly>true</SignAssembly>
  <PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
</PropertyGroup>

Other common build options

{
  "buildOptions": {
    "warningsAsErrors": true,
    "nowarn": ["CS0168", "CS0219"],
    "xmlDoc": true,
    "preserveCompilationContext": true,
    "outputName": "Different.AssemblyName",
    "debugType": "portable",
    "allowUnsafe": true,
    "define": ["TEST", "OTHERCONDITION"]
  }
}
<PropertyGroup>
  <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  <NoWarn>$(NoWarn);CS0168;CS0219</NoWarn>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
  <PreserveCompilationContext>true</PreserveCompilationContext>
  <AssemblyName>Different.AssemblyName</AssemblyName>
  <DebugType>portable</DebugType>
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  <DefineConstants>$(DefineConstants);TEST;OTHERCONDITION</DefineConstants>
</PropertyGroup>

packOptions

See also Files.

Common pack options

{
  "packOptions": {    
    "summary": "numl is a machine learning library intended to ease the use of using standard modeling techniques for both prediction and clustering.",
    "tags": ["machine learning", "framework"],
    "releaseNotes": "Version 0.9.12-beta",
    "iconUrl": "http://numl.net/images/ico.png",
    "projectUrl": "http://numl.net",
    "licenseUrl": "https://raw.githubusercontent.com/sethjuarez/numl/master/LICENSE.md",
    "requireLicenseAcceptance": false,
    "repository": {
      "type": "git",
      "url": "https://raw.githubusercontent.com/sethjuarez/numl"
    },
    "owners": ["Seth Juarez"]
  }
}
<PropertyGroup>
  <!-- summary is not migrated from project.json, but you can use the <Description> property for that if needed. -->
  <PackageTags>machine learning;framework</PackageTags>
  <PackageReleaseNotes>Version 0.9.12-beta</PackageReleaseNotes>
  <PackageIconUrl>http://numl.net/images/ico.png</PackageIconUrl>
  <PackageProjectUrl>http://numl.net</PackageProjectUrl>
  <PackageLicenseUrl>https://raw.githubusercontent.com/sethjuarez/numl/master/LICENSE.md</PackageLicenseUrl>
  <PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
  <RepositoryType>git</RepositoryType>
  <RepositoryUrl>https://raw.githubusercontent.com/sethjuarez/numl</RepositoryUrl>
  <!-- owners is not supported in MSBuild -->
</PropertyGroup>

There is no equivalent for the owners element in MSBuild. For summary, you can use the MSBuild <Description> property, even though the value of summary is not migrated automatically to that property, since that property is mapped to the description element.

scripts

{
  "scripts": {
    "precompile": "generateCode.cmd",
    "postpublish": [ "obfuscate.cmd", "removeTempFiles.cmd" ]
  }
}

Their equivalent in MSBuild are targets:

<Target Name="MyPreCompileTarget" BeforeTargets="Build">
  <Exec Command="generateCode.cmd" />
</Target>

<Target Name="MyPostCompileTarget" AfterTargets="Publish">
  <Exec Command="obfuscate.cmd" />
  <Exec Command="removeTempFiles.cmd" />
</Target>

runtimeOptions

{
  "runtimeOptions": {
    "configProperties": {
      "System.GC.Server": true,
      "System.GC.Concurrent": true,
      "System.GC.RetainVM": true,
      "System.Threading.ThreadPool.MinThreads": 4,
      "System.Threading.ThreadPool.MaxThreads": 25
    }
  }
}

All settings in this group, except for the "System.GC.Server" property, are placed into a file called runtimeconfig.template.json in the project folder, with options lifted to the root object during the migration process:

{
  "configProperties": {
    "System.GC.Concurrent": true,
    "System.GC.RetainVM": true,
    "System.Threading.ThreadPool.MinThreads": 4,
    "System.Threading.ThreadPool.MaxThreads": 25
  }
}

The "System.GC.Server" property is migrated into the csproj file:

<PropertyGroup>
  <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

However, you can set all those values in the csproj as well as MSBuild properties:

<PropertyGroup>
  <ServerGarbageCollection>true</ServerGarbageCollection>
  <ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
  <RetainVMGarbageCollection>true</RetainVMGarbageCollection>
  <ThreadPoolMinThreads>4</ThreadPoolMinThreads>
  <ThreadPoolMaxThreads>25</ThreadPoolMaxThreads>
</PropertyGroup>

shared

{
  "shared": "shared/**/*.cs"
}

Not supported in csproj. You must instead create include content files in your .nuspec file. For more information, see Including content files.

files

In project.json, build and pack could be extended to compile and embed from different folders. In MSBuild, this is done using items. The following example is a common conversion:

{
  "buildOptions": {
    "compile": {
      "copyToOutput": "notes.txt",
      "include": "../Shared/*.cs",
      "exclude": "../Shared/Not/*.cs"
    },
    "embed": {
      "include": "../Shared/*.resx"
    }
  },
  "packOptions": {
    "include": "Views/",
    "mappings": {
      "some/path/in/project.txt": "in/package.txt"
    }
  },
  "publishOptions": {
    "include": [
      "files/",
      "publishnotes.txt"
    ]
  }
}
<ItemGroup>
  <Compile Include="..\Shared\*.cs" Exclude="..\Shared\Not\*.cs" />
  <EmbeddedResource Include="..\Shared\*.resx" />
  <Content Include="Views\**\*" PackagePath="%(Identity)" />
  <None Include="some/path/in/project.txt" Pack="true" PackagePath="in/package.txt" />

  <None Include="notes.txt" CopyToOutputDirectory="Always" />
  <!-- CopyToOutputDirectory = { Always, PreserveNewest, Never } -->

  <Content Include="files\**\*" CopyToPublishDirectory="PreserveNewest" />
  <None Include="publishnotes.txt" CopyToPublishDirectory="Always" />
  <!-- CopyToPublishDirectory = { Always, PreserveNewest, Never } -->
</ItemGroup>
Note

Many of the default globbing patterns are added automatically by the .NET Core SDK. For more information, see Default Compile Item Values.

All MSBuild ItemGroup elements support Include, Exclude, and Remove.

Package layout inside the .nupkg can be modified with PackagePath="path".

Except for Content, most item groups require explicitly adding Pack="true" to be included in the package. Content will be put in the content folder in a package since the MSBuild <IncludeContentInPack> property is set to true by default. For more information, see Including content in a package.

PackagePath="%(Identity)" is a short way of setting package path to the project-relative file path.

testRunner

xUnit

{
  "testRunner": "xunit",
  "dependencies": {
    "dotnet-test-xunit": "<any>"
  }
}
<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>

MSTest

{
  "testRunner": "mstest",
  "dependencies": {
    "dotnet-test-mstest": "<any>"
  }
}
<ItemGroup>
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-*" />
  <PackageReference Include="MSTest.TestAdapter" Version="1.1.12-*" />
  <PackageReference Include="MSTest.TestFramework" Version="1.1.11-*" />
</ItemGroup>

See Also

High-level overview of changes in CLI