From b5a7daba2b8ffbffe722765b4e2811695350a6e2 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 17 May 2019 13:14:41 -0700 Subject: [PATCH] Do build time discovery of MVC ApplicationParts (dotnet/aspnetcore-tooling#598) * Do build time discovery of MVC ApplicationParts \n\nCommit migrated from https://github.com/dotnet/aspnetcore-tooling/commit/e79d5600f7c173198ec6974aab3729730316a7d2 --- .../src/AssemblyItem.cs | 14 ++ .../src/Microsoft.NET.Sdk.Razor.csproj | 10 +- .../src/ReferenceResolver.cs | 169 ++++++++++++++++ .../src/ResolveAssemblyWithReferences.cs | 55 ++++++ ...Razor.MvcApplicationPartsDiscovery.targets | 90 +++++++++ .../Sdk.Razor.CurrentVersion.targets | 4 +- ...ApplicationPartDiscoveryIntegrationTest.cs | 115 +++++++++++ .../test/Microsoft.NET.Sdk.Razor.Test.csproj | 2 +- .../test/ReferenceResolverTest.cs | 181 ++++++++++++++++++ .../ApplicationPartAttribute.cs | 13 ++ .../test/testassets/Directory.Build.props | 5 + .../LargeProject/LargeProject.csproj | 1 - 12 files changed, 653 insertions(+), 6 deletions(-) create mode 100644 src/Razor/Microsoft.NET.Sdk.Razor/src/AssemblyItem.cs create mode 100644 src/Razor/Microsoft.NET.Sdk.Razor/src/ReferenceResolver.cs create mode 100644 src/Razor/Microsoft.NET.Sdk.Razor/src/ResolveAssemblyWithReferences.cs create mode 100644 src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.MvcApplicationPartsDiscovery.targets create mode 100644 src/Razor/Microsoft.NET.Sdk.Razor/test/IntegrationTests/ApplicationPartDiscoveryIntegrationTest.cs create mode 100644 src/Razor/Microsoft.NET.Sdk.Razor/test/ReferenceResolverTest.cs create mode 100644 src/Razor/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Mvc.ApplicationParts/ApplicationPartAttribute.cs diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/AssemblyItem.cs b/src/Razor/Microsoft.NET.Sdk.Razor/src/AssemblyItem.cs new file mode 100644 index 0000000000..1f57029bf0 --- /dev/null +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/AssemblyItem.cs @@ -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; } + } +} diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/Microsoft.NET.Sdk.Razor.csproj b/src/Razor/Microsoft.NET.Sdk.Razor/src/Microsoft.NET.Sdk.Razor.csproj index ca4b3e0593..436cad2245 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/Microsoft.NET.Sdk.Razor.csproj +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/Microsoft.NET.Sdk.Razor.csproj @@ -1,7 +1,7 @@  Razor is a markup syntax for adding server-side logic to web pages. This package contains MSBuild support for Razor. - netcoreapp3.0;netstandard2.0;net46 + netcoreapp3.0;net46 Microsoft.NET.Sdk.Razor.Tasks $(MSBuildProjectName).nuspec @@ -17,8 +17,9 @@ + - + @@ -54,7 +55,10 @@ - + + + + diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/ReferenceResolver.cs b/src/Razor/Microsoft.NET.Sdk.Razor/src/ReferenceResolver.cs new file mode 100644 index 0000000000..657e9432cf --- /dev/null +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/ReferenceResolver.cs @@ -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 +{ + /// + /// Resolves assemblies that reference one of the specified "targetAssemblies" either directly or transitively. + /// + public class ReferenceResolver + { + private readonly HashSet _mvcAssemblies; + private readonly Dictionary _lookup = new Dictionary(StringComparer.Ordinal); + + public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList assemblyItems) + { + _mvcAssemblies = new HashSet(targetAssemblies, StringComparer.Ordinal); + + foreach (var item in assemblyItems) + { + var classifiedItem = new ClassifiedAssemblyItem(item); + _lookup[item.AssemblyName] = classifiedItem; + } + } + + public IReadOnlyList ResolveAssemblies() + { + var applicationParts = new List(); + 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 GetReferences(string file) + { + try + { + using var peReader = new PEReader(File.OpenRead(file)); + if (!peReader.HasMetadata) + { + return Array.Empty(); // not a managed assembly + } + + var metadataReader = peReader.GetMetadataReader(); + + var assemblyItems = new List(); + 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(); // 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; } + } + } +} diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/ResolveAssemblyWithReferences.cs b/src/Razor/Microsoft.NET.Sdk.Razor/src/ResolveAssemblyWithReferences.cs new file mode 100644 index 0000000000..3727e01088 --- /dev/null +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/ResolveAssemblyWithReferences.cs @@ -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(); + 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; + } + } +} diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.MvcApplicationPartsDiscovery.targets b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.MvcApplicationPartsDiscovery.targets new file mode 100644 index 0000000000..43a1fbc8db --- /dev/null +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.MvcApplicationPartsDiscovery.targets @@ -0,0 +1,90 @@ + + + + + + + true + + + _DiscoverMvcApplicationParts; + $(CoreCompileDependsOn); + + + <_MvcApplicationPartAttributeGeneratedFile>$(IntermediateOutputPath)$(TargetName).MvcApplicationPartsAssemblyInfo$(DefaultLanguageSourceExtension) + + + + + + <_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" /> + + + + + + + + <_MvcApplicationPartAttribute Include="Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute"> + <_Parameter1>%(_ApplicationPartAssemblyNames.Identity) + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Sdk.Razor.CurrentVersion.targets b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Sdk.Razor.CurrentVersion.targets index 15d1fb8165..0a4d6a4a62 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Sdk.Razor.CurrentVersion.targets +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Sdk.Razor.CurrentVersion.targets @@ -25,7 +25,7 @@ Copyright (c) .NET Foundation. All rights reserved. $(MSBuildThisFileDirectory)..\..\ $(RazorSdkDirectoryRoot)tasks\ - <_RazorSdkTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 + <_RazorSdkTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp3.0 <_RazorSdkTasksTFM Condition=" '$(_RazorSdkTasksTFM)' == ''">net46 $(RazorSdkBuildTasksDirectoryRoot)$(_RazorSdkTasksTFM)\Microsoft.NET.Sdk.Razor.Tasks.dll @@ -335,6 +335,8 @@ Copyright (c) .NET Foundation. All rights reserved. + + - diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/test/ReferenceResolverTest.cs b/src/Razor/Microsoft.NET.Sdk.Razor/test/ReferenceResolverTest.cs new file mode 100644 index 0000000000..7a24101947 --- /dev/null +++ b/src/Razor/Microsoft.NET.Sdk.Razor/test/ReferenceResolverTest.cs @@ -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> _references = new Dictionary>(); + private readonly Dictionary _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 GetReferences(string file) + { + if (_references.TryGetValue(file, out var result)) + { + return result; + } + + return Array.Empty(); + } + } + } +} diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Mvc.ApplicationParts/ApplicationPartAttribute.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Mvc.ApplicationParts/ApplicationPartAttribute.cs new file mode 100644 index 0000000000..136cb7cba6 --- /dev/null +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.MvcShim/Microsoft.AspNetCore.Mvc.ApplicationParts/ApplicationPartAttribute.cs @@ -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) {} + } +} diff --git a/src/Razor/test/testassets/Directory.Build.props b/src/Razor/test/testassets/Directory.Build.props index 3f73654c48..4250269a09 100644 --- a/src/Razor/test/testassets/Directory.Build.props +++ b/src/Razor/test/testassets/Directory.Build.props @@ -23,6 +23,11 @@ false + + + <_MvcAssemblyName Include="Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib" /> + + diff --git a/src/Razor/test/testassets/LargeProject/LargeProject.csproj b/src/Razor/test/testassets/LargeProject/LargeProject.csproj index 364466f735..b2b6b1019a 100644 --- a/src/Razor/test/testassets/LargeProject/LargeProject.csproj +++ b/src/Razor/test/testassets/LargeProject/LargeProject.csproj @@ -10,7 +10,6 @@ --> true - <_ReferencesAspNetCoreApp>true