Creating a NuGet-based plugin engine

Two of the main Open Source projects I work on have extensibility aspects to them, Umbraco and FunnelWeb.

We're a bit early in the development cycle for Umbraco 5 to be diving into the packaging, but FunnelWeb is more at a point where we can dive into this. So it got me thinking about how we'd go about creating a simple way that developers can share plugins or themes they've created?

Umbraco 4.x runs a decent package engine, but it's custom developed, running a custom server, and a bunch of other stuff. For a smallish Open Source project like FunnelWeb this is a large investment which we're rather avoid. Also with Umbraco 5 we're looking at whether the custom developed way is the best was to go or not, as again there is time and money that needs to be invested for it too.

My next thought was NuGet, it's all the rage at the moment (rightly so), so I was wondering if we can't just used it as our source?

Unsurprisingly I'm not the first person to look at this, it's powering Orchard's gallery, but I couldn't find any decent documentation on how to use it. So after cracking open the Orchard source, doing some investigation it seems to be working. In the rest of this article I'll cover a very basic way to do it.

What you'll need

There's two things you need:

  • A server
  • A consumer

There's a server available as part of the NuGet source code, or alternatively you can install the NuGet package for NuGet.Server ;).

Once you've installed the NuGet.Server package (I'm going to assume that you've done that) drop in your own NuGet packages into the /Packages folder and you're ready to go. If you want to test this add it to Visual Studio and you can test it via http://<your url>/nuget/Packages. Woot, one part down, now for the tricky part.

Consuming a NuGet feed yourself

Let's build a little console app which will view our packages, first off you need to add a reference to NuGet.Core and then we can start coding.

The first thing you need is a repository which you're going to work against:

    var repo =
        PackageRepositoryFactory.Default.CreateRepository(
            new PackageSource("http://nuget.local/nuget/Packages", "Default"));

It's easiest to just use the default repository, unless you're doing something truely scary, and for the PackageSource we're providing a source which is the URL of the OData feed which our packages sit behind (you can give a file system path if you're using that and it still works).

From the repository you can:

  • List the packages
  • Add a new package
  • Remove a package

(The last two I'm assuming are for the feature that's being toted for NuGet 1.2 which allows you to push new packages from the NuGet console)

There's a number of Extension Methods that are also available which make it easier find packages, so you can do something like this:

var package = repo.FindPackage("My-Awesome-Package");

Next thing we want to do is install a package, and for this you need a PackageManager:

var packageManager = new PackageManager(
	repo,
	new DefaultPackagePathResolver("http://nuget.local/nuget/Packages"),
	new PhysicalFileSystem(Environment.CurrentDirectory + @"\Packages")
);

For this we need to provide the following:

  • The repository to install from
  • A package path resolver
    • This takes the same path as the repository
  • A folder to install the packages into
    • This could be your /bin if it's a web app, or anything else you want

The PackageManager is what we use to integrate with our local application, and it's responsible for the install and uninstall process:

packageManager.Install(package, false);

For this we're providing:

  • The package to install (you can also provide the ID of the package)
  • Whether or not you want dependencies resolved (false tells it to ignore dependencies)

It's just that simple. And to uninstall it's equally as simple:

packageManager.Uninstall(package);

Again for this you just need to provide the package instance (or ID) of the package to uninstall.

Conclusion

As you can see from only a few lines of code you can create your own consumer of NuGet feeds:

class Program
{
    static void Main(string[] args)
    {
        var repo =
            PackageRepositoryFactory.Default.CreateRepository(
                new PackageSource("http://nuget.local/nuget/Packages", "Default"));

        var packageManager = new PackageManager(
            repo,
            new DefaultPackagePathResolver("http://nuget.local/nuget/Packages"),
            new PhysicalFileSystem(Environment.CurrentDirectory + @"\Packages")
            );

        var package = repo.FindPackage("My-Awesome-Package");

        packageManager.InstallPackage(package, false);

        Console.WriteLine("Installed!");
        Console.Read();

        packageManager.UninstallPackage(package);

        Console.WriteLine("Uninstalled!");
        Console.Read();
    }
}

So keep an eye on FunnelWeb as we work on using this to produce a theme and plugin engine.

And who knows, this may also be the way we do the packager which will ship in Umbraco 5.

nugetumbracofunnelweb
Posted by: Aaron Powell
Last revised: 21 Feb, 2011 01:37 PM History

Trackbacks

Comments

21 Feb, 2011 03:25 PM

Ever since you brought this up, I've wondered what it would take to make funnelweb core installable via nuget. Any thoughts?

21 Feb, 2011 08:54 PM

Alex - yes, you just have a different plugin manager, that's responsible for the install location.

joshka - you wouldn't put FunnelWeb core on NuGet. NuGet is designed for distribution libraries for applications, not distribution of applications. We've talked about getting it into Web PI, that is where you would distribute from.

22 Feb, 2011 04:05 AM

I guess what I'm doing there then is challenging the notion that funnelweb can't be seen as just a part of a .Net website rather than the full site. Obviously this would require portable areas, and other changes to core. I really don't see why File|New Empty ASP.Net MVC 3 app, Install-Package FunnelWeb, F5 shouldn't be a valid getting started strategy.

22 Feb, 2011 06:04 AM

Nice one! Def think this is the way to go for v5. I guess in v4 theres the whole notion of package actions... just need to make sure that NuGet can do the things that package actions can do (I think it's mostly just transforms but who knows)

22 Feb, 2011 06:16 AM

Josh - Yeah that'd require a lot of changes to the core, you'd be better forking FunnelWeb and using it as an underlying framework.

Shannon - Actually there's a lot more things you can do with package actions and since it's just .NET code you can run anything you want (add database tables, etc). The Orchard module activation process actually makes a lot of sense for that scenario (although I don't like their manifest file, but I have ideas around that). But that's a discussion for another blog post ;)

Alex Norcliffe
Alex Norcliffe
21 Feb, 2011 02:08 PM

Nicely done sir, did you look into custom unwrap locations yet on a per-package basis? (e.g. /Plugins/EditorControls/ vs /Plugins/Trees/)

03 May, 2011 07:33 AM

Many thanks for this blogpost. Been googling for this for days now :)

Mike
Mike
02 May, 2011 12:25 PM

+1 for Umbraco using NuGet packages. Imho you will get more from it in the long term, it's bound to develop and otherwise you should fork it if you need custom actions (maybe a fork with added events would be enough).

blog comments powered by Disqus