Reorganize source code in preparation to move into aspnet/Extensions

Prior to reorganization, this source code was found in https://github.com/aspnet/FileSystem/tree/dotnet/extensions@baebb8b0c672ab37bac72d7196da1b919d362cc5
\n\nCommit migrated from c087cadf1d
This commit is contained in:
Nate McMaster 2018-11-02 03:25:45 -07:00
commit 574a034ddd
46 changed files with 3572 additions and 0 deletions

View File

@ -0,0 +1,8 @@
<Project>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
<PropertyGroup>
<GenerateDocumentationFile Condition=" '$(IsTestProject)' != 'true' ">true</GenerateDocumentationFile>
<PackageTags>files;filesystem</PackageTags>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,181 @@
// 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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.Extensions.FileProviders.Embedded;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Extensions.FileProviders
{
/// <summary>
/// Looks up files using embedded resources in the specified assembly.
/// This file provider is case sensitive.
/// </summary>
public class EmbeddedFileProvider : IFileProvider
{
private static readonly char[] _invalidFileNameChars = Path.GetInvalidFileNameChars()
.Where(c => c != '/' && c != '\\').ToArray();
private readonly Assembly _assembly;
private readonly string _baseNamespace;
private readonly DateTimeOffset _lastModified;
/// <summary>
/// Initializes a new instance of the <see cref="EmbeddedFileProvider" /> class using the specified
/// assembly with the base namespace defaulting to the assembly name.
/// </summary>
/// <param name="assembly">The assembly that contains the embedded resources.</param>
public EmbeddedFileProvider(Assembly assembly)
: this(assembly, assembly?.GetName()?.Name)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EmbeddedFileProvider" /> class using the specified
/// assembly and base namespace.
/// </summary>
/// <param name="assembly">The assembly that contains the embedded resources.</param>
/// <param name="baseNamespace">The base namespace that contains the embedded resources.</param>
public EmbeddedFileProvider(Assembly assembly, string baseNamespace)
{
if (assembly == null)
{
throw new ArgumentNullException("assembly");
}
_baseNamespace = string.IsNullOrEmpty(baseNamespace) ? string.Empty : baseNamespace + ".";
_assembly = assembly;
_lastModified = DateTimeOffset.UtcNow;
if (!string.IsNullOrEmpty(_assembly.Location))
{
try
{
_lastModified = File.GetLastWriteTimeUtc(_assembly.Location);
}
catch (PathTooLongException)
{
}
catch (UnauthorizedAccessException)
{
}
}
}
/// <summary>
/// Locates a file at the given path.
/// </summary>
/// <param name="subpath">The path that identifies the file. </param>
/// <returns>
/// The file information. Caller must check Exists property. A <see cref="NotFoundFileInfo" /> if the file could
/// not be found.
/// </returns>
public IFileInfo GetFileInfo(string subpath)
{
if (string.IsNullOrEmpty(subpath))
{
return new NotFoundFileInfo(subpath);
}
var builder = new StringBuilder(_baseNamespace.Length + subpath.Length);
builder.Append(_baseNamespace);
// Relative paths starting with a leading slash okay
if (subpath.StartsWith("/", StringComparison.Ordinal))
{
builder.Append(subpath, 1, subpath.Length - 1);
}
else
{
builder.Append(subpath);
}
for (var i = _baseNamespace.Length; i < builder.Length; i++)
{
if (builder[i] == '/' || builder[i] == '\\')
{
builder[i] = '.';
}
}
var resourcePath = builder.ToString();
if (HasInvalidPathChars(resourcePath))
{
return new NotFoundFileInfo(resourcePath);
}
var name = Path.GetFileName(subpath);
if (_assembly.GetManifestResourceInfo(resourcePath) == null)
{
return new NotFoundFileInfo(name);
}
return new EmbeddedResourceFileInfo(_assembly, resourcePath, name, _lastModified);
}
/// <summary>
/// Enumerate a directory at the given path, if any.
/// This file provider uses a flat directory structure. Everything under the base namespace is considered to be one
/// directory.
/// </summary>
/// <param name="subpath">The path that identifies the directory</param>
/// <returns>
/// Contents of the directory. Caller must check Exists property. A <see cref="NotFoundDirectoryContents" /> if no
/// resources were found that match <paramref name="subpath" />
/// </returns>
public IDirectoryContents GetDirectoryContents(string subpath)
{
// The file name is assumed to be the remainder of the resource name.
if (subpath == null)
{
return NotFoundDirectoryContents.Singleton;
}
// EmbeddedFileProvider only supports a flat file structure at the base namespace.
if (subpath.Length != 0 && !string.Equals(subpath, "/", StringComparison.Ordinal))
{
return NotFoundDirectoryContents.Singleton;
}
var entries = new List<IFileInfo>();
// TODO: The list of resources in an assembly isn't going to change. Consider caching.
var resources = _assembly.GetManifestResourceNames();
for (var i = 0; i < resources.Length; i++)
{
var resourceName = resources[i];
if (resourceName.StartsWith(_baseNamespace, StringComparison.Ordinal))
{
entries.Add(new EmbeddedResourceFileInfo(
_assembly,
resourceName,
resourceName.Substring(_baseNamespace.Length),
_lastModified));
}
}
return new EnumerableDirectoryContents(entries);
}
/// <summary>
/// Embedded files do not change.
/// </summary>
/// <param name="pattern">This parameter is ignored</param>
/// <returns>A <see cref="NullChangeToken" /></returns>
public IChangeToken Watch(string pattern)
{
return NullChangeToken.Singleton;
}
private static bool HasInvalidPathChars(string path)
{
return path.IndexOfAny(_invalidFileNameChars) != -1;
}
}
}

View File

@ -0,0 +1,94 @@
// 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;
using System.IO;
using System.Reflection;
namespace Microsoft.Extensions.FileProviders.Embedded
{
/// <summary>
/// Represents a file embedded in an assembly.
/// </summary>
public class EmbeddedResourceFileInfo : IFileInfo
{
private readonly Assembly _assembly;
private readonly string _resourcePath;
private long? _length;
/// <summary>
/// Initializes a new instance of <see cref="EmbeddedFileProvider"/> for an assembly using <paramref name="resourcePath"/> as the base
/// </summary>
/// <param name="assembly">The assembly that contains the embedded resource</param>
/// <param name="resourcePath">The path to the embedded resource</param>
/// <param name="name">An arbitrary name for this instance</param>
/// <param name="lastModified">The <see cref="DateTimeOffset" /> to use for <see cref="LastModified" /></param>
public EmbeddedResourceFileInfo(
Assembly assembly,
string resourcePath,
string name,
DateTimeOffset lastModified)
{
_assembly = assembly;
_resourcePath = resourcePath;
Name = name;
LastModified = lastModified;
}
/// <summary>
/// Always true.
/// </summary>
public bool Exists => true;
/// <summary>
/// The length, in bytes, of the embedded resource
/// </summary>
public long Length
{
get
{
if (!_length.HasValue)
{
using (var stream = _assembly.GetManifestResourceStream(_resourcePath))
{
_length = stream.Length;
}
}
return _length.Value;
}
}
/// <summary>
/// Always null.
/// </summary>
public string PhysicalPath => null;
/// <summary>
/// The name of embedded file
/// </summary>
public string Name { get; }
/// <summary>
/// The time, in UTC, when the <see cref="EmbeddedFileProvider"/> was created
/// </summary>
public DateTimeOffset LastModified { get; }
/// <summary>
/// Always false.
/// </summary>
public bool IsDirectory => false;
/// <inheritdoc />
public Stream CreateReadStream()
{
var stream = _assembly.GetManifestResourceStream(_resourcePath);
if (!_length.HasValue)
{
_length = stream.Length;
}
return stream;
}
}
}

View File

@ -0,0 +1,39 @@
// 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;
using System.Collections;
using System.Collections.Generic;
namespace Microsoft.Extensions.FileProviders.Embedded
{
internal class EnumerableDirectoryContents : IDirectoryContents
{
private readonly IEnumerable<IFileInfo> _entries;
public EnumerableDirectoryContents(IEnumerable<IFileInfo> entries)
{
if (entries == null)
{
throw new ArgumentNullException(nameof(entries));
}
_entries = entries;
}
public bool Exists
{
get { return true; }
}
public IEnumerator<IFileInfo> GetEnumerator()
{
return _entries.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _entries.GetEnumerator();
}
}
}

View File

@ -0,0 +1,91 @@
// 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;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
internal class EmbeddedFilesManifest
{
private static readonly char[] _invalidFileNameChars = Path.GetInvalidFileNameChars()
.Where(c => c != Path.DirectorySeparatorChar && c != Path.AltDirectorySeparatorChar).ToArray();
private static readonly char[] _separators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
private readonly ManifestDirectory _rootDirectory;
internal EmbeddedFilesManifest(ManifestDirectory rootDirectory)
{
if (rootDirectory == null)
{
throw new ArgumentNullException(nameof(rootDirectory));
}
_rootDirectory = rootDirectory;
}
internal ManifestEntry ResolveEntry(string path)
{
if (string.IsNullOrEmpty(path) || HasInvalidPathChars(path))
{
return null;
}
// trimmed is a string without leading nor trailing path separators
// so if we find an empty string while iterating over the segments
// we know for sure the path is invalid and we treat it as the above
// case by returning null.
// Examples of invalid paths are: //wwwroot /\wwwroot //wwwroot//jquery.js
var trimmed = RemoveLeadingAndTrailingDirectorySeparators(path);
// Paths consisting only of a single path separator like / or \ are ok.
if (trimmed.Length == 0)
{
return _rootDirectory;
}
var tokenizer = new StringTokenizer(trimmed, _separators);
ManifestEntry currentEntry = _rootDirectory;
foreach (var segment in tokenizer)
{
if (segment.Equals(""))
{
return null;
}
currentEntry = currentEntry.Traverse(segment);
}
return currentEntry;
}
private static StringSegment RemoveLeadingAndTrailingDirectorySeparators(string path)
{
Debug.Assert(path.Length > 0);
var start = Array.IndexOf(_separators, path[0]) == -1 ? 0 : 1;
if (start == path.Length)
{
return StringSegment.Empty;
}
var end = Array.IndexOf(_separators, path[path.Length - 1]) == -1 ? path.Length : path.Length - 1;
var trimmed = new StringSegment(path, start, end - start);
return trimmed;
}
internal EmbeddedFilesManifest Scope(string path)
{
if (ResolveEntry(path) is ManifestDirectory directory && directory != ManifestEntry.UnknownPath)
{
return new EmbeddedFilesManifest(directory.ToRootDirectory());
}
throw new InvalidOperationException($"Invalid path: '{path}'");
}
private static bool HasInvalidPathChars(string path) => path.IndexOfAny(_invalidFileNameChars) != -1;
}
}

View File

