Adding support for flowing compilation settings to views

Fixes #871
This commit is contained in:
Pranav K 2014-12-04 19:18:26 -08:00
parent 62b9db93b6
commit 110ee28e3e
21 changed files with 420 additions and 62 deletions

View File

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Framework.Runtime;
using Microsoft.Framework.Runtime.Roslyn;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// Extension methods for <see cref="ICompilerOptionsProvider"/>.
/// </summary>
public static class CompilationOptionsProviderExtension
{
/// <summary>
/// Parses the <see cref="ICompilerOptions"/> for the current executing application and returns a
/// <see cref="CompilationSettings"/> used for Roslyn compilation.
/// </summary>
/// <param name="compilerOptionsProvider">A <see cref="ICompilerOptionsProvider"/> that reads compiler options.</param>
/// <param name="applicationEnvironment">The <see cref="IApplicationEnvironment"/> for the executing application.</param>
/// <returns>The <see cref="CompilationSettings"/> for the current application.</returns>
public static CompilationSettings GetCompilationSettings(
[NotNull] this ICompilerOptionsProvider compilerOptionsProvider,
[NotNull] IApplicationEnvironment applicationEnvironment)
{
return compilerOptionsProvider.GetCompilerOptions(applicationEnvironment.ApplicationBasePath,
applicationEnvironment.RuntimeFramework,
applicationEnvironment.Configuration)
.ToCompilationSettings(applicationEnvironment.RuntimeFramework);
}
}
}

View File

@ -15,7 +15,7 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Emit;
using Microsoft.Framework.Runtime; using Microsoft.Framework.Runtime;
namespace Microsoft.AspNet.Mvc.Razor.Compilation namespace Microsoft.AspNet.Mvc.Razor
{ {
/// <summary> /// <summary>
/// A type that uses Roslyn to compile C# content. /// A type that uses Roslyn to compile C# content.
@ -29,6 +29,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IApplicationEnvironment _environment; private readonly IApplicationEnvironment _environment;
private readonly IAssemblyLoadContext _loader; private readonly IAssemblyLoadContext _loader;
private readonly ICompilerOptionsProvider _compilerOptionsProvider;
private readonly Lazy<List<MetadataReference>> _applicationReferences; private readonly Lazy<List<MetadataReference>> _applicationReferences;
@ -44,12 +45,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
public RoslynCompilationService(IApplicationEnvironment environment, public RoslynCompilationService(IApplicationEnvironment environment,
IAssemblyLoadContextAccessor loaderAccessor, IAssemblyLoadContextAccessor loaderAccessor,
ILibraryManager libraryManager, ILibraryManager libraryManager,
ICompilerOptionsProvider compilerOptionsProvider,
IMvcRazorHost host) IMvcRazorHost host)
{ {
_environment = environment; _environment = environment;
_loader = loaderAccessor.GetLoadContext(typeof(RoslynCompilationService).GetTypeInfo().Assembly); _loader = loaderAccessor.GetLoadContext(typeof(RoslynCompilationService).GetTypeInfo().Assembly);
_libraryManager = libraryManager; _libraryManager = libraryManager;
_applicationReferences = new Lazy<List<MetadataReference>>(GetApplicationReferences); _applicationReferences = new Lazy<List<MetadataReference>>(GetApplicationReferences);
_compilerOptionsProvider = compilerOptionsProvider;
_classPrefix = host.MainClassNamePrefix; _classPrefix = host.MainClassNamePrefix;
} }
@ -60,15 +63,19 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
// map to the source file. If a file does not exist on a physical file system, PhysicalPath will be null. // map to the source file. If a file does not exist on a physical file system, PhysicalPath will be null.
// This prevents files that exist in a non-physical file system from being debugged. // This prevents files that exist in a non-physical file system from being debugged.
var path = fileInfo.PhysicalPath ?? fileInfo.Name; var path = fileInfo.PhysicalPath ?? fileInfo.Name;
var syntaxTrees = new[] { SyntaxTreeGenerator.Generate(compilationContent, path) }; var compilationSettings = _compilerOptionsProvider.GetCompilationSettings(_environment);
var syntaxTree = SyntaxTreeGenerator.Generate(compilationContent,
path,
compilationSettings);
var references = _applicationReferences.Value; var references = _applicationReferences.Value;
var assemblyName = Path.GetRandomFileName(); var assemblyName = Path.GetRandomFileName();
var compilationOptions = compilationSettings.CompilationOptions
.WithOutputKind(OutputKind.DynamicallyLinkedLibrary);
var compilation = CSharpCompilation.Create(assemblyName, var compilation = CSharpCompilation.Create(assemblyName,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), options: compilationOptions,
syntaxTrees: syntaxTrees, syntaxTrees: new[] { syntaxTree },
references: references); references: references);
using (var ms = new MemoryStream()) using (var ms = new MemoryStream())
@ -115,7 +122,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
.First(t => t.Name. .First(t => t.Name.
StartsWith(_classPrefix, StringComparison.Ordinal)); StartsWith(_classPrefix, StringComparison.Ordinal));
return UncachedCompilationResult.Successful(type); return UncachedCompilationResult.Successful(type, compilationContent);
} }
} }
} }

