static void

DotNet Core class libraries

Published Sunday 22 May 2016

Executables vs class libraries

In classic .net, class libraries had an "output type" of "class library" in the property pages (and .csproj) and the extension ".dll". Executable libraries have output types of "Console application" or "Windows application" and the extension ".exe". Asp.net projects are special, actually class libraries launched by IIS.

In dotnet Core, the project.json specifies executables (that have a "static void Main" - including asp.net Core projects) with this section in project.json:

"buildOptions": {
    "emitEntryPoint": true
},

The other key difference is the dependencies and frameworks. Libraries can refer to the "netstandard" abstract APIs (dependency "NETStandard.Library"). Executables need a runnable framework - "netcoreapp" (dependency "Microsoft.NETCore.App").

So a minimal library project has a project.json like this:
{
  "version": "1.0.0-*",
  "dependencies": {
    "NETStandard.Library": "1.5.0-rc2-24027"
  },
  "frameworks": {
    "netstandard1.5": {
      "imports": "dnxcore50"
    }
  }
}

 

A minimal executable project has a project.json like this:

{
  "version": "1.0.0-*",
  "buildOptions": { "emitEntryPoint": true },
  "dependencies": {
    "Microsoft.NETCore.App": {
      "type": "platform",
      "version": "1.0.0-rc2-3002702"
    }
  },
  "frameworks": {
    "netcoreapp1.0": {
      "imports": "dnxcore50"
    }
  }
}

 

Referencing projects in a solution

Almost all real-world .net solutions are not single projects- they contain multiple linked projects. A console or asp website will depend on one or more class libraries, containing the business logic or interfaces to databases or webservices. At the very least, there should also be one or more test projects containing the unit tests.

In classic .net, the .csproj project files had a special type of project reference between projects in a solution. In dotnet Core RC2, the project.json file lists all dependencies as nuget packages. When you add a project reference is Visual Studio (using the "Preview 1" tooling released with RC2), it appears just like a nuget reference. You must manually add a special qualifier to indicate a project reference.

"dependencies": {
    "CoreLib": {
        "version": "*",
        "target": "project"
    }
},
The "target" is by default "package", but here we make it "project". The "version" can be a wildcard (in a solution we're not worried by semver breaking changes).

You can build with Visual Studio and the "dotnet" command line- but in Visual Studio any cross-project references persistently have red lines and no intellisense. The tooling - at least as of "RC2/Preview 1", with and without Resharper - simply doesn't recognize cross-project references.

Netstandard

The "netstandard" framework means a class library is portable across common APIs - from dotnet Core ("netcoreapp") to the full .net framework, .net 4.6 ("net46").

As at RC2, a Core asp.net project uses "netcoreapp1.0", which is a superset of "netstandard1.5". It can also consume libraries that use "netstandard1.0", "netstandard1.1" up to 1.5.

Libraries should generally specify lower (less restrictive) netstandards. Don't lazily specify 1.5 when you can run in 1.0, and be used by a wider range of consumers.

netstandard1.0-1.2 do not include the [assembly: Guid("")] attribute in AssemblyInfo.cs. It's used for COM, so most libraries that are only used by .net can safely remove it.

Multi- targeting

You can explicitly add other targets apart from "netstandard". It doesn't make a lot of sense for "net46", which is netstandard, but you can specify "net45", "net40", "net35" or even "net20".

Let's take some traditional ADO:

using System;
using System.Data.Common;

namespace CoreLib
{
    public static class AdoLib
    {
        public static void ExecuteDbReader(DbConnection connection)
        {
            connection.Open();

            using (var cmd = connection.CreateCommand())
            {
                cmd.CommandText = "SELECT * FROM Products";
                using (var dr = cmd.ExecuteReader())
                {
                    while (dr.Read())
                    {
                        Console.WriteLine(dr["ProductName"].ToString());
                    }
                }
            }
        }
    }
}

For dotnet core, the dependencies will be NetStandard.Library, System.Console and System.Data.Common.

But if these are in the top level "dependencies" section, they aren't compatible with the "net" frameworks. Those libraries aren't in nuget packages, they are in GAC assemblies as part of the .net framework library.

The dotnet Core dependencies can be moved under the framework for "netstandard". For the "net" frameworks, we can specify GAC references (which may not correspond exactly to the nuget packages) within frameworkAssemblies. This is what the project.json looks like:

{
    "version": "1.0.0-*",

    "dependencies": {
    },

    "frameworks": {
        "netstandard1.1": {
            "imports": "dnxcore50",
            "dependencies": {
                "NETStandard.Library": "1.5.0-rc2-24027",
                "System.Console": "4.0.0-rc2-24027",
                "System.Data.Common": "4.0.1-rc2-24027"
            }
        },
        "net45": {
            "frameworkAssemblies": {
                "System.Data": "4.0.0.0"
            }
        },
        "net40": {
            "frameworkAssemblies": {
                "System.Data": "4.0.0.0"
            }
        },
        "net20": {
            "frameworkAssemblies": {
                "System.Data": "2.0.0.0"
            }
        }
    }
}

Now "dotnet publish" will generate a package that supports net2.0 to to dotnet core.

In practice, dotnet core APIs aren't exactly the same- for instance there are no DataTables. Most real life code will probably require conditional code, wrapped in compiler directories. Automatically we now get compiler symbols for each framework when it's built- "net20" and so on. You can also add your own, via "compilationOptions": { "define": ["CLASSICNET"] }

Previously: Updating a class library to dotnet Core RC2 (18 May 2016)