@ -0,0 +1,127 @@
// 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;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
internal class ManifestDirectory : ManifestEntry
{
protected ManifestDirectory(string name, ManifestEntry[] children)
: base(name)
{
if (children == null)
{
throw new ArgumentNullException(nameof(children));
}
Children = children;
}
public IReadOnlyList<ManifestEntry> Children { get; protected set; }
public override ManifestEntry Traverse(StringSegment segment)
{
if (segment.Equals(".", StringComparison.Ordinal))
{
return this;
}
if (segment.Equals("..", StringComparison.Ordinal))
{
return Parent;
}
foreach (var child in Children)
{
if (segment.Equals(child.Name, StringComparison.OrdinalIgnoreCase))
{
return child;
}
}
return UnknownPath;
}
public virtual ManifestDirectory ToRootDirectory() => CreateRootDirectory(CopyChildren());
public static ManifestDirectory CreateDirectory(string name, ManifestEntry[] children)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException($"'{nameof(name)}' must not be null, empty or whitespace.", nameof(name));
}
if (children == null)
{
throw new ArgumentNullException(nameof(children));
}
var result = new ManifestDirectory(name, children);
ValidateChildrenAndSetParent(children, result);
return result;
}
public static ManifestRootDirectory CreateRootDirectory(ManifestEntry[] children)
{
if (children == null)
{
throw new ArgumentNullException(nameof(children));
}
var result = new ManifestRootDirectory(children);
ValidateChildrenAndSetParent(children, result);
return result;
}
internal static void ValidateChildrenAndSetParent(ManifestEntry[] children, ManifestDirectory parent)
{
foreach (var child in children)
{
if (child == UnknownPath)
{
throw new InvalidOperationException($"Invalid entry type '{nameof(ManifestSinkDirectory)}'");
}
if (child is ManifestRootDirectory)
{
throw new InvalidOperationException($"Can't add a root folder as a child");
}
child.SetParent(parent);
}
}
private ManifestEntry[] CopyChildren()
{
var list = new List<ManifestEntry>();
for (int i = 0; i < Children.Count; i++)
{
var child = Children[i];
switch (child)
{
case ManifestSinkDirectory s:
case ManifestRootDirectory r:
throw new InvalidOperationException("Unexpected manifest node.");
case ManifestDirectory d:
var grandChildren = d.CopyChildren();
var newDirectory = CreateDirectory(d.Name, grandChildren);
list.Add(newDirectory);
break;
case ManifestFile f:
var file = new ManifestFile(f.Name, f.ResourcePath);
list.Add(file);
break;
default:
throw new InvalidOperationException("Unexpected manifest node.");
}
}
return list.ToArray();
}
}
}

View File

@ -0,0 +1,72 @@
// 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;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
internal class ManifestDirectoryContents : IDirectoryContents
{
private readonly DateTimeOffset _lastModified;
private IFileInfo[] _entries;
public ManifestDirectoryContents(Assembly assembly, ManifestDirectory directory, DateTimeOffset lastModified)
{
if (assembly == null)
{
throw new ArgumentNullException(nameof(assembly));
}
if (directory == null)
{
throw new ArgumentNullException(nameof(directory));
}
Assembly = assembly;
Directory = directory;
_lastModified = lastModified;
}
public bool Exists => true;
public Assembly Assembly { get; }
public ManifestDirectory Directory { get; }
public IEnumerator<IFileInfo> GetEnumerator()
{
return EnsureEntries().GetEnumerator();
IReadOnlyList<IFileInfo> EnsureEntries() => _entries = _entries ?? ResolveEntries().ToArray();
IEnumerable<IFileInfo> ResolveEntries()
{
if (Directory == ManifestEntry.UnknownPath)
{
yield break;
}
foreach (var entry in Directory.Children)
{
switch (entry)
{
case ManifestFile f:
yield return new ManifestFileInfo(Assembly, f, _lastModified);
break;
case ManifestDirectory d:
yield return new ManifestDirectoryInfo(d, _lastModified);
break;
default:
throw new InvalidOperationException("Unknown entry type");
}
}
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@ -0,0 +1,39 @@
// 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;
using System.IO;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
internal class ManifestDirectoryInfo : IFileInfo
{
public ManifestDirectoryInfo(ManifestDirectory directory, DateTimeOffset lastModified)
{
if (directory == null)
{
throw new ArgumentNullException(nameof(directory));
}
Directory = directory;
LastModified = lastModified;
}
public bool Exists => true;
public long Length => -1;
public string PhysicalPath => null;
public string Name => Directory.Name;
public DateTimeOffset LastModified { get; }
public bool IsDirectory => true;
public ManifestDirectory Directory { get; }
public Stream CreateReadStream() =>
throw new InvalidOperationException("Cannot create a stream for a directory.");
}
}

View File

@ -0,0 +1,34 @@
// 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;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
internal abstract class ManifestEntry
{
public ManifestEntry(string name)
{
Name = name;
}
public ManifestEntry Parent { get; private set; }
public string Name { get; }
public static ManifestEntry UnknownPath { get; } = ManifestSinkDirectory.Instance;
protected internal virtual void SetParent(ManifestDirectory directory)
{
if (Parent != null)
{
throw new InvalidOperationException("Directory already has a parent.");
}
Parent = directory;
}
public abstract ManifestEntry Traverse(StringSegment segment);
}
}

View File

@ -0,0 +1,31 @@
// 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;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
internal class ManifestFile : ManifestEntry
{
public ManifestFile(string name, string resourcePath)
: base(name)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException($"'{nameof(name)}' must not be null, empty or whitespace.", nameof(name));
}
if (string.IsNullOrWhiteSpace(resourcePath))
{
throw new ArgumentException($"'{nameof(resourcePath)}' must not be null, empty or whitespace.", nameof(resourcePath));
}
ResourcePath = resourcePath;
}
public string ResourcePath { get; }
public override ManifestEntry Traverse(StringSegment segment) => UnknownPath;
}
}

View File

@ -0,0 +1,71 @@
// 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;
using System.IO;
using System.Reflection;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
internal class ManifestFileInfo : IFileInfo
{
private long? _length;
public ManifestFileInfo(Assembly assembly, ManifestFile file, DateTimeOffset lastModified)
{
if (assembly == null)
{
throw new ArgumentNullException(nameof(assembly));
}
if (file == null)
{
throw new ArgumentNullException(nameof(file));
}
Assembly = assembly;
ManifestFile = file;
LastModified = lastModified;
}
public Assembly Assembly { get; }
public ManifestFile ManifestFile { get; }
public bool Exists => true;
public long Length => EnsureLength();
public string PhysicalPath => null;
public string Name => ManifestFile.Name;
public DateTimeOffset LastModified { get; }
public bool IsDirectory => false;
private long EnsureLength()
{
if (_length == null)
{
using (var stream = Assembly.GetManifestResourceStream(ManifestFile.ResourcePath))
{
_length = stream.Length;
}
}
return _length.Value;
}
public Stream CreateReadStream()
{
var stream = Assembly.GetManifestResourceStream(ManifestFile.ResourcePath);
if (!_length.HasValue)
{
_length = stream.Length;
}
return stream;
}
}
}

View File

@ -0,0 +1,159 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Xml;
using System.Xml.Linq;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
internal static class ManifestParser
{
private static readonly string DefaultManifestName = "Microsoft.Extensions.FileProviders.Embedded.Manifest.xml";
public static EmbeddedFilesManifest Parse(Assembly assembly)
{
return Parse(assembly, DefaultManifestName);
}
public static EmbeddedFilesManifest Parse(Assembly assembly, string name)
{
if (assembly == null)
{
throw new ArgumentNullException(nameof(assembly));
}
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var stream = assembly.GetManifestResourceStream(name);
if (stream == null)
{
throw new InvalidOperationException($"Could not load the embedded file manifest " +
$"'{name}' for assembly '{assembly.GetName().Name}'.");
}
var document = XDocument.Load(stream);
var manifest = EnsureElement(document, "Manifest");
var manifestVersion = EnsureElement(manifest, "ManifestVersion");
var version = EnsureText(manifestVersion);
if (!string.Equals("1.0", version, StringComparison.Ordinal))
{
throw new InvalidOperationException($"The embedded file manifest '{name}' for " +
$"assembly '{assembly.GetName().Name}' specifies an unsupported file format" +
$" version: '{version}'.");
}
var fileSystem = EnsureElement(manifest, "FileSystem");
var entries = fileSystem.Elements();
var entriesList = new List<ManifestEntry>();
foreach (var element in entries)
{
var entry = BuildEntry(element);
entriesList.Add(entry);
}
ValidateEntries(entriesList);
var rootDirectory = ManifestDirectory.CreateRootDirectory(entriesList.ToArray());
return new EmbeddedFilesManifest(rootDirectory);
}
private static void ValidateEntries(List<ManifestEntry> entriesList)
{
for (int i = 0; i < entriesList.Count - 1; i++)
{
for (int j = i + 1; j < entriesList.Count; j++)
{
if (string.Equals(entriesList[i].Name, entriesList[j].Name, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
"Found two entries with the same name but different casing:" +
$" '{entriesList[i].Name}' and '{entriesList[j]}'");
}
}
}
}
private static ManifestEntry BuildEntry(XElement element)
{
RuntimeHelpers.EnsureSufficientExecutionStack();
if (element.NodeType != XmlNodeType.Element)
{
throw new InvalidOperationException($"Invalid manifest format. Expected a 'File' or a 'Directory' node:" +
$" '{element.ToString()}'");
}
if (string.Equals(element.Name.LocalName, "File", StringComparison.Ordinal))
{
var entryName = EnsureName(element);
var path = EnsureElement(element, "ResourcePath");
var pathValue = EnsureText(path);
return new ManifestFile(entryName, pathValue);
}
if (string.Equals(element.Name.LocalName, "Directory", StringComparison.Ordinal))
{
var directoryName = EnsureName(element);
var children = new List<ManifestEntry>();
foreach (var child in element.Elements())
{
children.Add(BuildEntry(child));
}
ValidateEntries(children);
return ManifestDirectory.CreateDirectory(directoryName, children.ToArray());
}
throw new InvalidOperationException($"Invalid manifest format.Expected a 'File' or a 'Directory' node. " +
$"Got '{element.Name.LocalName}' instead.");
}
private static XElement EnsureElement(XContainer container, string elementName)
{
var element = container.Element(elementName);
if (element == null)
{
throw new InvalidOperationException($"Invalid manifest format. Missing '{elementName}' element name");
}
return element;
}
private static string EnsureName(XElement element)
{
var value = element.Attribute("Name")?.Value;
if (value == null)
{
throw new InvalidOperationException($"Invalid manifest format. '{element.Name}' must contain a 'Name' attribute.");
}
return value;
}
private static string EnsureText(XElement element)
{
if (element.Elements().Count() == 0 &&
!element.IsEmpty &&
element.Nodes().Count() == 1 &&
element.FirstNode.NodeType == XmlNodeType.Text)
{
return element.Value;
}
throw new InvalidOperationException(
$"Invalid manifest format. '{element.Name.LocalName}' must contain " +
$"a text value. '{element.Value}'");
}
}
}

View File

@ -0,0 +1,16 @@
// 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.
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
internal class ManifestRootDirectory : ManifestDirectory
{
public ManifestRootDirectory(ManifestEntry[] children)
: base(name: null, children: children)
{
SetParent(ManifestSinkDirectory.Instance);
}
public override ManifestDirectory ToRootDirectory() => this;
}
}

View File

@ -0,0 +1,22 @@
// 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;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
internal class ManifestSinkDirectory : ManifestDirectory
{
private ManifestSinkDirectory()
: base(name: null, children: Array.Empty<ManifestEntry>())
{
SetParent(this);
Children = new[] { this };
}
public static ManifestDirectory Instance { get; } = new ManifestSinkDirectory();
public override ManifestEntry Traverse(StringSegment segment) => this;
}
}

View File

