Installing a package programmatically

Topics: Ecosystem
Dec 31, 2013 at 5:39 PM
I'm building a NuGet installer for my online IDE project, and I'm trying to figure how to use the existing NuGet code. So far, I have come with two versions:
  1. Using the PackageManager class found in the console program. All files are downloaded and expanded into the packages folder, but the project file and folder are not modified at all (this is actually by design).
  2. Using the ProjectManager class found in the NuGet.Core library. The package is downloaded and expanded, the references are added to the project file, but the corresponding libraries are not copied from the temp folder to the package folder. As for the content files, it happens just the opposite way: they're copied to the project folder, but not added as project items to the project itself.
Anybody tried something similar?

Thanks a lot for any help!
Feb 11, 2014 at 1:27 PM
Edited Feb 11, 2014 at 1:34 PM
Hi, I've just been trying to do the same thing. First I tried using the ProjectManager class and came up with:
var packageSource = ...;
var packagesPath = Server.MapPath("~/App_Data/Packages");

var sourceRepository = PackageRepositoryFactory.Default.CreateRepository(packageSource);
var localRepository = PackageRepositoryFactory.Default.CreateRepository(packagesPath);
var projectManager = new ProjectManager(sourceRepository, new DefaultPackagePathResolver(packagesPath), new WebProjectSystem(Server.MapPath("~/")), localRepository);

var package = projectManager.SourceRepository.GetPackages().Single(p => p.Id == "TempPackage");

projectManager.AddPackageReference(package, false, false);
This moved the content correctly in to the root of the site but it didn't add the dll's into the bin directory. I eventually gave up with the ProjectManager and resorted to using the PackageManager class:
var packageSource = ...;
var packagesPath = Server.MapPath("~/App_Data/Packages");

var packageManager = new PackageManager(sourceRepository, packagesPath);

var package = packageManager.SourceRepository.GetPackages().Single(p => p.Id == "TempPackage");

packageManager.InstallPackage(package, false, false);
This does exactly as you have said, downloads and extracts the content of the package to a directory. I used the following code to move the content and dll's to the correct directory (I originally tried to use the packageManager.PackageInstalled event but it never executed):
foreach (var packagePath in Directory.GetDirectories(packagesPath)) {
    var contentPath = Path.Combine(packagePath, "content");

    if (Directory.Exists(contentPath)) {
        Move(contentPath, Server.MapPath("~/"));
        Directory.Delete(contentPath, true);
    }

    var libPath = Path.Combine(packagePath, "lib");

    if (Directory.Exists(libPath)) {
        Move(Path.Combine(libPath, "net45"), Server.MapPath("~/bin"));
        Move(libPath, Server.MapPath("~/bin"));
        Directory.Delete(libPath, true);
    }
}
Here's the Move method:
private void Move(string oldPath, string newPath) {
    var files = Directory.GetFiles(oldPath, "*.*", SearchOption.AllDirectories).ToList();

    foreach (var file in files) {
        var newFile = Path.Combine(newPath, file.Replace(oldPath, "").Substring(1));

        // Delete the file if it already exists
        if (System.IO.File.Exists(newFile))
            System.IO.File.Delete(newFile);

        // Create the directory if it doesn't exist
        if (!Directory.Exists(Path.GetDirectoryName(newFile)))
            Directory.CreateDirectory(Path.GetDirectoryName(newFile));

        System.IO.File.Move(file, newFile);
    }
}
However while this works for most packages it seems like a hack. Also it won't handle web.config modifications etc. Ideally I'd like to get the ProjectManager way to work but I have so far been unsuccessful.

