Use dependency context from all application parts when compiling views

Fixes #4498
This commit is contained in:
Pranav K 2016-05-02 10:05:11 -07:00
parent 27565c4e8d
commit ab76f743f4
14 changed files with 335 additions and 185 deletions

View File

@ -4,13 +4,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using Microsoft.Extensions.DependencyModel;
namespace Microsoft.AspNetCore.Mvc.ApplicationParts namespace Microsoft.AspNetCore.Mvc.ApplicationParts
{ {
/// <summary> /// <summary>
/// An <see cref="ApplicationPart"/> backed by an <see cref="Assembly"/>. /// An <see cref="ApplicationPart"/> backed by an <see cref="Assembly"/>.
/// </summary> /// </summary>
public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider, ICompilationLibrariesProvider
{ {
/// <summary> /// <summary>
/// Initalizes a new <see cref="AssemblyPart"/> instance. /// Initalizes a new <see cref="AssemblyPart"/> instance.
@ -38,5 +39,17 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<TypeInfo> Types => Assembly.DefinedTypes; public IEnumerable<TypeInfo> Types => Assembly.DefinedTypes;
/// <inheritdoc />
public IReadOnlyList<CompilationLibrary> GetCompilationLibraries()
{
var dependencyContext = DependencyContext.Load(Assembly);
if (dependencyContext != null)
{
return dependencyContext.CompileLibraries;
}
return new CompilationLibrary[0];
}
} }
} }

View File

@ -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
{
/// <summary>
/// Exposes <see cref="CompilationLibrary"/> instances from an <see cref="ApplicationPart"/>.
/// </summary>
public interface ICompilationLibrariesProvider
{
/// <summary>
/// Gets the sequence of <see cref="CompilationLibrary"/> instances.
/// </summary>
IReadOnlyList<CompilationLibrary> GetCompilationLibraries();
}
}

View File

@ -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
{
/// <summary>
/// Specifies the list of <see cref="MetadataReference"/> used in Razor compilation.
/// </summary>
public class MetadataReferenceFeature
{
/// <summary>
/// Gets the <see cref="MetadataReference"/> instances.
/// </summary>
public IList<MetadataReference> MetadataReferences { get; } = new List<MetadataReference>();
}
}

View File

@ -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
{
/// <summary>
/// An <see cref="IApplicationFeatureProvider{TFeature}"/> for <see cref="MetadataReferenceFeature"/> that
/// uses <see cref="DependencyContext"/> for registered <see cref="AssemblyPart"/> instances to create
/// <see cref="MetadataReference"/>.
/// </summary>
public class MetadataReferenceFeatureProvider : IApplicationFeatureProvider<MetadataReferenceFeature>
{
/// <inheritdoc />
public void PopulateFeature(IEnumerable<ApplicationPart> parts, MetadataReferenceFeature feature)
{
if (parts == null)
{
throw new ArgumentNullException(nameof(parts));
}
if (feature == null)
{
throw new ArgumentNullException(nameof(feature));
}
var libraryPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var providerPart in parts.OfType<ICompilationLibrariesProvider>())
{
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);
}
}
}
}

View File

@ -67,6 +67,11 @@ namespace Microsoft.Extensions.DependencyInjection
{ {
builder.PartManager.FeatureProviders.Add(new TagHelperFeatureProvider()); builder.PartManager.FeatureProviders.Add(new TagHelperFeatureProvider());
} }
if (!builder.PartManager.FeatureProviders.OfType<MetadataReferenceFeatureProvider>().Any())
{
builder.PartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider());
}
} }
/// <summary> /// <summary>

View File

