From ab76f743f4ee537939b69bdb9f79bfca35398545 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 2 May 2016 10:05:11 -0700 Subject: [PATCH] Use dependency context from all application parts when compiling views Fixes #4498 --- .../ApplicationParts/AssemblyPart.cs | 15 +- .../ICompilationLibrariesProvider.cs | 19 +++ .../Compilation/MetadataReferenceFeature.cs | 19 +++ .../MetadataReferenceFeatureProvider.cs | 67 ++++++++ .../MvcRazorMvcCoreBuilderExtensions.cs | 5 + .../DefaultRoslynCompilationService.cs | 80 ++-------- .../MetadataReferenceFeatureProviderTest.cs | 68 +++++++++ .../MvcRazorMvcCoreBuilderExtensionsTest.cs | 50 ++++++ .../DefaultRoslynCompilationServiceTest.cs | 144 +++++------------- .../MvcServiceCollectionExtensionsTest.cs | 10 +- .../project.json | 7 +- ...ssemblyMetadataReferenceFeatureProvider.cs | 20 +++ .../ControllersFromServicesWebSite/Startup.cs | 13 +- .../project.json | 3 +- 14 files changed, 335 insertions(+), 185 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ICompilationLibrariesProvider.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/MetadataReferenceFeatureProviderTest.cs create mode 100644 test/WebSites/ControllersFromServicesWebSite/AssemblyMetadataReferenceFeatureProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs index 61ac71bb01..12d251c725 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs @@ -4,13 +4,14 @@ using System; using System.Collections.Generic; using System.Reflection; +using Microsoft.Extensions.DependencyModel; namespace Microsoft.AspNetCore.Mvc.ApplicationParts { /// /// An backed by an . /// - public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider + public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider, ICompilationLibrariesProvider { /// /// Initalizes a new instance. @@ -38,5 +39,17 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts /// public IEnumerable Types => Assembly.DefinedTypes; + + /// + public IReadOnlyList GetCompilationLibraries() + { + var dependencyContext = DependencyContext.Load(Assembly); + if (dependencyContext != null) + { + return dependencyContext.CompileLibraries; + } + + return new CompilationLibrary[0]; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ICompilationLibrariesProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ICompilationLibrariesProvider.cs new file mode 100644 index 0000000000..81b28544b2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ICompilationLibrariesProvider.cs @@ -0,0 +1,19 @@ +// 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 Microsoft.Extensions.DependencyModel; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// Exposes instances from an . + /// + public interface ICompilationLibrariesProvider + { + /// + /// Gets the sequence of instances. + /// + IReadOnlyList GetCompilationLibraries(); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs new file mode 100644 index 0000000000..7ad99a2afe --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs @@ -0,0 +1,19 @@ +// 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 Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + /// + /// Specifies the list of used in Razor compilation. + /// + public class MetadataReferenceFeature + { + /// + /// Gets the instances. + /// + public IList MetadataReferences { get; } = new List(); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs new file mode 100644 index 0000000000..2651e209fc --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection.PortableExecutable; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.DependencyModel; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + /// + /// An for that + /// uses for registered instances to create + /// . + /// + public class MetadataReferenceFeatureProvider : IApplicationFeatureProvider + { + /// + public void PopulateFeature(IEnumerable parts, MetadataReferenceFeature feature) + { + if (parts == null) + { + throw new ArgumentNullException(nameof(parts)); + } + + if (feature == null) + { + throw new ArgumentNullException(nameof(feature)); + } + + var libraryPaths = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var providerPart in parts.OfType()) + { + var compileLibraries = providerPart.GetCompilationLibraries(); + + for (var i = 0; i < compileLibraries.Count; i++) + { + var library = compileLibraries[i]; + var referencePaths = library.ResolveReferencePaths(); + foreach (var path in referencePaths) + { + if (libraryPaths.Add(path)) + { + var metadataReference = CreateMetadataReference(path); + feature.MetadataReferences.Add(metadataReference); + } + } + } + } + } + + private static MetadataReference CreateMetadataReference(string path) + { + using (var stream = File.OpenRead(path)) + { + var moduleMetadata = ModuleMetadata.CreateFromStream(stream, PEStreamOptions.PrefetchMetadata); + var assemblyMetadata = AssemblyMetadata.Create(moduleMetadata); + + return assemblyMetadata.GetReference(filePath: path); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index 484229ac0f..3f8efab48e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -67,6 +67,11 @@ namespace Microsoft.Extensions.DependencyInjection { builder.PartManager.FeatureProviders.Add(new TagHelperFeatureProvider()); } + + if (!builder.PartManager.FeatureProviders.OfType().Any()) + { + builder.PartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); + } } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs index b886d11b41..c759f3d92f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs @@ -7,17 +7,15 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; -using System.Reflection.PortableExecutable; using System.Text; using System.Threading; using Microsoft.AspNetCore.Diagnostics; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; -using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -29,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal /// public class DefaultRoslynCompilationService : ICompilationService { - private readonly IHostingEnvironment _hostingEnvironment; + private readonly ApplicationPartManager _partManager; private readonly IFileProvider _fileProvider; private readonly Action _compilationCallback; private readonly CSharpParseOptions _parseOptions; @@ -37,22 +35,22 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal private readonly ILogger _logger; private object _applicationReferencesLock = new object(); private bool _applicationReferencesInitialized; - private List _applicationReferences; + private IList _applicationReferences; /// /// Initalizes a new instance of the class. /// - /// The . + /// The . /// Accessor to . /// The . /// The . public DefaultRoslynCompilationService( - IHostingEnvironment environment, + ApplicationPartManager partManager, IOptions optionsAccessor, IRazorViewEngineFileProviderAccessor fileProviderAccessor, ILoggerFactory loggerFactory) { - _hostingEnvironment = environment; + _partManager = partManager; _fileProvider = fileProviderAccessor.FileProvider; _compilationCallback = optionsAccessor.Value.CompilationCallback; _parseOptions = optionsAccessor.Value.ParseOptions; @@ -60,7 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal _logger = loggerFactory.CreateLogger(); } - private List ApplicationReferences + private IList ApplicationReferences { get { @@ -152,19 +150,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal } /// - /// Gets the . + /// Gets the sequence of instances used for compilation. /// - /// The . - /// The . - protected virtual DependencyContext GetDependencyContext(IHostingEnvironment hostingEnvironment) + /// The instances. + protected virtual IList GetApplicationReferences() { - if (hostingEnvironment.ApplicationName != null) - { - var applicationAssembly = Assembly.Load(new AssemblyName(hostingEnvironment.ApplicationName)); - return DependencyContext.Load(applicationAssembly); - } - - return null; + var feature = new MetadataReferenceFeature(); + _partManager.PopulateFeature(feature); + return feature.MetadataReferences; } private Assembly LoadStream(MemoryStream assemblyStream, MemoryStream pdbStream) @@ -240,53 +233,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal return diagnostic.Location.GetMappedLineSpan().Path; } - private List GetApplicationReferences() - { - var metadataReferences = new List(); - var dependencyContext = GetDependencyContext(_hostingEnvironment); - if (dependencyContext == null) - { - // Avoid null ref if the entry point does not have DependencyContext specified. - return metadataReferences; - } - - var libraryPaths = new HashSet(StringComparer.OrdinalIgnoreCase); - for (var i = 0; i < dependencyContext.CompileLibraries.Count; i++) - { - var library = dependencyContext.CompileLibraries[i]; - IEnumerable referencePaths; - try - { - referencePaths = library.ResolveReferencePaths(); - } - catch (InvalidOperationException) - { - continue; - } - - foreach (var path in referencePaths) - { - if (libraryPaths.Add(path)) - { - metadataReferences.Add(CreateMetadataFileReference(path)); - } - } - } - - return metadataReferences; - } - - private MetadataReference CreateMetadataFileReference(string path) - { - using (var stream = File.OpenRead(path)) - { - var moduleMetadata = ModuleMetadata.CreateFromStream(stream, PEStreamOptions.PrefetchMetadata); - var assemblyMetadata = AssemblyMetadata.Create(moduleMetadata); - - return assemblyMetadata.GetReference(filePath: path); - } - } - private static bool IsError(Diagnostic diagnostic) { return diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/MetadataReferenceFeatureProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/MetadataReferenceFeatureProviderTest.cs new file mode 100644 index 0000000000..6ae0f9d964 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/MetadataReferenceFeatureProviderTest.cs @@ -0,0 +1,68 @@ +// 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.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + public class MetadataReferenceFeatureProviderTest + { + [Fact] + public void PopulateFeature_ReturnsEmptyList_IfNoAssemblyPartsAreRegistered() + { + // Arrange + var applicationPartManager = new ApplicationPartManager(); + applicationPartManager.ApplicationParts.Add(Mock.Of()); + applicationPartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); + var feature = new MetadataReferenceFeature(); + + // Act + applicationPartManager.PopulateFeature(feature); + + // Assert + Assert.Empty(feature.MetadataReferences); + } + + [Fact] + public void PopulateFeature_ReturnsEmptySequence_IfAssemblyDoesNotPreserveCompilationContext() + { + // Arrange + var applicationPartManager = new ApplicationPartManager(); + var assemblyPart = new AssemblyPart(typeof(MetadataReferenceFeatureProvider).GetTypeInfo().Assembly); + applicationPartManager.ApplicationParts.Add(assemblyPart); + applicationPartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); + var feature = new MetadataReferenceFeature(); + + // Act + applicationPartManager.PopulateFeature(feature); + + // Assert + Assert.Empty(feature.MetadataReferences); + } + + [Fact] + public void PopulateFeature_AddsMetadataReferenceForAssemblyPartsWithDependencyContext() + { + // Arrange + var applicationPartManager = new ApplicationPartManager(); + var currentAssembly = GetType().GetTypeInfo().Assembly; + var assemblyPart1 = new AssemblyPart(currentAssembly); + applicationPartManager.ApplicationParts.Add(assemblyPart1); + var assemblyPart2 = new AssemblyPart(typeof(MetadataReferenceFeatureProvider).GetTypeInfo().Assembly); + applicationPartManager.ApplicationParts.Add(assemblyPart2); + applicationPartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); + var feature = new MetadataReferenceFeature(); + + // Act + applicationPartManager.PopulateFeature(feature); + + // Assert + Assert.Contains( + feature.MetadataReferences, + reference => reference.Display.Equals(currentAssembly.Location)); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcCoreBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcCoreBuilderExtensionsTest.cs index e57c5828d5..e1aba6824e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcCoreBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcCoreBuilderExtensionsTest.cs @@ -6,6 +6,7 @@ using System.Linq; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.Runtime.TagHelpers; @@ -55,6 +56,55 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.DependencyInjection Assert.Empty(builder.PartManager.ApplicationParts); } + [Fact] + public void AddRazorViewEngine_AddsMetadataReferenceFeatureProvider() + { + // Arrange + var services = new ServiceCollection(); + var builder = services.AddMvcCore(); + + // Act + builder.AddRazorViewEngine(); + + // Assert + Assert.Single(builder.PartManager.FeatureProviders.OfType()); + } + + [Fact] + public void AddRazorViewEngine_DoesNotAddMultipleMetadataReferenceFeatureProvider_OnMultipleInvocations() + { + // Arrange + var services = new ServiceCollection(); + var builder = services.AddMvcCore(); + + // Act - 1 + builder.AddRazorViewEngine(); + + // Act - 2 + builder.AddRazorViewEngine(); + + // Assert + Assert.Single(builder.PartManager.FeatureProviders.OfType()); + } + + [Fact] + public void AddRazorViewEngine_DoesNotReplaceExistingMetadataReferenceFeatureProvider() + { + // Arrange + var services = new ServiceCollection(); + var builder = services.AddMvcCore(); + var metadataReferenceFeatureProvider = new MetadataReferenceFeatureProvider(); + builder.PartManager.FeatureProviders.Add(metadataReferenceFeatureProvider); + + // Act + builder.AddRazorViewEngine(); + + // Assert + var actual = Assert.Single( + builder.PartManager.FeatureProviders.OfType()); + Assert.Same(metadataReferenceFeatureProvider, actual); + } + [Fact] public void AddTagHelpersAsServices_ReplacesTagHelperActivatorAndTagHelperTypeResolver() { diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs index 41abdb501e..cb223a9844 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs @@ -2,13 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; using System.Reflection; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; -using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Options; @@ -26,10 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var content = @" public class MyTestType {}"; - var compilationService = new TestableRoslynCompilationService( - GetDependencyContext(), - GetOptions(), - GetFileProviderAccessor()); + var compilationService = GetRoslynCompilationService(); var relativeFileInfo = new RelativeFileInfo( new TestFileInfo { PhysicalPath = "SomePath" }, "some-relative-path"); @@ -53,10 +48,7 @@ this should fail"; var fileProvider = new TestFileProvider(); var fileInfo = fileProvider.AddFile(viewPath, fileContent); - var compilationService = new TestableRoslynCompilationService( - GetDependencyContext(), - GetOptions(), - GetFileProviderAccessor(fileProvider)); + var compilationService = GetRoslynCompilationService(fileProvider: fileProvider); var relativeFileInfo = new RelativeFileInfo(fileInfo, "some-relative-path"); // Act @@ -77,10 +69,7 @@ this should fail"; var fileContent = "file content"; var content = @"this should fail"; - var compilationService = new TestableRoslynCompilationService( - GetDependencyContext(), - GetOptions(), - GetFileProviderAccessor()); + var compilationService = GetRoslynCompilationService(); var relativeFileInfo = new RelativeFileInfo( new TestFileInfo { Content = fileContent }, "some-relative-path"); @@ -112,10 +101,7 @@ this should fail"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, mockFileInfo.Object); - var compilationService = new TestableRoslynCompilationService( - GetDependencyContext(), - GetOptions(), - GetFileProviderAccessor()); + var compilationService = GetRoslynCompilationService(fileProvider: fileProvider); var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, path); // Act @@ -140,14 +126,9 @@ public class MyCustomDefinedClass {} public class MyNonCustomDefinedClass {} #endif "; - var options = GetOptions(); options.ParseOptions = options.ParseOptions.WithPreprocessorSymbols("MY_CUSTOM_DEFINE"); - - var compilationService = new TestableRoslynCompilationService( - GetDependencyContext(), - options, - GetFileProviderAccessor()); + var compilationService = GetRoslynCompilationService(options: options); var relativeFileInfo = new RelativeFileInfo( new TestFileInfo { PhysicalPath = "SomePath" }, "some-relative-path"); @@ -170,12 +151,7 @@ public class MyNonCustomDefinedClass {} fileProvider.AddFile(viewPath, "view-content"); var options = new RazorViewEngineOptions(); options.FileProviders.Add(fileProvider); - - var compilationService = new TestableRoslynCompilationService( - GetDependencyContext(), - options, - GetFileProviderAccessor(fileProvider)); - + var compilationService = GetRoslynCompilationService(options: options, fileProvider: fileProvider); var assemblyName = "random-assembly-name"; var diagnostics = new[] @@ -256,11 +232,8 @@ public class MyNonCustomDefinedClass {} // Arrange var content = "public class MyTestType {}"; RoslynCompilationContext usedCompilation = null; - - var compilationService = new TestableRoslynCompilationService( - GetDependencyContext(), - GetOptions(callback: c => usedCompilation = c), - GetFileProviderAccessor()); + var options = GetOptions(c => usedCompilation = c); + var compilationService = GetRoslynCompilationService(options: options); var relativeFileInfo = new RelativeFileInfo( new TestFileInfo { PhysicalPath = "SomePath" }, @@ -274,43 +247,12 @@ public class MyNonCustomDefinedClass {} } [Fact] - public void Compile_ThrowsIfDependencyContextIsNullAndTheApplicationFailsToCompileWithNoReferences() + public void Compile_ThrowsIfNoMetadataReferencesAreDiscoveredAndApplicationFailsToCompile() { // Arrange var content = "public class MyTestType {}"; - var compilationService = new TestableRoslynCompilationService( - dependencyContext: null, - viewEngineOptions: GetOptions(), - fileProviderAccessor: GetFileProviderAccessor()); - - var relativeFileInfo = new RelativeFileInfo( - new TestFileInfo { PhysicalPath = "SomePath" }, - "some-relative-path.cshtml"); - - var expected = "The Razor page 'some-relative-path.cshtml' failed to compile. Ensure that your " - + "application's project.json sets the 'preserveCompilationContext' compilation property."; - - // Act and Assert - var ex = Assert.Throws(() => - compilationService.Compile(relativeFileInfo, content)); - Assert.Equal(expected, ex.Message); - } - - [Fact] - public void Compile_ThrowsIfDependencyContextReturnsNoReferencesAndTheApplicationFailsToCompile() - { - // Arrange - var content = "public class MyTestType {}"; - var dependencyContext = new DependencyContext( - new TargetInfo("framework", "runtime", "signature", isPortable: true), - Extensions.DependencyModel.CompilationOptions.Default, - new CompilationLibrary[0], - new RuntimeLibrary[0], - Enumerable.Empty()); - var compilationService = new TestableRoslynCompilationService( - dependencyContext: dependencyContext, - viewEngineOptions: GetOptions(), - fileProviderAccessor: GetFileProviderAccessor()); + var applicationPartManager = new ApplicationPartManager(); + var compilationService = GetRoslynCompilationService(applicationPartManager); var relativeFileInfo = new RelativeFileInfo( new TestFileInfo { PhysicalPath = "SomePath" }, @@ -334,11 +276,7 @@ public class MyNonCustomDefinedClass {} context.Compilation = context.Compilation.RemoveAllReferences(); }); var content = "public class MyTestType {}"; - var compilationService = new TestableRoslynCompilationService( - dependencyContext: GetDependencyContext(), - viewEngineOptions: options, - fileProviderAccessor: GetFileProviderAccessor()); - + var compilationService = GetRoslynCompilationService(options: options); var relativeFileInfo = new RelativeFileInfo( new TestFileInfo { PhysicalPath = "SomePath" }, "some-relative-path.cshtml"); @@ -363,10 +301,8 @@ public class MyNonCustomDefinedClass {} .AddReferences(MetadataReference.CreateFromFile(assemblyLocation)); }); var content = "public class MyTestType {}"; - var compilationService = new TestableRoslynCompilationService( - dependencyContext: null, - viewEngineOptions: options, - fileProviderAccessor: GetFileProviderAccessor()); + var applicationPartManager = new ApplicationPartManager(); + var compilationService = GetRoslynCompilationService(applicationPartManager, options); var relativeFileInfo = new RelativeFileInfo( new TestFileInfo { PhysicalPath = "SomePath" }, @@ -399,7 +335,7 @@ public class MyNonCustomDefinedClass {} }; } - private IRazorViewEngineFileProviderAccessor GetFileProviderAccessor(IFileProvider fileProvider = null) + private static IRazorViewEngineFileProviderAccessor GetFileProviderAccessor(IFileProvider fileProvider = null) { var options = new Mock(); options.SetupGet(o => o.FileProvider) @@ -408,38 +344,36 @@ public class MyNonCustomDefinedClass {} return options.Object; } - private DependencyContext GetDependencyContext() + private static IOptions GetAccessor(RazorViewEngineOptions options) { - var assembly = typeof(DefaultRoslynCompilationServiceTest).GetTypeInfo().Assembly; - return DependencyContext.Load(assembly); + var optionsAccessor = new Mock>(); + optionsAccessor.SetupGet(a => a.Value).Returns(options); + return optionsAccessor.Object; } - private class TestableRoslynCompilationService : DefaultRoslynCompilationService + private static ApplicationPartManager GetApplicationPartManager() { - private readonly DependencyContext _dependencyContext; + var applicationPartManager = new ApplicationPartManager(); + var assembly = typeof(DefaultRoslynCompilationServiceTest).GetTypeInfo().Assembly; + applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly)); + applicationPartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); - public TestableRoslynCompilationService( - DependencyContext dependencyContext, - RazorViewEngineOptions viewEngineOptions, - IRazorViewEngineFileProviderAccessor fileProviderAccessor) - : base( - Mock.Of(), - GetAccessor(viewEngineOptions), - fileProviderAccessor, - NullLoggerFactory.Instance) - { - _dependencyContext = dependencyContext; - } + return applicationPartManager; + } - private static IOptions GetAccessor(RazorViewEngineOptions options) - { - var optionsAccessor = new Mock>(); - optionsAccessor.SetupGet(a => a.Value).Returns(options); - return optionsAccessor.Object; - } + private static DefaultRoslynCompilationService GetRoslynCompilationService( + ApplicationPartManager partManager = null, + RazorViewEngineOptions options = null, + IFileProvider fileProvider = null) + { + partManager = partManager ?? GetApplicationPartManager(); + options = options ?? GetOptions(); - protected override DependencyContext GetDependencyContext(IHostingEnvironment hostingEnvironment) - => _dependencyContext; + return new DefaultRoslynCompilationService( + partManager, + GetAccessor(options), + GetFileProviderAccessor(fileProvider), + NullLoggerFactory.Instance); } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs index f0b0ba4906..12bac9568a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs @@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; using Microsoft.AspNetCore.Mvc.TagHelpers; @@ -202,10 +203,11 @@ namespace Microsoft.AspNetCore.Mvc Assert.NotNull(descriptor.ImplementationInstance); var manager = Assert.IsType(descriptor.ImplementationInstance); - Assert.Equal(3, manager.FeatureProviders.Count); - Assert.IsType(manager.FeatureProviders[0]); - Assert.IsType(manager.FeatureProviders[1]); - Assert.IsType(manager.FeatureProviders[2]); + Assert.Collection(manager.FeatureProviders, + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature), + feature => Assert.IsType(feature)); } [Fact] diff --git a/test/WebSites/ControllersFromServicesClassLibrary/project.json b/test/WebSites/ControllersFromServicesClassLibrary/project.json index 224718137b..f6e850ae71 100644 --- a/test/WebSites/ControllersFromServicesClassLibrary/project.json +++ b/test/WebSites/ControllersFromServicesClassLibrary/project.json @@ -1,15 +1,18 @@ { + "buildOptions": { + "preserveCompilationContext": true + }, "dependencies": { "Microsoft.AspNetCore.Mvc": "1.0.0-*" }, "frameworks": { - "net451": {}, + "net451": { }, "netcoreapp1.0": { "imports": [ "dnxcore50", "portable-net451+win8" ], - "dependencies": {} + "dependencies": { } } } } \ No newline at end of file diff --git a/test/WebSites/ControllersFromServicesWebSite/AssemblyMetadataReferenceFeatureProvider.cs b/test/WebSites/ControllersFromServicesWebSite/AssemblyMetadataReferenceFeatureProvider.cs new file mode 100644 index 0000000000..99c96ceb38 --- /dev/null +++ b/test/WebSites/ControllersFromServicesWebSite/AssemblyMetadataReferenceFeatureProvider.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.CodeAnalysis; + +namespace ControllersFromServicesWebSite +{ + public class AssemblyMetadataReferenceFeatureProvider : IApplicationFeatureProvider + { + public void PopulateFeature(IEnumerable parts, MetadataReferenceFeature feature) + { + var currentAssembly = GetType().GetTypeInfo().Assembly; + feature.MetadataReferences.Add(MetadataReference.CreateFromFile(currentAssembly.Location)); + } + } +} diff --git a/test/WebSites/ControllersFromServicesWebSite/Startup.cs b/test/WebSites/ControllersFromServicesWebSite/Startup.cs index ceb7873090..32441f5a42 100644 --- a/test/WebSites/ControllersFromServicesWebSite/Startup.cs +++ b/test/WebSites/ControllersFromServicesWebSite/Startup.cs @@ -25,10 +25,15 @@ namespace ControllersFromServicesWebSite .AddMvc() .ConfigureApplicationPartManager(manager => manager.ApplicationParts.Clear()) .AddApplicationPart(typeof(TimeScheduleController).GetTypeInfo().Assembly) - .ConfigureApplicationPartManager(manager => manager.ApplicationParts.Add(new TypesPart( - typeof(AnotherController), - typeof(ComponentFromServicesViewComponent), - typeof(InServicesTagHelper)))) + .ConfigureApplicationPartManager(manager => + { + manager.ApplicationParts.Add(new TypesPart( + typeof(AnotherController), + typeof(ComponentFromServicesViewComponent), + typeof(InServicesTagHelper))); + + manager.FeatureProviders.Add(new AssemblyMetadataReferenceFeatureProvider()); + }) .AddControllersAsServices() .AddViewComponentsAsServices() .AddTagHelpersAsServices(); diff --git a/test/WebSites/ControllersFromServicesWebSite/project.json b/test/WebSites/ControllersFromServicesWebSite/project.json index cd61795fce..191edc3064 100644 --- a/test/WebSites/ControllersFromServicesWebSite/project.json +++ b/test/WebSites/ControllersFromServicesWebSite/project.json @@ -1,7 +1,6 @@ { "buildOptions": { - "emitEntryPoint": true, - "preserveCompilationContext": true + "emitEntryPoint": true }, "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1-*",