Do build time discovery of MVC ApplicationParts (dotnet/aspnetcore-tooling#598)

* Do build time discovery of MVC ApplicationParts
\n\nCommit migrated from e79d5600f7
This commit is contained in:
Pranav K 2019-05-17 13:14:41 -07:00 committed by GitHub
parent 96475e488d
commit b5a7daba2b
12 changed files with 653 additions and 6 deletions

View File

@ -0,0 +1,14 @@
// 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.AspNetCore.Razor.Tasks
{
public class AssemblyItem
{
public string Path { get; set; }
public bool IsSystemReference { get; set; }
public string AssemblyName { get; set; }
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Description>Razor is a markup syntax for adding server-side logic to web pages. This package contains MSBuild support for Razor.</Description> <Description>Razor is a markup syntax for adding server-side logic to web pages. This package contains MSBuild support for Razor.</Description>
<TargetFrameworks>netcoreapp3.0;netstandard2.0;net46</TargetFrameworks> <TargetFrameworks>netcoreapp3.0;net46</TargetFrameworks>
<TargetName>Microsoft.NET.Sdk.Razor.Tasks</TargetName> <TargetName>Microsoft.NET.Sdk.Razor.Tasks</TargetName>
<NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile> <NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile>
@ -17,8 +17,9 @@
<PackageReference Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildFrameworkPackageVersion)" /> <PackageReference Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildFrameworkPackageVersion)" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCorePackageVersion)" /> <PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCorePackageVersion)" />
<PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion)" /> <PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion)" />
<PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataPackageVersion)" Condition="'$(TargetFramework)'=='net46'" />
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" ReferenceOutputAssembly="false" Condition="'$(TargetFramework)'=='netstandard2.0'" /> <ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" ReferenceOutputAssembly="false" Condition="'$(TargetFramework)'=='netcoreapp3.0'" />
<ProjectReference Include="..\Microsoft.AspNetCore.Razor.Tools\Microsoft.AspNetCore.Razor.Tools.csproj" ReferenceOutputAssembly="false" Condition="'$(TargetFramework)'=='netcoreapp3.0'" /> <ProjectReference Include="..\Microsoft.AspNetCore.Razor.Tools\Microsoft.AspNetCore.Razor.Tools.csproj" ReferenceOutputAssembly="false" Condition="'$(TargetFramework)'=='netcoreapp3.0'" />
</ItemGroup> </ItemGroup>
@ -54,7 +55,10 @@
<ItemGroup> <ItemGroup>
<ProjectOutput Include="$(ArtifactsBinDir)Microsoft.NET.Sdk.Razor\$(Configuration)\net46*\Microsoft.NET.Sdk.Razor.Tasks.*" /> <ProjectOutput Include="$(ArtifactsBinDir)Microsoft.NET.Sdk.Razor\$(Configuration)\net46*\Microsoft.NET.Sdk.Razor.Tasks.*" />
<ProjectOutput Include="$(ArtifactsBinDir)Microsoft.NET.Sdk.Razor\$(Configuration)\netstandard2.0*\Microsoft.NET.Sdk.Razor.Tasks.*" /> <ProjectOutput Include="$(ArtifactsBinDir)Microsoft.NET.Sdk.Razor\$(Configuration)\netcoreapp3.0*\Microsoft.NET.Sdk.Razor.Tasks.*" />
<ProjectOutput Include="$(ArtifactsBinDir)Microsoft.NET.Sdk.Razor\$(Configuration)\net46*\System.Collections.Immutable.dll" />
<ProjectOutput Include="$(ArtifactsBinDir)Microsoft.NET.Sdk.Razor\$(Configuration)\net46*\System.Reflection.Metadata.dll" />
</ItemGroup> </ItemGroup>
<Copy SourceFiles="@(ProjectOutput)" DestinationFiles="$(SdkOutputPath)tasks\%(RecursiveDir)%(FileName)%(Extension)" SkipUnchangedFiles="true"> <Copy SourceFiles="@(ProjectOutput)" DestinationFiles="$(SdkOutputPath)tasks\%(RecursiveDir)%(FileName)%(Extension)" SkipUnchangedFiles="true">

