diff --git a/NuGet.config b/NuGet.config index 5500f6d507..02c66ca7d0 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,6 +1,7 @@  + diff --git a/src/Microsoft.AspNet.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 110f87b70c..a7309fcd16 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -78,7 +78,15 @@ namespace Microsoft.Extensions.DependencyInjection // Action Discovery // // These are consumed only when creating action descriptors, then they can be de-allocated - services.TryAddTransient(); + if (PlatformServices.Default?.LibraryManager != null) + { + services.TryAddTransient(); + } + else + { + services.TryAddTransient(); + } + services.TryAddTransient(); services.TryAddEnumerable( ServiceDescriptor.Transient()); diff --git a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/DefaultAssemblyProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/DefaultAssemblyProvider.cs index 788fb0a949..ffdf3d39ae 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/DefaultAssemblyProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/DefaultAssemblyProvider.cs @@ -41,8 +41,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure "Microsoft.AspNet.Mvc.Razor", "Microsoft.AspNet.Mvc.Razor.Host", "Microsoft.AspNet.Mvc.TagHelpers", - "Microsoft.AspNet.Mvc.ViewFeatures", - "Microsoft.AspNet.PageExecutionInstrumentation.Interfaces", + "Microsoft.AspNet.Mvc.ViewFeatures" }; /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/DependencyContextAssemblyProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/DependencyContextAssemblyProvider.cs new file mode 100644 index 0000000000..235769398c --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/DependencyContextAssemblyProvider.cs @@ -0,0 +1,78 @@ +// 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.Linq; +using System.Reflection; +using Microsoft.Extensions.DependencyModel; + +namespace Microsoft.AspNet.Mvc.Infrastructure +{ + public class DependencyContextAssemblyProvider : IAssemblyProvider + { + /// + /// Gets the set of assembly names that are used as root for discovery of + /// MVC controllers, view components and views. + /// + // DefaultControllerTypeProvider uses CandidateAssemblies to determine if the base type of a POCO controller + // lives in an assembly that references MVC. CandidateAssemblies excludes all assemblies from the + // ReferenceAssemblies set. Consequently adding WebApiCompatShim to this set would cause the ApiController to + // fail this test. + protected virtual HashSet ReferenceAssemblies { get; } = new HashSet(StringComparer.Ordinal) + { + "Microsoft.AspNet.Mvc", + "Microsoft.AspNet.Mvc.Abstractions", + "Microsoft.AspNet.Mvc.ApiExplorer", + "Microsoft.AspNet.Mvc.Core", + "Microsoft.AspNet.Mvc.Cors", + "Microsoft.AspNet.Mvc.DataAnnotations", + "Microsoft.AspNet.Mvc.Formatters.Json", + "Microsoft.AspNet.Mvc.Formatters.Xml", + "Microsoft.AspNet.Mvc.Localization", + "Microsoft.AspNet.Mvc.Razor", + "Microsoft.AspNet.Mvc.Razor.Host", + "Microsoft.AspNet.Mvc.TagHelpers", + "Microsoft.AspNet.Mvc.ViewFeatures" + }; + + /// + public IEnumerable CandidateAssemblies + { + get + { + return GetCandidateLibraries() + .SelectMany(l => l.Assemblies) + .Select(Load); + } + } + + /// + /// Returns a list of libraries that references the assemblies in . + /// By default it returns all assemblies that reference any of the primary MVC assemblies + /// while ignoring MVC assemblies. + /// + /// A set of . + protected virtual IEnumerable GetCandidateLibraries() + { + if (ReferenceAssemblies == null) + { + return Enumerable.Empty(); + } + + return DependencyContext.Default.RuntimeLibraries.Where(IsCandidateLibrary); + } + + private static Assembly Load(RuntimeAssembly assembly) + { + return Assembly.Load(assembly.Name); + } + + private bool IsCandidateLibrary(RuntimeLibrary library) + { + Debug.Assert(ReferenceAssemblies != null); + return library.Dependencies.Any(dependency => ReferenceAssemblies.Contains(dependency.Name)); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/project.json b/src/Microsoft.AspNet.Mvc.Core/project.json index e16c385e12..abff865d15 100644 --- a/src/Microsoft.AspNet.Mvc.Core/project.json +++ b/src/Microsoft.AspNet.Mvc.Core/project.json @@ -21,6 +21,7 @@ "version": "1.0.0-*" }, "Microsoft.Extensions.PlatformAbstractions": "1.0.0-*", + "Microsoft.Extensions.DependencyModel": "1.0.0-*", "Microsoft.Extensions.ClosedGenericMatcher.Sources": { "version": "1.0.0-*", "type": "build" @@ -44,6 +45,7 @@ "frameworks": { "net451": { "frameworkAssemblies": { + "System.Reflection": "", "System.Runtime": "" } }, diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs index 0e6f74041e..e3f91db0db 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -13,6 +12,7 @@ using System.Reflection.PortableExecutable; using System.Runtime.Loader; #endif using System.Runtime.Versioning; +using Microsoft.AspNet.Diagnostics; using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Mvc.Razor.Internal; @@ -22,7 +22,6 @@ using Microsoft.CodeAnalysis.Emit; using Microsoft.Dnx.Compilation.CSharp; using Microsoft.Extensions.PlatformAbstractions; using Microsoft.Extensions.Options; -using Microsoft.AspNet.Diagnostics; using Microsoft.Extensions.Logging; namespace Microsoft.AspNet.Mvc.Razor.Compilation @@ -30,13 +29,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation /// /// A type that uses Roslyn to compile C# content. /// - public class RoslynCompilationService : ICompilationService + public abstract class RoslynCompilationService : ICompilationService { private readonly Lazy _supportsPdbGeneration = new Lazy(SymbolsUtility.SupportsSymbolsGeneration); private readonly ConcurrentDictionary _metadataFileCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - private readonly Extensions.CompilationAbstractions.ILibraryExporter _libraryExporter; private readonly IApplicationEnvironment _environment; private readonly IFileProvider _fileProvider; private readonly Lazy> _applicationReferences; @@ -54,20 +52,17 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation /// Initalizes a new instance of the class. /// /// The environment for the executing application. - /// The library manager that provides export and reference information. /// The that was used to generate the code. /// Accessor to . /// The . public RoslynCompilationService( IApplicationEnvironment environment, - Extensions.CompilationAbstractions.ILibraryExporter libraryExporter, IMvcRazorHost host, IOptions optionsAccessor, IRazorViewEngineFileProviderAccessor fileProviderAccessor, ILoggerFactory loggerFactory) { _environment = environment; - _libraryExporter = libraryExporter; _applicationReferences = new Lazy>(GetApplicationReferences); _fileProvider = fileProviderAccessor.FileProvider; _classPrefix = host.MainClassNamePrefix; @@ -239,83 +234,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation return diagnostic.Location.GetMappedLineSpan().Path; } - private List GetApplicationReferences() - { - var references = new List(); + protected abstract List GetApplicationReferences(); - // Get the MetadataReference for the executing application. If it's a Roslyn reference, - // we can copy the references created when compiling the application to the Razor page being compiled. - // This avoids performing expensive calls to MetadataReference.CreateFromImage. - var libraryExport = _libraryExporter.GetExport(_environment.ApplicationName); - if (libraryExport?.MetadataReferences != null && libraryExport.MetadataReferences.Count > 0) - { - Debug.Assert(libraryExport.MetadataReferences.Count == 1, - "Expected 1 MetadataReferences, found " + libraryExport.MetadataReferences.Count); - var roslynReference = libraryExport.MetadataReferences[0] as IRoslynMetadataReference; - var compilationReference = roslynReference?.MetadataReference as CompilationReference; - if (compilationReference != null) - { - references.AddRange(compilationReference.Compilation.References); - references.Add(roslynReference.MetadataReference); - return references; - } - } - - var export = _libraryExporter.GetAllExports(_environment.ApplicationName); - if (export != null) - { - foreach (var metadataReference in export.MetadataReferences) - { - // Taken from https://github.com/aspnet/KRuntime/blob/757ba9bfdf80bd6277e715d6375969a7f44370ee/src/... - // Microsoft.Extensions.Runtime.Roslyn/RoslynCompiler.cs#L164 - // We don't want to take a dependency on the Roslyn bit directly since it pulls in more dependencies - // than the view engine needs (Microsoft.Extensions.Runtime) for example - references.Add(ConvertMetadataReference(metadataReference)); - } - } - - return references; - } - - private MetadataReference ConvertMetadataReference( - Extensions.CompilationAbstractions.IMetadataReference metadataReference) - { - var roslynReference = metadataReference as IRoslynMetadataReference; - - if (roslynReference != null) - { - return roslynReference.MetadataReference; - } - - var embeddedReference = metadataReference as Extensions.CompilationAbstractions.IMetadataEmbeddedReference; - - if (embeddedReference != null) - { - return MetadataReference.CreateFromImage(embeddedReference.Contents); - } - - var fileMetadataReference = metadataReference as Extensions.CompilationAbstractions.IMetadataFileReference; - - if (fileMetadataReference != null) - { - return CreateMetadataFileReference(fileMetadataReference.Path); - } - - var projectReference = metadataReference as Extensions.CompilationAbstractions.IMetadataProjectReference; - if (projectReference != null) - { - using (var ms = new MemoryStream()) - { - projectReference.EmitReferenceAssembly(ms); - - return MetadataReference.CreateFromImage(ms.ToArray()); - } - } - - throw new NotSupportedException(); - } - - private MetadataReference CreateMetadataFileReference(string path) + protected MetadataReference CreateMetadataFileReference(string path) { var metadata = _metadataFileCache.GetOrAdd(path, _ => { diff --git a/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index fbd46c3d09..37f707154a 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -119,9 +119,22 @@ namespace Microsoft.Extensions.DependencyInjection // Internal for testing. internal static void AddRazorViewEngineServices(IServiceCollection services) { - if (CompilationServices.Default != null) + var compilationServicesAvailible = CompilationServices.Default != null; + + if (compilationServicesAvailible) { services.TryAddSingleton(CompilationServices.Default.LibraryExporter); + + // This caches compilation related details that are valid across the lifetime of the application. + services.TryAddSingleton(); + } + else + { + services.TryAddEnumerable( + ServiceDescriptor.Transient, DependencyContextRazorViewEngineOptionsSetup>()); + + // This caches compilation related details that are valid across the lifetime of the application. + services.TryAddSingleton(); } services.TryAddEnumerable( @@ -142,9 +155,6 @@ namespace Microsoft.Extensions.DependencyInjection // Caches compilation artifacts across the lifetime of the application. services.TryAddSingleton(); - // This caches compilation related details that are valid across the lifetime of the application. - services.TryAddSingleton(); - // In the default scenario the following services are singleton by virtue of being initialized as part of // creating the singleton RazorViewEngine instance. services.TryAddTransient(); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRoslynCompilationServiceTest.cs similarity index 95% rename from test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs rename to test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRoslynCompilationServiceTest.cs index dc870f2de4..73c2eb4834 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRoslynCompilationServiceTest.cs @@ -14,7 +14,7 @@ using Xunit; namespace Microsoft.AspNet.Mvc.Razor.Compilation { - public class RoslynCompilationServiceTest + public class DefaultRoslynCompilationServiceTest { private const string ConfigurationName = "Release"; @@ -30,7 +30,7 @@ public class MyTestType {}"; mvcRazorHost.SetupGet(m => m.MainClassNamePrefix) .Returns(string.Empty); - var compilationService = new RoslynCompilationService( + var compilationService = new DefaultRoslynCompilationService( applicationEnvironment, libraryExporter, mvcRazorHost.Object, @@ -63,7 +63,7 @@ this should fail"; var fileProvider = new TestFileProvider(); var fileInfo = fileProvider.AddFile(viewPath, fileContent); - var compilationService = new RoslynCompilationService( + var compilationService = new DefaultRoslynCompilationService( applicationEnvironment, libraryExporter, mvcRazorHost, @@ -93,7 +93,7 @@ this should fail"; var libraryExporter = CompilationServices.Default.LibraryExporter; var mvcRazorHost = Mock.Of(); - var compilationService = new RoslynCompilationService( + var compilationService = new DefaultRoslynCompilationService( applicationEnvironment, libraryExporter, mvcRazorHost, @@ -134,7 +134,7 @@ this should fail"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, mockFileInfo.Object); - var compilationService = new RoslynCompilationService( + var compilationService = new DefaultRoslynCompilationService( applicationEnvironment, libraryExporter, mvcRazorHost, @@ -175,7 +175,7 @@ public class MyNonCustomDefinedClass {} var options = GetOptions(); options.Value.ParseOptions = options.Value.ParseOptions.WithPreprocessorSymbols("MY_CUSTOM_DEFINE"); - var compilationService = new RoslynCompilationService( + var compilationService = new DefaultRoslynCompilationService( applicationEnvironment, libraryExporter, mvcRazorHost.Object, @@ -207,7 +207,7 @@ public class NotRazorPrefixType {}"; mvcRazorHost.SetupGet(m => m.MainClassNamePrefix) .Returns("RazorPrefix"); - var compilationService = new RoslynCompilationService( + var compilationService = new DefaultRoslynCompilationService( applicationEnvironment, libraryExporter, mvcRazorHost.Object, @@ -240,7 +240,7 @@ public class NotRazorPrefixType {}"; var optionsAccessor = new Mock>(); optionsAccessor.SetupGet(o => o.Value) .Returns(options); - var compilationService = new RoslynCompilationService( + var compilationService = new DefaultRoslynCompilationService( PlatformServices.Default.Application, CompilationServices.Default.LibraryExporter, Mock.Of(), @@ -333,7 +333,7 @@ public class NotRazorPrefixType {}"; mvcRazorHost.SetupGet(m => m.MainClassNamePrefix) .Returns(string.Empty); - var compilationService = new RoslynCompilationService( + var compilationService = new DefaultRoslynCompilationService( applicationEnvironment, libraryExporter, mvcRazorHost.Object,