Runtime integration for static web assets (#10632)
* Runtime integration for static web assets * Adds an extension method to IWebHostBuilder to plug-in static web assets. * Plugs-in static web assets in development by default.
This commit is contained in:
parent
6abb874205
commit
62c190da6e
|
|
@ -17,6 +17,7 @@
|
|||
<Reference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<Reference Include="Microsoft.Extensions.Configuration.CommandLine" />
|
||||
<Reference Include="Microsoft.Extensions.Configuration.UserSecrets" />
|
||||
<Reference Include="Microsoft.Extensions.FileProviders.Composite" />
|
||||
<Reference Include="Microsoft.Extensions.Logging" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Configuration" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Console" />
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
namespace Microsoft.AspNetCore
|
||||
{
|
||||
public static partial class StaticWebAssetsWebHostBuilderExtensions
|
||||
{
|
||||
public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseStaticWebAssets(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder) { throw null; }
|
||||
}
|
||||
public static partial class WebHost
|
||||
{
|
||||
public static Microsoft.AspNetCore.Hosting.IWebHostBuilder CreateDefaultBuilder() { throw null; }
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
<Reference Include="Microsoft.Extensions.Configuration.Json" PrivateAssets="None" />
|
||||
<Reference Include="Microsoft.Extensions.Configuration.CommandLine" PrivateAssets="None" />
|
||||
<Reference Include="Microsoft.Extensions.Configuration.UserSecrets" PrivateAssets="None" />
|
||||
<Reference Include="Microsoft.Extensions.FileProviders.Composite" PrivateAssets="None" />
|
||||
<Reference Include="Microsoft.Extensions.Logging" PrivateAssets="None" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Configuration" PrivateAssets="None" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Console" PrivateAssets="None" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
// 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.Runtime.InteropServices;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore
|
||||
{
|
||||
// A file provider used for serving static web assets from referenced projects and packages during development.
|
||||
// The file provider maps folders from referenced projects and packages and prepends a prefix to their relative
|
||||
// paths.
|
||||
// At publish time the assets end up in the wwwroot folder of the published app under the prefix indicated here
|
||||
// as the base path.
|
||||
// For example, for a referenced project mylibrary with content under <<mylibrarypath>>\wwwroot will expose
|
||||
// static web assets under _content/mylibrary (this is by convention). The path prefix or base path we apply
|
||||
// is that (_content/mylibrary).
|
||||
// when the app gets published, the build pipeline puts the static web assets for mylibrary under
|
||||
// publish/wwwroot/_content/mylibrary/sample-asset.js
|
||||
// To allow for the same experience during development, StaticWebAssetsFileProvider maps the contents of
|
||||
// <<mylibrarypath>>\wwwroot\** to _content/mylibrary/**
|
||||
internal class StaticWebAssetsFileProvider : IFileProvider
|
||||
{
|
||||
private static readonly StringComparison FilePathComparison = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
|
||||
StringComparison.OrdinalIgnoreCase :
|
||||
StringComparison.Ordinal;
|
||||
|
||||
public StaticWebAssetsFileProvider(string pathPrefix, string contentRoot)
|
||||
{
|
||||
BasePath = new PathString(pathPrefix.StartsWith("/") ? pathPrefix : "/" + pathPrefix);
|
||||
InnerProvider = new PhysicalFileProvider(contentRoot);
|
||||
}
|
||||
|
||||
public PhysicalFileProvider InnerProvider { get; }
|
||||
|
||||
public PathString BasePath { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDirectoryContents GetDirectoryContents(string subpath)
|
||||
{
|
||||
if (!StartsWithBasePath(subpath, out var physicalPath))
|
||||
{
|
||||
return NotFoundDirectoryContents.Singleton;
|
||||
}
|
||||
else
|
||||
{
|
||||
return InnerProvider.GetDirectoryContents(physicalPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFileInfo GetFileInfo(string subpath)
|
||||
{
|
||||
if (!StartsWithBasePath(subpath, out var physicalPath))
|
||||
{
|
||||
return new NotFoundFileInfo(subpath);
|
||||
}
|
||||
else
|
||||
{
|
||||
return InnerProvider.GetFileInfo(physicalPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IChangeToken Watch(string filter)
|
||||
{
|
||||
return InnerProvider.Watch(filter);
|
||||
}
|
||||
|
||||
private bool StartsWithBasePath(string subpath, out PathString rest)
|
||||
{
|
||||
return new PathString(subpath).StartsWithSegments(BasePath, FilePathComparison, out rest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// 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 Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore
|
||||
{
|
||||
internal class StaticWebAssetsLoader
|
||||
{
|
||||
internal const string StaticWebAssetsManifestName = "Microsoft.AspNetCore.StaticWebAssets.xml";
|
||||
|
||||
internal static void UseStaticWebAssets(IWebHostEnvironment environment)
|
||||
{
|
||||
using (var manifest = ResolveManifest(environment))
|
||||
{
|
||||
if (manifest != null)
|
||||
{
|
||||
UseStaticWebAssetsCore(environment, manifest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void UseStaticWebAssetsCore(IWebHostEnvironment environment, Stream manifest)
|
||||
{
|
||||
var staticWebAssetsFileProvider = new List<IFileProvider>();
|
||||
var webRootFileProvider = environment.WebRootFileProvider;
|
||||
|
||||
var additionalFiles = StaticWebAssetsReader.Parse(manifest)
|
||||
.Select(cr => new StaticWebAssetsFileProvider(cr.BasePath, cr.Path))
|
||||
.OfType<IFileProvider>() // Upcast so we can insert on the resulting list.
|
||||
.ToList();
|
||||
|
||||
if (additionalFiles.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
additionalFiles.Insert(0, webRootFileProvider);
|
||||
environment.WebRootFileProvider = new CompositeFileProvider(additionalFiles);
|
||||
}
|
||||
}
|
||||
|
||||
internal static Stream ResolveManifest(IWebHostEnvironment environment)
|
||||
{
|
||||
// We plan to remove the embedded file resolution code path in
|
||||
// a future preview.
|
||||
Assembly assembly = null;
|
||||
try
|
||||
{
|
||||
assembly = Assembly.Load(environment.ApplicationName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
if (assembly != null && assembly.GetManifestResourceNames().Any(a => a == StaticWebAssetsManifestName))
|
||||
{
|
||||
return assembly.GetManifestResourceStream(StaticWebAssetsManifestName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to physical file as we plan to use a file on disk instead of the embedded resource.
|
||||
var filePath = Path.Combine(Path.GetDirectoryName(GetAssemblyLocation(assembly)), $"{environment.ApplicationName}.StaticWebAssets.xml");
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
return File.OpenRead(filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// A missing manifest might simply mean that the feature is not enabled, so we simply
|
||||
// return early. Misconfigurations will be uncommon given that the entire process is automated
|
||||
// at build time.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetAssemblyLocation(Assembly assembly)
|
||||
{
|
||||
if (Uri.TryCreate(assembly.CodeBase, UriKind.Absolute, out var result) &&
|
||||
result.IsFile && string.IsNullOrWhiteSpace(result.Fragment))
|
||||
{
|
||||
return result.LocalPath;
|
||||
}
|
||||
|
||||
return assembly.Location;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
// 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.Xml.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore
|
||||
{
|
||||
internal static class StaticWebAssetsReader
|
||||
{
|
||||
private const string ManifestRootElementName = "StaticWebAssets";
|
||||
private const string VersionAttributeName = "Version";
|
||||
private const string ContentRootElementName = "ContentRoot";
|
||||
|
||||
internal static IEnumerable<ContentRootMapping> Parse(Stream manifest)
|
||||
{
|
||||
var document = XDocument.Load(manifest);
|
||||
if (!string.Equals(document.Root.Name.LocalName, ManifestRootElementName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid manifest format. Manifest root must be '{ManifestRootElementName}'");
|
||||
}
|
||||
|
||||
var version = document.Root.Attribute(VersionAttributeName);
|
||||
if (version == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid manifest format. Manifest root element must contain a version '{VersionAttributeName}' attribute");
|
||||
}
|
||||
|
||||
if (version.Value != "1.0")
|
||||
{
|
||||
throw new InvalidOperationException($"Unknown manifest version. Manifest version must be '1.0'");
|
||||
}
|
||||
|
||||
foreach (var element in document.Root.Elements())
|
||||
{
|
||||
if (!string.Equals(element.Name.LocalName, ContentRootElementName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid manifest format. Invalid element '{element.Name.LocalName}'. All {StaticWebAssetsLoader.StaticWebAssetsManifestName} child elements must be '{ContentRootElementName}' elements.");
|
||||
}
|
||||
if (!element.IsEmpty)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid manifest format. {ContentRootElementName} can't have content.");
|
||||
}
|
||||
|
||||
var basePath = ParseRequiredAttribute(element, "BasePath");
|
||||
var path = ParseRequiredAttribute(element, "Path");
|
||||
yield return new ContentRootMapping(basePath, path);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ParseRequiredAttribute(XElement element, string attributeName)
|
||||
{
|
||||
var attribute = element.Attribute(attributeName);
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid manifest format. Missing {attributeName} attribute in '{ContentRootElementName}' element.");
|
||||
}
|
||||
return attribute.Value;
|
||||
}
|
||||
|
||||
internal readonly struct ContentRootMapping
|
||||
{
|
||||
public ContentRootMapping(string basePath, string path)
|
||||
{
|
||||
BasePath = basePath;
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public string BasePath { get; }
|
||||
public string Path { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// 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.AspNetCore.Hosting;
|
||||
|
||||
namespace Microsoft.AspNetCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for configuring static web assets for development.
|
||||
/// </summary>
|
||||
public static class StaticWebAssetsWebHostBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the <see cref="IWebHostEnvironment.WebRootFileProvider"/> to use static web assets
|
||||
/// defined by referenced projects and packages.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IWebHostBuilder"/>.</param>
|
||||
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
|
||||
public static IWebHostBuilder UseStaticWebAssets(this IWebHostBuilder builder)
|
||||
{
|
||||
builder.ConfigureAppConfiguration((context, configBuilder) =>
|
||||
{
|
||||
StaticWebAssetsLoader.UseStaticWebAssets(context.HostingEnvironment);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -206,6 +206,13 @@ namespace Microsoft.AspNetCore
|
|||
|
||||
internal static void ConfigureWebDefaults(IWebHostBuilder builder)
|
||||
{
|
||||
builder.ConfigureAppConfiguration((ctx, cb) =>
|
||||
{
|
||||
if (ctx.HostingEnvironment.IsDevelopment())
|
||||
{
|
||||
StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment);
|
||||
}
|
||||
});
|
||||
builder.UseKestrel((builderContext, options) =>
|
||||
{
|
||||
options.Configure(builderContext.Configuration.GetSection("Kestrel"));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
<StaticWebAssets Version="1.0">
|
||||
<ContentRoot Path="/Path" BasePath="/BasePath" />
|
||||
</StaticWebAssets>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
|
|
@ -7,6 +7,8 @@
|
|||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore" />
|
||||
<Reference Include="Microsoft.AspNetCore.TestHost" />
|
||||
<EmbeddedResource Include="Microsoft.AspNetCore.TestHost.StaticWebAssets.xml" LogicalName="Microsoft.AspNetCore.StaticWebAssets.xml" />
|
||||
<Content Include="Microsoft.AspNetCore.TestHost.StaticWebAssets.xml" CopyToOutputDirectory="Always" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
// 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.Runtime.InteropServices;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Tests
|
||||
{
|
||||
public class StaticWebAssetsFileProviderTests
|
||||
{
|
||||
[Fact]
|
||||
public void StaticWebAssetsFileProvider_ConstructorThrows_WhenPathIsNotFound()
|
||||
{
|
||||
// Arrange, Act & Assert
|
||||
var provider = Assert.Throws<DirectoryNotFoundException>(() => new StaticWebAssetsFileProvider("/prefix", "/nonexisting"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaticWebAssetsFileProvider_Constructor_PrependsPrefixWithSlashIfMissing()
|
||||
{
|
||||
// Arrange & Act
|
||||
var provider = new StaticWebAssetsFileProvider("_content", AppContext.BaseDirectory);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/_content", provider.BasePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaticWebAssetsFileProvider_Constructor_DoesNotPrependPrefixWithSlashIfPresent()
|
||||
{
|
||||
// Arrange & Act
|
||||
var provider = new StaticWebAssetsFileProvider("/_content", AppContext.BaseDirectory);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/_content", provider.BasePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetFileInfo_DoesNotMatch_IncompletePrefixSegments()
|
||||
{
|
||||
// Arrange
|
||||
var expectedResult = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
var provider = new StaticWebAssetsFileProvider(
|
||||
"_cont",
|
||||
Path.GetDirectoryName(new Uri(typeof(StaticWebAssetsFileProviderTests).Assembly.CodeBase).LocalPath));
|
||||
|
||||
// Act
|
||||
var file = provider.GetFileInfo("/_content/Microsoft.AspNetCore.TestHost.StaticWebAssets.xml");
|
||||
|
||||
// Assert
|
||||
Assert.False(file.Exists, "File exists");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetFileInfo_Prefix_RespectsOsCaseSensitivity()
|
||||
{
|
||||
// Arrange
|
||||
var expectedResult = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
var provider = new StaticWebAssetsFileProvider(
|
||||
"_content",
|
||||
Path.GetDirectoryName(new Uri(typeof(StaticWebAssetsFileProviderTests).Assembly.CodeBase).LocalPath));
|
||||
|
||||
// Act
|
||||
var file = provider.GetFileInfo("/_CONTENT/Microsoft.AspNetCore.TestHost.StaticWebAssets.xml");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, file.Exists);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDirectoryContents_Prefix_RespectsOsCaseSensitivity()
|
||||
{
|
||||
// Arrange
|
||||
var expectedResult = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
var provider = new StaticWebAssetsFileProvider(
|
||||
"_content",
|
||||
Path.GetDirectoryName(new Uri(typeof(StaticWebAssetsFileProviderTests).Assembly.CodeBase).LocalPath));
|
||||
|
||||
// Act
|
||||
var directory = provider.GetDirectoryContents("/_CONTENT");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, directory.Exists);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
// 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 Microsoft.AspNetCore.Hosting.Internal;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Tests
|
||||
{
|
||||
public class StaticWebAssetsLoaderTests
|
||||
{
|
||||
[Fact]
|
||||
public void UseStaticWebAssetsCore_CreatesCompositeRoot_WhenThereAreContentRootsInTheManifest()
|
||||
{
|
||||
// Arrange
|
||||
var manifestContent = @$"<StaticWebAssets Version=""1.0"">
|
||||
<ContentRoot Path=""{AppContext.BaseDirectory}"" BasePath=""/BasePath"" />
|
||||
</StaticWebAssets>";
|
||||
|
||||
var manifest = CreateManifest(manifestContent);
|
||||
var originalRoot = new NullFileProvider();
|
||||
var environment = new HostingEnvironment()
|
||||
{
|
||||
WebRootFileProvider = originalRoot
|
||||
};
|
||||
|
||||
// Act
|
||||
StaticWebAssetsLoader.UseStaticWebAssetsCore(environment, manifest);
|
||||
|
||||
// Assert
|
||||
var composite = Assert.IsType<CompositeFileProvider>(environment.WebRootFileProvider);
|
||||
Assert.Equal(2, composite.FileProviders.Count());
|
||||
Assert.Equal(originalRoot, composite.FileProviders.First());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseStaticWebAssetsCore_DoesNothing_WhenManifestDoesNotContainEntries()
|
||||
{
|
||||
// Arrange
|
||||
var manifestContent = @$"<StaticWebAssets Version=""1.0"">
|
||||
</StaticWebAssets>";
|
||||
|
||||
var manifest = CreateManifest(manifestContent);
|
||||
var originalRoot = new NullFileProvider();
|
||||
var environment = new HostingEnvironment()
|
||||
{
|
||||
WebRootFileProvider = originalRoot
|
||||
};
|
||||
|
||||
// Act
|
||||
StaticWebAssetsLoader.UseStaticWebAssetsCore(environment, manifest);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(originalRoot, environment.WebRootFileProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveManifest_FindsEmbeddedManifestProvider()
|
||||
{
|
||||
// Arrange
|
||||
var expectedManifest = @"<StaticWebAssets Version=""1.0"">
|
||||
<ContentRoot Path=""/Path"" BasePath=""/BasePath"" />
|
||||
</StaticWebAssets>
|
||||
";
|
||||
var originalRoot = new NullFileProvider();
|
||||
var environment = new HostingEnvironment()
|
||||
{
|
||||
ApplicationName = typeof(StaticWebAssetsReaderTests).Assembly.GetName().Name
|
||||
};
|
||||
|
||||
// Act
|
||||
var manifest = StaticWebAssetsLoader.ResolveManifest(environment);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedManifest, new StreamReader(manifest).ReadToEnd());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveManifest_ManifestFromFile()
|
||||
{
|
||||
// Arrange
|
||||
var expectedManifest = @"<StaticWebAssets Version=""1.0"">
|
||||
<ContentRoot Path=""/Path"" BasePath=""/BasePath"" />
|
||||
</StaticWebAssets>
|
||||
";
|
||||
|
||||
var environment = new HostingEnvironment()
|
||||
{
|
||||
ApplicationName = "Microsoft.AspNetCore.TestHost"
|
||||
};
|
||||
|
||||
// Act
|
||||
var manifest = StaticWebAssetsLoader.ResolveManifest(environment);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedManifest, new StreamReader(manifest).ReadToEnd());
|
||||
}
|
||||
|
||||
private Stream CreateManifest(string manifestContent)
|
||||
{
|
||||
return new MemoryStream(Encoding.UTF8.GetBytes(manifestContent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Tests
|
||||
{
|
||||
public class StaticWebAssetsReaderTests
|
||||
{
|
||||
[Fact]
|
||||
public void ParseManifest_ThrowsFor_EmptyManifest()
|
||||
{
|
||||
// Arrange
|
||||
var manifestContent = @"";
|
||||
var manifest = CreateManifest(manifestContent);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<XmlException>(() => StaticWebAssetsReader.Parse(manifest).ToArray());
|
||||
Assert.StartsWith("Root element is missing.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseManifest_ThrowsFor_UnknownRootElement()
|
||||
{
|
||||
// Arrange
|
||||
var manifestContent = @"<Invalid />";
|
||||
var manifest = CreateManifest(manifestContent);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => StaticWebAssetsReader.Parse(manifest).ToArray());
|
||||
Assert.StartsWith("Invalid manifest", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseManifest_ThrowsFor_MissingVersion()
|
||||
{
|
||||
// Arrange
|
||||
var manifestContent = @"<StaticWebAssets />";
|
||||
var manifest = CreateManifest(manifestContent);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => StaticWebAssetsReader.Parse(manifest).ToArray());
|
||||
Assert.StartsWith("Invalid manifest", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseManifest_ThrowsFor_UnknownVersion()
|
||||
{
|
||||
// Arrange
|
||||
var manifestContent = @"<StaticWebAssets Version=""2.0""/>";
|
||||
var manifest = CreateManifest(manifestContent);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => StaticWebAssetsReader.Parse(manifest).ToArray());
|
||||
Assert.StartsWith("Unknown manifest version", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseManifest_ThrowsFor_InvalidStaticWebAssetsChildren()
|
||||
{
|
||||
// Arrange
|
||||
var manifestContent = @"<StaticWebAssets Version=""1.0"">
|
||||
<Invalid />
|
||||
</StaticWebAssets>";
|
||||
var manifest = CreateManifest(manifestContent);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => StaticWebAssetsReader.Parse(manifest).ToArray());
|
||||
Assert.StartsWith("Invalid manifest", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseManifest_ThrowsFor_MissingBasePath()
|
||||
{
|
||||
// Arrange
|
||||
var manifestContent = @"<StaticWebAssets Version=""1.0"">
|
||||
<ContentRoot Path=""/Path"" />
|
||||
</StaticWebAssets>";
|
||||
|
||||
var manifest = CreateManifest(manifestContent);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => StaticWebAssetsReader.Parse(manifest).ToArray());
|
||||
Assert.StartsWith("Invalid manifest", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseManifest_ThrowsFor_MissingPath()
|
||||
{
|
||||
// Arrange
|
||||
var manifestContent = @"<StaticWebAssets Version=""1.0"">
|
||||
<ContentRoot BasePath=""/BasePath"" />
|
||||
</StaticWebAssets>";
|
||||
|
||||
var manifest = CreateManifest(manifestContent);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => StaticWebAssetsReader.Parse(manifest).ToArray());
|
||||
Assert.StartsWith("Invalid manifest", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseManifest_ThrowsFor_ChildContentRootContent()
|
||||
{
|
||||
// Arrange
|
||||
var manifestContent = @"<StaticWebAssets Version=""1.0"">
|
||||
<ContentRoot Path=""/Path"" BasePath=""/BasePath"">
|
||||
</ContentRoot>
|
||||
</StaticWebAssets>";
|
||||
|
||||
var manifest = CreateManifest(manifestContent);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => StaticWebAssetsReader.Parse(manifest).ToArray());
|
||||
Assert.StartsWith("Invalid manifest", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseManifest_ParsesManifest_WithSingleItem()
|
||||
{
|
||||
// Arrange
|
||||
var manifestContent = @"<StaticWebAssets Version=""1.0"">
|
||||
<ContentRoot Path=""/Path"" BasePath=""/BasePath"" />
|
||||
</StaticWebAssets>";
|
||||
|
||||
var manifest = CreateManifest(manifestContent);
|
||||
|
||||
// Act
|
||||
var mappings = StaticWebAssetsReader.Parse(manifest).ToArray();
|
||||
|
||||
// Assert
|
||||
var mapping = Assert.Single(mappings);
|
||||
Assert.Equal("/Path", mapping.Path);
|
||||
Assert.Equal("/BasePath", mapping.BasePath);
|
||||
}
|
||||
|
||||
private Stream CreateManifest(string manifestContent)
|
||||
{
|
||||
return new MemoryStream(Encoding.UTF8.GetBytes(manifestContent));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue