Initial support for static assets in Razor Class Libraries (dotnet/aspnetcore-tooling#580)

* Imports static assets from packages containing custom msbuild
  targets defining a StaticWebAsset item group.
* Generates an embeds a manifest into the application assembly
  that contains a list of paths to the content roots of the
  assets defined in the packages custom msbuild targets.\n\nCommit migrated from 8e70013b70
This commit is contained in:
Javier Calvarro Nelson 2019-05-21 15:03:00 +02:00 committed by GitHub
parent 111e26dd03
commit 13397dd4d6
21 changed files with 1226 additions and 11 deletions

View File

@ -0,0 +1,184 @@
// 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.Text;
using System.Xml;
using System.Xml.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.AspNetCore.Razor.Tasks
{
public class GenerateStaticWebAssetsManifest : Task
{
private const string ContentRoot = "ContentRoot";
private const string BasePath = "BasePath";
[Required]
public string TargetManifestPath { get; set; }
[Required]
public ITaskItem[] ContentRootDefinitions { get; set; }
public override bool Execute()
{
if (!ValidateArguments())
{
return false;
}
return ExecuteCore();
}
private bool ExecuteCore()
{
var document = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
var root = new XElement(
"StaticWebAssets",
new XAttribute("Version", "1.0"),
CreateNodes());
document.Add(root);
var settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
CloseOutput = true,
OmitXmlDeclaration = true,
Indent = true,
NewLineOnAttributes = false,
Async = true
};
using (var xmlWriter = GetXmlWriter(settings))
{
document.WriteTo(xmlWriter);
}
return !Log.HasLoggedErrors;
}
private IEnumerable<XElement> CreateNodes()
{
var nodes = new List<XElement>();
for (var i = 0; i < ContentRootDefinitions.Length; i++)
{
var contentRootDefinition = ContentRootDefinitions[i];
var basePath = contentRootDefinition.GetMetadata(BasePath);
var contentRoot = contentRootDefinition.GetMetadata(ContentRoot);
// basePath is meant to be a prefix for the files under contentRoot. MSbuild
// normalizes '\' according to the OS, but this is going to be part of the url
// so it needs to always be '/'.
var normalizedBasePath = basePath.Replace("\\", "/");
// At this point we already know that there are no elements with different base paths and same content roots
// or viceversa. Here we simply skip additional items that have the same base path and same content root.
if (!nodes.Exists(e => e.Attribute(BasePath).Value.Equals(normalizedBasePath, StringComparison.OrdinalIgnoreCase)))
{
nodes.Add(new XElement("ContentRoot",
new XAttribute("BasePath", normalizedBasePath),
new XAttribute("Path", contentRoot)));
}
}
return nodes;
}
private XmlWriter GetXmlWriter(XmlWriterSettings settings)
{
var fileStream = new FileStream(TargetManifestPath, FileMode.Create);
return XmlWriter.Create(fileStream, settings);
}
private bool ValidateArguments()
{
for (var i = 0; i < ContentRootDefinitions.Length; i++)
{
var contentRootDefinition = ContentRootDefinitions[i];
if (!EnsureRequiredMetadata(contentRootDefinition, BasePath) ||
!EnsureRequiredMetadata(contentRootDefinition, ContentRoot))
{
return false;
}
}
// We want to validate that there are no different item groups that share either the same base path
// but different content roots or that share the same content root but different base paths.
// We pass in all the static web assets that we discovered to this task without making any distinction for
// duplicates, so here we skip elements for which we are already tracking an element with the same
// content root path and same base path.
// Case-sensitivity depends on the underlying OS so we are not going to do anything to enforce it here.
// Any two items that match base path and content root in a case-insensitive way won't produce an error.
// Any other two items will produce an error even if there is only a casing difference between either the
// base paths or the content roots.
var basePaths = new Dictionary<string, ITaskItem>(StringComparer.OrdinalIgnoreCase);
var contentRootPaths = new Dictionary<string, ITaskItem>(StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < ContentRootDefinitions.Length; i++)
{
var contentRootDefinition = ContentRootDefinitions[i];
var basePath = contentRootDefinition.GetMetadata(BasePath);
var contentRoot = contentRootDefinition.GetMetadata(ContentRoot);
if (basePaths.TryGetValue(basePath, out var existingBasePath))
{
var existingBasePathContentRoot = existingBasePath.GetMetadata(ContentRoot);
if (!string.Equals(contentRoot, existingBasePathContentRoot, StringComparison.OrdinalIgnoreCase))
{
// Case:
// Item1: /_content/Library, /package/aspnetContent1
// Item2: /_content/Library, /package/aspnetContent2
Log.LogError($"Duplicate base paths '{basePath}' for content root paths '{contentRoot}' and '{existingBasePathContentRoot}'. " +
$"('{contentRootDefinition.ItemSpec}', '{existingBasePath.ItemSpec}')");
return false;
}
// It was a duplicate, so we skip it.
// Case:
// Item1: /_content/Library, /package/aspnetContent
// Item2: /_content/Library, /package/aspnetContent
}
else
{
if (contentRootPaths.TryGetValue(contentRoot, out var existingContentRoot))
{
// Case:
// Item1: /_content/Library1, /package/aspnetContent
// Item2: /_content/Library2, /package/aspnetContent
Log.LogError($"Duplicate content root paths '{contentRoot}' for base paths '{basePath}' and '{existingContentRoot.GetMetadata(BasePath)}' " +
$"('{contentRootDefinition.ItemSpec}', '{existingContentRoot.ItemSpec}')");
return false;
}
}
if (!basePaths.ContainsKey(basePath))
{
basePaths.Add(basePath, contentRootDefinition);
}
if (!contentRootPaths.ContainsKey(contentRoot))
{
contentRootPaths.Add(contentRoot, contentRootDefinition);
}
}
return true;
}
private bool EnsureRequiredMetadata(ITaskItem item, string metadataName)
{
var value = item.GetMetadata(metadataName);
if (string.IsNullOrEmpty(value))
{
Log.LogError($"Missing required metadata '{metadataName}' for '{item.ItemSpec}'.");
return false;
}
return true;
}
}
}