View File

@ -0,0 +1,169 @@
// 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.Reflection.Metadata;
using System.Reflection.PortableExecutable;
namespace Microsoft.AspNetCore.Razor.Tasks
{
/// <summary>
/// Resolves assemblies that reference one of the specified "targetAssemblies" either directly or transitively.
/// </summary>
public class ReferenceResolver
{
private readonly HashSet<string> _mvcAssemblies;
private readonly Dictionary<string, ClassifiedAssemblyItem> _lookup = new Dictionary<string, ClassifiedAssemblyItem>(StringComparer.Ordinal);
public ReferenceResolver(IReadOnlyList<string> targetAssemblies, IReadOnlyList<AssemblyItem> assemblyItems)
{
_mvcAssemblies = new HashSet<string>(targetAssemblies, StringComparer.Ordinal);
foreach (var item in assemblyItems)
{
var classifiedItem = new ClassifiedAssemblyItem(item);
_lookup[item.AssemblyName] = classifiedItem;
}
}
public IReadOnlyList<string> ResolveAssemblies()
{
var applicationParts = new List<string>();
foreach (var item in _lookup)
{
var classification = Resolve(item.Value);
if (classification == DependencyClassification.ReferencesMvc)
{
applicationParts.Add(item.Key);
}
// It's not interesting for us to know if a dependency has a classification of MvcReference.
// All applications targeting the Microsoft.AspNetCore.App will have have a reference to Mvc.
}
return applicationParts;
}
private DependencyClassification Resolve(ClassifiedAssemblyItem classifiedItem)
{
if (classifiedItem.DependencyClassification != DependencyClassification.Unknown)
{
return classifiedItem.DependencyClassification;
}
if (classifiedItem.AssemblyItem == null)
{
// We encountered a dependency that isn't part of this assembly's dependency set. We'll see if it happens to be an MVC assembly.
// This might be useful in scenarios where the app does not have a framework reference at the entry point,
// but the transitive dependency does.
classifiedItem.DependencyClassification = _mvcAssemblies.Contains(classifiedItem.Name) ?
DependencyClassification.MvcReference :
DependencyClassification.DoesNotReferenceMvc;
return classifiedItem.DependencyClassification;
}
if (classifiedItem.AssemblyItem.IsSystemReference)
{
// We do not allow transitive references to MVC via a framework reference to count.
// e.g. depending on Microsoft.AspNetCore.SomeThingNewThatDependsOnMvc would not result in an assembly being treated as
// referencing MVC.
classifiedItem.DependencyClassification = _mvcAssemblies.Contains(classifiedItem.Name) ?
DependencyClassification.MvcReference :
DependencyClassification.DoesNotReferenceMvc;
return classifiedItem.DependencyClassification;
}
if (_mvcAssemblies.Contains(classifiedItem.Name))
{
classifiedItem.DependencyClassification = DependencyClassification.MvcReference;
return classifiedItem.DependencyClassification;
}
var dependencyClassification = DependencyClassification.DoesNotReferenceMvc;
foreach (var assemblyItem in GetReferences(classifiedItem.AssemblyItem.Path))
{
var classification = Resolve(assemblyItem);
if (classification == DependencyClassification.MvcReference || classification == DependencyClassification.ReferencesMvc)
{
dependencyClassification = DependencyClassification.ReferencesMvc;
break;
}
}
classifiedItem.DependencyClassification = dependencyClassification;
return dependencyClassification;
}
protected virtual IReadOnlyList<ClassifiedAssemblyItem> GetReferences(string file)
{
try
{
using var peReader = new PEReader(File.OpenRead(file));
if (!peReader.HasMetadata)
{
return Array.Empty<ClassifiedAssemblyItem>(); // not a managed assembly
}
var metadataReader = peReader.GetMetadataReader();
var assemblyItems = new List<ClassifiedAssemblyItem>();
foreach (var handle in metadataReader.AssemblyReferences)
{
var reference = metadataReader.GetAssemblyReference(handle);
var referenceName = metadataReader.GetString(reference.Name);
if (_lookup.TryGetValue(referenceName, out var classifiedItem))
{
assemblyItems.Add(classifiedItem);
}
else
{
// A dependency references an item that isn't referenced by this project.
// We'll construct an item for so that we can calculate the classification based on it's name.
assemblyItems.Add(new ClassifiedAssemblyItem(referenceName));
}
}
return assemblyItems;
}
catch (BadImageFormatException)
{
// not a PE file, or invalid metadata
}
return Array.Empty<ClassifiedAssemblyItem>(); // not a managed assembly
}
protected enum DependencyClassification
{
Unknown,
DoesNotReferenceMvc,
ReferencesMvc,
MvcReference,
}
protected class ClassifiedAssemblyItem
{
public ClassifiedAssemblyItem(AssemblyItem classifiedItem)
: this(classifiedItem.AssemblyName)
{
AssemblyItem = classifiedItem;
}
public ClassifiedAssemblyItem(string name)
{
Name = name;
}
public string Name { get; }
public AssemblyItem AssemblyItem { get; }
public DependencyClassification DependencyClassification { get; set; }
}
}
}