@ -7,17 +7,15 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Reflection.PortableExecutable;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -29,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
/// </summary> /// </summary>
public class DefaultRoslynCompilationService : ICompilationService public class DefaultRoslynCompilationService : ICompilationService
{ {
private readonly IHostingEnvironment _hostingEnvironment; private readonly ApplicationPartManager _partManager;
private readonly IFileProvider _fileProvider; private readonly IFileProvider _fileProvider;
private readonly Action<RoslynCompilationContext> _compilationCallback; private readonly Action<RoslynCompilationContext> _compilationCallback;
private readonly CSharpParseOptions _parseOptions; private readonly CSharpParseOptions _parseOptions;
@ -37,22 +35,22 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
private readonly ILogger _logger; private readonly ILogger _logger;
private object _applicationReferencesLock = new object(); private object _applicationReferencesLock = new object();
private bool _applicationReferencesInitialized; private bool _applicationReferencesInitialized;
private List<MetadataReference> _applicationReferences; private IList<MetadataReference> _applicationReferences;
/// <summary> /// <summary>
/// Initalizes a new instance of the <see cref="DefaultRoslynCompilationService"/> class. /// Initalizes a new instance of the <see cref="DefaultRoslynCompilationService"/> class.
/// </summary> /// </summary>
/// <param name="environment">The <see cref="IHostingEnvironment"/>.</param> /// <param name="partManager">The <see cref="ApplicationPartManager"/>.</param>
/// <param name="optionsAccessor">Accessor to <see cref="RazorViewEngineOptions"/>.</param> /// <param name="optionsAccessor">Accessor to <see cref="RazorViewEngineOptions"/>.</param>
/// <param name="fileProviderAccessor">The <see cref="IRazorViewEngineFileProviderAccessor"/>.</param> /// <param name="fileProviderAccessor">The <see cref="IRazorViewEngineFileProviderAccessor"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public DefaultRoslynCompilationService( public DefaultRoslynCompilationService(
IHostingEnvironment environment, ApplicationPartManager partManager,
IOptions<RazorViewEngineOptions> optionsAccessor, IOptions<RazorViewEngineOptions> optionsAccessor,
IRazorViewEngineFileProviderAccessor fileProviderAccessor, IRazorViewEngineFileProviderAccessor fileProviderAccessor,
ILoggerFactory loggerFactory) ILoggerFactory loggerFactory)
{ {
_hostingEnvironment = environment; _partManager = partManager;
_fileProvider = fileProviderAccessor.FileProvider; _fileProvider = fileProviderAccessor.FileProvider;
_compilationCallback = optionsAccessor.Value.CompilationCallback; _compilationCallback = optionsAccessor.Value.CompilationCallback;
_parseOptions = optionsAccessor.Value.ParseOptions; _parseOptions = optionsAccessor.Value.ParseOptions;
@ -60,7 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
_logger = loggerFactory.CreateLogger<DefaultRoslynCompilationService>(); _logger = loggerFactory.CreateLogger<DefaultRoslynCompilationService>();
} }
private List<MetadataReference> ApplicationReferences private IList<MetadataReference> ApplicationReferences
{ {
get get
{ {
@ -152,19 +150,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
} }
/// <summary> /// <summary>
/// Gets the <see cref="DependencyContext"/>. /// Gets the sequence of <see cref="MetadataReference"/> instances used for compilation.
/// </summary> /// </summary>
/// <param name="hostingEnvironment">The <see cref="IHostingEnvironment"/>.</param> /// <returns>The <see cref="MetadataReference"/> instances.</returns>
/// <returns>The <see cref="DependencyContext"/>.</returns> protected virtual IList<MetadataReference> GetApplicationReferences()
protected virtual DependencyContext GetDependencyContext(IHostingEnvironment hostingEnvironment)
{ {
if (hostingEnvironment.ApplicationName != null) var feature = new MetadataReferenceFeature();
{ _partManager.PopulateFeature(feature);
var applicationAssembly = Assembly.Load(new AssemblyName(hostingEnvironment.ApplicationName)); return feature.MetadataReferences;
return DependencyContext.Load(applicationAssembly);
}
return null;
} }
private Assembly LoadStream(MemoryStream assemblyStream, MemoryStream pdbStream) private Assembly LoadStream(MemoryStream assemblyStream, MemoryStream pdbStream)
@ -240,53 +233,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
return diagnostic.Location.GetMappedLineSpan().Path; return diagnostic.Location.GetMappedLineSpan().Path;
} }
private List<MetadataReference> GetApplicationReferences()
{
var metadataReferences = new List<MetadataReference>();
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<string>(StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < dependencyContext.CompileLibraries.Count; i++)
{
var library = dependencyContext.CompileLibraries[i];
IEnumerable<string> 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) private static bool IsError(Diagnostic diagnostic)
{ {
return diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error; return diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error;

View File

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

View File

@ -6,6 +6,7 @@ using System.Linq;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers; using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
@ -55,6 +56,55 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.DependencyInjection
Assert.Empty(builder.PartManager.ApplicationParts); 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<MetadataReferenceFeatureProvider>());
}
[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<MetadataReferenceFeatureProvider>());
}
[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<MetadataReferenceFeatureProvider>());
Assert.Same(metadataReferenceFeatureProvider, actual);
}
[Fact] [Fact]
public void AddTagHelpersAsServices_ReplacesTagHelperActivatorAndTagHelperTypeResolver() public void AddTagHelpersAsServices_ReplacesTagHelperActivatorAndTagHelperTypeResolver()
{ {

View File

@ -2,13 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -26,10 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
var content = @" var content = @"
public class MyTestType {}"; public class MyTestType {}";
var compilationService = new TestableRoslynCompilationService( var compilationService = GetRoslynCompilationService();
GetDependencyContext(),
GetOptions(),
GetFileProviderAccessor());
var relativeFileInfo = new RelativeFileInfo( var relativeFileInfo = new RelativeFileInfo(
new TestFileInfo { PhysicalPath = "SomePath" }, new TestFileInfo { PhysicalPath = "SomePath" },
"some-relative-path"); "some-relative-path");
@ -53,10 +48,7 @@ this should fail";
var fileProvider = new TestFileProvider(); var fileProvider = new TestFileProvider();
var fileInfo = fileProvider.AddFile(viewPath, fileContent); var fileInfo = fileProvider.AddFile(viewPath, fileContent);
var compilationService = new TestableRoslynCompilationService( var compilationService = GetRoslynCompilationService(fileProvider: fileProvider);
GetDependencyContext(),
GetOptions(),
GetFileProviderAccessor(fileProvider));
var relativeFileInfo = new RelativeFileInfo(fileInfo, "some-relative-path"); var relativeFileInfo = new RelativeFileInfo(fileInfo, "some-relative-path");
// Act // Act
@ -77,10 +69,7 @@ this should fail";
var fileContent = "file content"; var fileContent = "file content";
var content = @"this should fail"; var content = @"this should fail";
var compilationService = new TestableRoslynCompilationService( var compilationService = GetRoslynCompilationService();
GetDependencyContext(),
GetOptions(),
GetFileProviderAccessor());
var relativeFileInfo = new RelativeFileInfo( var relativeFileInfo = new RelativeFileInfo(
new TestFileInfo { Content = fileContent }, new TestFileInfo { Content = fileContent },
"some-relative-path"); "some-relative-path");
@ -112,10 +101,7 @@ this should fail";
var fileProvider = new TestFileProvider(); var fileProvider = new TestFileProvider();
fileProvider.AddFile(path, mockFileInfo.Object); fileProvider.AddFile(path, mockFileInfo.Object);
var compilationService = new TestableRoslynCompilationService( var compilationService = GetRoslynCompilationService(fileProvider: fileProvider);
GetDependencyContext(),
GetOptions(),
GetFileProviderAccessor());
var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, path); var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, path);
// Act // Act
@ -140,14 +126,9 @@ public class MyCustomDefinedClass {}
public class MyNonCustomDefinedClass {} public class MyNonCustomDefinedClass {}
#endif #endif
"; ";
var options = GetOptions(); var options = GetOptions();
options.ParseOptions = options.ParseOptions.WithPreprocessorSymbols("MY_CUSTOM_DEFINE"); options.ParseOptions = options.ParseOptions.WithPreprocessorSymbols("MY_CUSTOM_DEFINE");
var compilationService = GetRoslynCompilationService(options: options);
var compilationService = new TestableRoslynCompilationService(
GetDependencyContext(),
options,
GetFileProviderAccessor());
var relativeFileInfo = new RelativeFileInfo( var relativeFileInfo = new RelativeFileInfo(
new TestFileInfo { PhysicalPath = "SomePath" }, new TestFileInfo { PhysicalPath = "SomePath" },
"some-relative-path"); "some-relative-path");
@ -170,12 +151,7 @@ public class MyNonCustomDefinedClass {}
fileProvider.AddFile(viewPath, "view-content"); fileProvider.AddFile(viewPath, "view-content");
var options = new RazorViewEngineOptions(); var options = new RazorViewEngineOptions();
options.FileProviders.Add(fileProvider); options.FileProviders.Add(fileProvider);
var compilationService = GetRoslynCompilationService(options: options, fileProvider: fileProvider);
var compilationService = new TestableRoslynCompilationService(
GetDependencyContext(),
options,
GetFileProviderAccessor(fileProvider));
var assemblyName = "random-assembly-name"; var assemblyName = "random-assembly-name";
var diagnostics = new[] var diagnostics = new[]
@ -256,11 +232,8 @@ public class MyNonCustomDefinedClass {}
// Arrange // Arrange
var content = "public class MyTestType {}"; var content = "public class MyTestType {}";
RoslynCompilationContext usedCompilation = null; RoslynCompilationContext usedCompilation = null;
var options = GetOptions(c => usedCompilation = c);
var compilationService = new TestableRoslynCompilationService( var compilationService = GetRoslynCompilationService(options: options);
GetDependencyContext(),
GetOptions(callback: c => usedCompilation = c),
GetFileProviderAccessor());
var relativeFileInfo = new RelativeFileInfo( var relativeFileInfo = new RelativeFileInfo(
new TestFileInfo { PhysicalPath = "SomePath" }, new TestFileInfo { PhysicalPath = "SomePath" },
@ -274,43 +247,12 @@ public class MyNonCustomDefinedClass {}
} }
[Fact] [Fact]
public void Compile_ThrowsIfDependencyContextIsNullAndTheApplicationFailsToCompileWithNoReferences() public void Compile_ThrowsIfNoMetadataReferencesAreDiscoveredAndApplicationFailsToCompile()
{ {
// Arrange // Arrange
var content = "public class MyTestType {}"; var content = "public class MyTestType {}";
var compilationService = new TestableRoslynCompilationService( var applicationPartManager = new ApplicationPartManager();
dependencyContext: null, var compilationService = GetRoslynCompilationService(applicationPartManager);
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<InvalidOperationException>(() =>
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<RuntimeFallbacks>());
var compilationService = new TestableRoslynCompilationService(
dependencyContext: dependencyContext,
viewEngineOptions: GetOptions(),
fileProviderAccessor: GetFileProviderAccessor());
var relativeFileInfo = new RelativeFileInfo( var relativeFileInfo = new RelativeFileInfo(
new TestFileInfo { PhysicalPath = "SomePath" }, new TestFileInfo { PhysicalPath = "SomePath" },
@ -334,11 +276,7 @@ public class MyNonCustomDefinedClass {}
context.Compilation = context.Compilation.RemoveAllReferences(); context.Compilation = context.Compilation.RemoveAllReferences();
}); });
var content = "public class MyTestType {}"; var content = "public class MyTestType {}";
var compilationService = new TestableRoslynCompilationService( var compilationService = GetRoslynCompilationService(options: options);
dependencyContext: GetDependencyContext(),
viewEngineOptions: options,
fileProviderAccessor: GetFileProviderAccessor());
var relativeFileInfo = new RelativeFileInfo( var relativeFileInfo = new RelativeFileInfo(
new TestFileInfo { PhysicalPath = "SomePath" }, new TestFileInfo { PhysicalPath = "SomePath" },
"some-relative-path.cshtml"); "some-relative-path.cshtml");
@ -363,10 +301,8 @@ public class MyNonCustomDefinedClass {}
.AddReferences(MetadataReference.CreateFromFile(assemblyLocation)); .AddReferences(MetadataReference.CreateFromFile(assemblyLocation));
}); });
var content = "public class MyTestType {}"; var content = "public class MyTestType {}";
var compilationService = new TestableRoslynCompilationService( var applicationPartManager = new ApplicationPartManager();
dependencyContext: null, var compilationService = GetRoslynCompilationService(applicationPartManager, options);
viewEngineOptions: options,
fileProviderAccessor: GetFileProviderAccessor());
var relativeFileInfo = new RelativeFileInfo( var relativeFileInfo = new RelativeFileInfo(
new TestFileInfo { PhysicalPath = "SomePath" }, 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<IRazorViewEngineFileProviderAccessor>(); var options = new Mock<IRazorViewEngineFileProviderAccessor>();
options.SetupGet(o => o.FileProvider) options.SetupGet(o => o.FileProvider)
@ -408,38 +344,36 @@ public class MyNonCustomDefinedClass {}
return options.Object; return options.Object;
} }
private DependencyContext GetDependencyContext() private static IOptions<RazorViewEngineOptions> GetAccessor(RazorViewEngineOptions options)
{ {
var assembly = typeof(DefaultRoslynCompilationServiceTest).GetTypeInfo().Assembly; var optionsAccessor = new Mock<IOptions<RazorViewEngineOptions>>();
return DependencyContext.Load(assembly); 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( return applicationPartManager;
DependencyContext dependencyContext, }
RazorViewEngineOptions viewEngineOptions,
IRazorViewEngineFileProviderAccessor fileProviderAccessor)
: base(
Mock.Of<IHostingEnvironment>(),
GetAccessor(viewEngineOptions),
fileProviderAccessor,
NullLoggerFactory.Instance)
{
_dependencyContext = dependencyContext;
}
private static IOptions<RazorViewEngineOptions> GetAccessor(RazorViewEngineOptions options) private static DefaultRoslynCompilationService GetRoslynCompilationService(
{ ApplicationPartManager partManager = null,
var optionsAccessor = new Mock<IOptions<RazorViewEngineOptions>>(); RazorViewEngineOptions options = null,
optionsAccessor.SetupGet(a => a.Value).Returns(options); IFileProvider fileProvider = null)
return optionsAccessor.Object; {
} partManager = partManager ?? GetApplicationPartManager();
options = options ?? GetOptions();
protected override DependencyContext GetDependencyContext(IHostingEnvironment hostingEnvironment) return new DefaultRoslynCompilationService(
=> _dependencyContext; partManager,
GetAccessor(options),
GetFileProviderAccessor(fileProvider),
NullLoggerFactory.Instance);
} }
} }
} }

