Converting From project.json To csproj

Before we begin in earnest, I just wanted to point out that this is a repost from my blog on .NET Core (see the bottom of this article for a direct link to it). The information contained in this article was correct at the time of writing, but for more up to date information, please head to my blog.

It’s Time to Celebrate!

This week, the eagerly awaited, Visual Studio 2017 was released.

Not only that, there was a live streamed event to announce all of the latest changes to:

  • The Visual Studio line up (including VS 2017, Visual Studio for Mac and Visual Studio Code)
  • VSTS, TFS and related tools
  • .NET Standard, Framework, Core, Xamarin and Mono

To name only a few things.

In this post I though I’d write about something that we all need to be able to do with our .NET Core projects going forward: migrate from project.json to csproj/MSBuild format.

I’ll simplify that to csproj for the rest of this article

I’m about to drop a little history on you, but if you want to skip it and go straight to migrating project.json to csproj, then you should scroll down to “How Do I Convert My Project?”.

csproj, project.json, and MSBuild

When .NET Core was first released, the default project format was a json file. This file contained all of the information that the .NET Core complier (called Roslyn) needed to know to compile the project, things like:

  • metadata
  • compilation options
  • dependencies and tools

You can read about all of the project.json options here

A real example of a project.json file looks like this:

This is actually from one of my GitHub projects

{
"version": "1.0.0.0",
"description": "A .NET Core WebApi project, utilizing SqlLite and EF Core, for searching Discworld Books and Characters.",
"authors": [
"Jamie Taylor"
],
"buildOptions": {
"emitEntryPoint": true
},
"tooling": {
"defaultNamespace": "dwCheckApi"
},
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.1.0",
"type": "platform"
},
"Microsoft.AspNetCore.Mvc": "1.0.1",
"Microsoft.AspNetCore.Routing": "1.0.1",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.AspNetCore.StaticFiles": "1.1.0",
"Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.EntityFrameworkCore.Design": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.EntityFrameworkCore": "1.1.0",
"Microsoft.EntityFrameworkCore.Sqlite": "1.1.0",
"Microsoft.EntityFrameworkCore.Sqlite.Design": {
"version": "1.1.0",
"type": "build"
},
"Microsoft.EntityFrameworkCore.Tools": {
"version": "1.0.0-preview2-final",
"type": "build"
}
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final",
"Microsoft.EntityFrameworkCore.Tools.DotNet": "1.0.0-preview3-final"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"dnxcore50",
"portable-net45+win8"
]
}
},
"publishOptions": {
"include": [
"wwwroot",
"Views",
"appsettings.json",
"web.config"
]
}
}

Compare this, for example, to a current .NET Framework csproj file (which is an XML file):

Again, from one of my GitHub projects. Even though this is an old project, the csproj file has the same format as one targeting .NET Framework 4.6.1

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{0433A471-36FD-46D4-A6C4-A550298710A0}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>WDTV_Live_MetaData_Generator</RootNamespace>
<AssemblyName>WDTV Live MetaData Generator</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data.DataSetExtensions">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="EpisodeNamePopUpBox.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="EpisodeNamePopUpBox.Designer.cs">
<DependentUpon>EpisodeNamePopUpBox.cs</DependentUpon>
</Compile>
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="EpisodeNamePopUpBox.resx">
<DependentUpon>EpisodeNamePopUpBox.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="TVShowForm.resx">
<DependentUpon>TVShowForm.cs</DependentUpon>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<Compile Include="TVShowForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="TVShowForm.Designer.cs">
<DependentUpon>TVShowForm.cs</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\movie.jpg" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\tv_show.jpg" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

I don’t know about you, but I find the project.json file easier to read.

So Why The Difference?

Digging into the way back machine:

I didn’t need to dig that far, .NET Core hasn’t been around that long yet.

Back when .NET Core was first being developed, the ASP.NET and the .NET teams weren’t working together. The ASP.NET team where working on .NET Core, which would run on the .NET team’s new .NET API surface.