View File

@ -0,0 +1,55 @@
// 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.Reflection;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.AspNetCore.Razor.Tasks
{
public class FindAssembliesWithReferencesTo : Task
{
[Required]
public ITaskItem[] TargetAssemblyNames { get; set; }
[Required]
public ITaskItem[] Assemblies { get; set; }
[Output]
public string[] ResolvedAssemblies { get; set; }
public override bool Execute()
{
var referenceItems = new List<AssemblyItem>();
foreach (var item in Assemblies)
{
const string FusionNameKey = "FusionName";
var fusionName = item.GetMetadata(FusionNameKey);
if (string.IsNullOrEmpty(fusionName))
{
Log.LogError($"Missing required metadata '{FusionNameKey}' for '{item.ItemSpec}.");
return false;
}
var assemblyName = new AssemblyName(fusionName).Name;
referenceItems.Add(new AssemblyItem
{
AssemblyName = assemblyName,
IsSystemReference = item.GetMetadata("IsSystemReference") == "true",
Path = item.ItemSpec,
});
}
var targetAssemblyNames = TargetAssemblyNames.Select(s => s.ItemSpec).ToList();
var provider = new ReferenceResolver(targetAssemblyNames, referenceItems);
var assemblyNames = provider.ResolveAssemblies();
ResolvedAssemblies = assemblyNames.ToArray();
return !Log.HasLoggedErrors;
}
}
}

View File