@ -0,0 +1,153 @@
// 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;
using System.IO;
using System.Reflection;
using Microsoft.Extensions.FileProviders.Embedded.Manifest;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Extensions.FileProviders
{
/// <summary>
/// An embedded file provider that uses a manifest compiled in the assembly to
/// reconstruct the original paths of the embedded files when they were embedded
/// into the assembly.
/// </summary>
public class ManifestEmbeddedFileProvider : IFileProvider
{
private readonly DateTimeOffset _lastModified;
/// <summary>
/// Initializes a new instance of <see cref="ManifestEmbeddedFileProvider"/>.
/// </summary>
/// <param name="assembly">The assembly containing the embedded files.</param>
public ManifestEmbeddedFileProvider(Assembly assembly)
: this(assembly, ManifestParser.Parse(assembly), ResolveLastModified(assembly)) { }
/// <summary>
/// Initializes a new instance of <see cref="ManifestEmbeddedFileProvider"/>.
/// </summary>
/// <param name="assembly">The assembly containing the embedded files.</param>
/// <param name="root">The relative path from the root of the manifest to use as root for the provider.</param>
public ManifestEmbeddedFileProvider(Assembly assembly, string root)
: this(assembly, root, ResolveLastModified(assembly))
{
}
/// <summary>
/// Initializes a new instance of <see cref="ManifestEmbeddedFileProvider"/>.
/// </summary>
/// <param name="assembly">The assembly containing the embedded files.</param>
/// <param name="root">The relative path from the root of the manifest to use as root for the provider.</param>
/// <param name="lastModified">The LastModified date to use on the <see cref="IFileInfo"/> instances
/// returned by this <see cref="IFileProvider"/>.</param>
public ManifestEmbeddedFileProvider(Assembly assembly, string root, DateTimeOffset lastModified)
: this(assembly, ManifestParser.Parse(assembly).Scope(root), lastModified)
{
}
/// <summary>
/// Initializes a new instance of <see cref="ManifestEmbeddedFileProvider"/>.
/// </summary>
/// <param name="assembly">The assembly containing the embedded files.</param>
/// <param name="root">The relative path from the root of the manifest to use as root for the provider.</param>
/// <param name="manifestName">The name of the embedded resource containing the manifest.</param>
/// <param name="lastModified">The LastModified date to use on the <see cref="IFileInfo"/> instances
/// returned by this <see cref="IFileProvider"/>.</param>
public ManifestEmbeddedFileProvider(Assembly assembly, string root, string manifestName, DateTimeOffset lastModified)
: this(assembly, ManifestParser.Parse(assembly, manifestName).Scope(root), lastModified)
{
}
internal ManifestEmbeddedFileProvider(Assembly assembly, EmbeddedFilesManifest manifest, DateTimeOffset lastModified)
{
if (assembly == null)
{
throw new ArgumentNullException(nameof(assembly));
}
if (manifest == null)
{
throw new ArgumentNullException(nameof(manifest));
}
Assembly = assembly;
Manifest = manifest;
_lastModified = lastModified;
}
/// <summary>
/// Gets the <see cref="Assembly"/> for this provider.
/// </summary>
public Assembly Assembly { get; }
internal EmbeddedFilesManifest Manifest { get; }
/// <inheritdoc />
public IDirectoryContents GetDirectoryContents(string subpath)
{
var entry = Manifest.ResolveEntry(subpath);
if (entry == null || entry == ManifestEntry.UnknownPath)
{
return NotFoundDirectoryContents.Singleton;
}
if (!(entry is ManifestDirectory directory))
{
return NotFoundDirectoryContents.Singleton;
}
return new ManifestDirectoryContents(Assembly, directory, _lastModified);
}
/// <inheritdoc />
public IFileInfo GetFileInfo(string subpath)
{
var entry = Manifest.ResolveEntry(subpath);
switch (entry)
{
case null:
return new NotFoundFileInfo(subpath);
case ManifestFile f:
return new ManifestFileInfo(Assembly, f, _lastModified);
case ManifestDirectory d when d != ManifestEntry.UnknownPath:
return new NotFoundFileInfo(d.Name);
}
return new NotFoundFileInfo(subpath);
}
/// <inheritdoc />
public IChangeToken Watch(string filter)
{
if (filter == null)
{
throw new ArgumentNullException(nameof(filter));
}
return NullChangeToken.Singleton;
}
private static DateTimeOffset ResolveLastModified(Assembly assembly)
{
var result = DateTimeOffset.UtcNow;
if (!string.IsNullOrEmpty(assembly.Location))
{
try
{
result = File.GetLastWriteTimeUtc(assembly.Location);
}
catch (PathTooLongException)
{
}
catch (UnauthorizedAccessException)
{
}
}
return result;
}
}
}

View File

@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>Microsoft.Extensions.FileProviders</RootNamespace>
<Description>File provider for files in embedded resources for Microsoft.Extensions.FileProviders.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Extensions.FileProviders.Abstractions" />
<ProjectReference Include="..\..\Manifest.MSBuildTask\src\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj" PrivateAssets="All" ReferenceOutputAssembly="false" />
</ItemGroup>
<Target Name="PopulateNuspec" BeforeTargets="GenerateNuspec" DependsOnTargets="BuiltProjectOutputGroup;DocumentationProjectOutputGroup;DebugSymbolsProjectOutputGroup;">
<PropertyGroup>
<NuspecProperties>
id=$(PackageId);
version=$(PackageVersion);
authors=$(Authors);
description=$(Description);
tags=$(PackageTags.Replace(';', ' '));
licenseUrl=$(PackageLicenseUrl);
projectUrl=$(PackageProjectUrl);
iconUrl=$(PackageIconUrl);
repositoryUrl=$(RepositoryUrl);
repositoryCommit=$(RepositoryCommit);
copyright=$(Copyright);
targetframework=$(TargetFramework);
AssemblyName=$(AssemblyName);
OutputBinary=@(BuiltProjectOutputGroupOutput);
OutputSymbol=@(DebugSymbolsProjectOutputGroupOutput);
OutputDocumentation=@(DocumentationProjectOutputGroupOutput);
<!-- Include the assembly and symbols from the tasks project -->
TaskAssemblyNetStandard=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\netstandard1.5\$(AssemblyName).Manifest.Task.dll;
TaskSymbolNetStandard=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\netstandard1.5\$(AssemblyName).Manifest.Task.pdb;
TaskAssemblyNet461=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\net461\$(AssemblyName).Manifest.Task.dll;
TaskSymbolNet461=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\net461\$(AssemblyName).Manifest.Task.pdb;
</NuspecProperties>
</PropertyGroup>
</Target>
</Project>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>$id$</id>
<version>$version$</version>
<authors>$authors$</authors>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<licenseUrl>$licenseUrl$</licenseUrl>
<projectUrl>$projectUrl$</projectUrl>
<iconUrl>$iconUrl$</iconUrl>
<description>$description$</description>
<copyright>$copyright$</copyright>
<tags>$tags$</tags>
<repository type="git" url="$repositoryUrl$" commit="$repositoryCommit$" />
<dependencies>
<group targetFramework=".NETStandard2.0">
<dependency id="Microsoft.Extensions.FileProviders.Abstractions" version="$version$" exclude="Build,Analyzers" />
</group>
</dependencies>
</metadata>
<files>
<file src="$OutputBinary$" target="lib\$targetframework$\" />
<file src="$OutputSymbol$" target="lib\$targetframework$\" />
<file src="$OutputDocumentation$" target="lib\$targetframework$\" />
<file src="build\**\*" target="build\" />
<file src="buildMultiTargeting\**\*" target="buildMultiTargeting\" />
<file src="$TaskAssemblyNetStandard$" target="tasks\netstandard1.5\$AssemblyName$.Manifest.Task.dll" />
<file src="$TaskSymbolNetStandard$" target="tasks\netstandard1.5\$AssemblyName$.Manifest.Task.pdb" />
<file src="$TaskAssemblyNet461$" target="tasks\net461\$AssemblyName$.Manifest.Task.dll" />
<file src="$TaskSymbolNet461$" target="tasks\net461\$AssemblyName$.Manifest.Task.pdb" />
</files>
</package>

View File

@ -0,0 +1,4 @@

