.NET is a popular platform for building software systems and apps of all shapes and sizes. The 2016 launch of .NET Core — a free, cross-platform, open source software development framework — has only accelerated .NET’s momentum.
Virtually every .NET-based software product that is running or developed currently leverages code reuse. Whether a small desktop or console application or a huge enterprise application, it will contain both original code and reference “borrowed” code in the form of libraries or packages or APIs. We say that our application depends on those external packages, and we call those external packages dependencies.
Code reuse, when applied wisely, decreases time-to-market and increases the overall quality of the product. However, the decision of what components you depend on and how you integrate them can make a huge impact on your development velocity.
As products grow, the management of these external dependencies becomes more and more complex. This post aims to describe some of the artifacts involved in the dependency management of .NET projects and how they interact.
.NET: A Timeline
Artifacts used in dependency management have evolved over time, influenced by the evolution of the .NET Framework, the release of .NET Core, and the emergence of NuGet as the package management solution for .NET projects.
.NET Framework pre-NuGet (before 2010)
Prior to the advent of .NET Core, a project was represented by a .csproj file, and all the dependencies were represented in the same file. If you wanted to reference a third-party library, you had to find it on the web, download it, place it in a folder, and add a project reference to it in the .csproj file.
.NET Framework with NuGet (2010 - 2016)
A project was represented by a .csproj file but the dependencies (including NuGet package references) were kept separately in a packages.config file. Here, you could get your third-party libraries from the NuGet central public collection and transfer your project to another machine without copying the libraries — simply copy the packages.config file and do a package restore.
Initial .NET Core (2015 - 2017)
The first .NET Core projects created in Visual Studio 2015 were represented by an .xproj file (similar to .csproj) and the dependencies (NuGet package references) stored in a project.json file. This approach is now deprecated.
.NET Core is born (2017 - today)
Since Visual Studio 2017, a project has been represented by a .csproj file with dependencies (NuGet package references) listed in the PackageReference section of this file. For some types of projects, dependencies are still kept in that separate packages.config file.
The .csproj File
Projects in .NET enable managing source code files as cohesive groups and also support the building of each cohesive group into one deployable artifact (.dll, .exe, etc.). Each project is represented by one project file.
Project files are essentially XML files that act as containers of files. Every Visual Studio project includes an MSBuild project file, with a file extension that reflects the type of project — for example .csproj for a C# project, .vbproj for a Visual Basic .NET project, or .dbproj for a database project.
A project file usually includes:
- The list of files included in the project
- Dependencies:
- Other projects from the same .sln
- Client-side libraries and other framework assemblies
- System assemblies
- Third-party libraries (aka NuGet packages) - PackageReference
- Build info and instructions for MSBuild: what to include, target platforms, tasks to be performed along with versioning info, WebServer or DB Server settings (old)
.csproj File Interactions
In the .NET Framework, when new files are added to the project in Visual Studio, they get mentioned in the .csproj explicitly. This is implicitly determined by the FileSystem in .NET Core; files and folders present in the project root folder (where the .csproj was first created) are considered part of the project.
When you build the code (either from Visual Studio IDE or via dotnet build command), the .csproj tells MSBuild which resources to include and what to do with them. This is done explicitly via Targets and Tasks for older .NET versions; currently, this is done implicitly by specifying only the type of project (predefined SDKs).
The .csproj file can be directly edited although this is somewhat risky (unless you have ample experience) as it can lead to corruption of the XML file, which may break the consistency of your project.
In .NET Core, when a new NuGet package is referenced, that is captured in the .csproj in the PackageReference section. In some project types, this may still be captured in the separate packages.config. Previously, NuGet package references were kept in a separate file — see project.json.
How to Use the .csproj File
When creating a new project, use a suitable project template — it will generate a folder structure and certain artifacts specific to that project type, which will save you some time. If needed, you can also create your own custom project templates.
When creating a new project in Visual Studio, if you don’t find the template you are looking for, you might need to install additional workloads. You can do this by clicking “Install more tools and features,” selecting the workloads to install, and hitting Modify.
If you want to edit the .csproj file in .NET Core, you can just right-click on the project name in VS and hit Edit Project File. For .NET versions prior to Core, you need to Unload project, Edit project file, Save project file, and then Reload the project.
The .sln File
Building anything in Visual Studio requires you to have a solution, and that solution must contain at least one project, one for each build output generated. Solutions are XML files (.sln) acting as containers that Visual Studio uses to organize one or more related projects.
.sln File Interactions
When you add/remove projects in Visual Studio, the changes are recorded in the .sln file.
When you open a solution, Visual Studio automatically loads all the projects that the solution contains.
How to Use the .sln File
If you have a lot of projects in your codebase, you can use multiple .sln files to minimize the scope and speed up development steps. For example, you might have:
- One solution for just the core functionality: database, API, web app
- One solution that also includes integration libraries related to some major integration features that don’t get modified too often
- One solution that includes every project
Files that are added to a solution directly will not be compiled. Only files that belong to projects are compiled. Solution items are therefore typically some form of content or documentation.
The packages.config File
Packages.config is an XML file used in some project types to hold the list of package dependencies. For most project types, it is superseded by PackageReference. An example:
Packages.config Interactions
When you add/remove/update NuGet package references (either via the Visual Studio NuGet Package Manager UI, the dotnet CLI, or the nuget.exe CLI), these changes will be reflected in the packages.config file.
Visual Studio and NuGet use the packages.config file as reference when doing a restore operation.
How to Use the packages.config File
In NuGet 2, you would have to capture every single package to be included. In NuGet 4 (VS 2017), only top-level dependencies need to be listed.
When you want to transfer the project to a different machine (e.g. build server), you don’t need to copy the packages. You can simply copy the project files — including the packages.config — and do a NuGet restore operation on the target machine.
PackageReference
PackageReference is actually not a file, but a section in the .csproj file structure of .NET Core projects. It holds references to all the NuGet packages installed for your project.
Previously, these references were kept in a separate file called project.json. Even earlier, they were stored in the packages.config file, which is actually still used for some types of projects.
PackageReference Interactions
When you add/remove/update NuGet package references (either via the Visual Studio NuGet Package Manager UI, the dotnet CLI, or the nuget.exe CLI) these changes will be reflected in the PackageReferences section.
The package restore operation installs all dependencies of the project, and it relies on the PackageReference contents as baseline. The package restore can be triggered in several ways:
dotnet build
anddotnet run
commands in .NET Core 2.0+- Building the project in Visual Studio
nuget restore
command from the nuget.exe CLIdotnet restore
command from the .NET CLI- xbuild on Mono
How to Use PackageReference
Packages can be conditionally included by adding conditions in the corresponding line in the section.
.NET developers typically find components they can reuse in their own applications by browsing nuget.org. You can search nuget.org directly or find and install packages within Visual Studio.
The project.json File
The project.json file is an XML file used in older .NET projects to hold the packages used. With NuGet 4.0+, it is superseded by PackageReference, as .NET Core went from the project.json to .csproj file format.
The file contains the following major sections:
- Dependencies: NuGet package dependencies of your project
- Frameworks: Frameworks that the project runs on (e.g. net45, netcoreapp, netstandard)
- Runtimes: The operating systems and architectures that your app runs on (e.g. win10-arm, win8-x64, win8-x86)
- Supports: A set of checks for package dependencies
- Imports: Designed to allow packages that use the dotnet TxM to operate with packages that don't declare a dotnet TxM
project.json Interactions
The part of the project.json file used by NuGet is a subset of that found in ASP.NET Core projects. In ASP.NET Core, project.json is used for project metadata, compilation information, and dependencies. When used in other projects, those three things are split into separate files, and project.json contains less information.
How to Use the project.json File
This file is deprecated in favor of .csproj and PackagesReference or packages.config. So, if possible don’t use it, or migrate away from it.
In projects that use the project.json, when performing a package restore operation, the project.lock.json file is created. It includes the version, contents, and dependencies of all the packages in your project as a snapshot of that moment.
The nuget.config File
nuget.config is an XML file containing settings that control the behavior of NuGet. This file is not directly involved in dependency management for a .NET project, but it does influence how NuGet will handle package management for all projects.
As an example, in the packageManagement section, the default package management format is set: 1 is for PackageReference, and 0 is for packages.config.
The .nuspec File
.nuspec is an XML file that functions as a manifest containing package metadata.
It is included in every NuGet package and is used both to build the package and as a source of information for the consumers of the package.
Microsoft recommends the following approaches for package creators:
- For non-SDK-style projects that use packages.config: Use
.nuspec
with thenuget.exe pack
command. - For SDK-style projects: The
.nuspec
file is not required, and you can use thedotnet.exe pack
ormsbuild pack target
commands. - For projects migrated from
packages.config
toPackageReference
: The.nuspec
file is not required to create the package. Themsbuild -t:pack
command should be used.
Conclusion
Hopefully this has brought a bit more clarity into dependency management in .NET. Whether you are managing dependencies in your legacy .NET framework system, your NuGet packages in your shiny new .NET Core application, or if you are thinking of migrating from the former to the latter, now you should have a basic understanding of the related artifacts and how they come into play.
Happy coding!
About the Author
Cristian Taran is a seasoned full-stack software engineer who specializes in the Microsoft tech stack. His recent weapons of choice include React JS, Python, .Net Core, and MS SQL Server. Whether he is coding, working out, or playing video games, he prefers doing it to the sound of music.