@ -0,0 +1,90 @@
<!--
***********************************************************************************************
Microsoft.NET.Sdk.Razor.ApplicationPartsDiscovery
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">
<UsingTask
TaskName="Microsoft.AspNetCore.Razor.Tasks.FindAssembliesWithReferencesTo"
AssemblyFile="$(RazorSdkBuildTasksAssembly)"
Condition="'$(RazorSdkBuildTasksAssembly)' != ''" />
<PropertyGroup>
<GenerateMvcApplicationPartsAttribute Condition="'$(GenerateMvcApplicationPartsAssemblyAttributes)' == '' AND '$(OutputType)' == 'Exe'">true</GenerateMvcApplicationPartsAttribute>
<CoreCompileDependsOn Condition="'$(GenerateMvcApplicationPartsAttribute)' == 'true' AND '$(DesignTimeBuild)' != 'true'">
_DiscoverMvcApplicationParts;
$(CoreCompileDependsOn);
</CoreCompileDependsOn>
<_MvcApplicationPartAttributeGeneratedFile>$(IntermediateOutputPath)$(TargetName).MvcApplicationPartsAssemblyInfo$(DefaultLanguageSourceExtension)</_MvcApplicationPartAttributeGeneratedFile>
</PropertyGroup>
<Target
Name="_DiscoverMvcApplicationParts"
Inputs="$(ProjectAssetsFile);$(MSBuildAllProjects)"
Outputs="$(_MvcApplicationPartAttributeGeneratedFile)"
DependsOnTargets="ResolveAssemblyReferences">
<ItemGroup>
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc" />
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc.Abstractions" />
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc.ApiExplorer" />
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc.Core" />
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc.Cors" />
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc.DataAnnotations" />
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc.Formatters.Json" />
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc.Formatters.Xml" />
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc.Localization" />
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" />
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc.Razor" />
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc.RazorPages" />
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc.TagHelpers" />
<_MvcAssemblyName Include="Microsoft.AspNetCore.Mvc.ViewFeatures" />
</ItemGroup>
<FindAssembliesWithReferencesTo Assemblies="@(ReferencePath)" TargetAssemblyNames="@(_MvcAssemblyName)">
<Output TaskParameter="ResolvedAssemblies" ItemName="_ApplicationPartAssemblyNames"/>
</FindAssembliesWithReferencesTo>
<ItemGroup>
<_MvcApplicationPartAttribute Include="Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute">
<_Parameter1>%(_ApplicationPartAssemblyNames.Identity)</_Parameter1>
</_MvcApplicationPartAttribute>
</ItemGroup>
<!-- If we found application part assemblies, generate attributes for it and add it to compilation list -->
<WriteCodeFragment
AssemblyAttributes="@(_MvcApplicationPartAttribute)"
Language="$(Language)"
OutputFile="$(_MvcApplicationPartAttributeGeneratedFile)"
Condition="'@(_ApplicationPartAssemblyNames->Count())' != '0'" />
<ItemGroup Condition="'@(_ApplicationPartAssemblyNames->Count())' != '0'">
<Compile Remove="$(_MvcApplicationPartAttributeGeneratedFile)" Condition="'$(Language)'!='F#'" />
<Compile Include="$(_MvcApplicationPartAttributeGeneratedFile)" Condition="'$(Language)'!='F#'" />
<CompileBefore Remove="$(_MvcApplicationPartAttributeGeneratedFile)" Condition="'$(Language)'=='F#'" />
<CompileBefore Include="$(_MvcApplicationPartAttributeGeneratedFile)" Condition="'$(Language)'=='F#'" />
</ItemGroup>
<!--
If we did not find any application parts, produce an empty file which is not added to compilation.
This is required to play nicely with incremental builds.
-->
<Touch
Files="$(_MvcApplicationPartAttributeGeneratedFile)"
AlwaysCreate="true" />
<ItemGroup>
<FileWrites Include="$(_MvcApplicationPartAttributeGeneratedFile)" />
</ItemGroup>
</Target>
</Project>

View File

@ -25,7 +25,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<!-- Paths to tools, tasks, and extensions are calculated relative to the RazorSdkDirectoryRoot. This can be modified to test a local build. --> <!-- Paths to tools, tasks, and extensions are calculated relative to the RazorSdkDirectoryRoot. This can be modified to test a local build. -->
<RazorSdkDirectoryRoot Condition="'$(RazorSdkDirectoryRoot)'==''">$(MSBuildThisFileDirectory)..\..\</RazorSdkDirectoryRoot> <RazorSdkDirectoryRoot Condition="'$(RazorSdkDirectoryRoot)'==''">$(MSBuildThisFileDirectory)..\..\</RazorSdkDirectoryRoot>
<RazorSdkBuildTasksDirectoryRoot Condition="'$(RazorSdkBuildTasksDirectoryRoot)'==''">$(RazorSdkDirectoryRoot)tasks\</RazorSdkBuildTasksDirectoryRoot> <RazorSdkBuildTasksDirectoryRoot Condition="'$(RazorSdkBuildTasksDirectoryRoot)'==''">$(RazorSdkDirectoryRoot)tasks\</RazorSdkBuildTasksDirectoryRoot>
<_RazorSdkTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">netstandard2.0</_RazorSdkTasksTFM> <_RazorSdkTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp3.0</_RazorSdkTasksTFM>
<_RazorSdkTasksTFM Condition=" '$(_RazorSdkTasksTFM)' == ''">net46</_RazorSdkTasksTFM> <_RazorSdkTasksTFM Condition=" '$(_RazorSdkTasksTFM)' == ''">net46</_RazorSdkTasksTFM>
<RazorSdkBuildTasksAssembly>$(RazorSdkBuildTasksDirectoryRoot)$(_RazorSdkTasksTFM)\Microsoft.NET.Sdk.Razor.Tasks.dll</RazorSdkBuildTasksAssembly> <RazorSdkBuildTasksAssembly>$(RazorSdkBuildTasksDirectoryRoot)$(_RazorSdkTasksTFM)\Microsoft.NET.Sdk.Razor.Tasks.dll</RazorSdkBuildTasksAssembly>
</PropertyGroup> </PropertyGroup>
@ -335,6 +335,8 @@ Copyright (c) .NET Foundation. All rights reserved.
<Import Project="Microsoft.NET.Sdk.Razor.GenerateAssemblyInfo.targets" /> <Import Project="Microsoft.NET.Sdk.Razor.GenerateAssemblyInfo.targets" />
<Import Project="Microsoft.NET.Sdk.Razor.MvcApplicationPartsDiscovery.targets" Condition="'$(_TargetingNETCoreApp30OrLater)' == 'true'" />
<!-- <!--
These are the targets that actually do compilation using CSC, separated from the main file for ease of maintenance. These are the targets that actually do compilation using CSC, separated from the main file for ease of maintenance.

