Tuesday, March 8, 2011

Take NuGet to the next level with sample packages

NuGet has drastically simplified the process of getting .NET libraries into your projects. What used to be an error prone and painful process has become as simple as adding an assembly reference.

While it has solved an important part of the developer workflow, it has the potential to also solve another key piece of the puzzle: helping user learn to use libraries.

I found these cool packages, but now what?

There are tons of cool packages available on NuGet today, and the number is growing daily. I’ve heard of a number of users who go down the list and install all kind of packages into their projects to try them out. But if you’re not familiar with a library, how do you get started with it?

As an example to illustrate the discussion, let’s take the nifty little Clay package written by the Orchard guys. Say you have installed it into your project and want to start using it. Here is what you might do:

  • The NuGet dialog gives you a link to the ‘project URL’. Typically, it’s a link to where the project is hosted on CodePlex/BitBucket/github, and indeed this one takes you to http://clay.codeplex.com/.
  • Once you’re there, you try clicking on the Documentation tab. Unfortunately, many projects don’t have much there. But here it at least has a pointer to Bertrand’s blog posts on the topic. So you now go to his post.
  • You read through it, and after a while, you can piece together enough bits and pieces to know what it’s about and start using it into your code.

I took Clay as an example, but this is a fairly typical experience. The fact is that a lot of knowledge about immature (yet useful) projects only exists in ‘blog post series’ rather than in any formal documentation. Not ideal, but that’s how things happen.

NuGet to the rescue with Sample Packages

Luckily, there is a simple and effective solution to this problem: use NuGet to distribute basic samples that get your users on the right path with less pain.

So to illustrate this post, I went ahead and created one such package for Clay: Clay.Sample. This package depends on Clay, such that installing it also installs Clay (as well as other things Clay depends on, like Castle).

It’s a ‘source only’ package, meaning that it doesn’t contain any binaries of its own. So let’s go ahead and try it in a brand new Console app (and change it NOT to use the client profile). Go in NuGet’s ‘Add Library Reference’ dialog and search for Clay. You’ll get this:

image

After you install it, your project will look like this:

image

First, note how you got all the expected references to Clay and to its dependencies: Castle.* and log4net.

But more interestingly, it also brought in a ClaySamples source file under Samples\Clay. It contains a number of Clay samples, which I shamelessly copied from Bertrand’s post. Here is one example:

public static void AnonymousObject() {
   dynamic New = new ClayFactory();

   var person = New.Person(new {
       FirstName = "Louis",
       LastName = "Dejardin"
   });

   Console.WriteLine("{0} {1}", person.FirstName, person.LastName);
}
There are about 10 such samples in there, which demonstrate everything that the post discusses. Now go to your Console Main and make a call to a method that runs all the samples:
class Program {
   static void Main(string[] args) {
       Samples.Clay.ClaySamples.RunAll();
   }
}

While there is nothing in there that’s not in the blog post, the big advantage is that you can trivially get it into your project via NuGet, and you can then directly run/debug the samples without having to piece them together.

Of course, the blog post (or documentation) may still be worth reading for extra insight. But you may find that the samples give you all you need for now, and save the deeper reading for later.

Call to packages authors: write Sample packages!

I think this type of packages can have a huge impact on developer productivity. But for that to actually happen, those packages need to be created! And while I created the one for Clay, I am not volunteering to create all the sample packages :) Clearly, the best person to do that is the author of the package, though anyone who knows it well enough can certainly do it as well.

So if you own a NuGet package, please try to take on that task. It’s super easy, and your users will thank you for it!

Conventions, conventions, conventions

I recently blogged about using the App_Start convention for WebActivator startup code and got a great response, with almost all WebActivator users converting their existing packages to use this.

The situation here is quite similar, and calls for a similar convention, which is what I showed above. In a nutshell:

  • If your package is named Blah, call the sample package Blah.Sample. If you want multiple sample packages, you can call them Blah.Sample.Something and Blah.Sample.SomethingElse.
  • Make your Blah.Sample package dependent on Blah.
  • Within that package, just include source files. Place those file under the Samples\Blah. You can have one or more, and call them whatever you think make sense.
  • The code on there is up to you, but the general idea to to include whatever you think will help the user get started. Try to make the sample code easily runnable without too much extra setup. This may be harder for some packages, but do your best :)

Creating the package

Taking Clay as an example, here is the structure of the files before packing them into a nupkg:

├  Clay.Sample.nuspec
└──Content
└──Samples
 └──Clay
    └  ClaySamples.cs.pp

So there are just two files, the nuspec and the preprocessed sample file. Here is the nuspec:

<?xml version="1.0"?>
<package xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<id>Clay.Sample</id>
<version>1.0</version>
<authors>Outercurve Foundation</authors>
<owners>Outercurve Foundation</owners>
<licenseUrl>http://www.opensource.org/licenses/ms-pl</licenseUrl>
<projectUrl>http://clay.codeplex.com</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>This package contains samples that demonstrate the use of the Clay library.</description>
<language>en-US</language>
<dependencies>
<dependency id="Clay" version="1.0" />
</dependencies>
</metadata>
</package>

The interesting parts here are the package Id, the description, and the dependency on Clay.

Then ClaySamples.cs.pp is a normal source file, except for a tiny bit of preprocessing for the namespace, e.g.