View File

@ -5,48 +5,29 @@ using System.Text;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using Microsoft.Framework.Runtime.Roslyn;
namespace Microsoft.AspNet.Mvc.Razor namespace Microsoft.AspNet.Mvc.Razor
{ {
public static class SyntaxTreeGenerator public static class SyntaxTreeGenerator
{ {
private static CSharpParseOptions DefaultOptions
{
get
{
return CSharpParseOptions.Default
.WithLanguageVersion(LanguageVersion.CSharp6);
}
}
public static SyntaxTree Generate([NotNull] string text, [NotNull] string path)
{
return GenerateCore(text, path, DefaultOptions);
}
public static SyntaxTree Generate([NotNull] string text, public static SyntaxTree Generate([NotNull] string text,
[NotNull] string path, [NotNull] string path,
[NotNull] CSharpParseOptions options) [NotNull] CompilationSettings compilationSettings)
{
return GenerateCore(text, path, options);
}
public static SyntaxTree GenerateCore([NotNull] string text,
[NotNull] string path,
[NotNull] CSharpParseOptions options)
{ {
var sourceText = SourceText.From(text, Encoding.UTF8); var sourceText = SourceText.From(text, Encoding.UTF8);
var syntaxTree = CSharpSyntaxTree.ParseText(sourceText, var syntaxTree = CSharpSyntaxTree.ParseText(sourceText,
path: path, path: path,
options: options); options: GetParseOptions(compilationSettings));
return syntaxTree; return syntaxTree;
} }
public static CSharpParseOptions GetParseOptions(CSharpCompilation compilation) public static CSharpParseOptions GetParseOptions(CompilationSettings compilationSettings)
{ {
return CSharpParseOptions.Default return new CSharpParseOptions(
.WithLanguageVersion(compilation.LanguageVersion); languageVersion: compilationSettings.LanguageVersion,
preprocessorSymbols: compilationSettings.Defines.AsImmutable());
} }
} }
} }

View File

@ -4,7 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.Framework.Runtime.Roslyn;
namespace Microsoft.AspNet.Mvc.Razor namespace Microsoft.AspNet.Mvc.Razor
{ {
@ -13,13 +13,13 @@ namespace Microsoft.AspNet.Mvc.Razor
private string _fileFormat; private string _fileFormat;
protected IReadOnlyList<RazorFileInfo> FileInfos { get; private set; } protected IReadOnlyList<RazorFileInfo> FileInfos { get; private set; }
protected CSharpParseOptions Options { get; private set; } protected CompilationSettings CompilationSettings { get; }
public RazorFileInfoCollectionGenerator([NotNull] IReadOnlyList<RazorFileInfo> fileInfos, public RazorFileInfoCollectionGenerator([NotNull] IReadOnlyList<RazorFileInfo> fileInfos,
[NotNull] CSharpParseOptions options) [NotNull] CompilationSettings compilationSettings)
{ {
FileInfos = fileInfos; FileInfos = fileInfos;
Options = options; CompilationSettings = compilationSettings;
} }
public virtual SyntaxTree GenerateCollection() public virtual SyntaxTree GenerateCollection()
@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var sourceCode = builder.ToString(); var sourceCode = builder.ToString();
var syntaxTree = SyntaxTreeGenerator.Generate(sourceCode, var syntaxTree = SyntaxTreeGenerator.Generate(sourceCode,
"__AUTO__GeneratedViewsCollection.cs", "__AUTO__GeneratedViewsCollection.cs",
Options); CompilationSettings);
return syntaxTree; return syntaxTree;
} }

