Converting a simple solution/project to .NET Core

Forcing the creation of a project over the top of another using 'dotnet new classlib --force'

I have a very small project I started writing back in 2015 for parsing responses from DNS servers, more because I was interested in how to do so than any real need. Because this mostly pre-dates .NET Core/.NET Standard, the project was created as a .NET Framework project. Mere moment after having updated references from Framework v4.5 to v4.7 I pondered to myself just how difficult it'd be to upgrade both the Dns parser project and its associated tests to .NET Core

Turns out, not difficult at all!

Using the dotnet command to force an upgrade

Whilst it's not technically speaking an upgade, it kinda-sorta is, so I'll call it that! To update the project file for the main Dns project (which for convenience I've tagged netfx prior to tweaking), I dropped to the command line and ran the command:

dotnet new classlib --force

Closely following that by:

dotnet build

To shake out the errors that this had left in the project. It turns out that for a very simple project with no 3rd party / NuGet references, you get very, very close! The output from build was:

Microsoft (R) Build Engine version 15.8.166+gd4e8d81a88 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 36.73 ms for D:\GitHub\Dns\Dns\Dns.csproj.
obj\Debug\netstandard2.0\Dns.AssemblyInfo.cs(14,12): error CS0579: Duplicate 'System.Reflection.AssemblyCompanyAttribute' attribute [D:\GitHub\Dns\Dns\Dns.csproj]
obj\Debug\netstandard2.0\Dns.AssemblyInfo.cs(15,12): error CS0579: Duplicate 'System.Reflection.AssemblyConfigurationAttribute' attribute [D:\GitHub\Dns\Dns\Dns.csproj]
obj\Debug\netstandard2.0\Dns.AssemblyInfo.cs(16,12): error CS0579: Duplicate 'System.Reflection.AssemblyFileVersionAttribute' attribute [D:\GitHub\Dns\Dns\Dns.csproj]
obj\Debug\netstandard2.0\Dns.AssemblyInfo.cs(18,12): error CS0579: Duplicate 'System.Reflection.AssemblyProductAttribute' attribute [D:\GitHub\Dns\Dns\Dns.csproj]
obj\Debug\netstandard2.0\Dns.AssemblyInfo.cs(19,12): error CS0579: Duplicate 'System.Reflection.AssemblyTitleAttribute' attribute [D:\GitHub\Dns\Dns\Dns.csproj]
obj\Debug\netstandard2.0\Dns.AssemblyInfo.cs(20,12): error CS0579: Duplicate 'System.Reflection.AssemblyVersionAttribute' attribute [D:\GitHub\Dns\Dns\Dns.csproj]

Build FAILED.

obj\Debug\netstandard2.0\Dns.AssemblyInfo.cs(14,12): error CS0579: Duplicate 'System.Reflection.AssemblyCompanyAttribute' attribute [D:\GitHub\Dns\Dns\Dns.csproj]
obj\Debug\netstandard2.0\Dns.AssemblyInfo.cs(15,12): error CS0579: Duplicate 'System.Reflection.AssemblyConfigurationAttribute' attribute [D:\GitHub\Dns\Dns\Dns.csproj]
obj\Debug\netstandard2.0\Dns.AssemblyInfo.cs(16,12): error CS0579: Duplicate 'System.Reflection.AssemblyFileVersionAttribute' attribute [D:\GitHub\Dns\Dns\Dns.csproj]
obj\Debug\netstandard2.0\Dns.AssemblyInfo.cs(18,12): error CS0579: Duplicate 'System.Reflection.AssemblyProductAttribute' attribute [D:\GitHub\Dns\Dns\Dns.csproj]
obj\Debug\netstandard2.0\Dns.AssemblyInfo.cs(19,12): error CS0579: Duplicate 'System.Reflection.AssemblyTitleAttribute' attribute [D:\GitHub\Dns\Dns\Dns.csproj]
obj\Debug\netstandard2.0\Dns.AssemblyInfo.cs(20,12): error CS0579: Duplicate 'System.Reflection.AssemblyVersionAttribute' attribute [D:\GitHub\Dns\Dns\Dns.csproj]
    0 Warning(s)
    6 Error(s)

Time Elapsed 00:00:02.31

These are caused by the presence of Properties\AssemblyInfo.cs which is getting pulled in. This originally contained the version number, copyright notice and other such metadata (in the form of assembly attributes) for the .NET Framework project. This data now gets stored in the project file and a temporary file (the one referenced in the errors above) is generated to feed into the compiler so that the attributes get applied to the generated assembly. Back to the command line:

del .\Properties\AssemblyInfo.cs
rd .\Properties
dotnet build

And, success:

Microsoft (R) Build Engine version 15.8.166+gd4e8d81a88 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 38.82 ms for D:\GitHub\Dns\Dns\Dns.csproj.
  Dns -> D:\GitHub\Dns\Dns\bin\Debug\netstandard2.0\Dns.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.77

Well, that was simple, though a project that doesn't have any dependencies on either (a) other projects, or (b) other libraries isn't going to. Don't forget to remove the added Class1.cs that forms part of the Class Library template!

The test project is a bit more complicated as it has both a dependency on the main Dns project and also on NUnit. Trying the same (dotnet new classlib) on the test project produces slightly more mixed results... the same assembly attribute errors but then a whole host of errors relating to NUnit, along with the components of the Dns project. Back to the command line:

D:\GitHub\Dns\Dns.Test>dotnet add reference ..\Dns
Reference `..\Dns\Dns.csproj` added to the project.

D:\GitHub\Dns\Dns.Test>dotnet add package NUnit
  Writing C:\Users\robertwray\AppData\Local\Temp\tmp580E.tmp
info : Adding PackageReference for package 'NUnit' into project 'D:\GitHub\Dns\Dns.Test\Dns.Test.csproj'.
log  : Restoring packages for D:\GitHub\Dns\Dns.Test\Dns.Test.csproj...
info :   GET https://api.nuget.org/v3-flatcontainer/nunit/index.json
info :   OK https://api.nuget.org/v3-flatcontainer/nunit/index.json 523ms
info : Package 'NUnit' is compatible with all the specified frameworks in project 'D:\GitHub\Dns\Dns.Test\Dns.Test.csproj'.
info : PackageReference for package 'NUnit' version '3.10.1' added to file 'D:\GitHub\Dns\Dns.Test\Dns.Test.csproj'.

By keeping a note of packages referenced (see packages.config) and adding them via the command line, migrating the project file becomes a lot simpler. Lastly I added a package reference to NUnit3TestRunner and Microsoft.Net.Test.Sdk before moving on.

Running the tests

This is where it all went a bit wrong, at least initially. Running a test via Visual Studio spat out:

[18/09/2018 22:44:14 Informational] ------ Run test started ------
[18/09/2018 22:44:14 Informational] NUnit Adapter 3.10.0.21: Test execution started
[18/09/2018 22:44:14 Informational] Running all tests in D:\GitHub\Dns\Dns.Test\bin\Debug\netstandard2.0\Dns.Test.dll
[18/09/2018 22:44:15 Warning] Exception NUnit.Engine.NUnitEngineException, Exception thrown executing tests in D:\GitHub\Dns\Dns.Test\bin\Debug\netstandard2.0\Dns.Test.dll
[18/09/2018 22:44:15 Warning] An exception occurred in the driver while loading tests.
[18/09/2018 22:44:15 Warning]    at NUnit.Engine.Runners.DirectTestRunner.LoadDriver(IFrameworkDriver driver, String testFile, TestPackage subPackage)
   at NUnit.Engine.Runners.DirectTestRunner.LoadPackage()
... snip ...
   at NUnit.VisualStudio.TestAdapter.NUnit3TestExecutor.RunAssembly(String assemblyPath, TestFilter filter) in D:\repos\nunit\nunit3-vs-adapter\src\NUnitTestAdapter\NUnit3TestExecutor.cs:line 229
[18/09/2018 22:44:15 Warning] Innerexception: System.IO.FileNotFoundException: Could not load file or assembly 'nunit.framework' or one of its dependencies. The system cannot find the file specified.
File name: 'nunit.framework'
   at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
... snip ...
   at NUnit.Engine.Drivers.NUnit3FrameworkDriver.CreateObject(String typeName, Object[] args)
   at NUnit.Engine.Drivers.NUnit3FrameworkDriver.Load(String testAssemblyPath, IDictionary`2 settings)
   at NUnit.Engine.Runners.DirectTestRunner.LoadDriver(IFrameworkDriver driver, String testFile, TestPackage subPackage)

WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

[18/09/2018 22:44:15 Informational] NUnit Adapter 3.10.0.21: Test execution complete
[18/09/2018 22:44:15 Warning] No test matches the given testcase filter `FullyQualifiedName=Dns.Test.DnsNameTests.ToByteArray.Returns_Correctly_For_Single_Label_Name` in D:\GitHub\Dns\Dns.Test\bin\Debug\netstandard2.0\Dns.Test.dll
[18/09/2018 22:44:15 Informational] ========== Run test finished: 0 run (0:00:00.8276633) ==========

Which is a wonderfully complex and long-winded way of saying "something's gone very wrong". Taking Visual Studio out of the equation by running the tests via the command line is my next step to try and work out what's gone wrong here, which gives some slightly more interesting / useful output:

D:\GitHub\Dns\Dns.Test>dotnet test
D:\GitHub\Dns\Dns.Test\Dns.Test.csproj : warning NU1701: Package 'NUnit3TestAdapter 3.10.0' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETStandard,Version=v2.0'. This package may not be fully compatible with your project.
Build started, please wait...
D:\GitHub\Dns\Dns.Test\Dns.Test.csproj : warning NU1701: Package 'NUnit3TestAdapter 3.10.0' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETStandard,Version=v2.0'. This package may not be fully compatible with your project.
Build completed.

Test run for D:\GitHub\Dns\Dns.Test\bin\Debug\netstandard2.0\Dns.Test.dll(.NETStandard,Version=v2.0)
Microsoft (R) Test Execution Command Line Tool Version 15.8.0
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...
System.IO.FileNotFoundException: Unable to find tests for D:\GitHub\Dns\Dns.Test\bin\Debug\netstandard2.0\Dns.Test.dll. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk" and framework version settings are appropriate. Rerun with /diag option to diagnose further.
   at Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting.DotnetTestHostManager.GetTestHostProcessStartInfo(IEnumerable`1 sources, IDictionary`2 environmentVariables, TestRunnerConnectionInfo connectionInfo)
   at Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.ProxyOperationManager.SetupChannel(IEnumerable`1 sources)
   at Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.ProxyExecutionManager.StartTestRun(TestRunCriteria testRunCriteria, ITestRunEventsHandler eventHandler)

Test Run Aborted.

So it looks like there's something not quite right with NUnit3TestAdaper!

First things first, clear up the bin/obj folders by running dotnet clean then make sure that it's done what you think (it won't have, I read something recently that explained that clean doesn't blow everything away, rather it uses some metadata stored during build to essentially remove any artifacts from the build). So, looking at the Debug folder it's clear that there are a load of files left which haven't been cleaned, the netstandard2.0 folder inside is entirely empty. Does it matter?

The complaint from dotnet test on the command line is that NUnitTestAdapter is the .NET Framework version rather than the .NET Core/Standard version so lets take a look at NUnit3.TestAdapter.dll and check. The file in the Debug folder has a size of 67,584 bytes, going up into the solution level packages folder and digging down into NUnitTestAdapter3.10.0\build gives two folders, net35 and netcoreapp1.0. The file in net35 has a size of 67,584 bytes, in netcoreapp1.0 it's 65,024 bytes, there's a potential culrpit!

One of two things has happened, either files have been left over from before I converted the projects in the solution fron .NET Framework to .NET Core or something went wrong when the packages were restored. I'm going to take a bet on the former by deleting the contents of the bin folder for both projects and seeing what happens:

D:\GitHub\Dns>del Dns\bin\*.* /S /Q
D:\GitHub\Dns>del Dns\obj\*.* /S /Q D:\GitHub\Dns>del Dns.Test\bin\*.* /S /Q
D:\GitHub>Dns>del Dns.Test\obj\*.* /S /Q
D:\GitHub\Dns>del packages\*.* /S /Q
D:\GitHub\Dns>del Dns\Packages.config
D:\GitHub\Dns>del Dns.Test\Packages.config

Just for info /S instructs delete to remove files from subdirectories as well and /Q tells it to not prompt with an 'Are you sure?' for each and every thing.

Doing what I should've done in the first place(!)

Even after all this, no dice so it's time for the option that should've been taken from the start, using the dotnet command to create an nunit project, overwriting the existing project file and then adding back the reference to the main project, so (from inside the Dns.Test directory):

dotnet new nunit --force
dotnet add reference ..\Dns
dotnet build

The result of that set of commands is much healthier:

Microsoft (R) Build Engine version 15.8.166+gd4e8d81a88 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restoring packages for D:\GitHub\Dns\Dns.Test\Dns.Test.csproj...
  Restore completed in 54.9 ms for D:\GitHub\Dns\Dns\Dns.csproj.
  Restore completed in 650.17 ms for D:\GitHub\Dns\Dns.Test\Dns.Test.csproj.
  Dns -> D:\GitHub\Dns\Dns\bin\Debug\netstandard2.0\Dns.dll
  Dns.Test -> D:\GitHub\Dns\Dns.Test\bin\Debug\netcoreapp2.0\Dns.Test.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:03.48

Finally comes running dotnet test to see what that gleans, and the net result is success along with the same in Visual Studio:

All the tests passing after actually running in Visual Studio 2017

Converting the project to .NET Core was a little bit fraught as I should've just let the tooling do the heavy lifting of creating an NUnit test project for me, but thanks to the defaults for "new style" project files where they include all source files by default, the amount of actual work was minimal!

About Rob

I've been interested in computing since the day my Dad purchased his first business PC (an Amstrad PC 1640 for anyone interested) which introduced me to MS-DOS batch programming and BASIC.

My skillset has matured somewhat since then, which you'll probably see from the posts here. You can read a bit more about me on the about page of the site, or check out some of the other posts on my areas of interest.

No Comments

Add a Comment