diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs
index 6426753d4b..d3b0f0fc8a 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs
@@ -12,8 +12,27 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
///
/// An backed by an .
///
- public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider, ICompilationReferencesProvider
+ public class AssemblyPart :
+ ApplicationPart,
+ IApplicationPartTypeProvider,
+ ICompilationReferencesProvider,
+ IViewsProvider
{
+ ///
+ /// Gets the suffix for the view assembly.
+ ///
+ public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";
+
+ ///
+ /// Gets the namespace for the type in the view assembly.
+ ///
+ public static readonly string ViewInfoContainerNamespace = "AspNetCore";
+
+ ///
+ /// Gets the type name for the view collection type in the view assembly.
+ ///
+ public static readonly string ViewInfoContainerTypeName = "__PrecompiledViewCollection";
+
///
/// Initalizes a new instance.
///
@@ -41,6 +60,27 @@ public AssemblyPart(Assembly assembly)
///
public IEnumerable Types => Assembly.DefinedTypes;
+ ///
+ public IEnumerable Views
+ {
+ get
+ {
+ var precompiledAssemblyName = new AssemblyName(Assembly.FullName);
+ precompiledAssemblyName.Name = precompiledAssemblyName.Name + PrecompiledViewsAssemblySuffix;
+
+ var typeName = $"{ViewInfoContainerNamespace}.{ViewInfoContainerTypeName},{precompiledAssemblyName}";
+ var viewInfoContainerTypeName = Type.GetType(typeName);
+
+ if (viewInfoContainerTypeName == null)
+ {
+ return null;
+ }
+
+ var precompiledViews = (ViewInfoContainer)Activator.CreateInstance(viewInfoContainerTypeName);
+ return precompiledViews.ViewInfos;
+ }
+ }
+
///
public IEnumerable GetReferencePaths()
{
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IViewsProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IViewsProvider.cs
new file mode 100644
index 0000000000..862401593e
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IViewsProvider.cs
@@ -0,0 +1,18 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationParts
+{
+ ///
+ /// Exposes a sequence of views associated with an .
+ ///
+ public interface IViewsProvider
+ {
+ ///
+ /// Gets the sequence of .
+ ///
+ IEnumerable Views { get; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ViewInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ViewInfo.cs
new file mode 100644
index 0000000000..eea6f630bc
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ViewInfo.cs
@@ -0,0 +1,34 @@
+// 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
+{
+ ///
+ /// Provides information for precompiled views.
+ ///
+ public class ViewInfo
+ {
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The path of the view.
+ /// The view .
+ public ViewInfo(string path, Type type)
+ {
+ Path = path;
+ Type = type;
+ }
+
+ ///
+ /// The path of the view.
+ ///
+ public string Path { get; }
+
+ ///
+ /// The view .
+ ///
+ public Type Type { get; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ViewInfoContainer.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ViewInfoContainer.cs
new file mode 100644
index 0000000000..22c5d39f22
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ViewInfoContainer.cs
@@ -0,0 +1,27 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationParts
+{
+ ///
+ /// A container for instances.
+ ///
+ public class ViewInfoContainer
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The sequence of .
+ public ViewInfoContainer(IReadOnlyList views)
+ {
+ ViewInfos = views;
+ }
+
+ ///
+ /// The of .
+ ///
+ public IReadOnlyList ViewInfos { get; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/IDesignTimeMvcBuilderConfiguration.cs b/src/Microsoft.AspNetCore.Mvc.Core/IDesignTimeMvcBuilderConfiguration.cs
new file mode 100644
index 0000000000..312642b052
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/IDesignTimeMvcBuilderConfiguration.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 Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Mvc
+{
+ ///
+ /// Configures the . Implement this interface to enable design-time configuration
+ /// (for instance during pre-compilation of views) of .
+ ///
+ public interface IDesignTimeMvcBuilderConfiguration
+ {
+ ///
+ /// Configures the .
+ ///
+ /// The .
+ void ConfigureMvc(IMvcBuilder builder);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeature.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeature.cs
new file mode 100644
index 0000000000..3f1b5045ae
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeature.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.
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
+{
+ public class ViewsFeature
+ {
+ public IDictionary Views { get; } =
+ new Dictionary(StringComparer.OrdinalIgnoreCase);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.cs
new file mode 100644
index 0000000000..360992886d
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.cs
@@ -0,0 +1,31 @@
+// 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 Microsoft.AspNetCore.Mvc.ApplicationParts;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
+{
+ ///
+ /// An for .
+ ///
+ public class ViewsFeatureProvider : IApplicationFeatureProvider
+ {
+ ///
+ public void PopulateFeature(IEnumerable parts, ViewsFeature feature)
+ {
+ foreach (var provider in parts.OfType())
+ {
+ var precompiledViews = provider.Views;
+ if (precompiledViews != null)
+ {
+ foreach (var viewInfo in precompiledViews)
+ {
+ feature.Views[viewInfo.Path] = viewInfo.Type;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs
index f944874d6f..4c71427961 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs
@@ -72,6 +72,11 @@ private static void AddRazorViewEngineFeatureProviders(IMvcCoreBuilder builder)
{
builder.PartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider());
}
+
+ if (!builder.PartManager.FeatureProviders.OfType().Any())
+ {
+ builder.PartManager.FeatureProviders.Add(new ViewsFeatureProvider());
+ }
}
///
@@ -127,6 +132,8 @@ public static IMvcCoreBuilder InitializeTagHelper(
// Internal for testing.
internal static void AddRazorViewEngineServices(IServiceCollection services)
{
+ services.TryAddSingleton();
+ services.TryAddSingleton();
// This caches compilation related details that are valid across the lifetime of the application.
services.TryAddSingleton();
@@ -165,7 +172,7 @@ internal static void AddRazorViewEngineServices(IServiceCollection services)
// creating the singleton RazorViewEngine instance.
services.TryAddTransient();
services.TryAddTransient();
- services.TryAddTransient();
+ services.TryAddTransient();
// This caches Razor page activation details that are valid for the lifetime of the application.
services.TryAddSingleton();
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs
new file mode 100644
index 0000000000..3100ef3eab
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs
@@ -0,0 +1,51 @@
+// 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 Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Emit;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Internal
+{
+ public class CSharpCompiler
+ {
+ private readonly CSharpCompilationOptions _compilationOptions;
+ private readonly CSharpParseOptions _parseOptions;
+ private readonly RazorReferenceManager _referenceManager;
+ private readonly DebugInformationFormat _pdbFormat =
+#if NET451
+ SymbolsUtility.SupportsFullPdbGeneration() ?
+ DebugInformationFormat.Pdb :
+ DebugInformationFormat.PortablePdb;
+#else
+ DebugInformationFormat.PortablePdb;
+#endif
+
+ public CSharpCompiler(RazorReferenceManager manager, IOptions optionsAccessor)
+ {
+ _referenceManager = manager;
+ _compilationOptions = optionsAccessor.Value.CompilationOptions;
+ _parseOptions = optionsAccessor.Value.ParseOptions;
+ EmitOptions = new EmitOptions(debugInformationFormat: _pdbFormat);
+ }
+
+ public EmitOptions EmitOptions { get; }
+
+ public SyntaxTree CreateSyntaxTree(SourceText sourceText)
+ {
+ return CSharpSyntaxTree.ParseText(
+ sourceText,
+ options: _parseOptions);
+ }
+
+ public CSharpCompilation CreateCompilation(string assemblyName)
+ {
+ return CSharpCompilation.Create(
+ assemblyName,
+ options: _compilationOptions,
+ references: _referenceManager.CompilationReferences);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilerCache.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilerCache.cs
index 575f1e5728..0a08695649 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilerCache.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilerCache.cs
@@ -42,22 +42,22 @@ public CompilerCache(IFileProvider fileProvider)
///
/// Initializes a new instance of populated with precompiled views
- /// specified by .
+ /// specified by .
///
/// used to locate Razor views.
- /// A mapping of application relative paths of view to the precompiled view
- /// s.
+ /// A mapping of application relative paths of view to s that
+ /// have already been compiled.
public CompilerCache(
IFileProvider fileProvider,
- IDictionary precompiledViews)
+ IDictionary views)
: this(fileProvider)
{
- if (precompiledViews == null)
+ if (views == null)
{
- throw new ArgumentNullException(nameof(precompiledViews));
+ throw new ArgumentNullException(nameof(views));
}
- foreach (var item in precompiledViews)
+ foreach (var item in views)
{
var cacheEntry = new CompilerCacheResult(item.Key, new CompilationResult(item.Value));
_cache.Set(GetNormalizedPath(item.Key), Task.FromResult(cacheEntry));
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs
index f38ddd4457..ba328ca136 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs
@@ -1,7 +1,8 @@
// 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 Microsoft.Extensions.Options;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
+using Microsoft.AspNetCore.Mvc.Razor.Compilation;
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{
@@ -13,10 +14,15 @@ public class DefaultCompilerCacheProvider : ICompilerCacheProvider
///
/// Initializes a new instance of .
///
+ /// The
/// The .
- public DefaultCompilerCacheProvider(IRazorViewEngineFileProviderAccessor fileProviderAccessor)
+ public DefaultCompilerCacheProvider(
+ ApplicationPartManager applicationPartManager,
+ IRazorViewEngineFileProviderAccessor fileProviderAccessor)
{
- Cache = new CompilerCache(fileProviderAccessor.FileProvider);
+ var feature = new ViewsFeature();
+ applicationPartManager.PopulateFeature(feature);
+ Cache = new CompilerCache(fileProviderAccessor.FileProvider, feature.Views);
}
///
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs
index 833c541949..ec0cedfa27 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs
@@ -8,13 +8,10 @@
using System.Linq;
using System.Reflection;
using System.Text;
-using System.Threading;
using Microsoft.AspNetCore.Diagnostics;
-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.FileProviders;
using Microsoft.Extensions.Logging;
@@ -33,59 +30,31 @@ public class DefaultRoslynCompilationService : ICompilationService
// error CS0246: The type or namespace name 'T' could not be found (are you missing a using directive
// or an assembly reference?)
private const string CS0246 = nameof(CS0246);
- private readonly DebugInformationFormat _pdbFormat =
-#if NET451
- SymbolsUtility.SupportsFullPdbGeneration() ?
- DebugInformationFormat.Pdb :
- DebugInformationFormat.PortablePdb;
-#else
- DebugInformationFormat.PortablePdb;
-#endif
- private readonly ApplicationPartManager _partManager;
+
+ private readonly CSharpCompiler _compiler;
private readonly IFileProvider _fileProvider;
- private readonly Action _compilationCallback;
- private readonly CSharpParseOptions _parseOptions;
- private readonly CSharpCompilationOptions _compilationOptions;
- private readonly IList _additionalMetadataReferences;
private readonly ILogger _logger;
- private object _compilationReferencesLock = new object();
- private bool _compilationReferencesInitialized;
- private IList _compilationReferences;
+ private readonly Action _compilationCallback;
///
/// Initalizes a new instance of the class.
///
- /// The .
+ /// The .
/// Accessor to .
/// The .
/// The .
public DefaultRoslynCompilationService(
- ApplicationPartManager partManager,
- IOptions optionsAccessor,
+ CSharpCompiler compiler,
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
+ IOptions optionsAccessor,
ILoggerFactory loggerFactory)
{
- _partManager = partManager;
+ _compiler = compiler;
_fileProvider = fileProviderAccessor.FileProvider;
_compilationCallback = optionsAccessor.Value.CompilationCallback;
- _parseOptions = optionsAccessor.Value.ParseOptions;
- _compilationOptions = optionsAccessor.Value.CompilationOptions;
- _additionalMetadataReferences = optionsAccessor.Value.AdditionalCompilationReferences;
_logger = loggerFactory.CreateLogger();
}
- private IList CompilationReferences
- {
- get
- {
- return LazyInitializer.EnsureInitialized(
- ref _compilationReferences,
- ref _compilationReferencesInitialized,
- ref _compilationReferencesLock,
- GetCompilationReferences);
- }
- }
-
///
public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationContent)
{
@@ -100,28 +69,10 @@ public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationCo
}
_logger.GeneratedCodeToAssemblyCompilationStart(fileInfo.RelativePath);
-
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
var assemblyName = Path.GetRandomFileName();
-
- var sourceText = SourceText.From(compilationContent, Encoding.UTF8);
- var syntaxTree = CSharpSyntaxTree.ParseText(
- sourceText,
- path: assemblyName,
- options: _parseOptions);
-
- var compilation = CSharpCompilation.Create(
- assemblyName,
- options: _compilationOptions,
- syntaxTrees: new[] { syntaxTree },
- references: CompilationReferences);
-
- compilation = Rewrite(compilation);
-
- var compilationContext = new RoslynCompilationContext(compilation);
- _compilationCallback(compilationContext);
- compilation = compilationContext.Compilation;
+ var compilation = CreateCompilation(compilationContent, assemblyName);
using (var assemblyStream = new MemoryStream())
{
@@ -130,7 +81,7 @@ public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationCo
var result = compilation.Emit(
assemblyStream,
pdbStream,
- options: new EmitOptions(debugInformationFormat: _pdbFormat));
+ options: _compiler.EmitOptions);
if (!result.Success)
{
@@ -144,8 +95,9 @@ public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationCo
assemblyStream.Seek(0, SeekOrigin.Begin);
pdbStream.Seek(0, SeekOrigin.Begin);
- var assembly = LoadStream(assemblyStream, pdbStream);
+ var assembly = LoadAssembly(assemblyStream, pdbStream);
var type = assembly.GetExportedTypes().FirstOrDefault(a => !a.IsNested);
+
_logger.GeneratedCodeToAssemblyCompilationEnd(fileInfo.RelativePath, startTimestamp);
return new CompilationResult(type);
@@ -153,50 +105,19 @@ public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationCo
}
}
- ///
- /// Gets the sequence of instances used for compilation.
- ///
- /// The instances.
- protected virtual IList GetCompilationReferences()
- {
- var feature = new MetadataReferenceFeature();
- _partManager.PopulateFeature(feature);
- var applicationReferences = feature.MetadataReferences;
-
- if (_additionalMetadataReferences.Count == 0)
- {
- return applicationReferences;
- }
-
- var compilationReferences = new List(applicationReferences.Count + _additionalMetadataReferences.Count);
- compilationReferences.AddRange(applicationReferences);
- compilationReferences.AddRange(_additionalMetadataReferences);
-
- return compilationReferences;
- }
-
- private Assembly LoadStream(MemoryStream assemblyStream, MemoryStream pdbStream)
- {
-#if NET451
- return Assembly.Load(assemblyStream.ToArray(), pdbStream.ToArray());
-#else
- return System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(assemblyStream, pdbStream);
-#endif
- }
-
- private CSharpCompilation Rewrite(CSharpCompilation compilation)
+ private CSharpCompilation CreateCompilation(string compilationContent, string assemblyName)
{
- var rewrittenTrees = new List();
- foreach (var tree in compilation.SyntaxTrees)
- {
- var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
- var rewriter = new ExpressionRewriter(semanticModel);
-
- var rewrittenTree = tree.WithRootAndOptions(rewriter.Visit(tree.GetRoot()), tree.Options);
- rewrittenTrees.Add(rewrittenTree);
- }
+ var sourceText = SourceText.From(compilationContent, Encoding.UTF8);
+ var syntaxTree = _compiler.CreateSyntaxTree(sourceText).WithFilePath(assemblyName);
+ var compilation = _compiler
+ .CreateCompilation(assemblyName)
+ .AddSyntaxTrees(syntaxTree);
+ compilation = ExpressionRewriter.Rewrite(compilation);
- return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(rewrittenTrees);
+ var compilationContext = new RoslynCompilationContext(compilation);
+ _compilationCallback(compilationContext);
+ compilation = compilationContext.Compilation;
+ return compilation;
}
// Internal for unit testing
@@ -265,6 +186,17 @@ private static bool IsError(Diagnostic diagnostic)
return diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error;
}
+ public static Assembly LoadAssembly(MemoryStream assemblyStream, MemoryStream pdbStream)
+ {
+ var assembly =
+#if NET451
+ Assembly.Load(assemblyStream.ToArray(), pdbStream.ToArray());
+#else
+ System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(assemblyStream, pdbStream);
+#endif
+ return assembly;
+ }
+
private static string ReadFileContentsSafely(IFileProvider fileProvider, string filePath)
{
var fileInfo = fileProvider.GetFileInfo(filePath);
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ExpressionRewriter.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ExpressionRewriter.cs
index 30b94a9f98..0c54c7e4ce 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ExpressionRewriter.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ExpressionRewriter.cs
@@ -1,7 +1,6 @@
// 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.Expressions;
@@ -32,6 +31,21 @@ public ExpressionRewriter(SemanticModel semanticModel)
private List> Expressions { get; }
+ public static CSharpCompilation Rewrite(CSharpCompilation compilation)
+ {
+ var rewrittenTrees = new List();
+ foreach (var tree in compilation.SyntaxTrees)
+ {
+ var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
+ var rewriter = new ExpressionRewriter(semanticModel);
+
+ var rewrittenTree = tree.WithRootAndOptions(rewriter.Visit(tree.GetRoot()), tree.Options);
+ rewrittenTrees.Add(rewrittenTree);
+ }
+
+ return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(rewrittenTrees);
+ }
+
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
{
if (IsInsideClass)
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorReferenceManager.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorReferenceManager.cs
new file mode 100644
index 0000000000..2e21de7705
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorReferenceManager.cs
@@ -0,0 +1,59 @@
+// 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.Threading;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
+using Microsoft.AspNetCore.Mvc.Razor.Compilation;
+using Microsoft.CodeAnalysis;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Internal
+{
+ public class RazorReferenceManager
+ {
+ private readonly ApplicationPartManager _partManager;
+ private readonly IList _additionalMetadataReferences;
+ private object _compilationReferencesLock = new object();
+ private bool _compilationReferencesInitialized;
+ private IList _compilationReferences;
+
+ public RazorReferenceManager(
+ ApplicationPartManager partManager,
+ IOptions optionsAccessor)
+ {
+ _partManager = partManager;
+ _additionalMetadataReferences = optionsAccessor.Value.AdditionalCompilationReferences;
+ }
+
+ public IList CompilationReferences
+ {
+ get
+ {
+ return LazyInitializer.EnsureInitialized(
+ ref _compilationReferences,
+ ref _compilationReferencesInitialized,
+ ref _compilationReferencesLock,
+ GetCompilationReferences);
+ }
+ }
+
+ private IList GetCompilationReferences()
+ {
+ var feature = new MetadataReferenceFeature();
+ _partManager.PopulateFeature(feature);
+ var applicationReferences = feature.MetadataReferences;
+
+ if (_additionalMetadataReferences.Count == 0)
+ {
+ return applicationReferences;
+ }
+
+ var compilationReferences = new List(applicationReferences.Count + _additionalMetadataReferences.Count);
+ compilationReferences.AddRange(applicationReferences);
+ compilationReferences.AddRange(_additionalMetadataReferences);
+
+ return compilationReferences;
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/ViewsFeatureProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/ViewsFeatureProviderTest.cs
new file mode 100644
index 0000000000..77250475e1
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/ViewsFeatureProviderTest.cs
@@ -0,0 +1,66 @@
+// 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 ViewsFeatureProviderTest
+ {
+ [Fact]
+ public void PopulateFeature_ReturnsEmptySequenceIfNoAssemblyPartHasViewAssembly()
+ {
+ // Arrange
+ var applicationPartManager = new ApplicationPartManager();
+ applicationPartManager.ApplicationParts.Add(
+ new AssemblyPart(typeof(ViewsFeatureProviderTest).GetTypeInfo().Assembly));
+ applicationPartManager.FeatureProviders.Add(new ViewsFeatureProvider());
+ var feature = new MetadataReferenceFeature();
+
+ // Act
+ applicationPartManager.PopulateFeature(feature);
+
+ // Assert
+ Assert.Empty(feature.MetadataReferences);
+ }
+
+ [Fact]
+ public void PopulateFeature_ReturnsViewsFromAllAvailableApplicationParts()
+ {
+ // Arrange
+ var applicationPart1 = new Mock();
+ var viewsProvider1 = applicationPart1
+ .As()
+ .SetupGet(p => p.Views)
+ .Returns(new[]
+ {
+ new ViewInfo("/Views/test/Index.cshtml", typeof(object))
+ });
+ var applicationPart2 = new Mock();
+ var viewsProvider2 = applicationPart2
+ .As()
+ .SetupGet(p => p.Views)
+ .Returns(new[]
+ {
+ new ViewInfo("/Areas/Admin/Views/Index.cshtml", typeof(string)),
+ new ViewInfo("/Areas/Admin/Views/About.cshtml", typeof(int))
+ });
+
+
+ var applicationPartManager = new ApplicationPartManager();
+ applicationPartManager.ApplicationParts.Add(applicationPart1.Object);
+ applicationPartManager.ApplicationParts.Add(applicationPart2.Object);
+ applicationPartManager.FeatureProviders.Add(new ViewsFeatureProvider());
+ var feature = new MetadataReferenceFeature();
+
+ // Act
+ applicationPartManager.PopulateFeature(feature);
+
+ // Assert
+ Assert.Empty(feature.MetadataReferences);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs
index b880459544..ed47de593e 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs
@@ -2,8 +2,6 @@
// 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 System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
@@ -297,34 +295,6 @@ public void Compile_SucceedsIfReferencesAreAddedInCallback()
Assert.NotNull(result.CompiledType);
}
- [Fact]
- public void GetCompilationReferences_CombinesApplicationPartAndOptionMetadataReferences()
- {
- // Arrange
- var options = new RazorViewEngineOptions();
- var objectAssemblyLocation = typeof(object).GetTypeInfo().Assembly.Location;
- var objectAssemblyMetadataReference = MetadataReference.CreateFromFile(objectAssemblyLocation);
- options.AdditionalCompilationReferences.Add(objectAssemblyMetadataReference);
- var applicationPartManager = GetApplicationPartManager();
- var compilationService = new TestRoslynCompilationService(applicationPartManager, options);
- var feature = new MetadataReferenceFeature();
- applicationPartManager.PopulateFeature(feature);
- var partReferences = feature.MetadataReferences;
- var expectedReferences = new List();
- expectedReferences.AddRange(partReferences);
- expectedReferences.Add(objectAssemblyMetadataReference);
- var expectedReferenceDisplays = expectedReferences.Select(reference => reference.Display);
-
- // Act
- var references = compilationService.GetCompilationReferencesPublic();
- var referenceDisplays = references.Select(reference => reference.Display);
-
- // Assert
- Assert.NotNull(references);
- Assert.NotEmpty(references);
- Assert.Equal(expectedReferenceDisplays, referenceDisplays);
- }
-
private static DiagnosticDescriptor GetDiagnosticDescriptor(string messageFormat)
{
return new DiagnosticDescriptor(
@@ -377,22 +347,15 @@ private static DefaultRoslynCompilationService GetRoslynCompilationService(
{
partManager = partManager ?? GetApplicationPartManager();
options = options ?? GetOptions();
+ var optionsAccessor = GetAccessor(options);
+ var referenceManager = new RazorReferenceManager(partManager, optionsAccessor);
+ var compiler = new CSharpCompiler(referenceManager, optionsAccessor);
return new DefaultRoslynCompilationService(
- partManager,
- GetAccessor(options),
+ compiler,
GetFileProviderAccessor(fileProvider),
+ optionsAccessor,
NullLoggerFactory.Instance);
}
-
- private class TestRoslynCompilationService : DefaultRoslynCompilationService
- {
- public TestRoslynCompilationService(ApplicationPartManager partManager, RazorViewEngineOptions options)
- : base(partManager, GetAccessor(options), GetFileProviderAccessor(), NullLoggerFactory.Instance)
- {
- }
-
- public IList GetCompilationReferencesPublic() => GetCompilationReferences();
- }
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ReferenceManagerTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ReferenceManagerTest.cs
new file mode 100644
index 0000000000..12fe6d81d6
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ReferenceManagerTest.cs
@@ -0,0 +1,61 @@
+// 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.Linq;
+using System.Reflection;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
+using Microsoft.AspNetCore.Mvc.Razor.Compilation;
+using Microsoft.AspNetCore.Mvc.Razor.Internal;
+using Microsoft.CodeAnalysis;
+using Microsoft.Extensions.Options;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Test.Internal
+{
+ public class ReferenceManagerTest
+ {
+ [Fact]
+ public void GetCompilationReferences_CombinesApplicationPartAndOptionMetadataReferences()
+ {
+ // Arrange
+ var options = new RazorViewEngineOptions();
+ var objectAssemblyLocation = typeof(object).GetTypeInfo().Assembly.Location;
+ var objectAssemblyMetadataReference = MetadataReference.CreateFromFile(objectAssemblyLocation);
+ options.AdditionalCompilationReferences.Add(objectAssemblyMetadataReference);
+
+ var applicationPartManager = GetApplicationPartManager();
+ var feature = new MetadataReferenceFeature();
+ applicationPartManager.PopulateFeature(feature);
+ var partReferences = feature.MetadataReferences;
+ var expectedReferenceDisplays = partReferences
+ .Concat(new[] { objectAssemblyMetadataReference })
+ .Select(r => r.Display);
+ var referenceManager = new RazorReferenceManager(applicationPartManager, GetAccessor(options));
+
+ // Act
+ var references = referenceManager.CompilationReferences;
+ var referenceDisplays = references.Select(reference => reference.Display);
+
+ // Assert
+ Assert.Equal(expectedReferenceDisplays, referenceDisplays);
+ }
+
+ private static ApplicationPartManager GetApplicationPartManager()
+ {
+ var applicationPartManager = new ApplicationPartManager();
+ var assembly = typeof(DefaultRoslynCompilationServiceTest).GetTypeInfo().Assembly;
+ applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly));
+ applicationPartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider());
+
+ return applicationPartManager;
+ }
+
+ private static IOptions GetAccessor(RazorViewEngineOptions options)
+ {
+ var optionsAccessor = new Mock>();
+ optionsAccessor.SetupGet(a => a.Value).Returns(options);
+ return optionsAccessor.Object;
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
index 4ad71818eb..ea1adf99f7 100644
--- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
@@ -211,7 +211,8 @@ public void AddMvcTwice_DoesNotAddApplicationFeatureProvidersTwice()
feature => Assert.IsType(feature),
feature => Assert.IsType(feature),
feature => Assert.IsType(feature),
- feature => Assert.IsType(feature));
+ feature => Assert.IsType(feature),
+ feature => Assert.IsType(feature));
}
[Fact]