View File

@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal; using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal;
using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.TagHelpers; using Microsoft.AspNetCore.Mvc.TagHelpers;
@ -202,10 +203,11 @@ namespace Microsoft.AspNetCore.Mvc
Assert.NotNull(descriptor.ImplementationInstance); Assert.NotNull(descriptor.ImplementationInstance);
var manager = Assert.IsType<ApplicationPartManager>(descriptor.ImplementationInstance); var manager = Assert.IsType<ApplicationPartManager>(descriptor.ImplementationInstance);
Assert.Equal(3, manager.FeatureProviders.Count); Assert.Collection(manager.FeatureProviders,
Assert.IsType<ControllerFeatureProvider>(manager.FeatureProviders[0]); feature => Assert.IsType<ControllerFeatureProvider>(feature),
Assert.IsType<ViewComponentFeatureProvider>(manager.FeatureProviders[1]); feature => Assert.IsType<ViewComponentFeatureProvider>(feature),
Assert.IsType<TagHelperFeatureProvider>(manager.FeatureProviders[2]); feature => Assert.IsType<TagHelperFeatureProvider>(feature),
feature => Assert.IsType<MetadataReferenceFeatureProvider>(feature));
} }
[Fact] [Fact]

View File

@ -1,15 +1,18 @@
{ {
"buildOptions": {
"preserveCompilationContext": true
},
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Mvc": "1.0.0-*" "Microsoft.AspNetCore.Mvc": "1.0.0-*"
}, },
"frameworks": { "frameworks": {
"net451": {}, "net451": { },
"netcoreapp1.0": { "netcoreapp1.0": {
"imports": [ "imports": [
"dnxcore50", "dnxcore50",
"portable-net451+win8" "portable-net451+win8"
], ],
"dependencies": {} "dependencies": { }
} }
} }
} }

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 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<MetadataReferenceFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, MetadataReferenceFeature feature)
{
var currentAssembly = GetType().GetTypeInfo().Assembly;
feature.MetadataReferences.Add(MetadataReference.CreateFromFile(currentAssembly.Location));
}
}
}