using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Extensions.FileProviders.Embedded.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -0,0 +1,343 @@
{
"AssemblyIdentity": "Microsoft.Extensions.FileProviders.Embedded, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.Extensions.FileProviders.EmbeddedFileProvider",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.Extensions.FileProviders.IFileProvider"
],
"Members": [
{
"Kind": "Method",
"Name": "GetFileInfo",
"Parameters": [
{
"Name": "subpath",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.FileProviders.IFileInfo",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetDirectoryContents",
"Parameters": [
{
"Name": "subpath",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.FileProviders.IDirectoryContents",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Watch",
"Parameters": [
{
"Name": "pattern",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.Primitives.IChangeToken",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "assembly",
"Type": "System.Reflection.Assembly"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "assembly",
"Type": "System.Reflection.Assembly"
},
{
"Name": "baseNamespace",
"Type": "System.String"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.FileProviders.ManifestEmbeddedFileProvider",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.Extensions.FileProviders.IFileProvider"
],
"Members": [
{
"Kind": "Method",
"Name": "get_Assembly",
"Parameters": [],
"ReturnType": "System.Reflection.Assembly",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetDirectoryContents",
"Parameters": [
{
"Name": "subpath",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.FileProviders.IDirectoryContents",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetFileInfo",
"Parameters": [
{
"Name": "subpath",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.FileProviders.IFileInfo",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Watch",
"Parameters": [
{
"Name": "filter",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.Primitives.IChangeToken",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "assembly",
"Type": "System.Reflection.Assembly"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "assembly",
"Type": "System.Reflection.Assembly"
},
{
"Name": "root",
"Type": "System.String"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "assembly",
"Type": "System.Reflection.Assembly"
},
{
"Name": "root",
"Type": "System.String"
},
{
"Name": "lastModified",
"Type": "System.DateTimeOffset"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "assembly",
"Type": "System.Reflection.Assembly"
},
{
"Name": "root",
"Type": "System.String"
},
{
"Name": "manifestName",
"Type": "System.String"
},
{
"Name": "lastModified",
"Type": "System.DateTimeOffset"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.FileProviders.Embedded.EmbeddedResourceFileInfo",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.Extensions.FileProviders.IFileInfo"
],
"Members": [
{
"Kind": "Method",
"Name": "get_Exists",
"Parameters": [],
"ReturnType": "System.Boolean",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Length",
"Parameters": [],
"ReturnType": "System.Int64",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_PhysicalPath",
"Parameters": [],
"ReturnType": "System.String",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Name",
"Parameters": [],
"ReturnType": "System.String",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_LastModified",
"Parameters": [],
"ReturnType": "System.DateTimeOffset",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_IsDirectory",
"Parameters": [],
"ReturnType": "System.Boolean",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "CreateReadStream",
"Parameters": [],
"ReturnType": "System.IO.Stream",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "assembly",
"Type": "System.Reflection.Assembly"
},
{
"Name": "resourcePath",
"Type": "System.String"
},
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "lastModified",
"Type": "System.DateTimeOffset"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
}
]
}

View File

@ -0,0 +1,17 @@
<Project TreatAsLocalProperty="_FileProviderTaskFolder;_FileProviderTaskAssembly">
<PropertyGroup>
<GenerateEmbeddedFilesManifest Condition="'$(GenerateEmbeddedFilesManifest)' == ''">false</GenerateEmbeddedFilesManifest>
<EmbeddedFilesManifestFileName Condition="'$(EmbeddedFilesManifestFileName)' == ''">Microsoft.Extensions.FileProviders.Embedded.Manifest.xml</EmbeddedFilesManifestFileName>
</PropertyGroup>
<PropertyGroup>
<_FileProviderTaskFolder Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard1.5</_FileProviderTaskFolder>
<_FileProviderTaskFolder Condition="'$(MSBuildRuntimeType)' != 'Core'">net461</_FileProviderTaskFolder>
<_FileProviderTaskAssembly>$(MSBuildThisFileDirectory)..\..\tasks\$(_FileProviderTaskFolder)\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.dll</_FileProviderTaskAssembly>
</PropertyGroup>
<UsingTask
AssemblyFile="$(_FileProviderTaskAssembly)"
TaskName="Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.GenerateEmbeddedResourcesManifest" />
</Project>

View File

@ -0,0 +1,69 @@
<Project>
<PropertyGroup>
<PrepareResourceNamesDependsOn>_CalculateEmbeddedFilesManifestInputs;$(PrepareResourceNamesDependsOn)</PrepareResourceNamesDependsOn>
</PropertyGroup>
<Target
Name="_CalculateEmbeddedFilesManifestInputs"
Condition="'$(GenerateEmbeddedFilesManifest)' == 'true'">
<PropertyGroup>
<_GeneratedManifestFile>$(IntermediateOutputPath)$(EmbeddedFilesManifestFileName)</_GeneratedManifestFile>
</PropertyGroup>
<ItemGroup>
<_FilesForManifest Include="@(EmbeddedResource)" />
<_FilesForManifest Remove="@(EmbeddedResource->WithMetadataValue('ExcludeFromManifest','true'))" />
</ItemGroup>
<Warning
Text="GenerateEmbeddedFilesManifest was set, but no EmbeddedResource items were found that could be added to the manifest."
Condition="@(_FilesForManifest->Count()) == 0" />
<ItemGroup Condition="@(_FilesForManifest->Count()) != 0">
<EmbeddedResource
Include="$(_GeneratedManifestFile)"
LogicalName="$(EmbeddedFilesManifestFileName)" />
</ItemGroup>
</Target>
<Target Name="_CreateGeneratedManifestInfoInputsCacheFile" DependsOnTargets="_CalculateEmbeddedFilesManifestInputs">
<PropertyGroup>
<_GeneratedManifestInfoInputsCacheFile>$(IntermediateOutputPath)$(MSBuildProjectName).EmbeddedFilesManifest.cache</_GeneratedManifestInfoInputsCacheFile>
</PropertyGroup>
<Hash ItemsToHash="@(_FilesForManifest)">
<Output TaskParameter="HashResult" PropertyName="_EmbeddedGeneratedManifestHash" />
</Hash>
<WriteLinesToFile
Lines="$(_EmbeddedGeneratedManifestHash)"
File="$(_GeneratedManifestInfoInputsCacheFile)"
Overwrite="True"
WriteOnlyWhenDifferent="True" />
<ItemGroup>
<FileWrites Include="$(_GeneratedManifestInfoInputsCacheFile)" />
</ItemGroup>
</Target>
<Target
Name="_GenerateEmbeddedFilesManifest"
DependsOnTargets="_CreateGeneratedManifestInfoInputsCacheFile"
AfterTargets="PrepareResourceNames"
Condition="'$(GenerateEmbeddedFilesManifest)' == 'true' AND @(_FilesForManifest->Count()) != 0"
Inputs="$(_GeneratedManifestInfoInputsCacheFile)"
Outputs="$(_GeneratedManifestFile)">
<ItemGroup>
<!-- Rebuild _FilesForManifest since PrepareResourceNames would have updated EmbeddedResource. -->
<_FilesForManifest Remove="@(_FilesForManifest)" />
<_FilesForManifest Include="@(EmbeddedResource)" />
<_FilesForManifest Remove="@(EmbeddedResource->WithMetadataValue('ExcludeFromManifest','true'))" />
</ItemGroup>
<GenerateEmbeddedResourcesManifest
EmbeddedFiles="@(_FilesForManifest)"
ManifestFile="$(_GeneratedManifestFile)" />
</Target>
</Project>

View File

@ -0,0 +1,3 @@
<Project>
<Import Project="..\build\netstandard2.0\Microsoft.Extensions.FileProviders.Embedded.props" />
</Project>

View File

@ -0,0 +1,3 @@
<Project>
<Import Project="..\build\netstandard2.0\Microsoft.Extensions.FileProviders.Embedded.targets" />
</Project>

View File

@ -0,0 +1,231 @@
// 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;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.Extensions.FileProviders.Embedded.Tests
{
public class EmbeddedFileProviderTests
{
private static readonly string Namespace = typeof(EmbeddedFileProviderTests).Namespace;
[Fact]
public void ConstructorWithNullAssemblyThrowsArgumentException()
{
Assert.Throws<ArgumentNullException>(() => new EmbeddedFileProvider(null));
}
[Fact]
public void GetFileInfo_ReturnsNotFoundFileInfo_IfFileDoesNotExist()
{
// Arrange
var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly);
// Act
var fileInfo = provider.GetFileInfo("DoesNotExist.Txt");
// Assert
Assert.NotNull(fileInfo);
Assert.False(fileInfo.Exists);
}
[Theory]
[InlineData("File.txt")]
[InlineData("/File.txt")]
public void GetFileInfo_ReturnsFilesAtRoot(string filePath)
{
// Arrange
var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly);
var expectedFileLength = 8;
// Act
var fileInfo = provider.GetFileInfo(filePath);
// Assert
Assert.NotNull(fileInfo);
Assert.True(fileInfo.Exists);
Assert.NotEqual(default(DateTimeOffset), fileInfo.LastModified);
Assert.Equal(expectedFileLength, fileInfo.Length);
Assert.False(fileInfo.IsDirectory);
Assert.Null(fileInfo.PhysicalPath);
Assert.Equal("File.txt", fileInfo.Name);
}
[Fact]
public void GetFileInfo_ReturnsNotFoundFileInfo_IfFileDoesNotExistUnderSpecifiedNamespace()
{
// Arrange
var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly, Namespace + ".SubNamespace");
// Act
var fileInfo = provider.GetFileInfo("File.txt");
// Assert
Assert.NotNull(fileInfo);
Assert.False(fileInfo.Exists);
}
[Fact]
public void GetFileInfo_ReturnsNotFoundIfPathStartsWithBackSlash()
{
// Arrange
var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly);
// Act
var fileInfo = provider.GetFileInfo("\\File.txt");
// Assert
Assert.NotNull(fileInfo);
Assert.False(fileInfo.Exists);
}
public static TheoryData GetFileInfo_LocatesFilesUnderSpecifiedNamespaceData
{
get
{
var theoryData = new TheoryData<string>
{
"ResourcesInSubdirectory/File3.txt"
};
if (TestPlatformHelper.IsWindows)
{
theoryData.Add("ResourcesInSubdirectory\\File3.txt");
}
return theoryData;
}
}
[Theory]
[MemberData(nameof(GetFileInfo_LocatesFilesUnderSpecifiedNamespaceData))]
public void GetFileInfo_LocatesFilesUnderSpecifiedNamespace(string path)
{
// Arrange
var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly, Namespace + ".Resources");
// Act
var fileInfo = provider.GetFileInfo(path);
// Assert
Assert.NotNull(fileInfo);
Assert.True(fileInfo.Exists);
Assert.NotEqual(default(DateTimeOffset), fileInfo.LastModified);
Assert.True(fileInfo.Length > 0);
Assert.False(fileInfo.IsDirectory);
Assert.Null(fileInfo.PhysicalPath);
Assert.Equal("File3.txt", fileInfo.Name);
}
public static TheoryData GetFileInfo_LocatesFilesUnderSubDirectoriesData
{
get
{
var theoryData = new TheoryData<string>
{
"Resources/File.txt"
};
if (TestPlatformHelper.IsWindows)
{
theoryData.Add("Resources\\File.txt");
}
return theoryData;
}
}
[Theory]
[MemberData(nameof(GetFileInfo_LocatesFilesUnderSubDirectoriesData))]
public void GetFileInfo_LocatesFilesUnderSubDirectories(string path)
{
// Arrange
var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly);
// Act
var fileInfo = provider.GetFileInfo(path);
// Assert
Assert.NotNull(fileInfo);
Assert.True(fileInfo.Exists);
Assert.NotEqual(default(DateTimeOffset), fileInfo.LastModified);
Assert.True(fileInfo.Length > 0);
Assert.False(fileInfo.IsDirectory);
Assert.Null(fileInfo.PhysicalPath);
Assert.Equal("File.txt", fileInfo.Name);
}
[Theory]
[InlineData("")]
[InlineData("/")]
public void GetDirectoryContents_ReturnsAllFilesInFileSystem(string path)
{
// Arrange
var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly, Namespace + ".Resources");
// Act
var files = provider.GetDirectoryContents(path);
// Assert
Assert.Collection(files.OrderBy(f => f.Name, StringComparer.Ordinal),
file => Assert.Equal("File.txt", file.Name),
file => Assert.Equal("ResourcesInSubdirectory.File3.txt", file.Name));
Assert.False(provider.GetDirectoryContents("file").Exists);
Assert.False(provider.GetDirectoryContents("file/").Exists);
Assert.False(provider.GetDirectoryContents("file.txt").Exists);
Assert.False(provider.GetDirectoryContents("file/txt").Exists);
}
[Fact]
public void GetDirectoryContents_ReturnsEmptySequence_IfResourcesDoNotExistUnderNamespace()
{
// Arrange
var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly, "Unknown.Namespace");
// Act
var files = provider.GetDirectoryContents(string.Empty);
// Assert
Assert.NotNull(files);
Assert.True(files.Exists);
Assert.Empty(files);
}
[Theory]
[InlineData("Resources")]
[InlineData("/Resources")]
public void GetDirectoryContents_ReturnsNotFoundDirectoryContents_IfHierarchicalPathIsSpecified(string path)
{
// Arrange
var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly);
// Act
var files = provider.GetDirectoryContents(path);
// Assert
Assert.NotNull(files);
Assert.False(files.Exists);
Assert.Empty(files);
}
[Fact]
public void Watch_ReturnsNoOpTrigger()
{
// Arange
var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly);
// Act
var token = provider.Watch("Resources/File.txt");
// Assert
Assert.NotNull(token);
Assert.False(token.ActiveChangeCallbacks);
Assert.False(token.HasChanged);
}
}
}

View File

@ -0,0 +1 @@
Hello

View File

@ -0,0 +1,34 @@
// 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;
using System.Collections.Generic;
namespace Microsoft.Extensions.FileProviders
{
internal class FileInfoComparer : IEqualityComparer<IFileInfo>
{
public static FileInfoComparer Instance { get; set; } = new FileInfoComparer();
public bool Equals(IFileInfo x, IFileInfo y)
{
if (x == null && y == null)
{
return true;
}
if ((x == null && y != null) || (x != null && y == null))
{
return false;
}
return x.Exists == y.Exists &&
x.IsDirectory == y.IsDirectory &&
x.Length == y.Length &&
string.Equals(x.Name, y.Name, StringComparison.Ordinal) &&
string.Equals(x.PhysicalPath, y.PhysicalPath, StringComparison.Ordinal);
}
public int GetHashCode(IFileInfo obj) => 0;
}
}

View File

@ -0,0 +1,58 @@
// 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 Xunit;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
public class EmbeddedFilesManifestTests
{
[Theory]
[InlineData("/wwwroot//jquery.validate.js")]
[InlineData("//wwwroot/jquery.validate.js")]
public void ResolveEntry_IgnoresInvalidPaths(string path)
{
// Arrange
var manifest = new EmbeddedFilesManifest(
ManifestDirectory.CreateRootDirectory(
new[]
{
ManifestDirectory.CreateDirectory("wwwroot",
new[]
{
new ManifestFile("jquery.validate.js","wwwroot.jquery.validate.js")
})
}));
// Act
var entry = manifest.ResolveEntry(path);
// Assert
Assert.Null(entry);
}
[Theory]
[InlineData("/")]
[InlineData("./")]
[InlineData("/wwwroot/jquery.validate.js")]
[InlineData("/wwwroot/")]
public void ResolveEntry_AllowsSingleDirectorySeparator(string path)
{
// Arrange
var manifest = new EmbeddedFilesManifest(
ManifestDirectory.CreateRootDirectory(
new[]
{
ManifestDirectory.CreateDirectory("wwwroot",
new[]
{
new ManifestFile("jquery.validate.js","wwwroot.jquery.validate.js")
})
}));
// Act
var entry = manifest.ResolveEntry(path);
// Assert
Assert.NotNull(entry);
}
}
}

View File

@ -0,0 +1,113 @@
// 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;
using Xunit;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
public class ManifestEntryTests
{
[Fact]
public void TraversingAFile_ReturnsUnknownPath()
{
// Arrange
var file = new ManifestFile("a", "a.b.c");
// Act
var result = file.Traverse(".");
// Assert
Assert.Equal(ManifestEntry.UnknownPath, result);
}
[Fact]
public void TraversingANonExistingFile_ReturnsUnknownPath()
{
// Arrange
var directory = ManifestDirectory.CreateDirectory("a", Array.Empty<ManifestEntry>());
// Act
var result = directory.Traverse("missing.txt");
// Assert
Assert.Equal(ManifestEntry.UnknownPath, result);
}
[Fact]
public void TraversingWithDot_ReturnsSelf()
{
// Arrange
var directory = ManifestDirectory.CreateDirectory("a", Array.Empty<ManifestEntry>());
// Act
var result = directory.Traverse(".");
// Assert
Assert.Same(directory, result);
}
[Fact]
public void TraversingWithDotDot_ReturnsParent()
{
// Arrange
var childDirectory = ManifestDirectory.CreateDirectory("b", Array.Empty<ManifestEntry>());
var directory = ManifestDirectory.CreateDirectory("a", new[] { childDirectory });
// Act
var result = childDirectory.Traverse("..");
// Assert
Assert.Equal(directory, result);
}
[Fact]
public void TraversingRootDirectoryWithDotDot_ReturnsSinkDirectory()
{
// Arrange
var directory = ManifestDirectory.CreateRootDirectory(Array.Empty<ManifestEntry>());
// Act
var result = directory.Traverse("..");
// Assert
Assert.Equal(ManifestEntry.UnknownPath, result);
}
[Fact]
public void ScopingAFolderAndTryingToGetAScopedFile_ReturnsSinkDirectory()
{
// Arrange
var directory = ManifestDirectory.CreateRootDirectory(new[] {
ManifestDirectory.CreateDirectory("a",
new[] { new ManifestFile("test1.txt", "text.txt") }),
ManifestDirectory.CreateDirectory("b",
new[] { new ManifestFile("test2.txt", "test2.txt") }) });
var newRoot = ((ManifestDirectory)directory.Traverse("a")).ToRootDirectory();
// Act
var result = newRoot.Traverse("../b/test.txt");
// Assert
Assert.Same(ManifestEntry.UnknownPath, result);
}
[Theory]
[InlineData("..")]
[InlineData(".")]
[InlineData("file.txt")]
[InlineData("folder")]
public void TraversingUnknownPath_ReturnsSinkDirectory(string path)
{
// Arrange
var directory = ManifestEntry.UnknownPath;
// Act
var result = directory.Traverse(path);
// Assert
Assert.Equal(ManifestEntry.UnknownPath, result);
}
}
}

