This is a series on the latest 2.* .Net Core bits, Following on from the original .Net Core Series
- Getting Started
- What’s in the box
- Using Multiple Projects <=(We are here)
- Testing
- NuGet
- Multi-targeting
- Publishing Portable Applications
- Self-contained Applications
(At the time of writing, 2.1.4. I use windows, you don’t have to!)
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 csproj
file means. But most projects tend to require multiple projects.
We are going to create a 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 console
dotnet run
File -> New Library
As well as creating empty console applications, dotnet
can also create other things. If you try typing dotnet new
you will see a full list of all templates available, which you may remember from Getting Started:
Usage: new [options]
Options:
-h, --help Displays help for this command.
-l, --list Lists templates containing the specified name. If no name is specified, lists all templates.
-n, --name The name for the output being created. If no name is specified, the name of the current directory is used.
-o, --output Location to place the generated output.
-i, --install Installs a source or a template pack.
-u, --uninstall Uninstalls a source or a template pack.
--type Filters templates based on available types. Predefined values are "project", "item" or "other".
--force Forces content to be generated even if it would change existing files.
-lang, --language Specifies the language of the template to create.
Templates Short Name Language Tags
--------------------------------------------------------------------------------------------------------
Console Application console [C#], F#, VB Common/Console
Class library classlib [C#], F#, VB Common/Library
Unit Test Project mstest [C#], F#, VB Test/MSTest
xUnit Test Project xunit [C#], F#, VB Test/xUnit
ASP.NET Core Empty web [C#], F# Web/Empty
ASP.NET Core Web App (Model-View-Controller) mvc [C#], F# Web/MVC
ASP.NET Core Web App razor [C#] Web/MVC/Razor Pages
ASP.NET Core with Angular angular [C#] Web/MVC/SPA
ASP.NET Core with React.js react [C#] Web/MVC/SPA
ASP.NET Core with React.js and Redux reactredux [C#] Web/MVC/SPA
ASP.NET Core Web API webapi [C#], F# Web/WebAPI
global.json file globaljson Config
NuGet Config nugetconfig Config
Web Config webconfig Config
Solution File sln Solution
Razor Page page Web/ASP.NET
MVC ViewImports viewimports Web/ASP.NET
MVC ViewStart viewstart Web/ASP.NET
Examples:
dotnet new mvc --auth Individual
dotnet new console
dotnet new --help
(Again the caveat that mine might be longer than yours because of some extra templates I have installed. This is also much changed since dotnet v1)
We want a Library so will use classlib
.
cd ../
mkdir MyNewLib
cd MyNewLib
dotnet new classlib
The template "Class library" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on C:\dev\MyComplexApplicationRepo\MyNewLib\MyNewLib.csproj...
Restoring packages for C:\dev\MyComplexApplicationRepo\MyNewLib\MyNewLib.csproj...
Generating MSBuild file C:\dev\MyComplexApplicationRepo\MyNewLib\obj\MyNewLib.csproj.nuget.g.props.
Generating MSBuild file C:\dev\MyComplexApplicationRepo\MyNewLib\obj\MyNewLib.csproj.nuget.g.targets.
Restore completed in 115.85 ms for C:\dev\MyComplexApplicationRepo\MyNewLib\MyNewLib.csproj.
Restore succeeded.
We briefly looked at the differences between these projects in the previous section What’s in the box. (Specifically, the app will have an extra <OutputType>Exe</OutputType>
setting in the csproj
file, and targeting netcoreapp2.0
for the app and netstandard2.0
for the library.)
.Net Standard Compatibility is a bit of a topic on its own. You can review the Version Compatibility Grid for dotnet to determine which target you want for your library. If you do leave it as netstandard2.0
, then it will be compatible with dotnet core runtime version 2.0
or newer, .Net full framework version 4.6.1
or newer, Mono 5.4
, and Windows UWP version 10.0.16299
or newer. Click through to see the full table, including Xamarin and Windows Phone/Silverlight compatibility. The idea is the higher your version, the more base class libraries and methods are available, the lower the version gives you wider compatibility.
So the classlib has access to different library classes and methods depending on which version you target. These are implicitly available to you to use (unlike dotnet v1 where the standard library was an explicit dependency). The dotnet application project target netcoreapp2.0
also includes implicitly available core runtime types, classes and methods as well. Additional libraries are then available from NuGet. (More on that later on in the series)
We can build our library, but of course, it cannot be run - its a library.
dotnet build
Microsoft (R) Build Engine version 15.5.180.51428 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
Restore completed in 22.02 ms for C:\dev\MyComplexApplicationRepo\MyNewLib\MyNewLib.csproj.
MyNewLib -> C:\dev\MyComplexApplicationRepo\MyNewLib\bin\Debug\netstandard2.0\MyNewLib.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:01.75
PPPPPPP(*)
Before we can reference between the two projects, we probably need something to depend on. We can go into the file Class1.cs
and make a few changes.
namespace MyNewLib
{
public class Class1
{
}
}
Our class can be called Calculator
, and we can implement a simple method, Add
.
using System;
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
Last time we briefly glossed over Frameworks and Dependencies. Let’s go over this again in more detail.
First, we want to reference our library from our application.
cd ../MyNewApp
dotnet add reference ../MyNewLib/MyNewLib.csproj
Reference `..\MyNewLib\MyNewLib.csproj` added to the project.
If we look at our csproj
file now, we can see the reference has been added.
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\MyNewLib\MyNewLib.csproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
</Project>
You can basically give any relative path to any csproj and this will work.
One to note: Whenever you now build MyNewApp, it will recursively try and build the project MyNewLib as well.
dotnet build
Microsoft (R) Build Engine version 15.5.180.51428 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
Restoring packages for C:\dev\MyComplexApplicationRepo\MyNewApp\MyNewApp.csproj...
Restore completed in 21.63 ms for C:\dev\MyComplexApplicationRepo\MyNewLib\MyNewLib.csproj.
Restore completed in 141.95 ms for C:\dev\MyComplexApplicationRepo\MyNewApp\MyNewApp.csproj.
MyNewLib -> C:\dev\MyComplexApplicationRepo\MyNewLib\bin\Debug\netstandard2.0\MyNewLib.dll
MyNewApp -> C:\dev\MyComplexApplicationRepo\MyNewApp\bin\Debug\netcoreapp2.0\MyNewApp.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:02.68
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 MyNewApp
{
class Program
{
static void Main(string[] args)
{
Calculator calc = new Calculator();
var answer = calc.Add(18, 24);
Console.WriteLine($"The answer is {answer}.");
}
}
}
Build and Run
From the MyNewApp folder, we can now build and run our application, including the library code from MyNewLib. All of this can be done by just using dotnet run (which will do a restore, if required, as well as a build, if required).
dotnet run
And we can see from the output that both projects are built and we see the correct output from our application.
The answer is 42.
Because we used run
, the restore and build output has been done silently. This is not the case if it fails, you will see the errors on restore, or errors on build. Often when errors are encountered, it pays to run the actual command to see all of the output for diagnosis.
Now we have 2 projects, and a reference between them. For convenience, though, we probably want to group these into some kind of working solution.
File -> New Solution
Unlike dotnet v1, dotnet 2.* SDK is using real MSBuild and has native support built into it to not only run against Visual Studio Solution (.sln
) files but manage them as well.
We start by creating a new solution in our parent folder:
cd ../
dotnet new sln
The template "Solution File" was created successfully.
“Solution File” is just another template available, which shows some of the flexibility and power of the dotnet template engine. This will have created a file in the parent folder called MyComplexApplicationRepo.sln
, which comes from the parent folder. You can rename this file if desired.
As we have come to expect from the dotnet CLI, a minimal sln
file is created:
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
To fill it up, we add our projects to the solution using the dotnet sln ...
commands:
dotnet sln add .\MyNewApp\MyNewApp.csproj
dotnet sln add .\MyNewLib\MyNewLib.csproj
This updates the sln
file.
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyNewApp", "MyNewApp\MyNewApp.csproj", "{32BA1F6D-4996-4F9E-A55A-D5ECC5A834A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyNewLib", "MyNewLib\MyNewLib.csproj", "{76D11E26-4764-4051-B597-9E1D88015259}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{32BA1F6D-4996-4F9E-A55A-D5ECC5A834A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32BA1F6D-4996-4F9E-A55A-D5ECC5A834A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32BA1F6D-4996-4F9E-A55A-D5ECC5A834A1}.Debug|x64.ActiveCfg = Debug|x64
{32BA1F6D-4996-4F9E-A55A-D5ECC5A834A1}.Debug|x64.Build.0 = Debug|x64
{32BA1F6D-4996-4F9E-A55A-D5ECC5A834A1}.Debug|x86.ActiveCfg = Debug|x86
{32BA1F6D-4996-4F9E-A55A-D5ECC5A834A1}.Debug|x86.Build.0 = Debug|x86
{32BA1F6D-4996-4F9E-A55A-D5ECC5A834A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32BA1F6D-4996-4F9E-A55A-D5ECC5A834A1}.Release|Any CPU.Build.0 = Release|Any CPU
{32BA1F6D-4996-4F9E-A55A-D5ECC5A834A1}.Release|x64.ActiveCfg = Release|x64
{32BA1F6D-4996-4F9E-A55A-D5ECC5A834A1}.Release|x64.Build.0 = Release|x64
{32BA1F6D-4996-4F9E-A55A-D5ECC5A834A1}.Release|x86.ActiveCfg = Release|x86
{32BA1F6D-4996-4F9E-A55A-D5ECC5A834A1}.Release|x86.Build.0 = Release|x86
{76D11E26-4764-4051-B597-9E1D88015259}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{76D11E26-4764-4051-B597-9E1D88015259}.Debug|Any CPU.Build.0 = Debug|Any CPU
{76D11E26-4764-4051-B597-9E1D88015259}.Debug|x64.ActiveCfg = Debug|x64
{76D11E26-4764-4051-B597-9E1D88015259}.Debug|x64.Build.0 = Debug|x64
{76D11E26-4764-4051-B597-9E1D88015259}.Debug|x86.ActiveCfg = Debug|x86
{76D11E26-4764-4051-B597-9E1D88015259}.Debug|x86.Build.0 = Debug|x86
{76D11E26-4764-4051-B597-9E1D88015259}.Release|Any CPU.ActiveCfg = Release|Any CPU
{76D11E26-4764-4051-B597-9E1D88015259}.Release|Any CPU.Build.0 = Release|Any CPU
{76D11E26-4764-4051-B597-9E1D88015259}.Release|x64.ActiveCfg = Release|x64
{76D11E26-4764-4051-B597-9E1D88015259}.Release|x64.Build.0 = Release|x64
{76D11E26-4764-4051-B597-9E1D88015259}.Release|x86.ActiveCfg = Release|x86
{76D11E26-4764-4051-B597-9E1D88015259}.Release|x86.Build.0 = Release|x86
EndGlobalSection
EndGlobal
While there is a lot of noise in here, the main thing is the first few lines where the 2 projects are set.
Now we have a single entry point to be able to build our application.
dotnet build
Microsoft (R) Build Engine version 15.5.180.51428 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
Restore completed in 15.72 ms for C:\dev\MyComplexApplicationRepo\MyNewLib\MyNewLib.csproj.
Restore completed in 16.16 ms for C:\dev\MyComplexApplicationRepo\MyNewApp\MyNewApp.csproj.
MyNewLib -> C:\dev\MyComplexApplicationRepo\MyNewLib\bin\Debug\netstandard2.0\MyNewLib.dll
MyNewApp -> C:\dev\MyComplexApplicationRepo\MyNewApp\bin\Debug\netcoreapp2.0\MyNewApp.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:03.77
You can also explicitly name the solution file when using the commands like dotnet build .\MyComplexApplicationRepo.sln
.
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.