Adding support for Razor precompilation

Fixes #3917
This commit is contained in:
Pranav K 2016-07-21 09:04:25 -07:00
parent 2f46113556
commit c942eab6e2
18 changed files with 501 additions and 157 deletions

View File

@ -12,8 +12,27 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
/// <summary>
/// An <see cref="ApplicationPart"/> backed by an <see cref="Assembly"/>.
/// </summary>
public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider, ICompilationReferencesProvider
public class AssemblyPart :
ApplicationPart,
IApplicationPartTypeProvider,
ICompilationReferencesProvider,
IViewsProvider
{
/// <summary>
/// Gets the suffix for the view assembly.
/// </summary>
public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";
/// <summary>
/// Gets the namespace for the <see cref="ViewInfoContainer"/> type in the view assembly.
/// </summary>
public static readonly string ViewInfoContainerNamespace = "AspNetCore";
/// <summary>
/// Gets the type name for the view collection type in the view assembly.
/// </summary>
public static readonly string ViewInfoContainerTypeName = "__PrecompiledViewCollection";
/// <summary>
/// Initalizes a new <see cref="AssemblyPart"/> instance.
/// </summary>
@ -41,6 +60,27 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
/// <inheritdoc />
public IEnumerable<TypeInfo> Types => Assembly.DefinedTypes;
/// <inheritdoc />
public IEnumerable<ViewInfo> 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;
}
}
/// <inheritdoc />
public IEnumerable<string> GetReferencePaths()
{

View File

@ -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
{
/// <summary>
/// Exposes a sequence of views associated with an <see cref="ApplicationPart"/> .
/// </summary>
public interface IViewsProvider
{
/// <summary>
/// Gets the sequence of <see cref="ViewInfo"/>.
/// </summary>
IEnumerable<ViewInfo> Views { get; }
}
}

View File

@ -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
{
/// <summary>
/// Provides information for precompiled views.
/// </summary>
public class ViewInfo
{
/// <summary>
/// Creates a new instance of <see cref="ViewInfo" />.
/// </summary>
/// <param name="path">The path of the view.</param>
/// <param name="type">The view <see cref="System.Type"/>.</param>
public ViewInfo(string path, Type type)
{
Path = path;
Type = type;
}
/// <summary>
/// The path of the view.
/// </summary>
public string Path { get; }
/// <summary>
/// The view <see cref="System.Type"/>.
/// </summary>
public Type Type { get; }
}
}

View File

@ -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
{
/// <summary>
/// A container for <see cref="ViewInfo"/> instances.
/// </summary>
public class ViewInfoContainer
{
/// <summary>
/// Initializes a new instance of <see cref="ViewInfos"/>.
/// </summary>
/// <param name="views">The sequence of <see cref="ViewInfo"/>.</param>
public ViewInfoContainer(IReadOnlyList<ViewInfo> views)
{
ViewInfos = views;
}
/// <summary>
/// The <see cref="IReadOnlyList{T}"/> of <see cref="ViewInfo"/>.
/// </summary>
public IReadOnlyList<ViewInfo> ViewInfos { get; }
}
}

View File

@ -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
{
/// <summary>
/// Configures the <see cref="IMvcBuilder"/>. Implement this interface to enable design-time configuration
/// (for instance during pre-compilation of views) of <see cref="IMvcBuilder"/>.
/// </summary>
public interface IDesignTimeMvcBuilderConfiguration
{
/// <summary>
/// Configures the <see cref="IMvcBuilder"/>.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
void ConfigureMvc(IMvcBuilder builder);
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
{
public class ViewsFeature
{
public IDictionary<string, Type> Views { get; } =
new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
}
}

View File

@ -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
{
/// <summary>
/// An <see cref="IApplicationFeatureProvider{TFeature}"/> for <see cref="ViewsFeature"/>.
/// </summary>
public class ViewsFeatureProvider : IApplicationFeatureProvider<ViewsFeature>
{
/// <inheritdoc />
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature)
{
foreach (var provider in parts.OfType<IViewsProvider>())
{
var precompiledViews = provider.Views;
if (precompiledViews != null)
{
foreach (var viewInfo in precompiledViews)
{
feature.Views[viewInfo.Path] = viewInfo.Type;
}
}
}
}
}
}

View File

@ -72,6 +72,11 @@ namespace Microsoft.Extensions.DependencyInjection
{
builder.PartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider());
}
if (!builder.PartManager.FeatureProviders.OfType<ViewsFeatureProvider>().Any())
{
builder.PartManager.FeatureProviders.Add(new ViewsFeatureProvider());
}
}
/// <summary>
@ -127,6 +132,8 @@ namespace Microsoft.Extensions.DependencyInjection
// Internal for testing.
internal static void AddRazorViewEngineServices(IServiceCollection services)
{
services.TryAddSingleton<CSharpCompiler>();
services.TryAddSingleton<RazorReferenceManager>();
// This caches compilation related details that are valid across the lifetime of the application.
services.TryAddSingleton<ICompilationService, DefaultRoslynCompilationService>();
@ -165,7 +172,7 @@ namespace Microsoft.Extensions.DependencyInjection
// creating the singleton RazorViewEngine instance.
services.TryAddTransient<IRazorPageFactoryProvider, DefaultRazorPageFactoryProvider>();
services.TryAddTransient<IRazorCompilationService, RazorCompilationService>();
services.TryAddTransient<IMvcRazorHost,MvcRazorHost>();
services.TryAddTransient<IMvcRazorHost, MvcRazorHost>();
// This caches Razor page activation details that are valid for the lifetime of the application.
services.TryAddSingleton<IRazorPageActivator, RazorPageActivator>();

View File

@ -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<RazorViewEngineOptions> 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);
}
}
}