View File

@ -0,0 +1,116 @@
// 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;
using Xunit;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
public class ManifestParserTests
{
[Fact]
public void Parse_UsesDefaultManifestNameForManifest()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.File("sample.txt")));
// Act
var manifest = ManifestParser.Parse(assembly);
// Assert
Assert.NotNull(manifest);
}
[Fact]
public void Parse_FindsManifestWithCustomName()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.File("sample.txt")),
manifestName: "Manifest.xml");
// Act
var manifest = ManifestParser.Parse(assembly, "Manifest.xml");
// Assert
Assert.NotNull(manifest);
}
[Fact]
public void Parse_ThrowsForEntriesWithDifferentCasing()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.File("sample.txt"),
TestEntry.File("SAMPLE.TXT")));
// Act & Assert
Assert.Throws<InvalidOperationException>(() => ManifestParser.Parse(assembly));
}
[Theory]
[MemberData(nameof(MalformedManifests))]
public void Parse_ThrowsForInvalidManifests(string invalidManifest)
{
// Arrange
var assembly = new TestAssembly(invalidManifest);
// Act & Assert
Assert.Throws<InvalidOperationException>(() => ManifestParser.Parse(assembly));
}
public static TheoryData<string> MalformedManifests =>
new TheoryData<string>
{
"<Manifest></Manifest>",
"<Manifest><ManifestVersion></ManifestVersion></Manifest>",
"<Manifest><ManifestVersion /></Manifest>",
"<Manifest><ManifestVersion><Version>2.0</Version></ManifestVersion></Manifest>",
"<Manifest><ManifestVersion>2.0</ManifestVersion></Manifest>",
@"<Manifest><ManifestVersion>1.0</ManifestVersion>
<FileSystem><File><ResourcePath>path</ResourcePath></File></FileSystem></Manifest>",
@"<Manifest><ManifestVersion>1.0</ManifestVersion>
<FileSystem><File Name=""sample.txt""><ResourcePath></ResourcePath></File></FileSystem></Manifest>",
@"<Manifest><ManifestVersion>1.0</ManifestVersion>
<FileSystem><File Name=""sample.txt"">sample.txt</File></FileSystem></Manifest>",
@"<Manifest><ManifestVersion>1.0</ManifestVersion>
<FileSystem><Directory></Directory></FileSystem></Manifest>",
@"<Manifest><ManifestVersion>1.0</ManifestVersion>
<FileSystem><Directory Name=""wwwroot""><Unknown /></Directory></FileSystem></Manifest>"
};
[Theory]
[MemberData(nameof(ManifestsWithAdditionalData))]
public void Parse_IgnoresAdditionalDataOnFileAndDirectoryNodes(string manifest)
{
// Arrange
var assembly = new TestAssembly(manifest);
// Act
var result = ManifestParser.Parse(assembly);
// Assert
Assert.NotNull(result);
}
public static TheoryData<string> ManifestsWithAdditionalData =>
new TheoryData<string>
{
@"<Manifest><ManifestVersion>1.0</ManifestVersion>
<FileSystem><Directory Name=""wwwroot"" AdditionalAttribute=""value""></Directory></FileSystem></Manifest>",
@"<Manifest><ManifestVersion>1.0</ManifestVersion>
<FileSystem><Directory Name=""wwwroot"" AdditionalAttribute=""value"">
<File Name=""sample.txt"" AdditionalValue=""value""><ResourcePath something=""abc"">path</ResourcePath><hash>1234</hash></File>
</Directory></FileSystem></Manifest>"
};
}
}

View File

@ -0,0 +1,41 @@
// 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.Linq;
using System.Xml.Linq;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest
{
class TestEntry
{
public bool IsFile => ResourcePath != null;
public string Name { get; set; }
public TestEntry[] Children { get; set; }
public string ResourcePath { get; set; }
public static TestEntry Directory(string name, params TestEntry[] entries) =>
new TestEntry() { Name = name, Children = entries };
public static TestEntry File(string name, string path = null) =>
new TestEntry() { Name = name, ResourcePath = path ?? name };
public XElement ToXElement() => IsFile ?
new XElement("File", new XAttribute("Name", Name), new XElement("ResourcePath", ResourcePath)) :
new XElement("Directory", new XAttribute("Name", Name), Children.Select(c => c.ToXElement()));
public IEnumerable<TestEntry> GetFiles()
{
if (IsFile)
{
return Enumerable.Empty<TestEntry>();
}
var files = Children.Where(c => c.IsFile).ToArray();
var otherFiles = Children.Where(c => !c.IsFile).SelectMany(d => d.GetFiles()).ToArray();
return files.Concat(otherFiles).ToArray();
}
}
}

View File