I'd appreciate any help. Thanks
Feb 11, 2014 at 1:30 PM
Edited Feb 11, 2014 at 2:05 PM
Here's my WebProjectSystem class incase you need it. Please note that if I try adding a break point in the AddReference method it doesn't get hit.
public class WebProjectSystem : PhysicalFileSystem, IProjectSystem, IFileSystem, IPropertyProvider {
    private const string BinDir = "bin";
    private const string AppCodeFolder = "App_Code";
    private static readonly string[] _generatedFilesFolder = new string[]
    {
        "Generated___Files"
    };
    private static readonly string[] _sourceFileExtensions = new string[]
    {
        ".cs",
        ".vb"
    };
    private static readonly string[] _knownPublicKeys = new string[]
    {
        "b03f5f7f11d50a3a",
        "b77a5c561934e089",
        "31bf3856ad364e35"
    };
    /// <summary>This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.</summary>
    public string ProjectName {
        get {
            return base.Root;
        }
    }
    /// <summary>This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.</summary>
    public FrameworkName TargetFramework {
        get {
            return VersionUtility.DefaultTargetFramework;
        }
    }
    /// <summary>This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.</summary>
    public WebProjectSystem(string root)
        : base(root) {
    }
    /// <summary>This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.</summary>
    public void AddReference(string referencePath, Stream stream) {
        string fileName = Path.GetFileName(referencePath);
        string fullPath = this.GetFullPath(this.GetReferencePath(fileName));
        this.AddFile(fullPath, stream);
    }
    /// <summary>This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.</summary>
    //[return: Dynamic]
    public dynamic GetPropertyValue(string propertyName) {
        if (propertyName == null) {
            return null;
        }
        if (propertyName.Equals("RootNamespace", StringComparison.OrdinalIgnoreCase)) {
            return string.Empty;
        }
        return null;
    }
    /// <summary>This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.</summary>
    public bool IsSupportedFile(string path) {
        return !Path.GetFileName(path).Equals("app.config", StringComparison.OrdinalIgnoreCase);
    }
    /// <summary>This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.</summary>
    public bool ReferenceExists(string name) {
        string referencePath = this.GetReferencePath(name);
        return this.FileExists(referencePath);
    }
    /// <summary>This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.</summary>
    public void RemoveReference(string name) {
        this.DeleteFile(this.GetReferencePath(name));
        if (!this.GetFiles("bin", false).Any<string>()) {
            this.DeleteDirectory("bin");
        }
    }
    /// <summary>This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.</summary>
    public void AddFrameworkReference(string name) {
        string text = WebProjectSystem.ResolvePartialAssemblyName(name);
        if (text == null) {
            //throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, PackageManagerResources.UnknownFrameworkReference, new object[]
            //{
            //    name
            //})); 
            throw new InvalidOperationException("Unknown framework reference");
        }
        WebProjectSystem.AddReferencesToConfig(this, text);
    }
    /// <summary>This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.</summary>
    public override IEnumerable<string> GetDirectories(string path) {
        if (WebProjectSystem.IsUnderAppCode(path)) {
            return base.GetDirectories(path).Except(WebProjectSystem._generatedFilesFolder, StringComparer.OrdinalIgnoreCase);
        }
        return base.GetDirectories(path);
    }
    /// <summary>This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.</summary>
    public string ResolvePath(string path) {
        if (WebProjectSystem.RequiresAppCodeRemapping(path)) {
            path = Path.Combine("App_Code", path);
        }
        return path;
    }
    /// <summary>This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.</summary>
    protected virtual string GetReferencePath(string name) {
        return Path.Combine("bin", name);
    }
    internal static string ResolvePartialAssemblyName(string name) {
        string[] knownPublicKeys = WebProjectSystem._knownPublicKeys;
        for (int i = 0; i < knownPublicKeys.Length; i++) {
            string text = knownPublicKeys[i];
            string text2 = string.Format(CultureInfo.InvariantCulture, "{0}, Version={1}, Culture=neutral, PublicKeyToken={2}", new object[]
            {
                name,
                VersionUtility.DefaultTargetFrameworkVersion,
                text
            });
            try {
                Assembly.Load(text2);
                return text2;
            } catch {
            }
        }
        return null;
    }
    internal static void AddReferencesToConfig(IFileSystem fileSystem, string references) {
        string text = Path.Combine(fileSystem.Root, "web.config");
        XDocument xDocument;
        if (fileSystem.FileExists(text)) {
            using (Stream stream = fileSystem.OpenFile(text)) {
                xDocument = XDocument.Load(stream, LoadOptions.PreserveWhitespace);
                goto IL_68;
            }
        }
        xDocument = new XDocument(new object[]
        {
            new XElement("configuration")
        });
        IL_68:
        XElement orCreateChild = WebProjectSystem.GetOrCreateChild(xDocument.Root, "system.web/compilation/assemblies");
        if (!(
            from item in orCreateChild.Elements()
            where !string.IsNullOrEmpty(item.GetOptionalAttributeValue("assembly", null))
            let assemblyName = new AssemblyName(item.Attribute("assembly").Value).Name
            where string.Equals(assemblyName, references, StringComparison.OrdinalIgnoreCase)
            select item).Any<XElement>()) {
            orCreateChild.Add(new XElement("add", new XAttribute("assembly", references)));
            WebProjectSystem.SaveDocument(fileSystem, text, xDocument);
        }
    }
    private static void SaveDocument(IFileSystem fileSystem, string webConfigPath, XDocument document) {
        using (MemoryStream memoryStream = new MemoryStream()) {
            document.Save(memoryStream);
            memoryStream.Seek(0L, SeekOrigin.Begin);
            fileSystem.AddFile(webConfigPath, memoryStream);
        }
    }
    private static XElement GetOrCreateChild(XElement element, string childName) {
        string[] array = childName.Split(new char[]
        {
            '/'
        });
        for (int i = 0; i < array.Length; i++) {
            string expandedName = array[i];
            XElement xElement = element.Element(expandedName);
            if (xElement == null) {
                xElement = new XElement(expandedName);
                element.Add(xElement);
            }
            element = xElement;
        }
        return element;
    }
    private static bool RequiresAppCodeRemapping(string path) {
        return !WebProjectSystem.IsUnderAppCode(path) && WebProjectSystem.IsSourceFile(path);
    }
    private static bool IsUnderAppCode(string path) {
        return path.StartsWith("App_Code" + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase);
    }
    private static bool IsSourceFile(string path) {
        return WebProjectSystem._sourceFileExtensions.Contains(Path.GetExtension(path), StringComparer.OrdinalIgnoreCase);
    }


