This is a series on the .Net Core 1.0 bits. Looking for .Net Core 2 Series?

Last time we learned how to package our libraries as NuGet packages. But it was only targeting netstandard1.6, the new netstandard target framework moniker.

Light reading

I recommend you go off and read through some of the architecture documents and NuGet pages to get a feel for the various targets that can or should be used. Starting with the .NET Platform Standard page.

In short, all the old profiles and various platforms have been mapped to this new netstandard system, and if you implement this target in your package, it can be used by other libraries that use these targets at an equal or higher level.

Further reading can be found on the NuGet.org target frameworks page.

Setup

If you have been following the series right through, you should be able to follow what the next script does to set up our starting project:

mkdir mynewpackage
cd mynewpackage
dotnet new -t Lib
dotnet restore
dotnet build

Like before, you may wish to replace Library.cs with a Calculator class.

namespace mynewpackage  
{
    public class Calculator
    {
        public int Add(int first, int second)
        {
            return first + second;
        }
    }
}

One more:

dotnet build

On with the show!

This all boils down to the project.json file. I’ll probably come back and revisit this when that finally goes away but for now this is what you need to do.

Starting with the initial project.json file:

{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable"
  },
  "dependencies": {},
  "frameworks": {
    "netstandard1.6": {
      "dependencies": {
        "NETStandard.Library": "1.6.0"
      }
    }
  }
}

Say we wanted to target netstandard1.0 instead. It would be as simple as changing "netstandard1.6" to "netstandard1.0".

Using some examples might help here. Let’s say we want to use some netstandard1.6 features, if they are available. But we also want to be compatible with netstandard1.0, with limited functionality, or alternative features used. We would do this to our project.json.

{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable"
  },
  "dependencies": {},
  "frameworks": {
    "netstandard1.0": {
      "dependencies": {
        "NETStandard.Library": "1.6.0"
      }
    },
    "netstandard1.6": {
      "dependencies": {
        "NETStandard.Library": "1.6.0"
      }
    }
  }
}

By adding new frameworks, we can target multiple platforms. By default, a build with now build all the frameworks.

I mentioned in a previous article, "NETStandard.Library": "1.6.0" is a Package, and version 1.6 actually contains the required parts for all netstandard* dependencies in the same package. So you don’t change the version of this, even when we do change the netstandard* one.

As whenever we change the project.json we run restore and build.

dotnet restore
dotnet build
Project mynewpackage (.NETStandard,Version=v1.0) will be compiled because expected outputs are missing
Compiling mynewpackage for .NETStandard,Version=v1.0

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

Time elapsed 00:00:01.5760786


Project mynewpackage (.NETStandard,Version=v1.6) will be compiled because inputs were modified
Compiling mynewpackage for .NETStandard,Version=v1.6

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

Time elapsed 00:00:01.5280100

What does this do to our file tree?

mynewpackage
|-- Library.cs
|-- project.json
|-- project.lock.json
+-- bin
     +-- Debug
          +-- netstandard1.0
          |    |-- mynewpackage.deps.json
          |    |-- mynewpackage.dll
          |    +-- mynewpackage.pdb
          +-- netstandard1.6
               |-- mynewpackage.deps.json
               |-- mynewpackage.dll
               +-- mynewpackage.pdb

We now have two target outputs, and two dll files, which target different frameworks.

Dependencies

As we can see from the above, there are three places where we can put dependencies. I’ve discussed this before, but let’s recap. If you put a dependency in the top level section, it will be available to all target frameworks. This means that it needs to contain a compatible target version to match every framework you have used. If you want to use a dependency only for a specific target, or you want to use different dependencies depending on the target, these can go in dependencies under that specific framework.

You might use this feature to include a bridging library against an older framework when the newer framework might contain it. An example of this is the Microsoft.Bcl.Async package. Or you might want to use a full framework package when targeting net40, and a dotnet core package when targeting netframework1.3.

Conditional Compilation

If we are targeting different frameworks, we likely have to adjust the code as well. We do this using the define buildOption.

{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable"
  },
  "dependencies": {},
  "frameworks": {
    "netstandard1.0": {
      "buildOptions": {
        "define": ["netstandard"]
      },
      "dependencies": {
        "NETStandard.Library": "1.6.0"
      }
    },
    "netstandard1.6": {
      "buildOptions": {
        "define": ["netstandard"]
      },
      "dependencies": {
        "NETStandard.Library": "1.6.0"
      }
    }
  }
}

By defining netstandard above, we can include it as part of a conditional compile in our code:

#if netstandard
        //Extra code for netstandard
        var x = 0;
#endif

Like before, if we add defines to the to buildOption section it will be defined for all targets. If we add it to a specific framework, it will only be defined when building against that target.

Packaging

Packaging into a nupkg file works the same as before.

dotnet pack
Project mynewpackage (.NETStandard,Version=v1.0) was previously compiled. Skipping compilation.
Project mynewpackage (.NETStandard,Version=v1.6) was previously compiled. Skipping compilation.
Producing nuget package "mynewpackage.1.0.0" for mynewpackage
mynewpackage -> C:\dev\temp\multitargetting\mynewpackage\bin\Debug\mynewpackage.1.0.0.nupkg
Producing nuget package "mynewpackage.1.0.0.symbols" for mynewpackage
mynewpackage -> C:\dev\temp\multitargetting\mynewpackage\bin\Debug\mynewpackage.1.0.0.symbols.nupkg