View File

@ -0,0 +1,115 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
public class ApplicationPartDiscoveryIntegrationTest : MSBuildIntegrationTestBase, IClassFixture<BuildServerTestFixture>
{
public ApplicationPartDiscoveryIntegrationTest(BuildServerTestFixture buildServer)
: base(buildServer)
{
}
[Fact]
[InitializeTestProject("AppWithP2PReference", additionalProjects: "ClassLibrary")]
public Task Build_ProjectWithDependencyThatReferencesMvc_AddsAttribute_WhenBuildingUsingDotnetMsbuild()
=> Build_ProjectWithDependencyThatReferencesMvc_AddsAttribute(MSBuildProcessKind.Dotnet);
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
[InitializeTestProject("AppWithP2PReference", additionalProjects: "ClassLibrary")]
public Task Build_ProjectWithDependencyThatReferencesMvc_AddsAttribute_WhenBuildingUsingDesktopMsbuild()
=> Build_ProjectWithDependencyThatReferencesMvc_AddsAttribute(MSBuildProcessKind.Desktop);
private async Task Build_ProjectWithDependencyThatReferencesMvc_AddsAttribute(MSBuildProcessKind msBuildProcessKind)
{
var result = await DotnetMSBuild("Build", msBuildProcessKind: msBuildProcessKind);
Assert.BuildPassed(result);
Assert.FileExists(result, IntermediateOutputPath, "AppWithP2PReference.MvcApplicationPartsAssemblyInfo.cs");
Assert.FileContains(result, Path.Combine(IntermediateOutputPath, "AppWithP2PReference.MvcApplicationPartsAssemblyInfo.cs"), "[assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute(\"ClassLibrary\")]");
}
[Fact]
[InitializeTestProject("SimpleMvc")]
public async Task Build_ProjectWithoutMvcReferencingDependencies_DoesNotGenerateAttribute()
{
var result = await DotnetMSBuild("Build");
Assert.BuildPassed(result);
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.MvcApplicationPartsAssemblyInfo.cs");
// We should produced an empty file for build incrementalism
Assert.Empty(File.ReadAllText(Path.Combine(result.Project.DirectoryPath, IntermediateOutputPath, "SimpleMvc.MvcApplicationPartsAssemblyInfo.cs")));
}
[Fact]
[InitializeTestProject("AppWithP2PReference", additionalProjects: "ClassLibrary")]
public async Task BuildIncrementalism_WhenApplicationPartAttributeIsGenerated()
{
var result = await DotnetMSBuild("Build");
Assert.BuildPassed(result);
var generatedAttributeFile = Path.Combine(IntermediateOutputPath, "AppWithP2PReference.MvcApplicationPartsAssemblyInfo.cs");
Assert.FileExists(result, generatedAttributeFile);
Assert.FileContains(result, generatedAttributeFile, "[assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute(\"ClassLibrary\")]");
var thumbPrint = GetThumbPrint(generatedAttributeFile);
result = await DotnetMSBuild("Build");
Assert.BuildPassed(result);
Assert.FileExists(result, generatedAttributeFile);
Assert.Equal(thumbPrint, GetThumbPrint(generatedAttributeFile));
}
[Fact]
[InitializeTestProject("SimpleMvcFSharp", language: "F#", additionalProjects: "ClassLibrary")]
public async Task Build_ProjectWithDependencyThatReferencesMvc_AddsAttributeToNonCSharpProjects()
{
AddProjectFileContent(
@"
<ItemGroup>
<ProjectReference Include=""..\ClassLibrary\ClassLibrary.csproj"" />
</ItemGroup>
");
var result = await DotnetMSBuild("Build");
Assert.BuildPassed(result);
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvcFSharp.MvcApplicationPartsAssemblyInfo.fs");
Assert.FileContains(result, Path.Combine(IntermediateOutputPath, "SimpleMvcFSharp.MvcApplicationPartsAssemblyInfo.fs"), "<assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute(\"ClassLibrary\")>");
}
[Fact]
[InitializeTestProject("SimpleMvc")]
public async Task BuildIncrementalism_WhenApplicationPartAttributeIsNotGenerated()
{
var result = await DotnetMSBuild("Build");
Assert.BuildPassed(result);
var generatedAttributeFile = Path.Combine(IntermediateOutputPath, "SimpleMvc.MvcApplicationPartsAssemblyInfo.cs");
Assert.FileExists(result, generatedAttributeFile);
Assert.Empty(File.ReadAllText(Path.Combine(result.Project.DirectoryPath, generatedAttributeFile)));
var thumbPrint = GetThumbPrint(generatedAttributeFile);
result = await DotnetMSBuild("Build");
Assert.BuildPassed(result);
Assert.FileExists(result, generatedAttributeFile);
Assert.Equal(thumbPrint, GetThumbPrint(generatedAttributeFile));
}
}
}