Incidentally, this is why the .NET community standups are called the ASP.NET Community Stand Ups

The ASP.NET team took this opportunity to re-build the project format from the ground up, as .NET Core was seen as a new beginning for .NET.

Which has been echo’d by many of the ASP.NET team in interviews and on podcasts; they’ve talked about not having to support some of the decisions made while designing the earlier versions of the .NET Framework in .NET Core

Once the open source community got their hands on the .NET Core betas and early releases, work started on the next generation of web applications using it. Most developers liked the new project format, and celebrated it for being simpler and it’s support for auto-complete.

Let’s be honest here, XML has never really been that easy to read

While the Release Candidate was being planned, it was decided that .NET Core would have to support MSBuild.

MSBuild is a series of tools which allow DevOps folks to automate the building and deployment of .NET projects.

If you’ve ever used Octopus Deploy or Cake, then you should be very familiar with MSBuild

MSBuild does not support the new project.json format, which put a lot of people off. Especially those who wanted to convert their already established .NET Framework projects to .NET Core. Starting a new .NET Core application wasn’t an issue, but not many enterprise solutions or big business applications were going to be re-written from the ground up.

Only the companies who where truly invested in .NET Core have done that. Especially since .NET Core and the tooling around it wasn’t very stable at the beginning

Because of this the .NET Standard team decided to retire the project.json format and give .NET Core support for the backward compatible csproj format. But not only that, the decision was made to simplify the format.

As we’ll see in a moment

How Do I Convert My Project?

Everything we’ve done so far, has been in the terminal, mainly because the tooling hasn’t been the most stable. But now that Visual Studio 2017 has moved out of Release Candidate and has shipped with version 1.0 of the .NET Core tooling, we can start working with that.

I’ve tried to convert a few projects with VS 2017 RC and have had mixed success so far. But that’s what you get with an RC, I guess

Either way, the first thing that you’ll want to do is update the version of the SDK that you have installed. If you’ve installed Visual Studio 2017, then that’s already been taken care of for you.

However, the latest version of the SDK is available for download here.

Terminal

From the terminal we need to confirm that we have the latest version of the .NET Core SDK installed:

dotnet --version

Obviously, the version numbers are going to change as Microsoft adds releases new versions of the .NET Core SDK, but the version I’m using as I write this post is: 1.0.0-rc4–004771.

Next we need to check that the version of the .NET Core SDK we have installed has the migrate command — which is used to convert to the new csproj format.

On a Unix like operating system (MacOs or one of the supported Linux distributions), you can do this:

dotnet --help | grep 'migrate'

Whereas, on a Windows machine you will have to run the following command:

dotnet --help | findstr /I "migrate"

Both of these commands should return the following:

migrate       Migrates a project.json based project to a msbuild based project.

Which shows that our install of the .NET Core SDK supports the new csproj format and can convert from project.json to it.

Now we need to cd into the root of the project and run the following command:

dotnet migrate

A Worked Example

Ok, “worked example” is probably not the right phrase, but stick with me here.

I’ve pulled commit 6402447aec of dwCheckApi

The latest version is already a csproj project 😛

and we’ll convert it to a csproj project right now.

We need to cd into the src directory and run the following command:

dotnet migrate

And you should get output which matches the following:

Project src migration succeeded (/Users/GaProgMan/Code/dwCheckApi/src).
Summary
Total Projects: 1
Succeeded Projects: 1
Failed Projects: 0
The project migration has finished. Please visit https://aka.ms/coremigration to report any issues you've encountered or ask for help.
Files backed up to /Users/GaProgMan/Code/dwCheckApi/src/backup/

If try to build the application, you’ll get a bunch of errors. I got 439 errors, and the first time I did this I was worried that I’d broken my application beyond repair.

Thank goodness for source control, huh?

But, as any developer worth their salt will tell you, always read the error messages. So let’s take a look at the very first one.

You’ll have to scroll quite far to get to the first one