View File

@ -42,22 +42,22 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
/// <summary>
/// Initializes a new instance of <see cref="CompilerCache"/> populated with precompiled views
/// specified by <paramref name="precompiledViews"/>.
/// specified by <paramref name="views"/>.
/// </summary>
/// <param name="fileProvider"><see cref="IFileProvider"/> used to locate Razor views.</param>
/// <param name="precompiledViews">A mapping of application relative paths of view to the precompiled view
/// <see cref="Type"/>s.</param>
/// <param name="views">A mapping of application relative paths of view to <see cref="Type"/>s that
/// have already been compiled.</param>
public CompilerCache(
IFileProvider fileProvider,
IDictionary<string, Type> precompiledViews)
IDictionary<string, Type> 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));

View File

@ -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 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
/// <summary>
/// Initializes a new instance of <see cref="DefaultCompilerCacheProvider"/>.
/// </summary>
/// <param name="applicationPartManager">The <see cref="ApplicationPartManager" /></param>
/// <param name="fileProviderAccessor">The <see cref="IRazorViewEngineFileProviderAccessor"/>.</param>
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);
}
/// <inheritdoc />

View File

@ -8,13 +8,10 @@ using System.IO;
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 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
// 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<RoslynCompilationContext> _compilationCallback;
private readonly CSharpParseOptions _parseOptions;
private readonly CSharpCompilationOptions _compilationOptions;
private readonly IList<MetadataReference> _additionalMetadataReferences;
private readonly ILogger _logger;
private object _compilationReferencesLock = new object();
private bool _compilationReferencesInitialized;
private IList<MetadataReference> _compilationReferences;
private readonly Action<RoslynCompilationContext> _compilationCallback;
/// <summary>
/// Initalizes a new instance of the <see cref="DefaultRoslynCompilationService"/> class.
/// </summary>
/// <param name="partManager">The <see cref="ApplicationPartManager"/>.</param>
/// <param name="compiler">The <see cref="CSharpCompiler"/>.</param>
/// <param name="optionsAccessor">Accessor to <see cref="RazorViewEngineOptions"/>.</param>
/// <param name="fileProviderAccessor">The <see cref="IRazorViewEngineFileProviderAccessor"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public DefaultRoslynCompilationService(
ApplicationPartManager partManager,
IOptions<RazorViewEngineOptions> optionsAccessor,
CSharpCompiler compiler,
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
IOptions<RazorViewEngineOptions> 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<DefaultRoslynCompilationService>();
}
private IList<MetadataReference> CompilationReferences
{
get
{
return LazyInitializer.EnsureInitialized(
ref _compilationReferences,
ref _compilationReferencesInitialized,
ref _compilationReferencesLock,
GetCompilationReferences);
}
}
/// <inheritdoc />
public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationContent)
{
@ -100,28 +69,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
}
_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 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
var result = compilation.Emit(
assemblyStream,
pdbStream,
options: new EmitOptions(debugInformationFormat: _pdbFormat));
options: _compiler.EmitOptions);
if (!result.Success)
{
@ -144,8 +95,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
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 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
}
}
/// <summary>
/// Gets the sequence of <see cref="MetadataReference"/> instances used for compilation.
/// </summary>
/// <returns>The <see cref="MetadataReference"/> instances.</returns>
protected virtual IList<MetadataReference> GetCompilationReferences()
private CSharpCompilation CreateCompilation(string compilationContent, string assemblyName)
{
var feature = new MetadataReferenceFeature();
_partManager.PopulateFeature(feature);
var applicationReferences = feature.MetadataReferences;
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);
if (_additionalMetadataReferences.Count == 0)
{
return applicationReferences;
}
var compilationReferences = new List<MetadataReference>(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)
{
var rewrittenTrees = new List<SyntaxTree>();
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);
var compilationContext = new RoslynCompilationContext(compilation);
_compilationCallback(compilationContext);
compilation = compilationContext.Compilation;
return compilation;
}
// Internal for unit testing
@ -265,6 +186,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
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);

