Alan Dean

CTO, Developer, Agile Practitioner

Photograph of Alan Dean

Wednesday, October 12, 2011

Not-so-simple NuGet Packaging

Moving beyond the simple case, here is what I did to package the Cavity log4net trace listener which I'm sharing because I encountered some frustration points.

First, the .nuspec file:

<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
  <metadata>
    <id>Cavity.Diagnostics.Log4Net</id>
    <version>1.1.0.444</version>
    <title>Cavity log4net trace listener</title>
    <authors>Alan Dean</authors>
    <owners />
    <licenseUrl>http://www.opensource.org/licenses/mit-license.php</licenseUrl>
    <projectUrl>http://code.google.com/p/cavity/</projectUrl>
    <iconUrl>http://www.alan-dean.com/nuget.png</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>A trace listener for log4net, allowing provider-agnostic tracing.</description>
    <summary />
    <copyright>Copyright © 2010 - 2011 Alan Dean</copyright>
    <language />
    <tags>Diagnostics</tags>
    <releaseNotes>Switched off license acceptance dialog.</releaseNotes>
    <dependencies>
      <dependency id="log4net" version="1.2.10" />
    </dependencies>
  </metadata>
  <files>
    <file src="content\app.config.transform" target="content\app.config.transform" />
    <file src="content\log4net.config.transform" target="content\log4net.config.transform" />
    <file src="content\web.config.transform" target="content\web.config.transform" />
    <file src="content\Properties\log4net.cs" target="content\Properties\log4net.cs" />
    <file src="lib\net35\Cavity.Diagnostics.Log4Net.dll" target="lib\net35\Cavity.Diagnostics.Log4Net.dll" />
    <file src="lib\net40\Cavity.Diagnostics.Log4Net.dll" target="lib\net40\Cavity.Diagnostics.Log4Net.dll" />
    <file src="tools\Install.ps1" target="tools\Install.ps1" />
  </files>
</package>

Right off the bat. you can see that more is going on here. The trace listener is dependent on log4net and I also have a bunch of content in additional to my two assemblies.

The target project will need to have either its' app.config or web.config extended so there is a transform for each. The target will need an assembly attribute applied, so I took the simple expedient of dropping log4net.cs into Properties alongside AssemblyInfo.cs. However, log4net also requires XmlConfigurator.Configure() to be called. This call needs to be in either Main() or Application_Start() and therefore existing code needs to be edited (rather than a new code file dropped in). To accomplish this, we have to use EnvDTE, as exposed in the Install.ps1 PowerShell script (I also mark the log4net.config file to copy to output during build):

param($installPath, $toolsPath, $package, $project)

$project.ProjectItems.Item("log4net.config").Properties.Item("CopyToOutputDirectory").Value = 1

try
{
  $item = $project.ProjectItems.Item("Program.cs")
}
catch [System.Management.Automation.MethodInvocationException]
{
}

if (!$item)
{
  $item = $project.ProjectItems.Item("global.asax").ProjectItems.Item("global.asax.cs")
}

$terminator = ""
if ($item.FileCodeModel.Language -eq "{B5E9BD34-6D3E-4B5D-925E-8A43B79820B4}")
{
  $terminator = ";"
}

$win = $item.Open("{7651A701-06E5-11D1-8EBD-00A0C90F26EA}")
$text = $win.Document.Object("TextDocument");
$namespace = $item.FileCodeModel.CodeElements | where-object {$_.Kind -eq 5}
$class = $namespace.Children | where-object {$_.Kind -eq 1}

$methods = $class.Children | where-object {$_.Name -eq "Main"}
if (!$methods)
{
  $methods = $class.Children | where-object {$_.Name -eq "Application_Start"}
  if (!$methods)
  {
    [system.windows.forms.messagebox]::show("methods is null")
  }
}

$edit = $methods.StartPoint.CreateEditPoint();
$edit.LineDown()
$edit.CharRight(1)
$edit.Insert([Environment]::NewLine)
$edit.Insert(" log4net.Config.XmlConfigurator.Configure()")
$edit.Insert($terminator)

I have to say that the PowerShell + EnvDTE experience was rather less than enjoyable but I got the basics of what I needed to work.

2 comments:

foo said...

Alan,

You can use WebActivator to execute code at application start in ASP.NET projects: http://nuget.org/List/Packages/WebActivator

++Alan

Alan Dean said...

Thanks for the hint - is there a clear example anywhere? The project link is not especially illuminating...