// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
using System.Xml.Linq;
using NuGet;
namespace System.Web.WebPages.Administration.PackageManager
{
public class WebProjectSystem : PhysicalFileSystem, IProjectSystem
{
private const string BinDir = "bin";
private const string AppCodeFolder = "App_Code";
private static readonly string[] _generatedFilesFolder = new[] { "Generated___Files" };
private static readonly string[] _sourceFileExtensions = new[] { ".cs", ".vb" };
///
/// Keys taken from the 4.0 RedistList.
///
private static readonly string[] _knownPublicKeys = new[] { "b03f5f7f11d50a3a", "b77a5c561934e089", "31bf3856ad364e35" };
public WebProjectSystem(string root)
: base(root)
{
}
public string ProjectName
{
get { return Root; }
}
public FrameworkName TargetFramework
{
get { return VersionUtility.DefaultTargetFramework; }
}
public void AddReference(string referencePath, Stream stream)
{
// Copy to bin by default
string referenceName = Path.GetFileName(referencePath);
string dest = GetFullPath(GetReferencePath(referenceName));
// Copy the reference over
AddFile(dest, stream);
}
public dynamic GetPropertyValue(string propertyName)
{
if (propertyName == null)
{
return null;
}
// Return empty string for the root namespace of this project.
if (propertyName.Equals("RootNamespace", StringComparison.OrdinalIgnoreCase))
{
return String.Empty;
}
return null;
}
public bool IsSupportedFile(string path)
{
return !Path.GetFileName(path).Equals("app.config", StringComparison.OrdinalIgnoreCase);
}
public bool ReferenceExists(string name)
{
string path = GetReferencePath(name);
return FileExists(path);
}
public void RemoveReference(string name)
{
DeleteFile(GetReferencePath(name));
// Delete the bin directory if this was the last reference
if (!GetFiles(BinDir).Any())
{
DeleteDirectory(BinDir);
}
}
public void AddFrameworkReference(string name)
{
// Before we add a framework assembly to web.config, verify that it exists in the GAC. This is important because a website would be completely unusable if the assembly reference
// does not exist and is added to web.config. Since the assembly name may be a partial name, We use the ResolveAssemblyReference task in Msbuild to identify a full name and if it is
// installed in the GAC.
var fullName = ResolvePartialAssemblyName(name);
if (fullName == null)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, PackageManagerResources.UnknownFrameworkReference, name));
}
AddReferencesToConfig(this, fullName);
}
public override IEnumerable GetDirectories(string path)
{
if (IsUnderAppCode(path))
{
// There is an invisible folder called Generated___Files under app code that we want to exclude from our search
return base.GetDirectories(path).Except(_generatedFilesFolder, StringComparer.OrdinalIgnoreCase);
}
return base.GetDirectories(path);
}
public string ResolvePath(string path)
{
if (RequiresAppCodeRemapping(path))
{
path = Path.Combine(AppCodeFolder, path);
}
return path;
}
protected virtual string GetReferencePath(string name)
{
return Path.Combine(BinDir, name);
}
///
/// Uses ResolveAssemblyReference to calculate a full name from a partial assembly name.
///
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "We never want to throw from this method. If Assembly.Load fails, the only message we want to display is that package could not be installed.")]
internal static string ResolvePartialAssemblyName(string name)
{
foreach (var key in _knownPublicKeys)
{
var assemblyFullName = String.Format(CultureInfo.InvariantCulture, "{0}, Version={1}, Culture=neutral, PublicKeyToken={2}",
name, VersionUtility.DefaultTargetFrameworkVersion, key);
try
{
Assembly.Load(assemblyFullName);
// Assembly.Load throws a FileNotFoundException if the assembly name cannot be resolved. If we managed to successfully locate the assembly, return it.
return assemblyFullName;
}
catch
{
// Do nothing. We don't want to throw from this method.
}
}
return null;
}
internal static void AddReferencesToConfig(IFileSystem fileSystem, string references)
{
var webConfigPath = Path.Combine(fileSystem.Root, "web.config");
XDocument document;
// Read the web.config file from the AppRoot if it exists.
if (fileSystem.FileExists(webConfigPath))
{
using (Stream stream = fileSystem.OpenFile(webConfigPath))
{
document = XDocument.Load(stream, LoadOptions.PreserveWhitespace);
}
}
else
{
document = new XDocument(new XElement("configuration"));
}
var assemblies = GetOrCreateChild(document.Root, "system.web/compilation/assemblies");
// Get the name of the existing references
// References are stored in the format
bool existingAssembly = (from item in assemblies.Elements()
where !String.IsNullOrEmpty(item.GetOptionalAttributeValue("assembly"))
let assemblyName = new AssemblyName(item.Attribute("assembly").Value).Name
where String.Equals(assemblyName, references, StringComparison.OrdinalIgnoreCase)
select item).Any();
if (!existingAssembly)
{
assemblies.Add(new XElement("add", new XAttribute("assembly", references)));
SaveDocument(fileSystem, webConfigPath, document);
}
}
private static void SaveDocument(IFileSystem fileSystem, string webConfigPath, XDocument document)
{
using (MemoryStream stream = new MemoryStream())
{
document.Save(stream);
stream.Seek(0, SeekOrigin.Begin);
fileSystem.AddFile(webConfigPath, stream);
}
}
private static XElement GetOrCreateChild(XElement element, string childName)
{
foreach (var item in childName.Split('/'))
{
XElement child = element.Element(item);
if (child == null)
{
child = new XElement(item);
element.Add(child);
}
element = child;
}
return element;
}
private static bool RequiresAppCodeRemapping(string path)
{
return !IsUnderAppCode(path) && IsSourceFile(path);
}
private static bool IsUnderAppCode(string path)
{
return path.StartsWith(AppCodeFolder + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase);
}
private static bool IsSourceFile(string path)
{
return _sourceFileExtensions.Contains(Path.GetExtension(path), StringComparer.OrdinalIgnoreCase);
}
}
}