View File

@ -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 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
private List<KeyValuePair<SimpleLambdaExpressionSyntax, IdentifierNameSyntax>> Expressions { get; }
public static CSharpCompilation Rewrite(CSharpCompilation compilation)
{
var rewrittenTrees = new List<SyntaxTree>();
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)

View File

@ -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<MetadataReference> _additionalMetadataReferences;
private object _compilationReferencesLock = new object();
private bool _compilationReferencesInitialized;
private IList<MetadataReference> _compilationReferences;
public RazorReferenceManager(
ApplicationPartManager partManager,
IOptions<RazorViewEngineOptions> optionsAccessor)
{
_partManager = partManager;
_additionalMetadataReferences = optionsAccessor.Value.AdditionalCompilationReferences;
}
public IList<MetadataReference> CompilationReferences
{
get
{
return LazyInitializer.EnsureInitialized(
ref _compilationReferences,
ref _compilationReferencesInitialized,
ref _compilationReferencesLock,
GetCompilationReferences);
}
}
private IList<MetadataReference> GetCompilationReferences()
{
var feature = new MetadataReferenceFeature();
_partManager.PopulateFeature(feature);
var applicationReferences = feature.MetadataReferences;
if (_additionalMetadataReferences.Count == 0)
{
return applicationReferences;
}
var compilationReferences = new List<MetadataReference>(applicationReferences.Count + _additionalMetadataReferences.Count);
compilationReferences.AddRange(applicationReferences);
compilationReferences.AddRange(_additionalMetadataReferences);
return compilationReferences;
}
}
}

View File

@ -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<ApplicationPart>();
var viewsProvider1 = applicationPart1
.As<IViewsProvider>()
.SetupGet(p => p.Views)
.Returns(new[]
{
new ViewInfo("/Views/test/Index.cshtml", typeof(object))
});
var applicationPart2 = new Mock<ApplicationPart>();
var viewsProvider2 = applicationPart2
.As<IViewsProvider>()
.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);
}
}
}

View File

@ -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 class MyNonCustomDefinedClass {}
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<MetadataReference>();
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 @@ public class MyNonCustomDefinedClass {}
{
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<MetadataReference> GetCompilationReferencesPublic() => GetCompilationReferences();
}
}
}

View File

@ -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<RazorViewEngineOptions> GetAccessor(RazorViewEngineOptions options)
{
var optionsAccessor = new Mock<IOptions<RazorViewEngineOptions>>();
optionsAccessor.SetupGet(a => a.Value).Returns(options);
return optionsAccessor.Object;
}
}
}

View File

@ -211,7 +211,8 @@ namespace Microsoft.AspNetCore.Mvc
feature => Assert.IsType<ControllerFeatureProvider>(feature),
feature => Assert.IsType<ViewComponentFeatureProvider>(feature),
feature => Assert.IsType<TagHelperFeatureProvider>(feature),
feature => Assert.IsType<MetadataReferenceFeatureProvider>(feature));
feature => Assert.IsType<MetadataReferenceFeatureProvider>(feature),
feature => Assert.IsType<ViewsFeatureProvider>(feature));
}
[Fact]