using System;
using ClaySharp;

namespace $rootnamespace$.Samples.Clay {
   public static class ClaySamples {
      // Sample code here
   }
}

And that’s it! Once you have that, just run ‘nuget pack’ from the folder with the nuspec, and you’ll have a sample package ready to be pushed to the feed.

18 comments:

  1. Glad I put Clay up there to give you a library to build a sample and blog against ;)

    ReplyDelete
  2. Is there any way to add a project to a solution using NuGet? That way you could have an executible project that demo'd a package.

    ReplyDelete
  3. @Scott +1

    @David For some reason the project I included Clay.Sample in isn't remembering its library references... maybe it's just me but I thought I would post just in case. It is a brand new console app.

    ReplyDelete
  4. @Aaron: yes, thanks for putting it up! :)

    @Scott: not directly, as NuGet is more about adding things *into* projects than about creating new ones. Though some have done interesting things with custom PowerShell script.

    ReplyDelete
  5. @Will: not sure I follow you. What 'library references' are you referring to?

    ReplyDelete
  6. @David: The type or namespace name 'ClaySharp' could not be found (are you missing a using directive or an assembly reference?)
    c:\users\will\documents\visual studio 2010\Projects\ConsoleApplication1\ConsoleApplication1\Samples\Clay\ClaySamples.cs

    If I view the object browser in VS before I build the project the refs are there. After I build the project, all the references created by Clay.Sample are magically gone and I see they error above. I click refresh on the solution explorer and they are back in the list but each build causes them to "disappear" again.

    I only added Clay.Sample from NuGet and relied on it to grab its dependencies. I've checked the packages folder and everything seems to be there. Also, the error above is referencing the line with the using statement for ClaySharp.

    ReplyDelete
  7. @Will: Ah yes, I know this problem too well! :) Go in the project properties and change the Target Framework from '.NET Framework 4.0 Client Profile' to just '.NET Framework 4.0'. See also http://blog.davidebbo.com/2011/02/creating-console-apps-that-use-server.html

    ReplyDelete
  8. @David: Thank you! Works perfectly :)

    ReplyDelete
  9. Can I suggest a slight convention change? Since not everyone codes in C# and I don't think you can combine the two languages in one source-only package, how about CSSample and VBSample to differentiate the two?

    ReplyDelete
  10. @Matt: Actually it shoud be possible to combine both in one package. Right now, it would probably work in a somewhat funky way where you'd get both the vs and cs sources when you install, but only the relevant one would be active. Fixing this would require a small NuGet fix.

    ReplyDelete
  11. @David, well one package does make maintenance easier, so if you think it can be done I would say go for it!

    ReplyDelete
  12. I filed a bug to track this: http://nuget.codeplex.com/workitem/790

    ReplyDelete
  13. Can NuGet install Orchard Module packages? If so, then you should be able to add a project to a directory in an solution. Maybe I am missing something. Nice convention!

    ReplyDelete
  14. PM> install-package Clay.Sample
    'Clay (≥ 1.0)' not installed. Attempting to retrieve dependency from source...
    Done.
    'Castle.DynamicProxy (≥ 2.1.0)' not installed. Attempting to retrieve dependency from source...
    Done.
    'Castle.Core (= 1.1.0)' not installed. Attempting to retrieve dependency from source...
    Done.
    'log4net' not installed. Attempting to retrieve dependency from source...
    Done.
    Install-Package : The operation has timed out
    At line:1 char:16
    + install-package <<<< Clay.Sample
    + CategoryInfo : NotSpecified: (:) [Install-Package], WebException
    + FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.Cmdlets.InstallPackageCmdlet

    ReplyDelete
  15. @nettech: you most likely ran into some random feed glitch, unrelated to this specific topic. If you continue to see this, please report it on http://nuget.codeplex.com/.

    ReplyDelete
  16. @David is it possible to deploy language resources into a project. I just found out about NuGet though MIX11 and our company is looking into possibly using NuGet, but I am doing the research to see if it can support all of our needs. Couple things I am looking for that I haven't found on NuGet yet.

    1) What is the story for working with Beta DLLs, say we want to deploy our Beta DLLs using NuGet is there a workflow with managing RC vs Beta and allowing users to rollback from a Beta DLL to the previous final.

    2) Deploying Localized Resource files into the project. We are an international company and we have needs to distribute our product for many culture types. Silverlight require you to modify project .csproj and add tags to the project. Does NuGet allow you to modify the project file? I read the you can modify app.config and web.config.

    3) Is it possible to include DLLs in the lib directiory but not add them to the project references, they would be optional DLLs files that a user may need but are not required for the core functionality. like myAssembly.Toolkit.dll(Optional) want to provide but not force into the reference on install.

    Where is the best place for me to ask these types of questions?

    ReplyDelete
  17. @Christopher: the best place for this type of questions is http://nuget.codeplex.com/, as the thread can then involve many more people who would not see the comments on my blog.

    ReplyDelete
  18. @David, thanks for posting this comment as this page was literally the only result that came up on google for my error and you gave the solution: Ah yes, I know this problem too well! :) Go in the project properties and change the Target Framework from '.NET Framework 4.0 Client Profile' to just '.NET Framework 4.0'. See also http://blog.davidebbo.com/2011/02/creating-console-apps-that-use-server.html

    ReplyDelete