@ -0,0 +1,428 @@
// 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;
using System.IO;
using System.Linq;
using Microsoft.Extensions.FileProviders.Embedded.Manifest;
using Xunit;
namespace Microsoft.Extensions.FileProviders
{
public class ManifestEmbeddedFileProviderTests
{
[Fact]
public void GetFileInfo_CanResolveSimpleFiles()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.File("jquery.validate.js"),
TestEntry.File("jquery.min.js"),
TestEntry.File("site.css")));
// Act
var provider = new ManifestEmbeddedFileProvider(assembly);
// Assert
var jqueryValidate = provider.GetFileInfo("jquery.validate.js");
Assert.True(jqueryValidate.Exists);
Assert.False(jqueryValidate.IsDirectory);
Assert.Equal("jquery.validate.js", jqueryValidate.Name);
Assert.Null(jqueryValidate.PhysicalPath);
Assert.Equal(0, jqueryValidate.Length);
var jqueryMin = provider.GetFileInfo("jquery.min.js");
Assert.True(jqueryMin.Exists);
Assert.False(jqueryMin.IsDirectory);
Assert.Equal("jquery.min.js", jqueryMin.Name);
Assert.Null(jqueryMin.PhysicalPath);
Assert.Equal(0, jqueryMin.Length);
var siteCss = provider.GetFileInfo("site.css");
Assert.True(siteCss.Exists);
Assert.False(siteCss.IsDirectory);
Assert.Equal("site.css", siteCss.Name);
Assert.Null(siteCss.PhysicalPath);
Assert.Equal(0, siteCss.Length);
}
[Fact]
public void GetFileInfo_CanResolveFilesInsideAFolder()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot",
TestEntry.File("jquery.validate.js"),
TestEntry.File("jquery.min.js"),
TestEntry.File("site.css"))));
// Act
var provider = new ManifestEmbeddedFileProvider(assembly);
// Assert
var jqueryValidate = provider.GetFileInfo(Path.Combine("wwwroot", "jquery.validate.js"));
Assert.True(jqueryValidate.Exists);
Assert.False(jqueryValidate.IsDirectory);
Assert.Equal("jquery.validate.js", jqueryValidate.Name);
Assert.Null(jqueryValidate.PhysicalPath);
Assert.Equal(0, jqueryValidate.Length);
var jqueryMin = provider.GetFileInfo(Path.Combine("wwwroot", "jquery.min.js"));
Assert.True(jqueryMin.Exists);
Assert.False(jqueryMin.IsDirectory);
Assert.Equal("jquery.min.js", jqueryMin.Name);
Assert.Null(jqueryMin.PhysicalPath);
Assert.Equal(0, jqueryMin.Length);
var siteCss = provider.GetFileInfo(Path.Combine("wwwroot", "site.css"));
Assert.True(siteCss.Exists);
Assert.False(siteCss.IsDirectory);
Assert.Equal("site.css", siteCss.Name);
Assert.Null(siteCss.PhysicalPath);
Assert.Equal(0, siteCss.Length);
}
[Fact]
public void GetFileInfo_ResolveNonExistingFile_ReturnsNotFoundFileInfo()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot",
TestEntry.File("jquery.validate.js"),
TestEntry.File("jquery.min.js"),
TestEntry.File("site.css"))));
var provider = new ManifestEmbeddedFileProvider(assembly);
// Act
var file = provider.GetFileInfo("some/non/existing/file.txt");
// Assert
Assert.IsType<NotFoundFileInfo>(file);
}
[Fact]
public void GetFileInfo_ResolveNonExistingDirectory_ReturnsNotFoundFileInfo()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot",
TestEntry.File("jquery.validate.js"),
TestEntry.File("jquery.min.js"),
TestEntry.File("site.css"))));
var provider = new ManifestEmbeddedFileProvider(assembly);
// Act
var file = provider.GetFileInfo("some");
// Assert
Assert.IsType<NotFoundFileInfo>(file);
}
[Fact]
public void GetFileInfo_ResolveExistingDirectory_ReturnsNotFoundFileInfo()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot",
TestEntry.File("jquery.validate.js"),
TestEntry.File("jquery.min.js"),
TestEntry.File("site.css"))));
var provider = new ManifestEmbeddedFileProvider(assembly);
// Act
var file = provider.GetFileInfo("wwwroot");
// Assert
Assert.IsType<NotFoundFileInfo>(file);
}
[Theory]
[InlineData("WWWROOT", "JQUERY.VALIDATE.JS")]
[InlineData("WwWRoOT", "JQuERY.VALiDATE.js")]
public void GetFileInfo_ResolvesFiles_WithDifferentCasing(string folder, string file)
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot",
TestEntry.File("jquery.validate.js"),
TestEntry.File("jquery.min.js"),
TestEntry.File("site.css"))));
// Act
var provider = new ManifestEmbeddedFileProvider(assembly);
// Assert
var jqueryValidate = provider.GetFileInfo(Path.Combine(folder, file));
Assert.True(jqueryValidate.Exists);
Assert.False(jqueryValidate.IsDirectory);
Assert.Equal("jquery.validate.js", jqueryValidate.Name);
Assert.Null(jqueryValidate.PhysicalPath);
Assert.Equal(0, jqueryValidate.Length);
}
[Fact]
public void GetFileInfo_AllowsLeadingDots_OnThePath()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot",
TestEntry.File("jquery.validate.js"),
TestEntry.File("jquery.min.js"),
TestEntry.File("site.css"))));
// Act
var provider = new ManifestEmbeddedFileProvider(assembly);
// Assert
var jqueryValidate = provider.GetFileInfo(Path.Combine(".", "wwwroot", "jquery.validate.js"));
Assert.True(jqueryValidate.Exists);
Assert.False(jqueryValidate.IsDirectory);
Assert.Equal("jquery.validate.js", jqueryValidate.Name);
Assert.Null(jqueryValidate.PhysicalPath);
Assert.Equal(0, jqueryValidate.Length);
}
[Fact]
public void GetFileInfo_EscapingFromTheRootFolder_ReturnsNotFound()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot",
TestEntry.File("jquery.validate.js"),
TestEntry.File("jquery.min.js"),
TestEntry.File("site.css"))));
// Act
var provider = new ManifestEmbeddedFileProvider(assembly);
// Assert
var jqueryValidate = provider.GetFileInfo(Path.Combine("..", "wwwroot", "jquery.validate.js"));
Assert.IsType<NotFoundFileInfo>(jqueryValidate);
}
[Theory]
[InlineData("wwwroot/jquery?validate.js")]
[InlineData("wwwroot/jquery*validate.js")]
[InlineData("wwwroot/jquery:validate.js")]
[InlineData("wwwroot/jquery<validate.js")]
[InlineData("wwwroot/jquery>validate.js")]
[InlineData("wwwroot/jquery\0validate.js")]
public void GetFileInfo_ReturnsNotFoundfileInfo_ForPathsWithInvalidCharacters(string path)
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot",
TestEntry.File("jquery.validate.js"),
TestEntry.File("jquery.min.js"),
TestEntry.File("site.css"))));
// Act
var provider = new ManifestEmbeddedFileProvider(assembly);
// Assert
var file = provider.GetFileInfo(path);
Assert.IsType<NotFoundFileInfo>(file);
Assert.Equal(path, file.Name);
}
[Fact]
public void GetDirectoryContents_CanEnumerateExistingFolders()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot",
TestEntry.File("jquery.validate.js"),
TestEntry.File("jquery.min.js"),
TestEntry.File("site.css"))));
var provider = new ManifestEmbeddedFileProvider(assembly);
var expectedContents = new[]
{
CreateTestFileInfo("jquery.validate.js"),
CreateTestFileInfo("jquery.min.js"),
CreateTestFileInfo("site.css")
};
// Act
var contents = provider.GetDirectoryContents("wwwroot").ToArray();
// Assert
Assert.Equal(expectedContents, contents, FileInfoComparer.Instance);
}
[Fact]
public void GetDirectoryContents_EnumeratesOnlyAGivenLevel()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot",
TestEntry.File("jquery.validate.js"),
TestEntry.File("jquery.min.js"),
TestEntry.File("site.css"))));
var provider = new ManifestEmbeddedFileProvider(assembly);
var expectedContents = new[]
{
CreateTestFileInfo("wwwroot", isDirectory: true)
};
// Act
var contents = provider.GetDirectoryContents(".").ToArray();
// Assert
Assert.Equal(expectedContents, contents, FileInfoComparer.Instance);
}
[Fact]
public void GetDirectoryContents_EnumeratesFilesAndDirectoriesOnAGivenPath()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot"),
TestEntry.File("site.css")));
var provider = new ManifestEmbeddedFileProvider(assembly);
var expectedContents = new[]
{
CreateTestFileInfo("wwwroot", isDirectory: true),
CreateTestFileInfo("site.css")
};
// Act
var contents = provider.GetDirectoryContents(".").ToArray();
// Assert
Assert.Equal(expectedContents, contents, FileInfoComparer.Instance);
}
[Fact]
public void GetDirectoryContents_ReturnsNoEntries_ForNonExistingDirectories()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot"),
TestEntry.File("site.css")));
var provider = new ManifestEmbeddedFileProvider(assembly);
// Act
var contents = provider.GetDirectoryContents("non-existing");
// Assert
Assert.IsType<NotFoundDirectoryContents>(contents);
}
[Fact]
public void GetDirectoryContents_ReturnsNoEntries_ForFilePaths()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot"),
TestEntry.File("site.css")));
var provider = new ManifestEmbeddedFileProvider(assembly);
// Act
var contents = provider.GetDirectoryContents("site.css");
// Assert
Assert.IsType<NotFoundDirectoryContents>(contents);
}
[Theory]
[InlineData("wwwro*t")]
[InlineData("wwwro?t")]
[InlineData("wwwro:t")]
[InlineData("wwwro<t")]
[InlineData("wwwro>t")]
[InlineData("wwwro\0t")]
public void GetDirectoryContents_ReturnsNotFoundDirectoryContents_ForPathsWithInvalidCharacters(string path)
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot",
TestEntry.File("jquery.validate.js"),
TestEntry.File("jquery.min.js"),
TestEntry.File("site.css"))));
// Act
var provider = new ManifestEmbeddedFileProvider(assembly);
// Assert
var directory = provider.GetDirectoryContents(path);
Assert.IsType<NotFoundDirectoryContents>(directory);
}
[Fact]
public void Contructor_CanScopeManifestToAFolder()
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot",
TestEntry.File("jquery.validate.js")),
TestEntry.File("site.css")));
var provider = new ManifestEmbeddedFileProvider(assembly);
var scopedProvider = new ManifestEmbeddedFileProvider(assembly, provider.Manifest.Scope("wwwroot"), DateTimeOffset.UtcNow);
// Act
var jqueryValidate = scopedProvider.GetFileInfo("jquery.validate.js");
// Assert
Assert.True(jqueryValidate.Exists);
Assert.False(jqueryValidate.IsDirectory);
Assert.Equal("jquery.validate.js", jqueryValidate.Name);
Assert.Null(jqueryValidate.PhysicalPath);
Assert.Equal(0, jqueryValidate.Length);
}
[Theory]
[InlineData("wwwroot/jquery.validate.js")]
[InlineData("../wwwroot/jquery.validate.js")]
[InlineData("site.css")]
[InlineData("../site.css")]
public void ScopedFileProvider_DoesNotReturnFilesOutOfScope(string path)
{
// Arrange
var assembly = new TestAssembly(
TestEntry.Directory("unused",
TestEntry.Directory("wwwroot",
TestEntry.File("jquery.validate.js")),
TestEntry.File("site.css")));
var provider = new ManifestEmbeddedFileProvider(assembly);
var scopedProvider = new ManifestEmbeddedFileProvider(assembly, provider.Manifest.Scope("wwwroot"), DateTimeOffset.UtcNow);
// Act
var jqueryValidate = scopedProvider.GetFileInfo(path);
// Assert
Assert.IsType<NotFoundFileInfo>(jqueryValidate);
}
private IFileInfo CreateTestFileInfo(string name, bool isDirectory = false) =>
new TestFileInfo(name, isDirectory);
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="File.txt;sub\**\*;Resources\**\*" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Extensions.FileProviders.Embedded" />
</ItemGroup>
</Project>

View File

@ -0,0 +1 @@
Resources-Hello

View File

@ -0,0 +1 @@
Hello3

View File

@ -0,0 +1,69 @@
// 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.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Microsoft.Extensions.FileProviders.Embedded.Manifest;
namespace Microsoft.Extensions.FileProviders
{
internal class TestAssembly : Assembly
{
public TestAssembly(string manifest, string manifestName = "Microsoft.Extensions.FileProviders.Embedded.Manifest.xml")
{
ManifestStream = new MemoryStream();
using (var writer = new StreamWriter(ManifestStream, Encoding.UTF8, 1024, leaveOpen: true))
{
writer.Write(manifest);
}
ManifestStream.Seek(0, SeekOrigin.Begin);
ManifestName = manifestName;
}
public TestAssembly(TestEntry entry, string manifestName = "Microsoft.Extensions.FileProviders.Embedded.Manifest.xml")
{
ManifestName = manifestName;
var manifest = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("Manifest",
new XElement("ManifestVersion", "1.0"),
new XElement("FileSystem", entry.Children.Select(c => c.ToXElement()))));
ManifestStream = new MemoryStream();
using (var writer = XmlWriter.Create(ManifestStream, new XmlWriterSettings { CloseOutput = false }))
{
manifest.WriteTo(writer);
}
ManifestStream.Seek(0, SeekOrigin.Begin);
Files = entry.GetFiles().Select(f => f.ResourcePath).ToArray();
}
public string ManifestName { get; }
public MemoryStream ManifestStream { get; private set; }
public string[] Files { get; private set; }
public override Stream GetManifestResourceStream(string name)
{
if (string.Equals(ManifestName, name))
{
return ManifestStream;
}
return Files.Contains(name) ? Stream.Null : null;
}
public override string Location => null;
public override AssemblyName GetName()
{
return new AssemblyName("TestAssembly");
}
}
}

View File

@ -0,0 +1,34 @@
// 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;
using System.IO;
namespace Microsoft.Extensions.FileProviders
{
internal class TestFileInfo : IFileInfo
{
private readonly string _name;
private readonly bool _isDirectory;
public TestFileInfo(string name, bool isDirectory)
{
_name = name;
_isDirectory = isDirectory;
}
public bool Exists => true;
public long Length => _isDirectory ? -1 : 0;
public string PhysicalPath => null;
public string Name => _name;
public DateTimeOffset LastModified => throw new NotImplementedException();
public bool IsDirectory => _isDirectory;
public Stream CreateReadStream() => Stream.Null;
}
}