View File

@ -25,10 +25,15 @@ namespace ControllersFromServicesWebSite
.AddMvc() .AddMvc()
.ConfigureApplicationPartManager(manager => manager.ApplicationParts.Clear()) .ConfigureApplicationPartManager(manager => manager.ApplicationParts.Clear())
.AddApplicationPart(typeof(TimeScheduleController).GetTypeInfo().Assembly) .AddApplicationPart(typeof(TimeScheduleController).GetTypeInfo().Assembly)
.ConfigureApplicationPartManager(manager => manager.ApplicationParts.Add(new TypesPart( .ConfigureApplicationPartManager(manager =>
typeof(AnotherController), {
typeof(ComponentFromServicesViewComponent), manager.ApplicationParts.Add(new TypesPart(
typeof(InServicesTagHelper)))) typeof(AnotherController),
typeof(ComponentFromServicesViewComponent),
typeof(InServicesTagHelper)));
manager.FeatureProviders.Add(new AssemblyMetadataReferenceFeatureProvider());
})
.AddControllersAsServices() .AddControllersAsServices()
.AddViewComponentsAsServices() .AddViewComponentsAsServices()
.AddTagHelpersAsServices(); .AddTagHelpersAsServices();

View File

@ -1,7 +1,6 @@
{ {
"buildOptions": { "buildOptions": {
"emitEntryPoint": true, "emitEntryPoint": true
"preserveCompilationContext": true
}, },
"dependencies": { "dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1-*", "Microsoft.NETCore.Platforms": "1.0.1-*",