1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 3 using System.Collections.Generic; 4 using System.ComponentModel; 5 using System.Diagnostics.CodeAnalysis; 6 using System.Globalization; 7 using System.IO; 8 using System.Linq; 9 using Microsoft.Internal.Web.Utils; 10 using NuGet; 11 using NuGet.Runtime; 12 13 namespace System.Web.WebPages.Administration.PackageManager 14 { 15 [EditorBrowsable(EditorBrowsableState.Never)] 16 public class WebProjectManager : IWebProjectManager 17 { 18 private const string WebPagesPreferredTag = " aspnetwebpages "; 19 private readonly IProjectManager _projectManager; 20 private readonly string _siteRoot; 21 WebProjectManager(string remoteSource, string siteRoot)22 public WebProjectManager(string remoteSource, string siteRoot) 23 { 24 if (String.IsNullOrEmpty(remoteSource)) 25 { 26 throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "remoteSource"); 27 } 28 if (String.IsNullOrEmpty(siteRoot)) 29 { 30 throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "siteRoot"); 31 } 32 33 _siteRoot = siteRoot; 34 string webRepositoryDirectory = GetWebRepositoryDirectory(siteRoot); 35 _projectManager = new ProjectManager(sourceRepository: PackageRepositoryFactory.Default.CreateRepository(remoteSource), 36 pathResolver: new DefaultPackagePathResolver(webRepositoryDirectory), 37 localRepository: PackageRepositoryFactory.Default.CreateRepository(webRepositoryDirectory), 38 project: new WebProjectSystem(siteRoot)); 39 } 40 WebProjectManager(IProjectManager projectManager, string siteRoot)41 internal WebProjectManager(IProjectManager projectManager, string siteRoot) 42 { 43 if (String.IsNullOrEmpty(siteRoot)) 44 { 45 throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "siteRoot"); 46 } 47 48 if (projectManager == null) 49 { 50 throw new ArgumentNullException("projectManager"); 51 } 52 53 _siteRoot = siteRoot; 54 _projectManager = projectManager; 55 } 56 57 public IPackageRepository LocalRepository 58 { 59 get { return _projectManager.LocalRepository; } 60 } 61 62 public IPackageRepository SourceRepository 63 { 64 get { return _projectManager.SourceRepository; } 65 } 66 67 internal bool DoNotAddBindingRedirects { get; set; } 68 69 [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", 70 Justification = "We want to ensure we get server-side counts for the IQueryable which can only be performed before we collapse versions.")] GetRemotePackages(string searchTerms, bool filterPreferred)71 public virtual IQueryable<IPackage> GetRemotePackages(string searchTerms, bool filterPreferred) 72 { 73 var packages = GetPackages(SourceRepository, searchTerms); 74 if (filterPreferred) 75 { 76 packages = packages.Where(p => p.Tags.ToLower().Contains(WebPagesPreferredTag)); 77 } 78 79 // Order by download count and Id to allow collapsing 80 return packages.OrderByDescending(p => p.DownloadCount) 81 .ThenBy(p => p.Id); 82 } 83 GetInstalledPackages(string searchTerms)84 public IQueryable<IPackage> GetInstalledPackages(string searchTerms) 85 { 86 return GetPackages(LocalRepository, searchTerms); 87 } 88 GetPackagesWithUpdates(string searchTerms, bool filterPreferredPackages)89 public IEnumerable<IPackage> GetPackagesWithUpdates(string searchTerms, bool filterPreferredPackages) 90 { 91 var packagesToUpdate = GetPackages(LocalRepository, searchTerms); 92 if (filterPreferredPackages) 93 { 94 packagesToUpdate = packagesToUpdate.Where(p => p.Tags.ToLower().Contains(WebPagesPreferredTag)); 95 } 96 return SourceRepository.GetUpdates(packagesToUpdate, includePrerelease: false).AsQueryable(); 97 } 98 InstallPackage(IPackage package)99 internal IEnumerable<string> InstallPackage(IPackage package) 100 { 101 return InstallPackage(package, AppDomain.CurrentDomain); 102 } 103 104 /// <summary> 105 /// Installs and adds a package reference to the project 106 /// </summary> 107 /// <returns>Warnings encountered when installing the package.</returns> InstallPackage(IPackage package, AppDomain appDomain)108 public IEnumerable<string> InstallPackage(IPackage package, AppDomain appDomain) 109 { 110 IEnumerable<string> result = PerformLoggedAction(() => 111 { 112 _projectManager.AddPackageReference(package.Id, package.Version, ignoreDependencies: false, allowPrereleaseVersions: false); 113 AddBindingRedirects(appDomain); 114 }); 115 return result; 116 } 117 UpdatePackage(IPackage package)118 internal IEnumerable<string> UpdatePackage(IPackage package) 119 { 120 return UpdatePackage(package, AppDomain.CurrentDomain); 121 } 122 123 /// <summary> 124 /// Updates a package reference. Installs the package to the App_Data repository if it does not already exist. 125 /// </summary> 126 /// <returns>Warnings encountered when updating the package.</returns> UpdatePackage(IPackage package, AppDomain appDomain)127 public IEnumerable<string> UpdatePackage(IPackage package, AppDomain appDomain) 128 { 129 return PerformLoggedAction(() => 130 { 131 _projectManager.UpdatePackageReference(package.Id, package.Version, updateDependencies: true, allowPrereleaseVersions: false); 132 AddBindingRedirects(appDomain); 133 }); 134 } 135 136 /// <summary> 137 /// Removes a package reference and uninstalls the package 138 /// </summary> 139 /// <returns>Warnings encountered when uninstalling the package.</returns> UninstallPackage(IPackage package, bool removeDependencies)140 public IEnumerable<string> UninstallPackage(IPackage package, bool removeDependencies) 141 { 142 return PerformLoggedAction(() => 143 { 144 _projectManager.RemovePackageReference(package.Id, forceRemove: false, removeDependencies: removeDependencies); 145 }); 146 } 147 148 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "It seems more appropriate to deal with IPackages")] IsPackageInstalled(IPackage package)149 public bool IsPackageInstalled(IPackage package) 150 { 151 return LocalRepository.Exists(package); 152 } 153 GetUpdate(IPackage package)154 public IPackage GetUpdate(IPackage package) 155 { 156 return SourceRepository.GetUpdates(new[] { package }, includePrerelease: false).SingleOrDefault(); 157 } 158 AddBindingRedirects(AppDomain appDomain)159 private void AddBindingRedirects(AppDomain appDomain) 160 { 161 if (DoNotAddBindingRedirects) 162 { 163 return; 164 } 165 // We can't use HttpRuntime.BinDirectory since there is no runtime when installing via WebMatrix. 166 var binDirectory = Path.Combine(_siteRoot, "bin"); 167 var assemblies = RemoteAssembly.GetAssembliesForBindingRedirect(appDomain, binDirectory); 168 var bindingRedirects = BindingRedirectResolver.GetBindingRedirects(assemblies); 169 170 if (bindingRedirects.Any()) 171 { 172 // NuGet ends up reading our web.config file regardless of if any bindingRedirects are needed. 173 var bindingRedirectManager = new BindingRedirectManager(_projectManager.Project, "web.config"); 174 bindingRedirectManager.AddBindingRedirects(bindingRedirects); 175 } 176 } 177 PerformLoggedAction(Action action)178 private IEnumerable<string> PerformLoggedAction(Action action) 179 { 180 ErrorLogger logger = new ErrorLogger(); 181 _projectManager.Logger = logger; 182 try 183 { 184 action(); 185 } 186 finally 187 { 188 _projectManager.Logger = null; 189 } 190 return logger.Errors; 191 } 192 193 /// <remarks> 194 /// Ensure that some form of sorting is applied to the IQueryable before this method is invoked. 195 /// </remarks> 196 /// <returns>A sequence with the most recent version for each package.</returns> CollapseVersions(IQueryable<IPackage> packages)197 public static IEnumerable<IPackage> CollapseVersions(IQueryable<IPackage> packages) 198 { 199 const int BufferSize = 30; 200 return packages.Where(package => package.IsLatestVersion) 201 .AsBufferedEnumerable(BufferSize) 202 .DistinctLast(PackageEqualityComparer.Id, PackageComparer.Version); 203 } 204 GetPackagesRequiringLicenseAcceptance(IPackage package)205 internal IEnumerable<IPackage> GetPackagesRequiringLicenseAcceptance(IPackage package) 206 { 207 return GetPackagesRequiringLicenseAcceptance(package, localRepository: LocalRepository, sourceRepository: SourceRepository); 208 } 209 GetPackagesRequiringLicenseAcceptance(IPackage package, IPackageRepository localRepository, IPackageRepository sourceRepository)210 internal static IEnumerable<IPackage> GetPackagesRequiringLicenseAcceptance(IPackage package, IPackageRepository localRepository, IPackageRepository sourceRepository) 211 { 212 var dependencies = GetPackageDependencies(package, localRepository, sourceRepository); 213 214 return from p in dependencies 215 where p.RequireLicenseAcceptance 216 select p; 217 } 218 GetPackageDependencies(IPackage package, IPackageRepository localRepository, IPackageRepository sourceRepository)219 private static IEnumerable<IPackage> GetPackageDependencies(IPackage package, IPackageRepository localRepository, IPackageRepository sourceRepository) 220 { 221 InstallWalker walker = new InstallWalker(localRepository: localRepository, sourceRepository: sourceRepository, logger: NullLogger.Instance, 222 ignoreDependencies: false, allowPrereleaseVersions: false); 223 IEnumerable<PackageOperation> operations = walker.ResolveOperations(package); 224 225 return from operation in operations 226 where operation.Action == PackageAction.Install 227 select operation.Package; 228 } 229 GetPackages(IPackageRepository repository, string searchTerm)230 internal static IQueryable<IPackage> GetPackages(IPackageRepository repository, string searchTerm) 231 { 232 return GetPackages(repository.GetPackages(), searchTerm); 233 } 234 GetPackages(IQueryable<IPackage> packages, string searchTerm)235 internal static IQueryable<IPackage> GetPackages(IQueryable<IPackage> packages, string searchTerm) 236 { 237 if (!String.IsNullOrEmpty(searchTerm)) 238 { 239 searchTerm = searchTerm.Trim(); 240 packages = packages.Find(searchTerm); 241 } 242 return packages; 243 } 244 GetWebRepositoryDirectory(string siteRoot)245 internal static string GetWebRepositoryDirectory(string siteRoot) 246 { 247 return Path.Combine(siteRoot, "App_Data", "packages"); 248 } 249 250 private class ErrorLogger : ILogger 251 { 252 private readonly IList<string> _errors = new List<string>(); 253 254 public IEnumerable<string> Errors 255 { 256 get { return _errors; } 257 } 258 Log(MessageLevel level, string message, params object[] args)259 public void Log(MessageLevel level, string message, params object[] args) 260 { 261 if (level == MessageLevel.Warning) 262 { 263 _errors.Add(String.Format(CultureInfo.CurrentCulture, message, args)); 264 } 265 } 266 } 267 } 268 } 269