So you’ve created a cool C# library that you want to share with your fellow developers, but don’t know how? Maybe you know you could do that with NuGet packages, but don’t know how to create one?
This post showcases how you can make this amazing library you’ve created publicly available so that others can enjoy working with it!
What is a NuGet package?
A NuGet package is a single, packaged library or set of libraries, complemented by metadata and dependencies, designed to easily be distributed and integrated into projects in .NET.
This saves time for the developers by avoiding rewriting or duplicating code, as we promote reusability by installing these packages and making use of their functionality.
Sample library code
We are going to be a very simple logger class that just prints a message to the console. This printing will evolve to showcase that new changes in our library code can be automatically deployed to the package feeds, but we will come to this in a second.
First, let’s create a class library project. We can do so via de command line:
dotnet new classlib -n Something
The outcome should look something like:
This ci.yaml file contains the code that will package our library and publish it to the corresponding package source. This file can do many more things, like building the app, running tests, generating docker images, and so on, we are just using a very small fraction of the abilities this gives us.
Now let’s see how this file can look depending on the target package source. For each one of them, I’ll first introduce the final version of the ci.yaml file and then explain the file in detail.
Publishing to GitHub Packages with GitHub Actions
Here’s the CI manifest file to publish your NuGet packages to GitHub packages, via Github actions:
name: ci
on:
push:
branches: [main]
jobs:
generate-version:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v2
- name: GitHub Tag Bump
id: tab_bump
uses: anothrNick/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INITIAL_VERSION: 1.0.0
DEFAULT_BUMP: patch
outputs:
new_version: ${{ steps.tab_bump.outputs.new_tag }}
package-and-publish-lib:
runs-on: ubuntu-latest
needs: generate-version
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 8.0.x
- name: Generate NuGet package
run: |
dotnet pack MyAmazingLogger/ \
--configuration Release \
-p:PackageVersion=${{ needs.generate-version.outputs.new_version }} \
-p:RepositoryUrl=https://github.com/RafaelJCamara/YT-Nuget-Pkg-Release \
-o packages
- name: Publish NuGet package
run: dotnet nuget push packages/*.nupkg --api-key ${{ secrets.PUSH_NUGET }} --source https://nuget.pkg.github.com/RafaelJCamara/index.json
Now let’s analyze the important bits.
When to trigger a package publish
Based on what we have in our manifest:
on:
push:
branches: [main]
Packages are going to be published whenever we have some code merged to main.
What steps are involved
We know that our packages need to have a version. This version follows a format called .
As you can see from our manifest file, we have the following environment variables necessary for this action to properly work:
GITHUB_TOKEN: This token is required for permissioning the tag in the repo. We can use a built-in token calledsecrets.GITHUB_TOKEN, you don’t have to configure anything to access this token. This token is automatically generated for your repo by GitHub and allows you to safely interact with GitHub’s Apis and your repository, allowing you to do things like publishing packages. As of the writing of this post, this token only has read permissions, therefore we can’t write anything. This is why we have this section at the top of thegenerate-versionjob:
permissions:
contents: write
This will give our token enough permission to write the tags.
If you don’t give write permissions, this is the error you will get:
.
As you may have noticed, we have defined an output from the job. This is useful so that we can use the produced tag number in other jobs, which is what we want.
outputs:
new_version: ${{ steps.tab_bump.outputs.new_tag }}
As we can see from the snippet, we are defining an output with key new_version, which is pretty much just returning the value from the GitHub Tag Bump step. Just note that we had to give an ID to the step (tab_bump) so that we could target the step’s output.
Analyzing the package-and-publish-lib job
The two main responsibilities of this job are:
- Package our code into a NuGet package (*.nupkg)
- Publish this package into a package feed (in this case GitHub packages)
This job has 3 main steps:
Choosing the appropriate .NET version (step Setup .NET)
Generating the NuGet package (step Generate NuGet package)
Publishing this NuGet package (step Publish NuGet package)
Let’s analyze each step with care.
Choosing the appropriate .NET version (step Setup .NET)
This step simply uses the action actions/setup-dotnet@v1 to set up our .NET version (in this case .NET 8).
Generating the NuGet package (step Generate NuGet package)
Let’s bring the package command closer to us so that we can analyze it better:
dotnet pack MyAmazingLogger/ \
--configuration Release \
-p:PackageVersion=${{ needs.generate-version.outputs.new_version }} \
-p:RepositoryUrl=https://github.com/RafaelJCamara/YT-Nuget-Pkg-Release \
-o packages
First, we need to understand that we use the dotnet pack command to generate our NuGet package. In our case, we are using a couple of flags:
--configuration Release: we want to use the Release configuration because it’s more performant and tends to generate bundles with lower storage requirements (smaller size bundles).-p:PackageVersion=${{ needs.generate-version.outputs.new_version }}: here we are specifying the NuGet package version. As you remember, we generated this version on the previous job (generate-version) and we need to access this output. To do so we can use theneedskeyword, then specify the job we want to get the output from (generate-version), and then access theoutputsproperty of this job and then the key of our output (which we namednew_version). When you do this, you can get the generated version!-p:RepositoryUrl=https://github.com/RafaelJCamara/YT-Nuget-Pkg-Release: What this is doing is adding the repository URL metadata to the published NuGet package. There is another way of doing this, which we will cover shortly.-o packages: Specifies the directory where the generated NuGet package should be placed. In this case, the output will be stored in thepackagesfolder.
After this step, we have our NuGet package ready to be published!
Publishing this NuGet package (step Publish NuGet package)
Let’s take a closer look at the publish command:
dotnet nuget push packages/*.nupkg --api-key ${{ secrets.PUSH_NUGET }} --source https://nuget.pkg.github.com/RafaelJCamara/index.json
To publish our package we need to use the dotnet nuget push command. We need to provide 3 important values to it:
Where the NuGet package is located. We do so by passing thepackages/*.nupkgparameter. This would normally match with what you’ve inserted as the output of your dotnet pack command.Where the package feed/repository is located. In this case, our source is
https://nuget.pkg.github.com/RafaelJCamara/index.json. Since we are using the GitHub package repository we have a special source structure:https://nuget.pkg.github.com/{github-username}/index.json. This means that if your username is LostJohn1234 your source would be something likehttps://nuget.pkg.github.com/LostJohn1234/index.json.The
api-keyto access the feed. As you can see, we are passing a token from the GitHub secrets. This means we need to create one token that has permission to write packages. In the next section, I’ll show you how to do it.
Creating a GitHub secret
Let’s see how we can create a secret token that has permission to write NuGet packages to our GitHub package feed.
First, let’s go to your profile Settings page.
Go to the Developer Settings section, and select Tokens (classic) inside the Personal access tokens. Generate a classic token.
Make sure you have something like this:
.
Every other step remains the same as before, so I advise you to take a read of what was written before and make the adjustments we are making in the section.
Creating a NuGet api-key
First of all, head out to
Your key should be now visible:
You will also notice that next to your commit you will have a success icon (or fail icon) if the underlying GitHub Action went fine:
As you can see, you should be able to see the package you have published, with the correct version number attached to it.
Regardless of how many changes you now do on your package, all of these changes will now be automatically published to your chosen package feed!
How to use your custom NuGet packages
This step depends on which package repository you’ve used.
If you have used the default NuGet feed, you don’t need to do anything special to use your newly created package. Just go ahead and install it! It’s a plug-and-play because this feed comes by default with .NET. To check the current feeds you have, run the command dotnet nuget list source. If you haven’t added any feed, this is the outcome you should have:
Now you can install your package freely!
Adding metadata to the published NuGet package
As I stated previously, we can set metadata related to our NuGet package in another way, and that another way is by adding metadata in the library .csproj file.
Each property specifies information that will be included in the package manifest, allowing others to understand the purpose, licensing, and source of the package when they download it from nuget.org or other sources. Here’s a breakdown of each property:
<PackageId>: This is the unique identifier for your NuGet package (in this case,MyAmazingLogger). It’s what users will search for when they want to install your package.<Version>: Defines the version of your package, following semantic versioning (e.g.,1.0.0). This version appears in NuGet package listings and helps users identify updates or specific versions to install. Currently, we are not going to use this, due to our version being created in the GitHub Action.<Authors>: Specifies the package author(s). This can be a single name or a list of contributors and helps users know who created and maintains the package.<PackageDescription>: Provides a short description of your package. This text is displayed onnuget.org, where users can see what your package does.<PackageLicenseExpression>: Indicates the license type for your package, such asMIT,Apache-2.0, orGPL. NuGet uses this to show the licensing terms, which is important for clarity regarding the legal usage.<PackageTags>: These tags help improve discoverability on NuGet. For example, tagging it asLoggers, it makes it easier for users to find your package when they search for logging-related functionality.<RepositoryUrl>: This is the URL for the repository where the package source is hosted, often on GitHub. This allows users to view the source code, file issues, or contribute to the project.
When you package and publish the project to NuGet, these metadata values are included in the .nuspec file (the package manifest) and displayed on the package’s page on nuget.org. Don’t worry if some of this information does not showcase if you publish your package in the GitHub package repository, from my experience it tends to not show some of the metadata. Regardless, it’s interesting and important to have such metadata.
Here’s an example of how your metadata can look like in nuget.org:
for this post.
If you are more of a visual learner, I’ve created a video to showcase the tutorial explained here.
SOCIAL SHARE CARD GENERATOR