View File

@ -0,0 +1,172 @@
<!--
***********************************************************************************************
Microsoft.NET.Sdk.Razor.StaticWebAssets.targets
WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
created a backup copy. Incorrect changes to this file will make it
impossible to load or build your projects from the command-line or the IDE.
Copyright (c) .NET Foundation. All rights reserved.
***********************************************************************************************
-->
<Project ToolsVersion="14.0">
<!-- Targets that support static content scenarios in ASP.NET Core.
The main targets are:
* GenerateStaticWebAssetsManifest: Creates a manifest file to use in development with
the paths to all the references packages and projects content roots.
* ResolveStaticWebAssetsInputs: Collects all the static assets from different sources
* Current project.
* Referenced project.
* Referenced packages.
-->
<UsingTask
TaskName="Microsoft.AspNetCore.Razor.Tasks.GenerateStaticWebAssetsManifest"
AssemblyFile="$(RazorSdkBuildTasksAssembly)"
Condition="'$(RazorSdkBuildTasksAssembly)' != ''" />
<PropertyGroup>
<GenerateStaticWebAssetsManifestDependsOn>
ResolveStaticWebAssetsInputs;
_CreateStaticWebAssetsInputsCacheFile
</GenerateStaticWebAssetsManifestDependsOn>
<AssignTargetPathsDependsOn>
GenerateStaticWebAssetsManifest;
$(AssignTargetPathsDependsOn)
</AssignTargetPathsDependsOn>
</PropertyGroup>
<PropertyGroup>
<_GeneratedStaticWebAssetsInputsCacheFile>$(IntermediateOutputPath)$(TargetName).StaticWebAssets.cache</_GeneratedStaticWebAssetsInputsCacheFile>
<_GeneratedStaticWebAssetsDevelopmentManifest>$(IntermediateOutputPath)$(TargetName).StaticWebAssets.xml</_GeneratedStaticWebAssetsDevelopmentManifest>
</PropertyGroup>
<Target
Name="_CreateStaticWebAssetsInputsCacheFile"
DependsOnTargets="ResolveStaticWebAssetsInputs">
<ItemGroup>
<!--
This is the list of inputs that will be used for generating the manifest used during development.
-->
<_ExternalStaticWebAsset
Include="%(StaticWebAsset.Identity)"
Condition="'%(SourceType)' != ''">
<BasePath>%(StaticWebAsset.BasePath)</BasePath>
<ContentRoot>%(StaticWebAsset.ContentRoot)</ContentRoot>
</_ExternalStaticWebAsset>
</ItemGroup>
<!-- We need a transform here to make sure we hash the metadata -->
<Hash ItemsToHash="@(_ExternalStaticWebAsset->'%(Identity)%(BasePath)%(ContentRoot)')">
<Output TaskParameter="HashResult" PropertyName="_StaticWebAssetsCacheHash" />
</Hash>
<WriteLinesToFile
Lines="$(_StaticWebAssetsCacheHash)"
File="$(_GeneratedStaticWebAssetsInputsCacheFile)"
Overwrite="True"
WriteOnlyWhenDifferent="True" />
<ItemGroup>
<FileWrites Include="$(_GeneratedStaticWebAssetsInputsCacheFile)" />
</ItemGroup>
</Target>
<!--
This target generates a manifest for development time that includes information
about the base path for the referenced package and project static web assets. The
manifest includes the content root and the base path for each of the referenced
packages and projects.
Ideally, each package/project contains a unique base path and a given content
root, but we don't check for duplicates on either of them.
-->
<Target
Name="GenerateStaticWebAssetsManifest"
Inputs="$(_GeneratedStaticWebAssetsInputsCacheFile)"
Outputs="$(_GeneratedStaticWebAssetsDevelopmentManifest)"
DependsOnTargets="$(GenerateStaticWebAssetsManifestDependsOn)">
<GenerateStaticWebAssetsManifest
ContentRootDefinitions="@(_ExternalStaticWebAsset)"
TargetManifestPath="$(_GeneratedStaticWebAssetsDevelopmentManifest)" />
<!-- This is the list of inputs that will be used for generating the manifest used during development. -->
<ItemGroup>
<EmbeddedResource Condition="'@(_ExternalStaticWebAsset->Count())' != '0'"
Include="$(_GeneratedStaticWebAssetsDevelopmentManifest)"
LogicalName="Microsoft.AspNetCore.StaticWebAssets.xml" />
</ItemGroup>
<ItemGroup>
<FileWrites Include="$(_GeneratedStaticWebAssetsDevelopmentManifest)" />
</ItemGroup>
</Target>
<!-- This target collects all the StaticWebAssets from different sources:
* The current project StaticWebAssets that come from wwwroot\** by default.
* Assets from referenced projects that get retrieved invoking an MSBuild target on
the referenced projects.
* Assets from the referenced packages. These will be implicitly included when nuget
restores the package and includes the package props file for the package.
-->
<Target
Name="ResolveStaticWebAssetsInputs">
<PropertyGroup>
<!-- The _SafeBasePath is used as a path segment in the urls that we will
be exposing content from when the library is referenced as a package
or as a project by a web application. Our convention will be to expose
content directly on _content/<<_SafeBasePath>>
We simply remove the dots from the package id so that a package
like Microsoft.AspNetCore.Identity becomes MicrosoftAspNetCoreIdentity
TODO: Investigate if we need to do something more sophisticated here.
-->
<_SafeBasePath>$(PackageId.Replace('.',''))</_SafeBasePath>
</PropertyGroup>
<!-- StaticWebAssets from the current project -->
<ItemGroup>
<!--
Should we promote 'wwwroot\**'' to a property?
We don't want to capture any content outside the content root, that's why we don't do
@(Content) here.
-->
<StaticWebAsset Include="wwwroot\**" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)">
<!-- (PackageReference,ProjectReference,'' (CurrentProject)) -->
<SourceType></SourceType>
<!-- Identifier describing the source, the package id, the project name, empty for the current project. -->
<SourceId></SourceId>
<!--
Full path to the content root for the item:
* For packages it corresponds to %userprofile%/.nuget/packages/<<PackageId>>/<<PackageVersion>>/razorContent
* For referenced projects it corresponds to <<FullProjectRefPath>>/wwwroot
* For the current projects it corresponds to $(MSBuildThisProjectFileDirectory)wwwroot\
-->
<ContentRoot>$(MSBuildProjectDirectory)\wwwroot\</ContentRoot>
<!-- Subsection (folder) from the url space where content for this library will be served. -->
<BasePath>_content\$(_SafeBasePath)\</BasePath>
<!-- Relative path from the content root for the file. At publish time, we combine the BasePath + Relative
path to determine the final path for the file. -->
<RelativePath>%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
</StaticWebAsset>
</ItemGroup>
<!-- StaticWebAssets from referenced projects. -->
<!-- TODO: Include implementation -->
<!-- StaticWebAssets from packages are already available, so we don't do anything. -->
</Target>
</Project>