/usr/local/share/dotnet/sdk/1.0.0-rc4-004771/Sdks/Microsoft.NET.Sdk/build/Microsoft.PackageDependencyResolution.targets(154,5): error : Assets file '/Users/GaProgMan/Code/dwCheckApi/src/obj/project.assets.json' not found. Run a NuGet package restore to generate this file. [/Users/GaProgMan/Code/dwCheckApi/src/src.csproj]

Here’s the most important part of the error message:

Assets file ‘/Users/GaProgMan/Code/dwCheckApi/src/obj/project.assets.json‘ not found. Run a NuGet package restore to generate this file

So all we need to do is restore packages

Even if you’ve previously restored packages for the project, because there are changes brought about by migrating to csproj

and rebuild. Let’s do that now:

dotnet restore
dotnet build

and it’ll build just fine, producing output similar to this:

Microsoft (R) Build Engine version 15.1.545.13942
Copyright (C) Microsoft Corporation. All rights reserved.
src -> /Users/GaProgMan/Code/dwCheckApi/src/bin/Debug/netcoreapp1.0/src.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:06.81

Visual Studio 2017

Migrating using Visual Studio 2017 is easier than using the terminal (especially for those who prefer to use a GUI), and it’s a case of opening a project.json project after which a wizard will guide you through migrating to the new format.

During the keynote at the recent Visual Studio launch event

Which you can rewatch here

Beth Massi gave a demonstration on how to migrate a project to the new csproj format.

At the above link, if you jump to 35:30, you’ll see Beth showing how it’s done with an xproj solution.

The short version is that you just open the xproj file with Visual Studio 2017 and it starts a migration wizard.

Visual Studio just calls the dotnet migrate command on your source, in this wizard
– Beth Massi

This does require you to have worked on your project in Visual Studio 2015 (as xproj is it’s default format for .NET Core projects).

Worked Example

The project format in commit 6402447aec of dwCheckApi was project.json without an xproj file (because I had been working exclusively in VS Code with the command line).

If your project does not have an xproj file, I recommend that you use the command line tools (mentioned above) to migrate your project, then open the MSBuild version with Visual Studio 2017.

However, here are the steps that you need to take with an xproj project.

First, open the project with Visual Studio 2017

We want to choose Open Project/Solution

Then we want to find the xproj file, opening this file will show the migration wizard.

This wizard is one way, but will backup any files that it replaces (i.e. xproj)

When the migration is complete, the csproj project will be loaded into Visual Studio 2017 and all packages restored.

Our project is migrated, opened and restored

The new csproj

Let’s take a quick look at the new csproj format to see how it’s changed from our project.json.

First the project.json for the project we just migrated (dwCheckApi):

{
"version": "1.0.0.0",
"description": "A .NET Core WebApi project, utilizing SqlLite and EF Core, for searching Discworld Books and Characters.",
"authors": [
"Jamie Taylor"
],
"buildOptions": {
"emitEntryPoint": true
},
"tooling": {
"defaultNamespace": "dwCheckApi"
},
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.1.0",
"type": "platform"
},
"Microsoft.AspNetCore.Mvc": "1.0.1",
"Microsoft.AspNetCore.Routing": "1.0.1",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.AspNetCore.StaticFiles": "1.1.0",
"Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.EntityFrameworkCore.Design": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.EntityFrameworkCore": "1.1.0",
"Microsoft.EntityFrameworkCore.Sqlite": "1.1.0",
"Microsoft.EntityFrameworkCore.Sqlite.Design": {
"version": "1.1.0",
"type": "build"
},
"Microsoft.EntityFrameworkCore.Tools": {
"version": "1.0.0-preview2-final",
"type": "build"
}
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final",
"Microsoft.EntityFrameworkCore.Tools.DotNet": "1.0.0-preview3-final"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"dnxcore50",
"portable-net45+win8"
]
}
},
"publishOptions": {
"include": [
"wwwroot",
"Views",
"appsettings.json",
"web.config"
]
}
}