View File

@ -34,9 +34,9 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Tools\Microsoft.AspNetCore.Razor.Tools.csproj" /> <ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Tools\Microsoft.AspNetCore.Razor.Tools.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.csproj" /> <ProjectReference Include="..\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.csproj" />
<ProjectReference Include="..\..\src\Microsoft.NET.Sdk.Razor\Microsoft.NET.Sdk.Razor.csproj" />
<!-- We don't need anything in this assembly, we just want to make sure it's built --> <!-- We don't need anything in this assembly, we just want to make sure it's built -->
<ProjectReference Include="..\..\src\Microsoft.NET.Sdk.Razor\Microsoft.NET.Sdk.Razor.csproj" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" ReferenceOutputAssembly="false" /> <ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" ReferenceOutputAssembly="false" />
</ItemGroup> </ItemGroup>

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.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Tasks
{
public class ReferenceResolverTest
{
internal static readonly string[] MvcAssemblies = new[]
{
"Microsoft.AspNetCore.Mvc",
"Microsoft.AspNetCore.Mvc.Abstractions",
"Microsoft.AspNetCore.Mvc.ApiExplorer",
"Microsoft.AspNetCore.Mvc.Core",
"Microsoft.AspNetCore.Mvc.Cors",
"Microsoft.AspNetCore.Mvc.DataAnnotations",
"Microsoft.AspNetCore.Mvc.Formatters.Json",
"Microsoft.AspNetCore.Mvc.Formatters.Xml",
"Microsoft.AspNetCore.Mvc.Localization",
"Microsoft.AspNetCore.Mvc.NewtonsoftJson",
"Microsoft.AspNetCore.Mvc.Razor",
"Microsoft.AspNetCore.Mvc.RazorPages",
"Microsoft.AspNetCore.Mvc.TagHelpers",
"Microsoft.AspNetCore.Mvc.ViewFeatures",
};
[Fact]
public void Resolve_ReturnsEmptySequence_IfNoAssemblyReferencesMvc()
{
// Arrange
var resolver = new TestReferencesToMvcResolver(new[]
{
CreateAssemblyItem("Microsoft.AspNetCore.Blazor"),
CreateAssemblyItem("Microsoft.AspNetCore.Components"),
CreateAssemblyItem("Microsoft.JSInterop"),
CreateAssemblyItem("System.Net.Http"),
CreateAssemblyItem("System.Runtime"),
});
resolver.Add("Microsoft.AspNetCore.Blazor", "Microsoft.AspNetCore.Components", "Microsoft.JSInterop");
resolver.Add("Microsoft.AspNetCore.Components", "Microsoft.JSInterop", "System.Net.Http", "System.Runtime");
resolver.Add("System.Net.Http", "System.Runtime");
// Act
var assemblies = resolver.ResolveAssemblies();
// Assert
Assert.Empty(assemblies);
}
[Fact]
public void Resolve_ReturnsEmptySequence_IfNoDependencyReferencesMvc()
{
// Arrange
var resolver = new TestReferencesToMvcResolver(new[]
{
CreateAssemblyItem("MyApp.Models"),
CreateAssemblyItem("Microsoft.AspNetCore.Mvc", isSystemReference: true),
CreateAssemblyItem("Microsoft.AspNetCore.Hosting", isSystemReference: true),
CreateAssemblyItem("Microsoft.AspNetCore.HttpAbstractions", isSystemReference: true),
CreateAssemblyItem("Microsoft.AspNetCore.KestrelHttpServer", isSystemReference: true),
CreateAssemblyItem("Microsoft.AspNetCore.StaticFiles", isSystemReference: true),
CreateAssemblyItem("Microsoft.Extensions.Primitives", isSystemReference: true),
CreateAssemblyItem("System.Net.Http", isSystemReference: true),
CreateAssemblyItem("Microsoft.EntityFrameworkCore"),
});
resolver.Add("MyApp.Models", "Microsoft.EntityFrameworkCore");
resolver.Add("Microsoft.AspNetCore.Mvc", "Microsoft.AspNetCore.HttpAbstractions");
resolver.Add("Microsoft.AspNetCore.KestrelHttpServer", "Microsoft.AspNetCore.Hosting", "Microsoft.AspNetCore.HttpAbstractions");
resolver.Add("Microsoft.AspNetCore.StaticFiles", "Microsoft.AspNetCore.HttpAbstractions", "Microsoft.Extensions.Primitives");
resolver.Add("Microsoft.AspNetCore.Hosting", "Microsoft.AspNetCore.HttpAbstractions");
resolver.Add("Microsoft.AspNetCore.HttpAbstractions", "Microsoft.Extensions.Primitives");
// Act
var assemblies = resolver.ResolveAssemblies();
// Assert
Assert.Empty(assemblies);
}
[Fact]
public void Resolve_ReturnsReferences_ThatReferenceMvc()
{
// Arrange
var resolver = new TestReferencesToMvcResolver(new[]
{
CreateAssemblyItem("Microsoft.AspNetCore.Mvc", isSystemReference: true),
CreateAssemblyItem("Microsoft.AspNetCore.Mvc.TagHelpers", isSystemReference: true),
CreateAssemblyItem("MyTagHelpers"),
CreateAssemblyItem("MyControllers"),
CreateAssemblyItem("MyApp.Models"),
CreateAssemblyItem("Microsoft.AspNetCore.Hosting", isSystemReference: true),
CreateAssemblyItem("Microsoft.AspNetCore.HttpAbstractions", isSystemReference: true),
CreateAssemblyItem("Microsoft.AspNetCore.KestrelHttpServer", isSystemReference: true),
CreateAssemblyItem("Microsoft.AspNetCore.StaticFiles", isSystemReference: true),
CreateAssemblyItem("Microsoft.Extensions.Primitives", isSystemReference: true),
CreateAssemblyItem("Microsoft.EntityFrameworkCore"),
});
resolver.Add("MyTagHelpers", "Microsoft.AspNetCore.Mvc.TagHelpers");
resolver.Add("MyControllers", "Microsoft.AspNetCore.Mvc");
resolver.Add("MyApp.Models", "Microsoft.EntityFrameworkCore");
resolver.Add("Microsoft.AspNetCore.Mvc", "Microsoft.AspNetCore.HttpAbstractions", "Microsoft.AspNetCore.Mvc.TagHelpers");
resolver.Add("Microsoft.AspNetCore.KestrelHttpServer", "Microsoft.AspNetCore.Hosting", "Microsoft.AspNetCore.HttpAbstractions");
resolver.Add("Microsoft.AspNetCore.StaticFiles", "Microsoft.AspNetCore.HttpAbstractions", "Microsoft.Extensions.Primitives");
resolver.Add("Microsoft.AspNetCore.Hosting", "Microsoft.AspNetCore.HttpAbstractions");
resolver.Add("Microsoft.AspNetCore.HttpAbstractions", "Microsoft.Extensions.Primitives");
// Act
var assemblies = resolver.ResolveAssemblies();
// Assert
Assert.Equal(new[] { "MyControllers", "MyTagHelpers" }, assemblies.OrderBy(a => a));
}
[Fact]
public void Resolve_ReturnsItemsThatTransitivelyReferenceMvc()
{
// Arrange
var resolver = new TestReferencesToMvcResolver(new[]
{
CreateAssemblyItem("MyCMS"),
CreateAssemblyItem("MyCMS.Core"),
CreateAssemblyItem("Microsoft.AspNetCore.Mvc.ViewFeatures", isSystemReference: true),
});
resolver.Add("MyCMS", "MyCMS.Core");
resolver.Add("MyCMS.Core", "Microsoft.AspNetCore.Mvc.ViewFeatures");
// Act
var assemblies = resolver.ResolveAssemblies();
// Assert
Assert.Equal(new[] { "MyCMS", "MyCMS.Core" }, assemblies.OrderBy(a => a));
}
public AssemblyItem CreateAssemblyItem(string name, bool isSystemReference = false)
{
return new AssemblyItem
{
AssemblyName = name,
IsSystemReference = isSystemReference,
Path = name,
};
}
private class TestReferencesToMvcResolver : ReferenceResolver
{
private readonly Dictionary<string, List<ClassifiedAssemblyItem>> _references = new Dictionary<string, List<ClassifiedAssemblyItem>>();
private readonly Dictionary<string, ClassifiedAssemblyItem> _lookup;
public TestReferencesToMvcResolver(AssemblyItem[] referenceItems)
: base(MvcAssemblies, referenceItems)
{
_lookup = referenceItems.ToDictionary(r => r.AssemblyName, r => new ClassifiedAssemblyItem(r));
}
public void Add(string assembly, params string[] references)
{
var assemblyItems = references.Select(r => _lookup[r]).ToList();
_references[assembly] = assemblyItems;
}
protected override IReadOnlyList<ClassifiedAssemblyItem> GetReferences(string file)
{
if (_references.TryGetValue(file, out var result))
{
return result;
}
return Array.Empty<ClassifiedAssemblyItem>();
}
}
}
}

View File

@ -0,0 +1,13 @@
// 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.AspNetCore.Mvc.ApplicationParts
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public class ApplicationPartAttribute : Attribute
{
public ApplicationPartAttribute(string assemblyName) {}
}
}

View File

@ -23,6 +23,11 @@
<UseRazorBuildServer>false</UseRazorBuildServer> <UseRazorBuildServer>false</UseRazorBuildServer>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<!-- Have the SDK treat the MvcShim as an MVC assembly -->
<_MvcAssemblyName Include="Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib" />
</ItemGroup>
<Import Project="After.Directory.Build.props" Condition="Exists('After.Directory.Build.props')" /> <Import Project="After.Directory.Build.props" Condition="Exists('After.Directory.Build.props')" />
</Project> </Project>

View File

@ -10,7 +10,6 @@
--> -->
<PropertyGroup> <PropertyGroup>
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences> <DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
<_ReferencesAspNetCoreApp>true</_ReferencesAspNetCoreApp>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>