View File

@ -74,6 +74,10 @@ Copyright (c) .NET Foundation. All rights reserved.
<None Remove="**\*.razor" />
</ItemGroup>
<PropertyGroup>
<EnableRazorSdkContent Condition=" '$(UsingMicrosoftNETSdkWeb)' == '' ">true</EnableRazorSdkContent>
</PropertyGroup>
<Import
Project="$(MSBuildThisFileDirectory)..\..\Sdk\Sdk.Razor.StaticAssets.ProjectSystem.props"
Condition=" '$(EnableRazorSdkContent)' == 'true' " />

View File

@ -338,6 +338,8 @@ Copyright (c) .NET Foundation. All rights reserved.
<Import Project="Microsoft.NET.Sdk.Razor.Component.targets" Condition="'$(_Targeting30OrNewerRazorLangVersion)' == 'true'" />
<Import Project="Microsoft.NET.Sdk.Razor.StaticWebAssets.targets" Condition="'$(_Targeting30OrNewerRazorLangVersion)' == 'true'" />
<Import Project="Microsoft.NET.Sdk.Razor.GenerateAssemblyInfo.targets" />
<Import Project="Microsoft.NET.Sdk.Razor.MvcApplicationPartsDiscovery.targets" Condition="'$(_TargetingNETCoreApp30OrLater)' == 'true'" />
@ -480,6 +482,7 @@ Copyright (c) .NET Foundation. All rights reserved.
'$(EnableDefaultRazorGenerateItems)'=='true'">
<Content Condition="'%(Content.Extension)'=='.cshtml'" Pack="$(IncludeRazorContentInPack)" />
<Content Condition="'%(Content.Extension)'=='.razor'" Pack="$(IncludeRazorContentInPack)" />
</ItemGroup>
</Target>

View File