View File

@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel; using Microsoft.Framework.OptionsModel;
using Microsoft.Framework.Runtime; using Microsoft.Framework.Runtime;
using Microsoft.Framework.Runtime.Roslyn;
namespace Microsoft.AspNet.Mvc.Razor namespace Microsoft.AspNet.Mvc.Razor
{ {
@ -19,24 +20,28 @@ namespace Microsoft.AspNet.Mvc.Razor
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IMvcRazorHost _host; private readonly IMvcRazorHost _host;
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider) : public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
[NotNull] CompilationSettings compilationSettings) :
this(designTimeServiceProvider, this(designTimeServiceProvider,
designTimeServiceProvider.GetRequiredService<IMvcRazorHost>(), designTimeServiceProvider.GetRequiredService<IMvcRazorHost>(),
designTimeServiceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>()) designTimeServiceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>(),
compilationSettings)
{ {
} }
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider, public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
[NotNull] IMvcRazorHost host, [NotNull] IMvcRazorHost host,
[NotNull] IOptions<RazorViewEngineOptions> optionsAccessor) [NotNull] IOptions<RazorViewEngineOptions> optionsAccessor,
[NotNull] CompilationSettings compilationSettings)
{ {
_serviceProvider = designTimeServiceProvider; _serviceProvider = designTimeServiceProvider;
_host = host; _host = host;
var appEnv = _serviceProvider.GetRequiredService<IApplicationEnvironment>();
_fileSystem = optionsAccessor.Options.FileSystem; _fileSystem = optionsAccessor.Options.FileSystem;
CompilationSettings = compilationSettings;
} }
protected CompilationSettings CompilationSettings { get; }
protected virtual string FileExtension { get; } = ".cshtml"; protected virtual string FileExtension { get; } = ".cshtml";
public virtual void CompileViews([NotNull] IBeforeCompileContext context) public virtual void CompileViews([NotNull] IBeforeCompileContext context)
@ -47,7 +52,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{ {
var collectionGenerator = new RazorFileInfoCollectionGenerator( var collectionGenerator = new RazorFileInfoCollectionGenerator(
descriptors, descriptors,
SyntaxTreeGenerator.GetParseOptions(context.CSharpCompilation)); CompilationSettings);
var tree = collectionGenerator.GenerateCollection(); var tree = collectionGenerator.GenerateCollection();
context.CSharpCompilation = context.CSharpCompilation.AddSyntaxTrees(tree); context.CSharpCompilation = context.CSharpCompilation.AddSyntaxTrees(tree);
@ -57,14 +62,11 @@ namespace Microsoft.AspNet.Mvc.Razor
protected virtual IReadOnlyList<RazorFileInfo> CreateCompilationDescriptors( protected virtual IReadOnlyList<RazorFileInfo> CreateCompilationDescriptors(
[NotNull] IBeforeCompileContext context) [NotNull] IBeforeCompileContext context)
{ {
var options = SyntaxTreeGenerator.GetParseOptions(context.CSharpCompilation);
var list = new List<RazorFileInfo>(); var list = new List<RazorFileInfo>();
foreach (var info in GetFileInfosRecursive(string.Empty)) foreach (var info in GetFileInfosRecursive(string.Empty))
{ {
var descriptor = ParseView(info, var descriptor = ParseView(info, context);
context,
options);
if (descriptor != null) if (descriptor != null)
{ {
@ -107,8 +109,7 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
protected virtual RazorFileInfo ParseView([NotNull] RelativeFileInfo fileInfo, protected virtual RazorFileInfo ParseView([NotNull] RelativeFileInfo fileInfo,
[NotNull] IBeforeCompileContext context, [NotNull] IBeforeCompileContext context)
[NotNull] CSharpParseOptions options)
{ {
using (var stream = fileInfo.FileInfo.CreateReadStream()) using (var stream = fileInfo.FileInfo.CreateReadStream())
{ {
@ -124,7 +125,9 @@ namespace Microsoft.AspNet.Mvc.Razor
if (generatedCode != null) if (generatedCode != null)
{ {
var syntaxTree = SyntaxTreeGenerator.Generate(generatedCode, fileInfo.FileInfo.PhysicalPath, options); var syntaxTree = SyntaxTreeGenerator.Generate(generatedCode,
fileInfo.FileInfo.PhysicalPath,
CompilationSettings);
var fullTypeName = results.GetMainClassName(_host, syntaxTree); var fullTypeName = results.GetMainClassName(_host, syntaxTree);
if (fullTypeName != null) if (fullTypeName != null)

View File

@ -8,7 +8,7 @@
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" }, "Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
"Microsoft.AspNet.Mvc.Core": "6.0.0-*", "Microsoft.AspNet.Mvc.Core": "6.0.0-*",
"Microsoft.AspNet.Mvc.Razor.Host": "6.0.0-*", "Microsoft.AspNet.Mvc.Razor.Host": "6.0.0-*",
"Microsoft.CodeAnalysis.CSharp": "1.0.0-beta2-*" "Microsoft.Framework.Runtime.Roslyn.Common": "1.0.0-*"
}, },
"frameworks": { "frameworks": {
"aspnet50": { "aspnet50": {

View File

@ -9,7 +9,6 @@ using Microsoft.AspNet.Mvc.Internal;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.OptionDescriptors; using Microsoft.AspNet.Mvc.OptionDescriptors;
using Microsoft.AspNet.Mvc.Razor; using Microsoft.AspNet.Mvc.Razor;
using Microsoft.AspNet.Mvc.Razor.Compilation;
using Microsoft.AspNet.Mvc.Razor.OptionDescriptors; using Microsoft.AspNet.Mvc.Razor.OptionDescriptors;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Mvc.Routing;

View File

@ -29,14 +29,17 @@ namespace Microsoft.AspNet.Mvc
public virtual void BeforeCompile(IBeforeCompileContext context) public virtual void BeforeCompile(IBeforeCompileContext context)
{ {
var appEnv = _appServices.GetRequiredService<IApplicationEnvironment>(); var applicationEnvironment = _appServices.GetRequiredService<IApplicationEnvironment>();
var compilerOptionsProvider = _appServices.GetRequiredService<ICompilerOptionsProvider>();
var compilationSettings = compilerOptionsProvider.GetCompilationSettings(applicationEnvironment);
var setup = new RazorViewEngineOptionsSetup(appEnv); var setup = new RazorViewEngineOptionsSetup(applicationEnvironment);
var sc = new ServiceCollection(); var sc = new ServiceCollection();
sc.ConfigureOptions(setup); sc.ConfigureOptions(setup);
sc.AddMvc(); sc.AddMvc();
var viewCompiler = new RazorPreCompiler(BuildFallbackServiceProvider(sc, _appServices)); var serviceProvider = BuildFallbackServiceProvider(sc, _appServices);
var viewCompiler = new RazorPreCompiler(serviceProvider, compilationSettings);
viewCompiler.CompileViews(context); viewCompiler.CompileViews(context);
} }

View File

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.TestHost;
using RazorWebSite;
using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
// Test to verify compilation options from the application are used to compile
// precompiled and dynamically compiled views.
public class CompilationOptionsTests
{
private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(RazorWebSite));
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
[Fact]
public async Task CompilationOptions_AreUsedByViewsAndPartials()
{
// Arrange
#if ASPNET50
var expected =
@"This method is running from ASPNET50
This method is only defined in ASPNET50";
#elif ASPNETCORE50
var expected =
@"This method is running from ASPNETCORE50
This method is only defined in ASPNETCORE50";
#endif
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var body = await client.GetStringAsync("http://localhost/ViewsConsumingCompilationOptions/");
// Assert
Assert.Equal(expected, body.Trim());
}
}
}

View File

@ -11,6 +11,7 @@ using Microsoft.AspNet.Builder;
using Microsoft.AspNet.TestHost; using Microsoft.AspNet.TestHost;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Runtime; using Microsoft.Framework.Runtime;
using PrecompilationWebSite;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests namespace Microsoft.AspNet.Mvc.FunctionalTests
@ -18,8 +19,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
public class PrecompilationTest public class PrecompilationTest
{ {
private static readonly TimeSpan _cacheDelayInterval = TimeSpan.FromSeconds(2); private static readonly TimeSpan _cacheDelayInterval = TimeSpan.FromSeconds(2);
private readonly IServiceProvider _services = TestHelper.CreateServices("PrecompilationWebSite"); private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(PrecompilationWebSite));
private readonly Action<IApplicationBuilder> _app = new PrecompilationWebSite.Startup().Configure; private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
[Fact] [Fact]
public async Task PrecompiledView_RendersCorrectly() public async Task PrecompiledView_RendersCorrectly()
@ -37,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// We will render a view that writes the fully qualified name of the Assembly containing the type of // We will render a view that writes the fully qualified name of the Assembly containing the type of
// the view. If the view is precompiled, this assembly will be PrecompilationWebsite. // the view. If the view is precompiled, this assembly will be PrecompilationWebsite.
var assemblyName = typeof(PrecompilationWebSite.Startup).GetTypeInfo().Assembly.GetName().ToString(); var assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().ToString();
try try
{ {
@ -142,6 +143,30 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
} }
} }
[Fact]
public async Task PrecompiledView_UsesCompilationOptionsFromApplication()
{
// Arrange
var assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().ToString();
#if ASPNET50
var expected =
@"Value set inside ASPNET50 " + assemblyName;
#elif ASPNETCORE50
var expected =
@"Value set inside ASPNETCORE50 " + assemblyName;
#endif
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/Home/PrecompiledViewsCanConsumeCompilationOptions");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(expected, responseContent.Trim());
}
private static Task TouchFile(string viewsDir, string file) private static Task TouchFile(string viewsDir, string file)
{ {
File.AppendAllText(Path.Combine(viewsDir, file), " "); File.AppendAllText(Path.Combine(viewsDir, file), " ");

View File

@ -0,0 +1,170 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.IO;
using System.Reflection;
using System.Runtime.Versioning;
using Microsoft.Framework.Runtime;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Razor
{
public class RoslynCompilationServiceTest
{
[Fact]
public void Compile_ReturnsUncachedCompilationResultWithCompiledContent()
{
// Arrange
var content = @"
public class MyTestType {}";
var applicationEnvironment = GetApplicationEnvironment();
var accessor = GetLoadContextAccessor();
var libraryManager = GetLibraryManager();
var compilerOptionsProvider = new Mock<ICompilerOptionsProvider>();
compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationBasePath,
applicationEnvironment.RuntimeFramework,
applicationEnvironment.Configuration))
.Returns(new CompilerOptions());
var mvcRazorHost = new Mock<IMvcRazorHost>();
mvcRazorHost.SetupGet(m => m.MainClassNamePrefix)
.Returns(string.Empty);
var compilationService = new RoslynCompilationService(applicationEnvironment,
accessor,
libraryManager,
compilerOptionsProvider.Object,
mvcRazorHost.Object);
// Act
var result = compilationService.Compile(new TestFileInfo { PhysicalPath = "SomePath" }, content);
// Assert
var uncachedResult = Assert.IsType<UncachedCompilationResult>(result);
Assert.Equal("MyTestType", result.CompiledType.Name);
Assert.Equal(content, result.CompiledContent);
}
[Fact]
public void Compile_UsesApplicationsCompilationSettings_ForParsingAndCompilation()
{
// Arrange
var content = @"
#if MY_CUSTOM_DEFINE
public class MyCustomDefinedClass {}
#else
public class MyNonCustomDefinedClass {}
#endif
";
var applicationEnvironment = GetApplicationEnvironment();
var accessor = GetLoadContextAccessor();
var libraryManager = GetLibraryManager();
var compilerOptionsProvider = new Mock<ICompilerOptionsProvider>();
compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationBasePath,
applicationEnvironment.RuntimeFramework,
applicationEnvironment.Configuration))
.Returns(new CompilerOptions { Defines = new[] { "MY_CUSTOM_DEFINE" } });
var mvcRazorHost = new Mock<IMvcRazorHost>();
mvcRazorHost.SetupGet(m => m.MainClassNamePrefix)
.Returns("My");
var compilationService = new RoslynCompilationService(applicationEnvironment,
accessor,
libraryManager,
compilerOptionsProvider.Object,
mvcRazorHost.Object);
// Act
var result = compilationService.Compile(new TestFileInfo { PhysicalPath = "SomePath" }, content);
// Assert
Assert.NotNull(result.CompiledType);
Assert.Equal("MyCustomDefinedClass", result.CompiledType.Name);
}
[Fact]
public void Compile_ReturnsSingleTypeThatStartsWithMainClassNamePrefix()
{
// Arrange
var content = @"
public class RazorPrefixType {}
public class NotRazorPrefixType {}";
var applicationEnvironment = GetApplicationEnvironment();
var accessor = GetLoadContextAccessor();
var libraryManager = GetLibraryManager();
var compilerOptionsProvider = new Mock<ICompilerOptionsProvider>();
compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationBasePath,
applicationEnvironment.RuntimeFramework,
applicationEnvironment.Configuration))
.Returns(new CompilerOptions());
var mvcRazorHost = new Mock<IMvcRazorHost>();
mvcRazorHost.SetupGet(m => m.MainClassNamePrefix)
.Returns("RazorPrefix");
var compilationService = new RoslynCompilationService(applicationEnvironment,
accessor,
libraryManager,
compilerOptionsProvider.Object,
mvcRazorHost.Object);
// Act
var result = compilationService.Compile(new TestFileInfo { PhysicalPath = "SomePath" }, content);
// Assert
Assert.NotNull(result.CompiledType);
Assert.Equal("RazorPrefixType", result.CompiledType.Name);
}
private static ILibraryManager GetLibraryManager()
{
var fileReference = new Mock<IMetadataFileReference>();
fileReference.SetupGet(f => f.Path)
.Returns(typeof(string).Assembly.Location);
var libraryExport = new Mock<ILibraryExport>();
libraryExport.SetupGet(e => e.MetadataReferences)
.Returns(new[] { fileReference.Object });
libraryExport.SetupGet(e => e.SourceReferences)
.Returns(new ISourceReference[0]);
var libraryManager = new Mock<ILibraryManager>();
libraryManager.Setup(l => l.GetAllExports(It.IsAny<string>()))
.Returns(libraryExport.Object);
return libraryManager.Object;
}
private static IAssemblyLoadContextAccessor GetLoadContextAccessor()
{
var loadContext = new Mock<IAssemblyLoadContext>();
loadContext.Setup(s => s.LoadStream(It.IsAny<Stream>(), It.IsAny<Stream>()))
.Returns((Stream stream, Stream pdb) =>
{
var memoryStream = (MemoryStream)stream;
return Assembly.Load(memoryStream.ToArray());
});
var accessor = new Mock<IAssemblyLoadContextAccessor>();
accessor.Setup(a => a.GetLoadContext(typeof(RoslynCompilationService).Assembly))
.Returns(loadContext.Object);
return accessor.Object;
}
private IApplicationEnvironment GetApplicationEnvironment()
{
var applicationEnvironment = new Mock<IApplicationEnvironment>();
applicationEnvironment.SetupGet(a => a.ApplicationName)
.Returns("MyApp");
applicationEnvironment.SetupGet(a => a.RuntimeFramework)
.Returns(new FrameworkName("ASPNET", new Version(5, 0)));
applicationEnvironment.SetupGet(a => a.Configuration)
.Returns("Debug");
applicationEnvironment.SetupGet(a => a.ApplicationBasePath)
.Returns("MyBasePath");
return applicationEnvironment.Object;
}
}
}

View File

@ -11,5 +11,10 @@ namespace PrecompilationWebSite.Controllers
{ {
return View(); return View();
} }
public IActionResult PrecompiledViewsCanConsumeCompilationOptions()
{
return View("~/Views/ViewsConsumingCompilationOptions/Index");
}
} }
} }

View File

@ -0,0 +1,9 @@
@{
string message =
#if ASPNET50
"Value set inside ASPNET50 " + GetType().Assembly.FullName;
#elif ASPNETCORE50
"Value set inside ASPNETCORE50 " + System.Reflection.IntrospectionExtensions.GetTypeInfo(GetType()).Assembly.FullName;
#endif
}
@message

View File

@ -12,8 +12,16 @@
"Microsoft.AspNet.StaticFiles": "1.0.0-*" "Microsoft.AspNet.StaticFiles": "1.0.0-*"
}, },
"frameworks": { "frameworks": {
"aspnet50": { }, "aspnet50": {
"aspnetcore50": { } "compilationOptions": {
"define": [ "CUSTOM_ASPNET50_DEFINE" ]
}
},
"aspnetcore50": {
"compilationOptions": {
"define": [ "CUSTOM_ASPNETCORE50_DEFINE" ]
}
}
}, },
"webroot": "wwwroot" "webroot": "wwwroot"
} }

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
namespace RazorWebSite.Controllers
{
// Views returned by this controller use #ifdefs for defines specified in project.json
// The intent of this controller is to verify that view compilation uses the app's compilation settings.
public class ViewsConsumingCompilationOptionsController : Controller
{
public ViewResult Index()
{
return View();
}
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace RazorWebSite
{
public class FrameworkSpecificHelper
{
public string ExecuteOperation()
{
#if ASPNET50
return "This method is running from ASPNET50";
#elif ASPNETCORE50
return "This method is running from ASPNETCORE50";
#endif
}
#if ASPNET50_CUSTOM_DEFINE
public string ExecuteAspNet50Operation()
{
return "This method is only defined in ASPNET50";
}
#endif
#if ASPNETCORE50_CUSTOM_DEFINE
public string ExecuteAspNetCore50Operation()
{
return "This method is only defined in ASPNETCORE50";
}
#endif
}
}

View File

@ -21,6 +21,7 @@ namespace RazorWebSite
services.AddMvc(configuration); services.AddMvc(configuration);
services.AddTransient<InjectedHelper>(); services.AddTransient<InjectedHelper>();
services.AddTransient<TaskReturningService>(); services.AddTransient<TaskReturningService>();
services.AddTransient<FrameworkSpecificHelper>();
services.Configure<RazorViewEngineOptions>(options => services.Configure<RazorViewEngineOptions>(options =>
{ {
var expander = new LanguageViewLocationExpander( var expander = new LanguageViewLocationExpander(

View File

@ -0,0 +1,3 @@
@inject FrameworkSpecificHelper MyHelper
@MyHelper.ExecuteOperation()
@Html.Partial("_Partial")

View File

@ -0,0 +1,11 @@
@inject FrameworkSpecificHelper MyHelper
@{
string value =
#if ASPNET50_CUSTOM_DEFINE
MyHelper.ExecuteAspNet50Operation();
#endif
#if ASPNETCORE50_CUSTOM_DEFINE
MyHelper.ExecuteAspNetCore50Operation();
#endif
}
@value

View File

@ -12,8 +12,16 @@
"Microsoft.AspNet.StaticFiles": "1.0.0-*" "Microsoft.AspNet.StaticFiles": "1.0.0-*"
}, },
"frameworks": { "frameworks": {
"aspnet50": { }, "aspnet50": {
"aspnetcore50": { } "compilationOptions": {
"define": [ "ASPNET50_CUSTOM_DEFINE" ]
}
},
"aspnetcore50": {
"compilationOptions": {
"define": [ "ASPNETCORE50_CUSTOM_DEFINE" ]
}
}
}, },
"webroot": "wwwroot" "webroot": "wwwroot"
} }