Introduction
NuGet packages provide flexibility to add third party libraries with lesser effort and time, and avoid delving into complex, time consuming and error-prone activities may be required by the target library you want to add in your project. In this article, I will explain how to create NuGet package manually. Although there are many things to consider when creating a package as per the requirements of any particular package, I am not going into details of all those configuration options. It will restrict the discussion with the minimum points that need to explain the fundamentals of creating a NuGet Package. You will learn how to:
- Add a simple C# code file
- Make changes in app.config file (or web.config)
- Add a dependency project (
log4net
) - Execute Powershell script on Install & Uninstall
Background
While there are thousands of packages available publicly free of cost, you may be wondering why we need to create our own package. There could be many reasons like you need to share your own custom library across your company projects or even publicly, you may need to make small functional changes of an existing package, or you may just want to keep a log of all your useful code classes or libraries in different repositories so that might help you in future projects to save time of re-writing.
This article is using log4net
library as dependency package, let's take a look at this first.
log4net (A Dependency for Our Package)
If you have ever considered a logging framework or library, you definitely came across log4net. log4net
is a tool to help the programmer output log statements to a variety of output targets. It offers a list of appenders which you can use according to your requirements. Some of the appenders are:
AdoNetAppender
AnsiColorTerminalAppender
AspNetTraceAppender
ColoredConsoleAppender
ConsoleAppender
EventLogAppender
FileAppender
NetSendAppender
RemoteSyslogAppender
RemotingAppender
RollingFileAppender
SmtpAppender
SmtpPickupDirAppender
TelnetAppender
UdpAppender
You can find more information at What is Apache log4net.
Our Package Overview
Generally, Nuget package is created from .NET Framework Class Library. In our example, we use a C# Class Library project. I will create a package which provides a C# class CustomLog_Appender
inheriting AppenderSkeleton
class of log4net
library. Of course, this package is not very useful but it will clarify certain concepts which you need to understand for creating a new package. I want to implement my own target and logic for storing log details, let's say I need to send log messages to my WCF service which is not available in default Appenders list. I am creating this package so that whenever I will have similar requirement, I will install this package and it will give me a template class of custom appender and I will just start writing my own code of calling my WCF service without digging into further configuration of log4net
. Hence, this package is basically a wrapper around log4net
library to provide an appender class.
Create Content for the Package
Let's start creating content for your package. As I mentioned earlier, we will create the following content items to include in our package:
- Add a simple C# code file - Although we need to ship only single C# file in package installation, we will create this file in C# Class Library project.
- Make changes in app.config file (or web.config) - Stand-alone config files will be enough for the purpose of this article, you can use any text-editor to create these files.
- Add a dependency project (
log4net
) - We don't have to do anything with this library, only we have make an entry in .nuspec file to instruct the Package Manager to install this as dependency. - Execute Powershell script on Install & Uninstall - We will add two separate powershell script files to execute when this package will be installed or uninstall
1. Our Project (C# Class Library)
Create a new project with C# Class Library (.NET Framework) project template. Add a new folder Model, add new class named CustomLog_Appender
with the following code listing:
using log4net.Appender;
using log4net.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1.Model
{
public class CustomLog_Appender : AppenderSkeleton
{
protected override void Append(LoggingEvent loggingEvent)
{
}
}
}
This class is inherited from AppenderSkeleton
base class and overrides the method Append
which is receiving LoggingEvent
object as parameter. This method will be invoked whenever you call one of logger's logging methods (Info()
, Debug()
, Warn()
, Error()
, Fatal()
) in your code. Inside this method, we are passing the required information to the WCF service's client object. This is the code file which we need to add in your package so that when this package will be installed in any other project, it will add this C# file by itself.
Rename the file name by appending .pp at the end, e.g., CustomLog_Appender.cs will become CustomLog_Appender.cs.pp.
2. Make Changes in app.config File
Log4net
library needs you to provide custom section in app.config (or web.config, procedure is the same in both config files). Since we are using a custom appender class, we have to tell log4net
library to use this class as the appender. I am using XDT syntax to transform config file. Text from docs.microsoft.com:
Using any text-editor, you need to create app.config.install.xdt and web.config.install.xdt files in your package's content folder, with XDT syntax to describe the desired changes. Using similar syntax, you can also include a .uninstall.xdt file to reverse the changes when the package is removed from a project.
Here is the content of app.config.install.xdt:
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<configSections xdt:Transform="InsertIfMissing">
</configSections>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,
log4net" xdt:Transform="Insert"/>
</configSections>
<log4net xdt:Transform="Insert">
<root>
<level value="ALL" />
<appender-ref ref="CustomLog4Appender" />
</root>
<appender name="CustomLog4Appender"
type="ConsoleApplication1.Model.CustomLog_Appender">
</appender>
</log4net>
</configuration>
And here is the content of app.config.uninstall.xdt:
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,
log4net" xdt:Transform="Remove" xdt:Locator="Match(name)"/>
</configSections>
<log4net xdt:Transform="Remove">
<root>
<level value="ALL" />
<appender-ref ref="CustomLog4Appender" />
</root>
<appender name="CustomLog4Appender"
type="ConsoleApplication1.Model.CustomLog_Appender">
</appender>
</log4net>
</configuration>
Place both files in a directory named "content" at project's root.
3. Execute Powershell Script on Install & Uninstall
As per log4net
requirements, we have to add the following line at the bottom of the AssemblyInfo.cs file.
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
To add this line, I am using Powershell script, which will append this line when our package is installed and delete it when the package will be uninstalled. We will create two powershell script files inside "content" folder.
Create file Install.ps1 with the following content, this will add log4net
config line in AssemblyInfo.cs file.
param($installPath, $toolsPath, $package, $project)
$lineText = "`r`n[assembly: log4net.Config.XmlConfigurator
(Watch = true)]" + " // Added by: (" + $package.Id + ")"
$file1 = Split-Path (Get-Project).FileName
$file1 = $file1 + "\Properties\AssemblyInfo.cs"
Add-Content $file1 $lineText
Create file Uninstall.ps1 with the following content, this will delete log4net
config line from AssemblyInfo.cs file.
param($installPath, $toolsPath, $package, $project)
$lineText = "[assembly: log4net.Config.XmlConfigurator
(Watch = true)]" + " // Added by: (" + $package.Id + ")"
$file1 = Split-Path (Get-Project).FileName
$file1 = $file1 + "\Properties\AssemblyInfo.cs"
(Get-Content $file1 ) -split '`r`n' | Where-Object {$_ -ne $lineText } | Set-Content $file1
Let's Start Creating Our Package
First, you need to download and install nuget.exe. And put the location in installed directory in environment PATH
variable or copy it in the root directory (where solution file .sln exists) of the project.
Open the command prompt and move to the project directory, and run the following command:
nuget spec
The above command will create a file named Package.nuspec. This is the main component of Nuget package. This is an XML file that represents a package, it contains all the information that requires to publish. Open Package.nuspec file in text editor and update any required field values in XML. Here is the updated XML file which I edited according to this example.
<package >
<metadata>
<id>Log4net.CustomLog_Appender</id>
<version>1.0.0</version>
<authors>midrees</authors>
<owners>midrees</owners>
<licenseUrl>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE</licenseUrl>
<projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>
<iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>This is Log4net custom appender</description>
<releaseNotes>Add a CustomAppender class with empty Append() method,
you can write your own code to implement log writing mechanism in this method.</releaseNotes>
<copyright>Copyright 2019</copyright>
<tags>Log4net CustomAppender</tags>
<dependencies>
<dependency id="log4net" version="2.0.8" />
</dependencies>
</metadata>
<files>
<file src="readme.txt" target=""/>
<file src="ConsoleApplication1\Model\CustomLog_Appender.cs.pp" target="content\Log4net"/>
<file src="content\app.config.install.xdt" target="content" />
<file src="content\app.config.uninstall.xdt" target="content" />
<file src="content\Install.ps1" target="tools\Install.ps1" />
<file src="content\Uninstall.ps1" target="tools\Uninstall.ps1" />
</files>
</package>
Following are the elements you need to specify according to your package.
Element | Description |
id | The case-insensitive package identifier. It must be unique if you want to host on Nuget.org. For the purpose of this article, we can use any desired id. |
version | The version of the package (in major.minor.patch pattern). You need to update this value for each subsequent package deployment. |
description | Description of the package |
authors | A comma-separated list of packages authors |
projectUrl | A URL for your package's home page. |
licenseUrl | A URL for your package's license, if you want to have |
iconUrl | A URL for an image/icon for the package |
releaseNotes | A brief description of the changes made in this release |
copyright | Copyright details for your package |
tags | Space separated list of tag you may want to attach with your package. |
dependencies | This is optional and may contain one or more dependencies your package may need. In your case, we are having one entry for log4net . |
files | List of files you need to copy to the target project where this package will be installed. |
Let's Review the Files Tag
This is the tag from Package.nuspec file, where we have to include the content files for our package. The content files we have created in the above sections. For most file entries, you have to specify src
and target
attributes, except the readme.txt file which we don't want to copy to the target project, instead we only want to display it to the user when our package installation will be finished.
Here is the description of file entries I made for package.
- readme.txt - Only mentioned in
src
attribute, target is empty. - CustomLog_Appender.cs.pp - This file is inside Model directory, and target should be the path where you want it to copy. Please make sure to add prefix "content\" to target path, it will be considered as the root directory of the target project.
- app.config.install.xdt - will be transformed to the target project's app.config file.
- app.config.uninstall.xdt - will be transformed/removed to the target project's app.config file when you uninstall this package.
- Install.ps1 - This is the powershell script we need to execute when our package will install. In order to execute this in target project, we have to specify it in target attribute by adding prefix "tools\" to the file name. This is not a physical directory in target project, only purpose of this is if you want to run script you can pleace it in "tools\" directory while specifying target attribute.
- Uninstall.ps1 - This is the powershell script we need to execute when our package will be uninstalled.
After finishing with Package.nuspec file. Run the following command to generate final package file.
nuget pack Package.nuspec
This will create the package file with nupkg extension in the same directory (project root), values of Id
and Version
elements from Package.nuspec
are concatenated in the resulting file name. In this example, it will create file Log4net.CustomLog_Appender.1.0.0.nupkg.
Points of Interest
The attached download contains only bare minimum content which I mentioned in this article. I created a stand-alone directory with the required files, i.e., readme.txt, CustomLog_Appender.cs.pp, app.config.install.xdt, app.config.uninstall.xdt, Install.ps1, and Uninstall.ps1. You can download this content and copy nuget.exe file in the same directory if you have not set nuget.exe in environment PATH
variable, and run the command nuget pack Package.nuspec
, it will just create the output package file in the same folder, which you can try installing in any project.
History
- 4th February, 2019: Initial version
References