And now the hot new sexiness that is .NET Core’s csproj file

I worry about you sometimes, Jamie

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

<PropertyGroup>
<Description>A .NET Core WebApi project, utilizing SqlLite and EF Core, for searching Discworld Books and Characters.</Description>
<VersionPrefix>1.0.0.0</VersionPrefix>
<Authors>Jamie Taylor</Authors>
<TargetFramework>netcoreapp1.0</TargetFramework>
<AssemblyName>src</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>src</PackageId>
<RuntimeFrameworkVersion>1.1.0</RuntimeFrameworkVersion>
<PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;dnxcore50;portable-net45+win8</PackageTargetFallback>
</PropertyGroup>

<ItemGroup>
<Content Update="wwwroot;Views;appsettings.json;web.config">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Routing" Version="1.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.0">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.0.0-msbuild3-final">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0-preview3-final" />
</ItemGroup>

</Project>

It’s not immediately as easy to read as the project.json

In my opinion, anyway

But, it’s more concise and has fewer lines (even with the empty lines inserted).

A Quick Note About Visual Studio Code

If you open a freshly migrated project in VS Code, and try to run it you’ll see a few strange things.

We have no configuration, which is a bit naff

Clicking on the run button will cause the command pallet to extend and give you some choices.

These options will inform VS Code which type of launch.json file to create

Choosing .NET Core will produce a file similar to this:

{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceRoot}/bin/Debug/<target-framework>/<project-name.dll>",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": false
},
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceRoot}/bin/Debug/<target-framework>/<project-name.dll>",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"launchBrowser": {
"enabled": true,
"args": "${auto-detect-url}",
"windows": {
"command": "cmd.exe",
"args": "/C start ${auto-detect-url}"
},
"osx": {
"command": "open"
},
"linux": {
"command": "xdg-open"
}
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceRoot}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command.pickProcess}"
}
]
}

There are a few issues with this file, and we’ll come back to them in a moment.

Now if you hit the run button again…

We’ve no task runner

We’ll need to configure a task runner to be able to debug this application, so let’s do that.

We’ve loads of options here

I chose .NET Core, but you should chose whatever suits your project.

Once that’s done, you’ll have a tasks.json. Here’s mine for .NET Core:

{
"version": "0.1.0",
"command": "dotnet",
"isShellCommand": true,
"args": [],
"tasks": [
{
"taskName": "build",
"args": [ ],
"isBuildCommand": true,
"showOutput": "silent",
"problemMatcher": "$msCompile"
}
]
}

launch.json

Now then, we had issues with our launch.json. To see what they are go ahead and click the run button again.

Eep!

What’s happened here is that the first two configuration entries in out launch.json have badly formatted entries.

Here’s the relevant part of the file:

"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceRoot}/bin/Debug/<target-framework>/<project-name.dll>",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": false
},
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceRoot}/bin/Debug/<target-framework>/<project-name.dll>",
"args": [],

The problem with these lines are the two following parts:

  • <target-framework>
  • <project-name.dll>

Neither of these are valid entires for the launch.json.

What we need to do is replace them with valid strings. It’s pretty simple really, we need to do is look at our csproj for our target framework and the assembly name strings.

<TargetFramework>netcoreapp1.0</TargetFramework>
<AssemblyName>src</AssemblyName>

These two lines are found within the PropertyGroup section of the csproj file

We’ll use these strings in place of the <target-framework> and <project-name.dll> strings in the launch.json. So, in the case of dwCheckApi, we want to alter the launch.json to read like this:

"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/src.dll",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": false
},
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/src.dll",
"args": [],

Clicking run one last time, should build and run the application for you.

How cool is that?

Conclusion

Moving forward, all .NET Core projects will have to be csproj ones (in order to benefit from all of the features of the new versions of the SDK, anyway).

Migrating to csproj is relatively painless, as long as you’re aware of some of the potential edge cases and issue areas ahead of time.


Originally published at dotnetcore.gaprogman.com on March 9, 2017.