    public void AddImport(string targetPath, ProjectImportLocation location) {
        throw new NotImplementedException();
    }

    public bool FileExistsInProject(string path) {
        //Log.DebugFormat("FileExistsInProject()... : {0}", path);
        return base.FileExists(path);
    }

    public bool IsBindingRedirectSupported {
        get { return false; }
    }

    public void RemoveImport(string targetPath) {
        throw new NotImplementedException();
    }
}
Feb 12, 2014 at 9:01 AM
I managed to get the ProjectManager working although it does have a small bug. See https://nuget.codeplex.com/discussions/253750 for more information.
Feb 13, 2014 at 7:54 AM
I ended with a custom class for a ProjectSystem that adds items to the project:
    public class BetterThanMSBuildProjectSystem : MSBuildProjectSystem {

        public override void AddFile(string path, System.IO.Stream stream) {
            base.AddFile(path, stream);
            var rootElement = ProjectRootElement.Open(ProjectPath);
            rootElement.AddItem("Content", path);
        }

        public string ProjectPath { get; private set; }

        public BetterThanMSBuildProjectSystem(string projectFile)
            : base(projectFile) {
            ProjectPath = projectFile;
        }
    }
Here's how I use it:
var projectSystem = new BetterThanMSBuildProjectSystem(projectPath) { Logger = _console };
var projectManager = new ProjectManager(_packageRepository, packagePathResolver, projectSystem, localRepository) {Logger = _console};
For adding assembly files, I used an event handler:
projectManager.PackageReferenceAdded += (sender, args) => args.Package.GetLibFiles()
                                                                          .Each(file => SaveAssemblyFile(args.InstallPath, file));
private void SaveAssemblyFile(string installPath, IPackageFile file) {
        var targetPath = installPath.AppendPath(file.Path);
        Directory.CreateDirectory(targetPath.ParentDirectory());
        using (Stream outputStream = File.Create(targetPath)){
            file.GetStream().CopyTo(outputStream);
        }

    }
Feb 13, 2014 at 8:28 AM
Thanks this is a lot cleaner than the hacky solution I came up with. I've posted an issue which causes the package to be empty. See https://nuget.codeplex.com/workitem/4029.
Nov 11, 2014 at 9:43 AM
Final solution and Full source code sample about it ?

NuGet installer for my online IDE Project : Addin VS or VSIX package for VS 2012 ?
Nov 11, 2014 at 4:02 PM
Blogged about it: http://ivonna.biz/blog/2014/9/21/installing-a-nuget-package-programmatically.aspx

Addin won't do here -- is an online project, remember?