View File

@ -0,0 +1 @@
Hello2

Binary file not shown.

View File

@ -0,0 +1,21 @@
// 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;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest.Task
{
public class EmbeddedItem : IEquatable<EmbeddedItem>
{
public string ManifestFilePath { get; set; }
public string AssemblyResourceName { get; set; }
public bool Equals(EmbeddedItem other) =>
string.Equals(ManifestFilePath, other?.ManifestFilePath, StringComparison.Ordinal) &&
string.Equals(AssemblyResourceName, other?.AssemblyResourceName, StringComparison.Ordinal);
public override bool Equals(object obj) => Equals(obj as EmbeddedItem);
public override int GetHashCode() => ManifestFilePath.GetHashCode() ^ AssemblyResourceName.GetHashCode();
}
}

View File

@ -0,0 +1,120 @@
// 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;
using System.Collections.Generic;
using System.Diagnostics;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Internal
{
/// <summary>
/// This type is for internal uses only and is not meant to be consumed by any other library.
/// </summary>
[DebuggerDisplay("{Name,nq}")]
public class Entry : IEquatable<Entry>
{
public bool IsFile { get; private set; }
public string Name { get; private set; }
public string AssemblyResourceName { get; private set; }
public ISet<Entry> Children { get; } = new SortedSet<Entry>(NameComparer.Instance);
public static Entry Directory(string name) =>
new Entry { Name = name };
public static Entry File(string name, string assemblyResourceName) =>
new Entry { Name = name, AssemblyResourceName = assemblyResourceName, IsFile = true };
internal void AddChild(Entry child)
{
if (IsFile)
{
throw new InvalidOperationException("Tried to add children to a file.");
}
if (Children.Contains(child))
{
throw new InvalidOperationException($"An item with the name '{child.Name}' already exists.");
}
Children.Add(child);
}
internal Entry GetDirectory(string currentSegment)
{
if (IsFile)
{
throw new InvalidOperationException("Tried to get a directory from a file.");
}
foreach (var child in Children)
{
if (child.HasName(currentSegment))
{
if (child.IsFile)
{
throw new InvalidOperationException("Tried to find a directory but found a file instead");
}
else
{
return child;
}
}
}
return null;
}
public bool Equals(Entry other)
{
if (other == null || !other.HasName(Name) || other.IsFile != IsFile)
{
return false;
}
if (IsFile)
{
return string.Equals(other.AssemblyResourceName, AssemblyResourceName, StringComparison.Ordinal);
}
else
{
return SameChildren(Children, other.Children);
}
}
private bool HasName(string currentSegment)
{
return string.Equals(Name, currentSegment, StringComparison.Ordinal);
}
private bool SameChildren(ISet<Entry> left, ISet<Entry> right)
{
if (left.Count != right.Count)
{
return false;
}
var le = left.GetEnumerator();
var re = right.GetEnumerator();
while (le.MoveNext() && re.MoveNext())
{
if (!le.Current.Equals(re.Current))
{
return false;
}
}
return true;
}
private class NameComparer : IComparer<Entry>
{
public static NameComparer Instance { get; } = new NameComparer();
public int Compare(Entry x, Entry y) =>
string.Compare(x?.Name, y?.Name, StringComparison.Ordinal);
}
}
}

View File

@ -0,0 +1,104 @@
// 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;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Microsoft.Build.Framework;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest.Task
{
/// <summary>
/// Task for generating a manifest file out of the embedded resources in an
/// assembly.
/// </summary>
public class GenerateEmbeddedResourcesManifest : Microsoft.Build.Utilities.Task
{
private const string LogicalName = "LogicalName";
private const string ManifestResourceName = "ManifestResourceName";
private const string TargetPath = "TargetPath";
[Required]
public ITaskItem[] EmbeddedFiles { get; set; }
[Required]
public string ManifestFile { get; set; }
/// <inheritdoc />
public override bool Execute()
{
var processedItems = CreateEmbeddedItems(EmbeddedFiles);
var manifest = BuildManifest(processedItems);
var document = manifest.ToXmlDocument();
var settings = new XmlWriterSettings()
{
Encoding = Encoding.UTF8,
CloseOutput = true
};
using (var xmlWriter = GetXmlWriter(settings))
{
document.WriteTo(xmlWriter);
}
return true;
}
protected virtual XmlWriter GetXmlWriter(XmlWriterSettings settings)
{
if (settings == null)
{
throw new ArgumentNullException(nameof(settings));
}
var fileStream = new FileStream(ManifestFile, FileMode.Create);
return XmlWriter.Create(fileStream, settings);
}
public EmbeddedItem[] CreateEmbeddedItems(params ITaskItem[] items)
{
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
return items.Select(er => new EmbeddedItem
{
ManifestFilePath = GetManifestPath(er),
AssemblyResourceName = GetAssemblyResourceName(er)
}).ToArray();
}
public Manifest BuildManifest(EmbeddedItem[] processedItems)
{
if (processedItems == null)
{
throw new ArgumentNullException(nameof(processedItems));
}
var manifest = new Manifest();
foreach (var item in processedItems)
{
manifest.AddElement(item.ManifestFilePath, item.AssemblyResourceName);
}
return manifest;
}
private string GetManifestPath(ITaskItem taskItem) => string.Equals(taskItem.GetMetadata(LogicalName), taskItem.GetMetadata(ManifestResourceName)) ?
taskItem.GetMetadata(TargetPath) :
NormalizePath(taskItem.GetMetadata(LogicalName));
private string GetAssemblyResourceName(ITaskItem taskItem) => string.Equals(taskItem.GetMetadata(LogicalName), taskItem.GetMetadata(ManifestResourceName)) ?
taskItem.GetMetadata(ManifestResourceName) :
taskItem.GetMetadata(LogicalName);
private string NormalizePath(string path) => Path.DirectorySeparatorChar == '\\' ?
path.Replace("/", "\\") : path.Replace("\\", "/");
}
}

View File