And results in some new files:

mynewpackage
|-- Library.cs
|-- project.json
|-- project.lock.json
+-- bin
     +-- Debug
          |-- mynewpackage.1.0.0.nupkg
          |-- mynewpackage.1.0.0.symbols.nupkg
          +-- netstandard1.0
          |    |-- mynewpackage.deps.json
          |    |-- mynewpackage.dll
          |    +-- mynewpackage.pdb
          +-- netstandard1.6
               |-- mynewpackage.deps.json
               |-- mynewpackage.dll
               +-- mynewpackage.pdb

Instead of getting a package per target, we get one package that contains both targets.

mynewpackage.1.0.0.nupkg  
|-- mynewpackage.nuspec
+-- lib
     +-- netstandard1.0
     |    +-- mynewpackage.dll
     +-- netstandard1.6
          +-- mynewpackage.dll

A sidenote on running

You can also use multi-targeting on an application. When you want to run an application with multiple targets, you can use the dotnet run command with the -f flag.

dotnet run -f netcoreapp1.0

Of course, netcoreapp1.0 is the application target for the .Net Core runtime, compared to using net45 perhaps to target .Net 4.5 runtime instead.

net40

The target netstandard1.0 is compatible with .Net 4.5 and newer, UWP apps, Windows 8 & Windows Phone 8 or newer, Mono and Xamarin, and of course new .Net Core applications. But we all know that there are a ton of applications out there targeting .Net 4.0, and even .Net 3.5 that we still maintain and support that we might want our library packages to target.

So we can easily target net40 and net35 as well.

{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable"
  },
  "dependencies": {},
  "frameworks": {
    "net35": {
      "buildOptions": {
        "define": ["net35"]
      },
      "dependencies": {
      }
    },
    "net40": {
      "buildOptions": {
        "define": ["net40"]
      },
      "dependencies": {
      }
    },
    "netstandard1.0": {
      "buildOptions": {
        "define": ["netstandard"]
      },
      "dependencies": {
        "NETStandard.Library": "1.6.0"
      }
    },
    "netstandard1.6": {
      "buildOptions": {
        "define": ["netstandard"]
      },
      "dependencies": {
        "NETStandard.Library": "1.6.0"
      }
    }
  }
}

This simple library doesn’t need anything outside the standard library, bu if it did, we would add in target-specific packages and libraries to the net35 and net40 targets as well.

dotnet restore
dotnet build
Project mynewpackage (.NETFramework,Version=v3.5) will be compiled because expected outputs are missing
Compiling mynewpackage for .NETFramework,Version=v3.5

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

Time elapsed 00:00:01.1810785


Project mynewpackage (.NETFramework,Version=v4.0) will be compiled because expected outputs are missing
Compiling mynewpackage for .NETFramework,Version=v4.0

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

Time elapsed 00:00:01.1155631


Project mynewpackage (.NETStandard,Version=v1.0) will be compiled because inputs were modified
Compiling mynewpackage for .NETStandard,Version=v1.0

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

Time elapsed 00:00:01.0899710


Project mynewpackage (.NETStandard,Version=v1.6) will be compiled because inputs were modified
Compiling mynewpackage for .NETStandard,Version=v1.6

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

Time elapsed 00:00:01.0806550

We can pack this.

dotnet pack
Project mynewpackage (.NETFramework,Version=v3.5) was previously compiled. Skipping compilation.
Project mynewpackage (.NETFramework,Version=v4.0) was previously compiled. Skipping compilation.
Project mynewpackage (.NETStandard,Version=v1.0) was previously compiled. Skipping compilation.
Project mynewpackage (.NETStandard,Version=v1.6) was previously compiled. Skipping compilation.
Producing nuget package "mynewpackage.1.0.0" for mynewpackage
mynewpackage -> C:\dev\temp\multitargetting\mynewpackage\bin\Debug\mynewpackage.1.0.0.nupkg
Producing nuget package "mynewpackage.1.0.0.symbols" for mynewpackage
mynewpackage -> C:\dev\temp\multitargetting\mynewpackage\bin\Debug\mynewpackage.1.0.0.symbols.nupkg

Then we will get:

mynewpackage
|-- Library.cs
|-- project.json
|-- project.lock.json
+-- bin
     +-- Debug
          |-- mynewpackage.1.0.0.nupkg
          |-- mynewpackage.1.0.0.symbols.nupkg
          +-- net35
          |    |-- mynewpackage.dll
          |    +-- mynewpackage.pdb
          +-- net40
          |    |-- mynewpackage.dll
          |    +-- mynewpackage.pdb
          +-- netstandard1.0
          |    |-- mynewpackage.deps.json
          |    |-- mynewpackage.dll
          |    +-- mynewpackage.pdb
          +-- netstandard1.6
               |-- mynewpackage.deps.json
               |-- mynewpackage.dll
               +-- mynewpackage.pdb

And:

mynewpackage.1.0.0.nupkg  
|-- mynewpackage.nuspec
+-- lib
     +-- net35
     |    +-- mynewpackage.dll
     +-- net40
     |    +-- mynewpackage.dll
     +-- netstandard1.0
     |    +-- mynewpackage.dll
     +-- netstandard1.6
          +-- mynewpackage.dll

What’s Left?

That should give you a reasonable (albeit simple) overview of multi-targeting libraries. The last focus is on applications, and how we publish and distribute these using the dotnet CLI tools. Up Next!