@ -0,0 +1,296 @@
// 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 Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Tasks
{
public class GenerateStaticWebAssetsManifestTest
{
[Fact]
public void ReturnsError_WhenBasePathIsMissing()
{
// Arrange
var errorMessages = new List<string>();
var buildEngine = new Mock<IBuildEngine>();
buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
.Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
var task = new GenerateStaticWebAssetsManifest
{
BuildEngine = buildEngine.Object,
ContentRootDefinitions = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot", "sample.js"), new Dictionary<string,string>
{
["ContentRoot"] = "/"
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal($"Missing required metadata 'BasePath' for '{Path.Combine("wwwroot", "sample.js")}'.", message);
}
[Fact]
public void ReturnsError_WhenContentRootIsMissing()
{
// Arrange
var errorMessages = new List<string>();
var buildEngine = new Mock<IBuildEngine>();
buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
.Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
var task = new GenerateStaticWebAssetsManifest
{
BuildEngine = buildEngine.Object,
ContentRootDefinitions = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","sample.js"), new Dictionary<string,string>
{
["BasePath"] = "MyLibrary"
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal($"Missing required metadata 'ContentRoot' for '{Path.Combine("wwwroot", "sample.js")}'.", message);
}
[Fact]
public void ReturnsError_ForDuplicateBasePaths()
{
// Arrange
var errorMessages = new List<string>();
var buildEngine = new Mock<IBuildEngine>();
buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
.Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
var task = new GenerateStaticWebAssetsManifest
{
BuildEngine = buildEngine.Object,
ContentRootDefinitions = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","sample.js"), new Dictionary<string,string>
{
["BasePath"] = "MyLibrary",
["ContentRoot"] = Path.Combine("nuget","MyLibrary")
}),
CreateItem(Path.Combine("wwwroot", "otherLib.js"), new Dictionary<string,string>
{
["BasePath"] = "MyLibrary",
["ContentRoot"] = Path.Combine("nuget", "MyOtherLibrary")
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal(
$"Duplicate base paths 'MyLibrary' for content root paths '{Path.Combine("nuget", "MyOtherLibrary")}' and '{Path.Combine("nuget", "MyLibrary")}'. " +
$"('{Path.Combine("wwwroot", "otherLib.js")}', '{Path.Combine("wwwroot", "sample.js")}')",
message);
}
[Fact]
public void ReturnsError_ForDuplicateContentRoots()
{
// Arrange
var errorMessages = new List<string>();
var buildEngine = new Mock<IBuildEngine>();
buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
.Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
var task = new GenerateStaticWebAssetsManifest
{
BuildEngine = buildEngine.Object,
ContentRootDefinitions = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","sample.js"), new Dictionary<string,string>
{
["BasePath"] = "MyLibrary",
["ContentRoot"] = Path.Combine(".", "MyLibrary")
}),
CreateItem(Path.Combine("wwwroot", "otherLib.js"), new Dictionary<string,string>
{
["BasePath"] = "MyOtherLibrary",
["ContentRoot"] = Path.Combine(".", "MyLibrary")
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal(
$"Duplicate content root paths '{Path.Combine(".", "MyLibrary")}' for base paths 'MyOtherLibrary' and 'MyLibrary' " +
$"('{Path.Combine("wwwroot", "otherLib.js")}', '{Path.Combine("wwwroot", "sample.js")}')",
message);
}
[Fact]
public void Generates_EmptyManifest_WhenNoItems_Passed()
{
// Arrange
var file = Path.GetTempFileName();
var expectedDocument = @"<StaticWebAssets Version=""1.0"" />";
try
{
var buildEngine = new Mock<IBuildEngine>();
var task = new GenerateStaticWebAssetsManifest
{
BuildEngine = buildEngine.Object,
ContentRootDefinitions = new TaskItem[] { },
TargetManifestPath = file
};
// Act
var result = task.Execute();
// Assert
Assert.True(result);
var document = File.ReadAllText(file);
Assert.Equal(expectedDocument, document);
}
finally
{
if (File.Exists(file))
{
File.Delete(file);
}
}
}
[Fact]
public void Generates_Manifest_WhenContentRootsAvailable()
{
// Arrange
var file = Path.GetTempFileName();
var expectedDocument = $@"<StaticWebAssets Version=""1.0"">
<ContentRoot BasePath=""MyLibrary"" Path=""{Path.Combine(".", "nuget", "MyLibrary", "razorContent")}"" />
</StaticWebAssets>";
try
{
var buildEngine = new Mock<IBuildEngine>();
var task = new GenerateStaticWebAssetsManifest
{
BuildEngine = buildEngine.Object,
ContentRootDefinitions = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","sample.js"), new Dictionary<string,string>
{
["BasePath"] = "MyLibrary",
["ContentRoot"] = Path.Combine(".", "nuget", "MyLibrary", "razorContent")
}),
},
TargetManifestPath = file
};
// Act
var result = task.Execute();
// Assert
Assert.True(result);
var document = File.ReadAllText(file);
Assert.Equal(expectedDocument, document);
}
finally
{
if (File.Exists(file))
{
File.Delete(file);
}
}
}
[Fact]
public void SkipsAdditionalElements_WithSameBasePathAndSameContentRoot()
{
// Arrange
var file = Path.GetTempFileName();
var expectedDocument = $@"<StaticWebAssets Version=""1.0"">
<ContentRoot BasePath=""Base/MyLibrary"" Path=""{Path.Combine(".", "nuget", "MyLibrary", "razorContent")}"" />
</StaticWebAssets>";
try
{
var buildEngine = new Mock<IBuildEngine>();
var task = new GenerateStaticWebAssetsManifest
{
BuildEngine = buildEngine.Object,
ContentRootDefinitions = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","sample.js"), new Dictionary<string,string>
{
// Base path needs to be normalized to '/' as it goes in the url
["BasePath"] = "Base\\MyLibrary",
["ContentRoot"] = Path.Combine(".", "nuget", "MyLibrary", "razorContent")
}),
// Comparisons are case insensitive
CreateItem(Path.Combine("wwwroot, site.css"), new Dictionary<string,string>
{
["BasePath"] = "Base\\MyLIBRARY",
["ContentRoot"] = Path.Combine(".", "nuget", "MyLIBRARY", "razorContent")
}),
},
TargetManifestPath = file
};
// Act
var result = task.Execute();
// Assert
Assert.True(result);
var document = File.ReadAllText(file);
Assert.Equal(expectedDocument, document);
}
finally
{
if (File.Exists(file))
{
File.Delete(file);
}
}
}
private static TaskItem CreateItem(
string spec,
IDictionary<string, string> metadata)
{
var result = new TaskItem(spec);
foreach (var (key, value) in metadata)
{
result.SetMetadata(key, value);
}
return result;
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
@ -344,6 +345,58 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
}
}
public static Stream ContainsEmbeddedResource(string assemblyPath, string resourceName)
{
var stream = ExtractEmbeddedResource(assemblyPath, resourceName);
Assert.NotNull(stream);
return stream;
}
public static void DoesNotContainEmbeddedResource(string assemblyPath, string resourceName)
{
var stream = ExtractEmbeddedResource(assemblyPath, resourceName);
Assert.Null(stream);
}
private static Stream ExtractEmbeddedResource(string path, string expectedResourceName)
{
using (var peStream = File.OpenRead(path))
{
using (var peReader = new PEReader(peStream))
{
var mdReader = peReader.GetMetadataReader();
foreach (var resourceHandle in mdReader.ManifestResources)
{
var resource = mdReader.GetManifestResource(resourceHandle);
if (!resource.Implementation.IsNil)
{
continue; // resource is not embedded.
}
var resourceName = mdReader.GetString(resource.Name);
if (!string.Equals(expectedResourceName, resourceName))
{
continue;
}
// We are not taking resource.Offset into account here.
// We currently only have the casuistic that we are embedding a single resource.
// If that changes we'll have to change this test code, but as its hard we won't do it for now.
var resourcesSection = peReader.GetSectionData(peReader.PEHeaders.CorHeader.ResourcesDirectory.RelativeVirtualAddress);
var resourcesReader = resourcesSection.GetReader();
var resourceSizeInBytes = resourcesReader.ReadInt32();
var resourceBytes = resourcesReader.ReadBytes(resourceSizeInBytes);
return new MemoryStream(resourceBytes, writable: false);
}
}
}
return null;
}
public static void NuspecContains(MSBuildResult result, string nuspecPath, string expected)
{
if (result == null)

View File

@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -13,6 +15,9 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
public abstract class MSBuildIntegrationTestBase
{
internal static readonly string LocalNugetPackagesCacheTempPath =
Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()) + Path.DirectorySeparatorChar;
private static readonly AsyncLocal<ProjectDirectory> _project = new AsyncLocal<ProjectDirectory>();
private static readonly AsyncLocal<string> _projectTfm = new AsyncLocal<string>();
@ -42,6 +47,10 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
set { _project.Value = value; }
}
// Whether to use a local cache or not to prevent polluting the global cache
// with test packages.
public bool UseLocalPackageCache { get; set; }
protected string RazorIntermediateOutputPath => Path.Combine(IntermediateOutputPath, "Razor");
protected string RazorComponentIntermediateOutputPath => Path.Combine(IntermediateOutputPath, "RazorDeclaration");
@ -64,18 +73,42 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
bool runRestoreBeforeBuildOrPublish = true)
{
var timeout = suppressTimeout ? (TimeSpan?)Timeout.InfiniteTimeSpan : null;
// Additional restore sources for packages used in testing
var additionalRestoreSources = string.Join(
',',
typeof(PackageTestProjectsFixture).Assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
.Where(a => a.Key == "Testing.AdditionalRestoreSources")
.Select(a => a.Value)
.ToArray());
var buildArgumentList = new List<string>
{
// Disable node-reuse. We don't want msbuild processes to stick around
// once the test is completed.
"/nr:false",
// Always generate a bin log for debugging purposes
"/bl",
// Let the test app know it is running as part of a test.
"/p:RunningAsTest=true",
$"/p:MicrosoftNETCoreApp30PackageVersion={BuildVariables.MicrosoftNETCoreApp30PackageVersion}",
// Additional restore sources for projects that require built packages
$"/p:RuntimeAdditionalRestoreSources={additionalRestoreSources}",
};
if (UseLocalPackageCache)
{
if (!Directory.Exists(LocalNugetPackagesCacheTempPath))
{
// The local cache folder needs to exist so that nuget
Directory.CreateDirectory(LocalNugetPackagesCacheTempPath);
}
}
if (!suppressBuildServer)
{
buildArgumentList.Add($@"/p:_RazorBuildServerPipeName=""{buildServerPipeName ?? BuildServer.PipeName}""");
@ -110,7 +143,8 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Project,
buildArguments,
timeout,
msBuildProcessKind);
msBuildProcessKind,
UseLocalPackageCache ? LocalNugetPackagesCacheTempPath : null);
}
internal void AddProjectFileContent(string content)

View File

@ -3,6 +3,9 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -16,7 +19,8 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
ProjectDirectory project,
string arguments,
TimeSpan? timeout = null,
MSBuildProcessKind msBuildProcessKind = MSBuildProcessKind.Dotnet)
MSBuildProcessKind msBuildProcessKind = MSBuildProcessKind.Dotnet,
string localPackageCache = null)
{
var processStartInfo = new ProcessStartInfo()
{
@ -26,6 +30,11 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
RedirectStandardOutput = true,
};
if (localPackageCache != null)
{
processStartInfo.Environment.Add("NUGET_PACKAGES", localPackageCache);
}
if (msBuildProcessKind == MSBuildProcessKind.Desktop)
{
if (string.IsNullOrEmpty(BuildVariables.MSBuildPath))

View File

@ -0,0 +1,261 @@
// 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;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.CommandLineUtils;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
public class StaticWebAssetsIntegrationTest : MSBuildIntegrationTestBase, IClassFixture<BuildServerTestFixture>, IClassFixture<PackageTestProjectsFixture>, IAsyncLifetime
{
public StaticWebAssetsIntegrationTest(
BuildServerTestFixture buildServer,
PackageTestProjectsFixture packageTestProjects,
ITestOutputHelper output)
: base(buildServer)
{
UseLocalPackageCache = true;
PackageTestProjects = packageTestProjects;
Output = output;
}
public PackageTestProjectsFixture PackageTestProjects { get; private set; }
public ITestOutputHelper Output { get; private set; }
[Fact]
[InitializeTestProject("AppWithPackageAndP2PReference")]
public async Task Build_GeneratesStaticWebAssetsManifest_Success_CreatesManifest()
{
var result = await DotnetMSBuild("Build", "/restore");
var expectedManifest = GetExpectedManifest();
Assert.BuildPassed(result);
// GenerateStaticWebAssetsManifest should generate the manifest and the cache.
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache");
var path = Assert.FileExists(result, OutputPath, "AppWithPackageAndP2PReference.dll");
var assembly = Assert.ContainsEmbeddedResource(path, "Microsoft.AspNetCore.StaticWebAssets.xml");
using (var reader = new StreamReader(assembly))
{
var data = await reader.ReadToEndAsync();
Assert.Equal(expectedManifest, data);
}
}
[Fact]
[InitializeTestProject("SimpleMvc")]
public async Task Build_DoesNotEmbedManifestWhen_NoStaticResourcesAvailable()
{
var result = await DotnetMSBuild("Build", "/restore");
Assert.BuildPassed(result);
// GenerateStaticWebAssetsManifest should generate the manifest and the cache.
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.StaticWebAssets.cache");
var path = Assert.FileExists(result, OutputPath, "SimpleMvc.dll");
Assert.DoesNotContainEmbeddedResource(path, "SimpleMvc.StaticWebAssets.xml");
}
[Fact]
[InitializeTestProject("AppWithPackageAndP2PReference")]
public async Task Clean_Success_RemovesManifestAndCache()
{
var result = await DotnetMSBuild("Build", "/restore");
Assert.BuildPassed(result);
// GenerateStaticWebAssetsManifest should generate the manifest and the cache.
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache");
var cleanResult = await DotnetMSBuild("Clean");
Assert.BuildPassed(cleanResult);
// Clean should delete the manifest and the cache.
Assert.FileDoesNotExist(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache");
Assert.FileDoesNotExist(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
}
[Fact]
[InitializeTestProject("AppWithPackageAndP2PReference")]
public async Task Rebuild_Success_RecreatesManifestAndCache()
{
// Arrange
var result = await DotnetMSBuild("Build", "/restore");
var expectedManifest = GetExpectedManifest();
Assert.BuildPassed(result);
// GenerateStaticWebAssetsManifest should generate the manifest and the cache.
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache");
var directoryPath = Path.Combine(result.Project.DirectoryPath, IntermediateOutputPath);
var thumbPrints = new Dictionary<string, FileThumbPrint>();
var thumbPrintFiles = new[]
{
Path.Combine(directoryPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml"),
Path.Combine(directoryPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache"),
};
foreach (var file in thumbPrintFiles)
{
var thumbprint = GetThumbPrint(file);
thumbPrints[file] = thumbprint;
}
// Act
var rebuild = await DotnetMSBuild("Rebuild");
// Assert
Assert.BuildPassed(rebuild);
foreach (var file in thumbPrintFiles)
{
var thumbprint = GetThumbPrint(file);
Assert.NotEqual(thumbPrints[file], thumbprint);
}
var path = Assert.FileExists(result, OutputPath, "AppWithPackageAndP2PReference.dll");
var assembly = Assert.ContainsEmbeddedResource(path, "Microsoft.AspNetCore.StaticWebAssets.xml");
using (var reader = new StreamReader(assembly))
{
var data = reader.ReadToEnd();
Assert.Equal(expectedManifest, data);
}
}
[Fact]
[InitializeTestProject("AppWithPackageAndP2PReference")]
public async Task GenerateStaticWebAssetsManifest_IncrementalBuild_ReusesManifest()
{
var result = await DotnetMSBuild("GenerateStaticWebAssetsManifest", "/restore");
Assert.BuildPassed(result);
// GenerateStaticWebAssetsManifest should generate the manifest and the cache.
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache");
var directoryPath = Path.Combine(result.Project.DirectoryPath, IntermediateOutputPath);
var thumbPrints = new Dictionary<string, FileThumbPrint>();
var thumbPrintFiles = new[]
{
Path.Combine(directoryPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml"),
Path.Combine(directoryPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache"),
};
foreach (var file in thumbPrintFiles)
{
var thumbprint = GetThumbPrint(file);
thumbPrints[file] = thumbprint;
}
// Act
var incremental = await DotnetMSBuild("GenerateStaticWebAssetsManifest");
// Assert
Assert.BuildPassed(incremental);
foreach (var file in thumbPrintFiles)
{
var thumbprint = GetThumbPrint(file);
Assert.Equal(thumbPrints[file], thumbprint);
}
}
public Task InitializeAsync()
{
return PackageTestProjects.PackAsync(Output);
}
public Task DisposeAsync()
{
return Task.CompletedTask;
}
private string GetExpectedManifest()
{
var restorePath = LocalNugetPackagesCacheTempPath;
var projects = new[]
{
Path.Combine(restorePath, "packagelibrarytransitivedependency", "1.0.0", "buildTransitive", "..", "razorContent") + Path.DirectorySeparatorChar,
Path.Combine(restorePath, "packagelibrarydirectdependency", "1.0.0", "build", "..", "razorContent") + Path.DirectorySeparatorChar
};
return $@"<StaticWebAssets Version=""1.0"">
<ContentRoot BasePath=""_content/PackageLibraryTransitiveDependency"" Path=""{projects[0]}"" />
<ContentRoot BasePath=""_content/PackageLibraryDirectDependency"" Path=""{projects[1]}"" />
</StaticWebAssets>";
}
}
public class PackageTestProjectsFixture
{
private bool _packed;
internal async Task PackAsync(ITestOutputHelper output)
{
if (_packed)
{
return;
}
var projectsToPack = GetProjectsToPack();
foreach (var project in projectsToPack)
{
output.WriteLine(project);
}
foreach (var project in projectsToPack)
{
var psi = new ProcessStartInfo
{
FileName = DotNetMuxer.MuxerPathOrDefault(),
#if DEBUG
Arguments = "msbuild /t:Restore;Pack /p:Configuration=Debug",
#else
Arguments = "msbuild /t:Restore;Pack /p:Configuration=Release",
#endif
WorkingDirectory = project,
RedirectStandardOutput = true,
RedirectStandardError = true
};
var result = await MSBuildProcessManager.RunProcessCoreAsync(
psi,
TimeSpan.FromMinutes(2));
output.WriteLine(result.Output);
Assert.Equal(0, result.ExitCode);
}
_packed = true;
}
public static string[] GetProjectsToPack()
{
return typeof(PackageTestProjectsFixture).Assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
.Where(a => a.Key == "Testing.ProjectToPack")
.Select(a => a.Value)
.ToArray();
}
}
}

View File

@ -21,11 +21,30 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCorePackageVersion)" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(MicrosoftExtensionsDependencyModelPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" />
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>Testing.ProjectToPack</_Parameter1>
<_Parameter2>$(MSBuildThisFileDirectory)..\testapps\PackageLibraryDirectDependency</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>Testing.ProjectToPack</_Parameter1>
<_Parameter2>$(MSBuildThisFileDirectory)..\testapps\PackageLibraryTransitiveDependency</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>Testing.AdditionalRestoreSources</_Parameter1>
<_Parameter2>$(MSBuildThisFileDirectory)..\testapps\TestPackageRestoreSource</_Parameter2>
</AssemblyAttribute>
</ItemGroup>
<!-- The test projects rely on these binaries being available -->
<ItemGroup>
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="$(SystemDiagnosticsDiagnosticSourcePackageVersion)" />
@ -51,11 +70,7 @@
Inputs="$(MSBuildAllProjects)"
Outputs="$(MSBuildLocationFileOutput)">
<Exec
Condition="'$(OS)' == 'Windows_NT'"
Command='"$(NuGetPackageRoot)vswhere\$(VSWhereVersion)\tools\vswhere.exe" -latest -prerelease -property installationPath -requires Microsoft.Component.MSBuild'
ConsoleToMsBuild="true"
StandardErrorImportance="high">
<Exec Condition="'$(OS)' == 'Windows_NT'" Command="&quot;$(NuGetPackageRoot)vswhere\$(VSWhereVersion)\tools\vswhere.exe&quot; -latest -prerelease -property installationPath -requires Microsoft.Component.MSBuild" ConsoleToMsBuild="true" StandardErrorImportance="high">
<Output TaskParameter="ConsoleOutput" PropertyName="_VSInstallDir" />
</Exec>
<Error Condition="'$(OS)' == 'Windows_NT' and '$(_VSInstallDir)'==''" Text="Visual Studio not found on Windows." />

View File

@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<RazorSdkDirectoryRoot>$(RazorSdkArtifactsDirectory)$(Configuration)\sdk-output\</RazorSdkDirectoryRoot>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<RuntimeAdditionalRestoreSources Condition="'$(RuntimeAdditionalRestoreSources)' == ''">$(MSBuildThisFileDirectory)..\TestPackageRestoreSource\</RuntimeAdditionalRestoreSources>
<RestoreSources>
$(RestoreSources);
$(RuntimeAdditionalRestoreSources)
</RestoreSources>
</PropertyGroup>
<ItemGroup>
<!-- Avoid referencing the AspNetCore framework since we want the ability to target work-in-progress shims -->
<FrameworkReference Remove="Microsoft.AspNetCore.App" />
</ItemGroup>
<PropertyGroup Condition="'$(RunningAsTest)' == ''">
<!-- We don't want to run build server when not running as tests. -->
<UseRazorBuildServer>false</UseRazorBuildServer>
</PropertyGroup>
<!-- Test Placeholder -->
<ItemGroup>
<PackageReference Include="PackageLibraryDirectDependency" Version="1.0.0" />
</ItemGroup>
<PropertyGroup Condition="'$(BinariesRoot)'==''">
<!-- In test scenarios $(BinariesRoot) is defined in a generated Directory.Build.props file -->
<BinariesRoot>$(RepositoryRoot)artifacts\bin\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\$(Configuration)\netstandard2.0\</BinariesRoot>
</PropertyGroup>
<ItemGroup Condition="'$(BinariesRoot)'!=''">
<Reference Include="$(BinariesRoot)\Microsoft.AspNetCore.Razor.Test.ComponentShim.dll" />
<Reference Include="$(BinariesRoot)\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.dll" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@

namespace AppWithP2PReference
{
public class Program
{
public static void Main(string[] args)
{
// Just make sure we have a reference to the MvcShim
var t = typeof(Microsoft.AspNetCore.Mvc.IActionResult);
System.Console.WriteLine(t.FullName);
}
}
}

View File

@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<RazorSdkDirectoryRoot>$(RazorSdkArtifactsDirectory)$(Configuration)\sdk-output\</RazorSdkDirectoryRoot>
</PropertyGroup>
<PropertyGroup>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<Copyright>© Microsoft</Copyright>
<Product>Razor Test</Product>
<Company>Microsoft</Company>
<Description>PackageLibraryDirectDependency Description</Description>
<PackageOutputPath>$(MSBuildThisFileDirectory)..\TestPackageRestoreSource</PackageOutputPath>
<DeterministicSourcePaths>false</DeterministicSourcePaths>
</PropertyGroup>
<PropertyGroup Condition="'$(RunningAsTest)' == ''">
<!-- We don't want to run build server when not running as tests. -->
<UseRazorBuildServer>false</UseRazorBuildServer>
</PropertyGroup>
<PropertyGroup Condition="'$(BinariesRoot)'==''">
<!-- In test scenarios $(BinariesRoot) is defined in a generated Directory.Build.props file -->
<BinariesRoot>$(RepositoryRoot)artifacts\bin\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\$(Configuration)\netstandard2.0\</BinariesRoot>
</PropertyGroup>
<ItemGroup Condition="'$(BinariesRoot)'!=''">
<Reference Include="$(BinariesRoot)\Microsoft.AspNetCore.Razor.Test.ComponentShim.dll"/>
<Reference Include="$(BinariesRoot)\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.dll"/>
</ItemGroup>
<!-- Test Placeholder -->
<ItemGroup>
<Content Include="build\**" Pack="true" PackagePath="build" />
<!-- We will remove the line below when we do the item to support packing -->
<Content Update="wwwroot\**" Pack="true" PackagePath="razorContent" />
<ProjectReference Include="..\PackageLibraryTransitiveDependency\PackageLibraryTransitiveDependency.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,11 @@
<Project>
<ItemGroup>
<StaticWebAsset Include="$(MSBuildThisFileDirectory)..\razorContent\**">
<SourceType>Package</SourceType>
<SourceId>PackageLibraryDirectDependency</SourceId>
<ContentRoot>$([MSBuild]::EnsureTrailingSlash('$(MSBuildThisFileDirectory)..\razorContent'))</ContentRoot>
<BasePath>_content\PackageLibraryDirectDependency</BasePath>
<RelativePath>%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
</StaticWebAsset>
</ItemGroup>
</Project>

View File

@ -0,0 +1 @@
div.fluent { display: inline-block }

View File

@ -0,0 +1,3 @@
(function () {
document.getElementById('pkg-direct-dep').innerHTML = 'pkg-direct-dep';
})()

View File

@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<RazorSdkDirectoryRoot>$(RazorSdkArtifactsDirectory)$(Configuration)\sdk-output\</RazorSdkDirectoryRoot>
</PropertyGroup>
<PropertyGroup>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Copyright>© Microsoft</Copyright>
<Product>Razor Test</Product>
<Company>Microsoft</Company>
<Description>PackageLibraryTransitiveDependency Description</Description>
<PackageOutputPath>$(MSBuildThisFileDirectory)..\TestPackageRestoreSource</PackageOutputPath>
<DeterministicSourcePaths>false</DeterministicSourcePaths>
</PropertyGroup>
<PropertyGroup Condition="'$(RunningAsTest)' == ''">
<!-- We don't want to run build server when not running as tests. -->
<UseRazorBuildServer>false</UseRazorBuildServer>
</PropertyGroup>
<PropertyGroup Condition="'$(BinariesRoot)'==''">
<!-- In test scenarios $(BinariesRoot) is defined in a generated Directory.Build.props file -->
<BinariesRoot>$(RepositoryRoot)artifacts\bin\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\$(Configuration)\netstandard2.0\</BinariesRoot>
</PropertyGroup>
<ItemGroup Condition="'$(BinariesRoot)'!=''">
<Reference Include="$(BinariesRoot)\Microsoft.AspNetCore.Razor.Test.ComponentShim.dll"/>
<Reference Include="$(BinariesRoot)\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.dll"/>
</ItemGroup>
<!-- Test Placeholder -->
<ItemGroup>
<Content Include="build\**" Pack="true" PackagePath="buildTransitive" />
<!-- We will remove the line below when we do the item to support packing -->
<Content Update="wwwroot\**" Pack="true" PackagePath="razorContent" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,11 @@
<Project>
<ItemGroup>
<StaticWebAsset Include="$(MSBuildThisFileDirectory)..\razorContent\**">
<SourceType>Package</SourceType>
<SourceId>PackageLibraryTransitiveDependency</SourceId>
<ContentRoot>$([MSBuild]::EnsureTrailingSlash('$(MSBuildThisFileDirectory)..\razorContent'))</ContentRoot>
<BasePath>_content\PackageLibraryTransitiveDependency</BasePath>
<RelativePath>%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
</StaticWebAsset>
</ItemGroup>
</Project>

View File

@ -0,0 +1,3 @@
(function () {
document.getElementById('pkg-transitive-dep').innerHTML = 'pkg-transitive-dep';
})()

View File

@ -4,6 +4,12 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SimpleMvc11NetFx\SimpleMvc11NetFx.csproj" />
<ProjectReference Include="..\SimpleMvc11\SimpleMvc11.csproj" />
<ProjectReference Include="..\SimpleMvc21\SimpleMvc21.csproj" />
<ProjectReference Include="..\SimpleMvc22\SimpleMvc22.csproj" />
<ProjectReference Include="..\ClassLibraryMvc21\ClassLibraryMvc21.csproj" />
<ProjectReference Include="..\AppWithP2PReference\AppWithP2PReference.csproj" />
<ProjectReference Include="..\ClassLibrary\ClassLibrary.csproj" />
<ProjectReference Include="..\ClassLibrary2\ClassLibrary2.csproj" />
@ -14,12 +20,15 @@
<ProjectReference Include="..\MvcWithComponents\MvcWithComponents.csproj" />
<ProjectReference Include="..\SimpleMvcFSharp\SimpleMvcFSharp.fsproj" />
<ProjectReference Include="..\SimpleMvc\SimpleMvc.csproj" />
<ProjectReference Include="..\SimpleMvc11NetFx\SimpleMvc11NetFx.csproj" />
<ProjectReference Include="..\SimpleMvc11\SimpleMvc11.csproj" />
<ProjectReference Include="..\SimpleMvc21\SimpleMvc21.csproj" />
<ProjectReference Include="..\SimpleMvc22\SimpleMvc22.csproj" />
<ProjectReference Include="..\SimplePages\SimplePages.csproj" />
<!--
We don't add AppWithPackageAndP2PReference, PackageLibraryDirectDependency or PackageLibraryTransitiveDependency here
as PackageLibraryDirectDependency and PackageLibraryTransitiveDependency are test packages that
need to be produced on the fly and AppWithPackageAndP2PReference depends on them so adding them here
will make restore fail.
-->
<ProjectReference Include="..\..\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.csproj"/>
<ProjectReference Include="..\..\Microsoft.AspNetCore.Razor.Test.ComponentShim\Microsoft.AspNetCore.Razor.Test.ComponentShim.csproj"/>
</ItemGroup>