This is a series on the .Net Core 1.0 bits. Looking for .Net Core 2 Series?
- Getting Started
- What’s in the box
- Using Multiple Projects <=(We are here)
- Testing
- NuGet
- Multi-targeting
- Publishing Portable Applications
- Self-contained Applications
We know how to create new projects, we know how to build and run our application and we know (roughly) what the content in the project.json
file means. But most projects tend to either require multiple projects or are actually libraries. We can tackle both of these problems together.
We are going to create and library project, and an application project. We can reference the library from the application, to see how referencing projects work.
File -> New Application
We should be old pros at this bit so let’s go:
mkdir MyNewApp
cd MyNewApp
dotnet new
dotnet restore
dotnet build
dotnet run
With our new project created, we can move on to something new - Libraries.
File -> New Library
As well as creating empty console applications (by default), dotnet
can also create other things:
Avaiable types for C# :
- Console
- Web
- Lib
- xunittest
(It was actually spelt out as Avaiable in the help text too… but I fixed it for you)
Just a quick reminder that like most things tooling, the way templates will work going forward is subject to change.
Web
is obviously the ASP.NET core template, which I won’t be covering in this series, and the xunittest
is a test project, which is the subject of the next article.
We want a Library so will use Lib
. To do so we add the -t|--type <TYPE>
argument to the command dotnet new
.
cd ../
mkdir MyNewLib
cd MyNewLib
dotnet new -t Lib
Created new C# project in C:\dev\MyProject\MyNewLib.
But what’s the different between Console
and Lib
I hear you ask? Patience! It’s coming up:
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable"
},
"dependencies": {},
"frameworks": {
"netstandard1.6": {
"dependencies": {
"NETStandard.Library": "1.6.0"
}
}
}
}
For starters, the buildOptions
value "emitEntryPoint": true
is not present. This is a library, so it doesn’t need an entry point.
And instead of targeting the concrete framework netcoreapp1.0
, we instead target the profile of netstandard1.6
.
I briefly covered this in the last article. Essentially netcoreapp1.0
and possibly .Net 4.6.3 are (currently) the only implementations of NETStandard version 1.6 (+ mono/Xamarin, though which versions are compatible is unknown to me).
If we want our library to be more widely usable, such as by older versions of .Net we can lower this to netstandard1.0
, which means all .Net 4.5 or greater runtimes are compatible, plus Windows Phone Silverlight (8.0+8.1). It is worth noting that .Net older than version 4.5 is not compatible with this new NetStandard versioning scheme. So if we want to also target .Net 4.0 or .Net3.5, we will have to add these as other frameworks. (More on this in NuGet and Multi-targeting.)
In our console app last time, we targeted a dependency Microsoft.NETCore.App
. This was type platform
, and declared a dependency on the runtime for our application. This time, rather than depend on a runtime, we want to depend on a compatible library version, to provide the contracts to the actual libraries, classes and methods in the framework version we declared above. That is, things like System
, System.IO
, System.Collections
, System.Linq
etc etc.
As mentioned at length last time, there is a the NETStandard.Library. Our library project declares a dependency on "NETStandard.Library": "1.6.0"
. As discussed, this NuGet package contains the library references for all netstandard
versions, so even if we were to change to target netstandard1.0
the package version stays 1.6. It gives us all of the NuGet packages for all of the system functionality that comes in the framework, by adding only a single dependency. Later on, we might want to come back, and replace this just with the packages we actually use. For now, while we start development, this gets us going quickly.
The last thing missing compared to the Console application is the "imports": "dnxcore50"
. Because this is a compatibility measure, for now (as far as I can tell), I hope we don’t need to use it with our library. But if we decide to depend on a package that was built using dnxcore
, we might need to add it later.
We can build our library, but of course it cannot be run - its a library.
dotnet build
PPPPPPP(*)
Before we can reference between the two projects, we probably need something to depend on. We can go into the file Library.cs
and make a few changes.
namespace ClassLibrary
{
public class Class1
{
public void Method1()
{
}
}
}
Change the namespace to match our project name for starters, MyNewLib
. Our class can be called Calculator
, and we can implement a simple method, Add
.
namespace MyNewLib
{
public class Calculator
{
public int Add(int first, int second)
{
return first + second;
}
}
}
Now it is a bit more interesting, at least.
Project References
To reference a project isn’t much different to adding a dependency to a NuGet package. The main difference is that we add "target": "project"
as a property. This is similar to the dependency property "type": "platform"
we saw before used with Microsoft.NETCore.App
.
If we add a dependency on our library MyNewLib
from our console application MyNewApp
, the project.json
file in MyNewApp
looks like this:
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {},
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"
},
"MyNewLib": {
"target": "project"
}
},
"imports": "dnxcore50"
}
}
}
Assuming your “MyNewApp” and “MyNewLib” folders appear together in the same parent folder, that should be enough to get this thing going.
Two things to note: Whenever you now build MyNewApp, it will recursively try and build the project MyNewLib. The second thing to note is that build doesn’t perform a restore. You need to make sure that both projects have had dotnet restore
called before trying to build the application now. Luckily, it turns out that dotnet restore
can be run in the parent folder and all direct child folders with a project.json will be restored.
And we will add some code to make the dependency complete. Open MyNewApp’s Program.cs
file and add a call to our Calculator.
using System;
using MyNewLib;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Calculator calc = new Calculator();
var answer = calc.Add(18, 24);
Console.WriteLine($"The answer is {answer}.");
}
}
}
One last thing, since we changed the project.json
file, is to run the restore command:
dotnet restore
Build and Run
From the MyNewApp folder, we can now build and run our application, including the library code from MyNewLib.
dotnet build
dotnet run
And we can see from the output that both projects are built and we see the correct output from our application.
Project MyNewLib (.NETStandard,Version=v1.6) was previously compiled. Skipping compilation.
Project MyNewApp2 (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.
The answer is 42.
Up Next
Of course, we don’t just want to build and ship code, we want to test it actually works. Next time we will create a test project, and write some tests against our library code above.