@ -0,0 +1,85 @@
// 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.IO;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Internal;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest.Task
{
public class Manifest
{
public Entry Root { get; set; } = Entry.Directory("");
public void AddElement(string originalPath, string assemblyResourceName)
{
if (originalPath == null)
{
throw new System.ArgumentNullException(nameof(originalPath));
}
if (assemblyResourceName == null)
{
throw new System.ArgumentNullException(nameof(assemblyResourceName));
}
var paths = originalPath.Split(Path.DirectorySeparatorChar);
var current = Root;
for (int i = 0; i < paths.Length - 1; i++)
{
var currentSegment = paths[i];
var next = current.GetDirectory(currentSegment);
if (next == null)
{
next = Entry.Directory(currentSegment);
current.AddChild(next);
}
current = next;
}
current.AddChild(Entry.File(paths[paths.Length - 1], assemblyResourceName));
}
public XDocument ToXmlDocument()
{
var document = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
var root = new XElement(ElementNames.Root,
new XElement(ElementNames.ManifestVersion, "1.0"),
new XElement(ElementNames.FileSystem,
Root.Children.Select(e => BuildNode(e))));
document.Add(root);
return document;
}
private XElement BuildNode(Entry entry)
{
if (entry.IsFile)
{
return new XElement(ElementNames.File,
new XAttribute(ElementNames.Name, entry.Name),
new XElement(ElementNames.ResourcePath, entry.AssemblyResourceName));
}
else
{
var directory = new XElement(ElementNames.Directory, new XAttribute(ElementNames.Name, entry.Name));
directory.Add(entry.Children.Select(c => BuildNode(c)));
return directory;
}
}
private class ElementNames
{
public static readonly string Directory = "Directory";
public static readonly string Name = "Name";
public static readonly string FileSystem = "FileSystem";
public static readonly string Root = "Manifest";
public static readonly string File = "File";
public static readonly string ResourcePath = "ResourcePath";
public static readonly string ManifestVersion = "ManifestVersion";
}
}
}

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>MSBuild task to generate a manifest that can be used by Microsoft.Extensions.FileProviders.Embedded to preserve
metadata of the files embedded in the assembly at compilation time.</Description>
<TargetFrameworks>netstandard1.5;net461</TargetFrameworks>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<EnableApiCheck>false</EnableApiCheck>
<IsPackable>false</IsPackable>
<IsImplementationProject>false</IsImplementationProject>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.5'">
<Reference Include="Microsoft.Build" />
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="Microsoft.Build.Utilities.Core" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
<Reference Include="Microsoft.Build" />
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="Microsoft.Build.Utilities.v4.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,388 @@
// 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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Internal;
using Xunit;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest.Task
{
public class GenerateEmbeddedResourcesManifestTest
{
[Fact]
public void CreateEmbeddedItems_MapsMetadataFromEmbeddedResources_UsesTheTargetPath()
{
// Arrange
var task = new TestGenerateEmbeddedResourcesManifest();
var embeddedFiles = CreateEmbeddedResource(
CreateMetadata(@"lib\js\jquery.validate.js"));
var expectedItems = new[]
{
CreateEmbeddedItem(@"lib\js\jquery.validate.js","lib.js.jquery.validate.js")
};
// Act
var embeddedItems = task.CreateEmbeddedItems(embeddedFiles);
// Assert
Assert.Equal(expectedItems, embeddedItems);
}
[Fact]
public void CreateEmbeddedItems_MapsMetadataFromEmbeddedResources_WithLogicalName()
{
// Arrange
var task = new TestGenerateEmbeddedResourcesManifest();
var DirectorySeparator = (Path.DirectorySeparatorChar == '\\' ? '/' : '\\');
var embeddedFiles = CreateEmbeddedResource(
CreateMetadata("site.css", null, "site.css"),
CreateMetadata("lib/jquery.validate.js", null, $"dist{DirectorySeparator}jquery.validate.js"));
var expectedItems = new[]
{
CreateEmbeddedItem("site.css","site.css"),
CreateEmbeddedItem(Path.Combine("dist","jquery.validate.js"),$"dist{DirectorySeparator}jquery.validate.js")
};
// Act
var embeddedItems = task.CreateEmbeddedItems(embeddedFiles);
// Assert
Assert.Equal(expectedItems, embeddedItems);
}
[Fact]
public void BuildManifest_CanCreatesManifest_ForTopLevelFiles()
{
// Arrange
var task = new TestGenerateEmbeddedResourcesManifest();
var embeddedFiles = CreateEmbeddedResource(
CreateMetadata("jquery.validate.js"),
CreateMetadata("jquery.min.js"),
CreateMetadata("Site.css"));
var manifestFiles = task.CreateEmbeddedItems(embeddedFiles);
var expectedManifest = new Manifest()
{
Root = Entry.Directory("").AddRange(
Entry.File("jquery.validate.js", "jquery.validate.js"),
Entry.File("jquery.min.js", "jquery.min.js"),
Entry.File("Site.css", "Site.css"))
};
// Act
var manifest = task.BuildManifest(manifestFiles);
// Assert
Assert.Equal(expectedManifest, manifest, ManifestComparer.Instance);
}
[Fact]
public void BuildManifest_CanCreatesManifest_ForFilesWithinAFolder()
{
// Arrange
var task = new TestGenerateEmbeddedResourcesManifest();
var embeddedFiles = CreateEmbeddedResource(
CreateMetadata(Path.Combine("wwwroot", "js", "jquery.validate.js")),
CreateMetadata(Path.Combine("wwwroot", "js", "jquery.min.js")),
CreateMetadata(Path.Combine("wwwroot", "css", "Site.css")),
CreateMetadata(Path.Combine("Areas", "Identity", "Views", "Account", "Index.cshtml")));
var manifestFiles = task.CreateEmbeddedItems(embeddedFiles);
var expectedManifest = new Manifest()
{
Root = Entry.Directory("").AddRange(
Entry.Directory("wwwroot").AddRange(
Entry.Directory("js").AddRange(
Entry.File("jquery.validate.js", "wwwroot.js.jquery.validate.js"),
Entry.File("jquery.min.js", "wwwroot.js.jquery.min.js")),
Entry.Directory("css").AddRange(
Entry.File("Site.css", "wwwroot.css.Site.css"))),
Entry.Directory("Areas").AddRange(
Entry.Directory("Identity").AddRange(
Entry.Directory("Views").AddRange(
Entry.Directory("Account").AddRange(
Entry.File("Index.cshtml", "Areas.Identity.Views.Account.Index.cshtml"))))))
};
// Act
var manifest = task.BuildManifest(manifestFiles);
// Assert
Assert.Equal(expectedManifest, manifest, ManifestComparer.Instance);
}
[Fact]
public void BuildManifest_RespectsEntriesWithLogicalName()
{
// Arrange
var task = new TestGenerateEmbeddedResourcesManifest();
var embeddedFiles = CreateEmbeddedResource(
CreateMetadata("jquery.validate.js", null, @"wwwroot\lib\js\jquery.validate.js"),
CreateMetadata("jquery.min.js", null, @"wwwroot\lib/js\jquery.min.js"),
CreateMetadata("Site.css", null, "wwwroot/lib/css/site.css"));
var manifestFiles = task.CreateEmbeddedItems(embeddedFiles);
var expectedManifest = new Manifest()
{
Root = Entry.Directory("").AddRange(
Entry.Directory("wwwroot").AddRange(
Entry.Directory("lib").AddRange(
Entry.Directory("js").AddRange(
Entry.File("jquery.validate.js", @"wwwroot\lib\js\jquery.validate.js"),
Entry.File("jquery.min.js", @"wwwroot\lib/js\jquery.min.js")),
Entry.Directory("css").AddRange(
Entry.File("site.css", "wwwroot/lib/css/site.css")))))
};
// Act
var manifest = task.BuildManifest(manifestFiles);
// Assert
Assert.Equal(expectedManifest, manifest, ManifestComparer.Instance);
}
[Fact]
public void BuildManifest_SupportsFilesAndFoldersWithDifferentCasing()
{
// Arrange
var task = new TestGenerateEmbeddedResourcesManifest();
var embeddedFiles = CreateEmbeddedResource(
CreateMetadata(Path.Combine("A", "b", "c.txt")),
CreateMetadata(Path.Combine("A", "B", "c.txt")),
CreateMetadata(Path.Combine("A", "B", "C.txt")),
CreateMetadata(Path.Combine("A", "b", "C.txt")),
CreateMetadata(Path.Combine("A", "d")),
CreateMetadata(Path.Combine("A", "D", "e.txt")));
var manifestFiles = task.CreateEmbeddedItems(embeddedFiles);
var expectedManifest = new Manifest()
{
Root = Entry.Directory("").AddRange(
Entry.Directory("A").AddRange(
Entry.Directory("b").AddRange(
Entry.File("c.txt", @"A.b.c.txt"),
Entry.File("C.txt", @"A.b.C.txt")),
Entry.Directory("B").AddRange(
Entry.File("c.txt", @"A.B.c.txt"),
Entry.File("C.txt", @"A.B.C.txt")),
Entry.Directory("D").AddRange(
Entry.File("e.txt", "A.D.e.txt")),
Entry.File("d", "A.d")))
};
// Act
var manifest = task.BuildManifest(manifestFiles);
// Assert
Assert.Equal(expectedManifest, manifest, ManifestComparer.Instance);
}
[Fact]
public void BuildManifest_ThrowsInvalidOperationException_WhenTryingToAddAFileWithTheSameNameAsAFolder()
{
// Arrange
var task = new TestGenerateEmbeddedResourcesManifest();
var embeddedFiles = CreateEmbeddedResource(
CreateMetadata(Path.Combine("A", "b", "c.txt")),
CreateMetadata(Path.Combine("A", "b")));
var manifestFiles = task.CreateEmbeddedItems(embeddedFiles);
// Act & Assert
Assert.Throws<InvalidOperationException>(() => task.BuildManifest(manifestFiles));
}
[Fact]
public void BuildManifest_ThrowsInvalidOperationException_WhenTryingToAddAFolderWithTheSameNameAsAFile()
{
// Arrange
var task = new TestGenerateEmbeddedResourcesManifest();
var embeddedFiles = CreateEmbeddedResource(
CreateMetadata(Path.Combine("A", "b")),
CreateMetadata(Path.Combine("A", "b", "c.txt")));
var manifestFiles = task.CreateEmbeddedItems(embeddedFiles);
// Act & Assert
Assert.Throws<InvalidOperationException>(() => task.BuildManifest(manifestFiles));
}
[Fact]
public void ToXmlDocument_GeneratesTheCorrectXmlDocument()
{
// Arrange
var manifest = new Manifest()
{
Root = Entry.Directory("").AddRange(
Entry.Directory("A").AddRange(
Entry.Directory("b").AddRange(
Entry.File("c.txt", @"A.b.c.txt"),
Entry.File("C.txt", @"A.b.C.txt")),
Entry.Directory("B").AddRange(
Entry.File("c.txt", @"A.B.c.txt"),
Entry.File("C.txt", @"A.B.C.txt")),
Entry.Directory("D").AddRange(
Entry.File("e.txt", "A.D.e.txt")),
Entry.File("d", "A.d")))
};
var expectedDocument = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("Manifest",
new XElement("ManifestVersion", "1.0"),
new XElement("FileSystem",
new XElement("Directory", new XAttribute("Name", "A"),
new XElement("Directory", new XAttribute("Name", "B"),
new XElement("File", new XAttribute("Name", "C.txt"), new XElement("ResourcePath", "A.B.C.txt")),
new XElement("File", new XAttribute("Name", "c.txt"), new XElement("ResourcePath", "A.B.c.txt"))),
new XElement("Directory", new XAttribute("Name", "D"),
new XElement("File", new XAttribute("Name", "e.txt"), new XElement("ResourcePath", "A.D.e.txt"))),
new XElement("Directory", new XAttribute("Name", "b"),
new XElement("File", new XAttribute("Name", "C.txt"), new XElement("ResourcePath", "A.b.C.txt")),
new XElement("File", new XAttribute("Name", "c.txt"), new XElement("ResourcePath", "A.b.c.txt"))),
new XElement("File", new XAttribute("Name", "d"), new XElement("ResourcePath", "A.d"))))));
// Act
var document = manifest.ToXmlDocument();
// Assert
Assert.Equal(expectedDocument.ToString(), document.ToString());
}
[Fact]
public void Execute_WritesManifest_ToOutputFile()
{
// Arrange
var task = new TestGenerateEmbeddedResourcesManifest();
var embeddedFiles = CreateEmbeddedResource(
CreateMetadata(Path.Combine("A", "b", "c.txt")),
CreateMetadata(Path.Combine("A", "B", "c.txt")),
CreateMetadata(Path.Combine("A", "B", "C.txt")),
CreateMetadata(Path.Combine("A", "b", "C.txt")),
CreateMetadata(Path.Combine("A", "d")),
CreateMetadata(Path.Combine("A", "D", "e.txt")));
task.EmbeddedFiles = embeddedFiles;
task.ManifestFile = Path.Combine("obj", "debug", "netstandard2.0");
var expectedDocument = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("Manifest",
new XElement("ManifestVersion", "1.0"),
new XElement("FileSystem",
new XElement("Directory", new XAttribute("Name", "A"),
new XElement("Directory", new XAttribute("Name", "B"),
new XElement("File", new XAttribute("Name", "C.txt"), new XElement("ResourcePath", "A.B.C.txt")),
new XElement("File", new XAttribute("Name", "c.txt"), new XElement("ResourcePath", "A.B.c.txt"))),
new XElement("Directory", new XAttribute("Name", "D"),
new XElement("File", new XAttribute("Name", "e.txt"), new XElement("ResourcePath", "A.D.e.txt"))),
new XElement("Directory", new XAttribute("Name", "b"),
new XElement("File", new XAttribute("Name", "C.txt"), new XElement("ResourcePath", "A.b.C.txt")),
new XElement("File", new XAttribute("Name", "c.txt"), new XElement("ResourcePath", "A.b.c.txt"))),
new XElement("File", new XAttribute("Name", "d"), new XElement("ResourcePath", "A.d"))))));
var expectedOutput = new MemoryStream();
var writer = XmlWriter.Create(expectedOutput, new XmlWriterSettings { Encoding = Encoding.UTF8 });
expectedDocument.WriteTo(writer);
writer.Flush();
expectedOutput.Seek(0, SeekOrigin.Begin);
// Act
task.Execute();
// Assert
task.Output.Seek(0, SeekOrigin.Begin);
using (var expectedReader = new StreamReader(expectedOutput))
{
using (var reader = new StreamReader(task.Output))
{
Assert.Equal(expectedReader.ReadToEnd(), reader.ReadToEnd());
}
}
}
private EmbeddedItem CreateEmbeddedItem(string manifestPath, string assemblyName) =>
new EmbeddedItem
{
ManifestFilePath = manifestPath,
AssemblyResourceName = assemblyName
};
public class TestGenerateEmbeddedResourcesManifest
: GenerateEmbeddedResourcesManifest
{
public TestGenerateEmbeddedResourcesManifest()
: this(new MemoryStream())
{
}
public TestGenerateEmbeddedResourcesManifest(Stream output)
{
Output = output;
}
public Stream Output { get; }
protected override XmlWriter GetXmlWriter(XmlWriterSettings settings)
{
settings.CloseOutput = false;
return XmlWriter.Create(Output, settings);
}
}
private ITaskItem[] CreateEmbeddedResource(params IDictionary<string, string>[] files) =>
files.Select(f => CreateTaskItem(f)).ToArray();
private ITaskItem CreateTaskItem(IDictionary<string, string> metadata)
{
var result = new TaskItem();
foreach (var kvp in metadata)
{
result.SetMetadata(kvp.Key, kvp.Value);
}
return result;
}
private static IDictionary<string, string>
CreateMetadata(
string targetPath,
string manifestResourceName = null,
string logicalName = null) =>
new Dictionary<string, string>
{
["TargetPath"] = targetPath,
["ManifestResourceName"] = manifestResourceName ?? targetPath.Replace("/", ".").Replace("\\", "."),
["LogicalName"] = logicalName ?? targetPath.Replace("/", ".").Replace("\\", "."),
};
private class ManifestComparer : IEqualityComparer<Manifest>
{
public static IEqualityComparer<Manifest> Instance { get; } = new ManifestComparer();
public bool Equals(Manifest x, Manifest y)
{
return x.Root.Equals(y.Root);
}
public int GetHashCode(Manifest obj)
{
return obj.Root.GetHashCode();
}
}
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\src\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,20 @@
// 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 Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Internal;
namespace Microsoft.Extensions.FileProviders.Embedded.Manifest.Task
{
internal static class SetExtensions
{
public static Entry AddRange(this Entry source, params Entry[] elements)
{
foreach (var element in elements)
{
source.Children.Add(element);
}
return source;
}
}
}