diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln
index f67e635663..90fabd036d 100644
--- a/Mvc.NoFun.sln
+++ b/Mvc.NoFun.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.24720.0
+VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@@ -81,6 +81,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Te
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MvcSandbox", "samples\MvcSandbox\MvcSandbox.xproj", "{14ED4476-9F24-4776-8417-EA6927F6C9C9}"
EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools", "src\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj", "{F8BF7D95-0633-407F-BB0B-02563F13C068}"
+EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design", "src\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design\Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.xproj", "{4339FC9B-AEC6-442A-B413-A41555ED76C7}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -464,6 +468,30 @@ Global
{14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|x86.ActiveCfg = Release|Any CPU
{14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|x86.Build.0 = Release|Any CPU
+ {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F8BF7D95-0633-407F-BB0B-02563F13C068}.Debug|x86.Build.0 = Debug|Any CPU
+ {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|x86.ActiveCfg = Release|Any CPU
+ {F8BF7D95-0633-407F-BB0B-02563F13C068}.Release|x86.Build.0 = Release|Any CPU
+ {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Debug|x86.Build.0 = Debug|Any CPU
+ {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|x86.ActiveCfg = Release|Any CPU
+ {4339FC9B-AEC6-442A-B413-A41555ED76C7}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -502,5 +530,7 @@ Global
{8FC726B5-E766-44E0-8B38-1313B6D8D9A7} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{9879B5D5-2325-4A81-B4DF-F279FE8FEEB4} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{14ED4476-9F24-4776-8417-EA6927F6C9C9} = {DAAE4C74-D06F-4874-A166-33305D2643CE}
+ {F8BF7D95-0633-407F-BB0B-02563F13C068} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
+ {4339FC9B-AEC6-442A-B413-A41555ED76C7} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
EndGlobalSection
EndGlobal
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs
index 6426753d4b..7ad0d8fa0d 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using Microsoft.AspNetCore.Mvc.Core.ApplicationParts;
using Microsoft.Extensions.DependencyModel;
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
@@ -12,8 +13,16 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
///
/// An backed by an .
///
- public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider, ICompilationReferencesProvider
+ public class AssemblyPart :
+ ApplicationPart,
+ IApplicationPartTypeProvider,
+ ICompilationReferencesProvider,
+ IPrecompiledViewsProvider
{
+ public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";
+ public static readonly string ViewCollectionNamespace = "AspNetCore";
+ public static readonly string ViewCollectionTypeName = "__PrecompiledViewCollection";
+
///
/// Initalizes a new instance.
///
@@ -41,6 +50,27 @@ public AssemblyPart(Assembly assembly)
///
public IEnumerable Types => Assembly.DefinedTypes;
+ ///
+ public IReadOnlyCollection PrecompiledViews
+ {
+ get
+ {
+ var precompiledAssemblyName = new AssemblyName(Assembly.FullName);
+ precompiledAssemblyName.Name = precompiledAssemblyName.Name + PrecompiledViewsAssemblySuffix;
+
+ var viewFactoryTypeName = $"{ViewCollectionNamespace}.{ViewCollectionTypeName},{precompiledAssemblyName}";
+ var viewFactoryType = Type.GetType(viewFactoryTypeName);
+
+ if (viewFactoryType == null)
+ {
+ return null;
+ }
+
+ var precompiledViews = Activator.CreateInstance(viewFactoryType) as PrecompiledViews;
+ return precompiledViews?.ViewInfos;
+ }
+ }
+
///
public IEnumerable GetReferencePaths()
{
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs
new file mode 100644
index 0000000000..0325777330
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IPrecompiledViewsProvider.cs
@@ -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.AspNetCore.Mvc.ApplicationParts;
+
+namespace Microsoft.AspNetCore.Mvc.Core.ApplicationParts
+{
+ ///
+ /// Exposes a sequence of precompiled views associated with an .
+ ///
+ public interface IPrecompiledViewsProvider
+ {
+ ///
+ /// Gets the sequence of .
+ ///
+ IReadOnlyCollection PrecompiledViews { get; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.cs
new file mode 100644
index 0000000000..3cfc14cfa8
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViewInfo.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.Core.ApplicationParts
+{
+ ///
+ /// Provides information for precompiled views.
+ ///
+ public class PrecompiledViewInfo
+ {
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The path of the view.
+ /// The view .
+ public PrecompiledViewInfo(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/PrecompiledViews.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViews.cs
new file mode 100644
index 0000000000..4fed43ff6b
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/PrecompiledViews.cs
@@ -0,0 +1,24 @@
+// 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.Core.ApplicationParts
+{
+ ///
+ /// A container for instances.
+ ///
+ public abstract class PrecompiledViews
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The sequence of .
+ protected PrecompiledViews(IReadOnlyCollection precompiledViews)
+ {
+ ViewInfos = precompiledViews;
+ }
+
+ public IReadOnlyCollection ViewInfos { get; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DesignTime/IMvcBuilderConfiguration.cs b/src/Microsoft.AspNetCore.Mvc.Core/DesignTime/IMvcBuilderConfiguration.cs
new file mode 100644
index 0000000000..49f9a90d6c
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/DesignTime/IMvcBuilderConfiguration.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.DesignTime
+{
+ ///
+ /// Configures the . Implement this interface to enable design-time configuration
+ /// (for instance during pre-compilation of views) of .
+ ///
+ public interface IMvcBuilderConfiguration
+ {
+ ///
+ /// Configures the .
+ ///
+ ///
+ void ConfigureMvc(IMvcBuilder builder);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs
new file mode 100644
index 0000000000..9540724a97
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/CommonOptions.cs
@@ -0,0 +1,39 @@
+// 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.CommandLineUtils;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal
+{
+ public class CommonOptions
+ {
+ public static readonly string ConfigureCompilationTypeTemplate = "--configure-compilation-type";
+ public static readonly string ContentRootTemplate = "--content-root";
+
+ public CommandArgument ProjectArgument { get; private set; }
+
+ public CommandOption ConfigureCompilationType { get; private set; }
+
+ public CommandOption ContentRootOption { get; private set; }
+
+ public void Configure(CommandLineApplication app)
+ {
+ app.Description = "Precompiles an application.";
+ app.HelpOption("-?|-h|--help");
+
+ ProjectArgument = app.Argument(
+ "project",
+ "The path to the project (project folder or project.json) with precompilation.");
+
+ ConfigureCompilationType = app.Option(
+ ConfigureCompilationTypeTemplate,
+ "Type with Configure method",
+ CommandOptionType.SingleValue);
+
+ ContentRootOption = app.Option(
+ ContentRootTemplate,
+ "The application's content root.",
+ CommandOptionType.SingleValue);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/DebugHelper.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/DebugHelper.cs
new file mode 100644
index 0000000000..dde371bb33
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/DebugHelper.cs
@@ -0,0 +1,25 @@
+// 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.
+
+#if DEBUG
+using System;
+using System.Diagnostics;
+using System.Linq;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal
+{
+ public static class DebugHelper
+ {
+ public static void HandleDebugSwitch(ref string[] args)
+ {
+ if (args.Length > 0 && string.Equals("--debug", args[0], StringComparison.OrdinalIgnoreCase))
+ {
+ args = args.Skip(1).ToArray();
+ Console.WriteLine("Waiting for debugger to attach. Press ENTER to continue");
+ Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}");
+ Console.ReadLine();
+ }
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs
new file mode 100644
index 0000000000..f9be9aaa37
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/MvcServicesProvider.cs
@@ -0,0 +1,115 @@
+// 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.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.AspNetCore.Mvc.DesignTime;
+using Microsoft.AspNetCore.Mvc.Internal;
+using Microsoft.AspNetCore.Mvc.Razor.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal
+{
+ public class MvcServicesProvider
+ {
+ private readonly string _projectPath;
+ private readonly string _contentRoot;
+ private readonly string _applicationName;
+
+ public MvcServicesProvider(
+ string projectPath,
+ string applicationName,
+ string contentRoot,
+ string configureCompilationType)
+ {
+ _projectPath = projectPath;
+ _contentRoot = contentRoot;
+ _applicationName = applicationName;
+
+ var mvcBuilderConfiguration = GetConfigureCompilationAction(configureCompilationType);
+ var serviceProvider = GetProvider(mvcBuilderConfiguration);
+
+ Host = serviceProvider.GetRequiredService();
+ Compiler = serviceProvider.GetRequiredService();
+
+ FileProvider = serviceProvider.GetRequiredService().FileProvider;
+ }
+
+ public IMvcRazorHost Host { get; }
+
+ public CSharpCompiler Compiler { get; }
+
+ public IFileProvider FileProvider { get; }
+
+ private IMvcBuilderConfiguration GetConfigureCompilationAction(string configureCompilationType)
+ {
+ Type type;
+ if (!string.IsNullOrEmpty(configureCompilationType))
+ {
+ type = Type.GetType(configureCompilationType);
+ if (type == null)
+ {
+ throw new InvalidOperationException($"Unable to find type '{type}.");
+ }
+ }
+ else
+ {
+ var assemblyName = new AssemblyName(_applicationName);
+ var assembly = Assembly.Load(assemblyName);
+ type = assembly
+ .GetExportedTypes()
+ .FirstOrDefault(typeof(IMvcBuilderConfiguration).IsAssignableFrom);
+ }
+
+ if (type == null)
+ {
+ return null;
+ }
+
+ var instance = Activator.CreateInstance(type) as IMvcBuilderConfiguration;
+ if (instance == null)
+ {
+ throw new InvalidOperationException($"Type {configureCompilationType} does not implement " +
+ $"{typeof(IMvcBuilderConfiguration)}.");
+ }
+
+ return instance;
+ }
+
+ private IServiceProvider GetProvider(IMvcBuilderConfiguration mvcBuilderConfiguration)
+ {
+ var services = new ServiceCollection();
+
+ var hostingEnvironment = new HostingEnvironment
+ {
+ ApplicationName = _applicationName,
+ WebRootFileProvider = new PhysicalFileProvider(_projectPath),
+ ContentRootFileProvider = new PhysicalFileProvider(_contentRoot),
+ ContentRootPath = _contentRoot,
+ };
+ var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore");
+
+ services
+ .AddSingleton(hostingEnvironment)
+ .AddSingleton(diagnosticSource)
+ .AddLogging()
+ .AddSingleton();
+
+ var mvcCoreBuilder = services
+ .AddMvcCore()
+ .AddRazorViewEngine();
+
+ var mvcBuilder = new MvcBuilder(mvcCoreBuilder.Services, mvcCoreBuilder.PartManager);
+ mvcBuilderConfiguration?.ConfigureMvc(mvcBuilder);
+
+ return mvcBuilder.Services.BuildServiceProvider();
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationApplication.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationApplication.cs
new file mode 100644
index 0000000000..72e4861055
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompilationApplication.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;
+using System.Reflection;
+using Microsoft.Extensions.CommandLineUtils;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal
+{
+ public class PrecompilationApplication : CommandLineApplication
+ {
+ private readonly Type _callingType;
+
+ public PrecompilationApplication(Type callingType)
+ {
+ _callingType = callingType;
+
+ Name = "razor-precompile";
+ FullName = "Microsoft Razor Precompilation Utility";
+ Description = "Precompiles Razor views.";
+ ShortVersionGetter = GetInformationalVersion;
+
+ HelpOption("-?|-h|--help");
+
+ OnExecute(() =>
+ {
+ ShowHelp();
+ return 2;
+ });
+ }
+
+ public new int Execute(params string[] args)
+ {
+ try
+ {
+ return base.Execute(args);
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine(ex.Message);
+#if DEBUG
+ Console.Error.WriteLine(ex);
+#endif
+ return 1;
+ }
+ }
+
+ private string GetInformationalVersion()
+ {
+ var assembly = _callingType.GetTypeInfo().Assembly;
+ var attributes = assembly.GetCustomAttributes(
+ typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute[];
+
+ var versionAttribute = attributes.Length == 0 ?
+ assembly.GetName().Version.ToString() :
+ attributes[0].InformationalVersion;
+
+ return versionAttribute;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs
new file mode 100644
index 0000000000..51ec692613
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/PrecompileRunCommand.cs
@@ -0,0 +1,238 @@
+// 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.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
+using Microsoft.AspNetCore.Mvc.Razor.Compilation;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Emit;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.Extensions.CommandLineUtils;
+using Microsoft.Extensions.FileProviders;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal
+{
+ public class PrecompileRunCommand
+ {
+ public static readonly string ApplicationNameTemplate = "--applicationName";
+ public static readonly string OutputPathTemplate = "--output-path";
+ private static readonly ParallelOptions ParalellOptions = new ParallelOptions
+ {
+ MaxDegreeOfParallelism = 4
+ };
+
+ private CommandOption OutputPathOption { get; set; }
+
+ private CommandOption ApplicationNameOption { get; set; }
+
+ private MvcServicesProvider ServicesProvider { get; set; }
+
+ private CommonOptions Options { get; } = new CommonOptions();
+
+ private StrongNameOptions StrongNameOptions { get; } = new StrongNameOptions();
+
+ private string ProjectPath { get; set; }
+
+ public void Configure(CommandLineApplication app)
+ {
+ Options.Configure(app);
+ StrongNameOptions.Configure(app);
+
+ OutputPathOption = app.Option(
+ OutputPathTemplate,
+ "Path to the emit the precompiled assembly to.",
+ CommandOptionType.SingleValue);
+
+ ApplicationNameOption = app.Option(
+ ApplicationNameTemplate,
+ "Name of the application to produce precompiled assembly for.",
+ CommandOptionType.SingleValue);
+
+ app.OnExecute(() => Execute());
+ }
+
+ private int Execute()
+ {
+ ParseArguments();
+
+ ServicesProvider = new MvcServicesProvider(
+ ProjectPath,
+ ApplicationNameOption.Value(),
+ Options.ContentRootOption.Value(),
+ Options.ConfigureCompilationType.Value());
+
+ Console.WriteLine("Running Razor view precompilation.");
+
+ var stopWatch = Stopwatch.StartNew();
+ var results = ParseViews();
+ var success = true;
+ foreach (var result in results)
+ {
+ if (!result.GeneratorResults.Success)
+ {
+ success = false;
+ foreach (var error in result.GeneratorResults.ParserErrors)
+ {
+ Console.Error.WriteLine($"{error.Location.FilePath} ({error.Location.LineIndex}): {error.Message}");
+ }
+ }
+ }
+
+ if (!success)
+ {
+ return 1;
+ }
+
+ var precompileAssemblyName = $"{ApplicationNameOption.Value()}{AssemblyPart.PrecompiledViewsAssemblySuffix}";
+ var compilation = CompileViews(results, precompileAssemblyName);
+
+ var assemblyPath = Path.Combine(OutputPathOption.Value(), precompileAssemblyName + ".dll");
+ var emitResult = EmitAssembly(compilation, assemblyPath);
+
+ if (!emitResult.Success)
+ {
+ foreach (var diagnostic in emitResult.Diagnostics)
+ {
+ Console.Error.WriteLine(CSharpDiagnosticFormatter.Instance.Format(diagnostic));
+ }
+
+ return 1;
+ }
+
+ stopWatch.Stop();
+ Console.WriteLine($"Precompiled views emitted to {assemblyPath}.");
+ Console.WriteLine($"Successfully compiled {results.Length} Razor views in {stopWatch.ElapsedMilliseconds}ms.");
+ return 0;
+ }
+
+ private EmitResult EmitAssembly(CSharpCompilation compilation, string assemblyPath)
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(assemblyPath));
+
+ EmitResult emitResult;
+ using (var assemblyStream = File.OpenWrite(assemblyPath))
+ {
+ using (var pdbStream = File.OpenWrite(Path.ChangeExtension(assemblyPath, ".pdb")))
+ {
+ emitResult = compilation.Emit(
+ assemblyStream,
+ pdbStream,
+ options: ServicesProvider.Compiler.EmitOptions);
+ }
+ }
+
+ return emitResult;
+ }
+
+ private CSharpCompilation CompileViews(ViewCompilationInfo[] results, string assemblyname)
+ {
+ var compiler = ServicesProvider.Compiler;
+ var compilation = compiler.CreateCompilation(assemblyname);
+ var syntaxTrees = new SyntaxTree[results.Length];
+
+ Parallel.For(0, results.Length, ParalellOptions, i =>
+ {
+ var result = results[i];
+ var sourceText = SourceText.From(result.GeneratorResults.GeneratedCode, Encoding.UTF8);
+ var fileInfo = result.RelativeFileInfo;
+ var syntaxTree = compiler.CreateSyntaxTree(sourceText)
+ .WithFilePath(fileInfo.FileInfo.PhysicalPath ?? fileInfo.RelativePath);
+ syntaxTrees[i] = syntaxTree;
+ });
+
+ compilation = compilation.AddSyntaxTrees(syntaxTrees);
+ for (var i = 0; i < results.Length; i++)
+ {
+ results[i].TypeName = ReadTypeInfo(compilation, syntaxTrees[i]);
+ }
+
+ compilation = compiler.ProcessCompilation(compilation);
+ var codeGenerator = new ViewCollectionCodeGenerator(compiler, compilation);
+ codeGenerator.AddViewFactory(results);
+
+ var assemblyName = new AssemblyName(ApplicationNameOption.Value());
+ assemblyName = Assembly.Load(assemblyName).GetName();
+ codeGenerator.AddAssemblyMetadata(assemblyName, StrongNameOptions);
+
+ return codeGenerator.Compilation;
+ }
+
+ private void ParseArguments()
+ {
+ ProjectPath = Options.ProjectArgument.Value;
+ if (string.IsNullOrEmpty(ProjectPath))
+ {
+ throw new ArgumentException("Project path not specified.");
+ }
+
+ if (!OutputPathOption.HasValue())
+ {
+ throw new ArgumentException($"Option {OutputPathTemplate} does not specify a value.");
+ }
+
+ if (!ApplicationNameOption.HasValue())
+ {
+ throw new ArgumentException($"Option {ApplicationNameTemplate} does not specify a value.");
+ }
+ }
+
+ private ViewCompilationInfo[] ParseViews()
+ {
+ var files = new List();
+ GetRazorFiles(ServicesProvider.FileProvider, files, root: string.Empty);
+ var results = new ViewCompilationInfo[files.Count];
+ Parallel.For(0, results.Length, ParalellOptions, i =>
+ {
+ var fileInfo = files[i];
+ using (var fileStream = fileInfo.FileInfo.CreateReadStream())
+ {
+ var result = ServicesProvider.Host.GenerateCode(fileInfo.RelativePath, fileStream);
+ results[i] = new ViewCompilationInfo(fileInfo, result);
+ }
+ });
+
+ return results;
+ }
+
+ private static void GetRazorFiles(IFileProvider fileProvider, List razorFiles, string root)
+ {
+ foreach (var fileInfo in fileProvider.GetDirectoryContents(root))
+ {
+ var relativePath = Path.Combine(root, fileInfo.Name);
+ if (fileInfo.IsDirectory)
+ {
+ GetRazorFiles(fileProvider, razorFiles, relativePath);
+ }
+ else if (fileInfo.Name.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase))
+ {
+ razorFiles.Add(new RelativeFileInfo(fileInfo, relativePath));
+ }
+ }
+ }
+
+ private string ReadTypeInfo(CSharpCompilation compilation, SyntaxTree syntaxTree)
+ {
+ var semanticModel = compilation.GetSemanticModel(syntaxTree, ignoreAccessibility: true);
+ var classDeclarations = syntaxTree.GetRoot().DescendantNodes().OfType();
+ foreach (var declaration in classDeclarations)
+ {
+ var typeModel = semanticModel.GetDeclaredSymbol(declaration);
+ if (typeModel.ContainingType == null && typeModel.DeclaredAccessibility == Accessibility.Public)
+ {
+ return typeModel.ToDisplayString();
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/SnkUtils.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/SnkUtils.cs
new file mode 100644
index 0000000000..db72ea864c
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/SnkUtils.cs
@@ -0,0 +1,88 @@
+// 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.Immutable;
+using System.IO;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal
+{
+ // Copied from https://github.com/dotnet/cli/blob/rel/1.0.0/src/Microsoft.DotNet.ProjectModel.Workspaces/SnkUtils.cs
+ public static class SnkUtils
+ {
+ const byte PUBLICKEYBLOB = 0x06;
+ const byte PRIVATEKEYBLOB = 0x07;
+
+ private const uint CALG_RSA_SIGN = 0x00002400;
+ private const uint CALG_SHA = 0x00008004;
+
+ private const uint RSA1 = 0x31415352; //"RSA1" publickeyblob
+ private const uint RSA2 = 0x32415352; //"RSA2" privatekeyblob
+
+ private const int VersionOffset = 1;
+ private const int ModulusLengthOffset = 12;
+ private const int ExponentOffset = 16;
+ private const int MagicPrivateKeyOffset = 8;
+ private const int MagicPublicKeyOffset = 20;
+
+ public static ImmutableArray ExtractPublicKey(byte[] snk)
+ {
+ ValidateBlob(snk);
+
+ if (snk[0] != PRIVATEKEYBLOB)
+ {
+ return ImmutableArray.Create(snk);
+ }
+
+ var version = snk[VersionOffset];
+ int modulusBitLength = ReadInt32(snk, ModulusLengthOffset);
+ uint exponent = (uint)ReadInt32(snk, ExponentOffset);
+ var modulus = new byte[modulusBitLength >> 3];
+
+ Array.Copy(snk, 20, modulus, 0, modulus.Length);
+
+ return CreatePublicKey(version, exponent, modulus);
+ }
+
+ private static void ValidateBlob(byte[] snk)
+ {
+ // 160 - the size of public key
+ if (snk.Length >= 160)
+ {
+ if (snk[0] == PRIVATEKEYBLOB && ReadInt32(snk, MagicPrivateKeyOffset) == RSA2 || // valid private key
+ snk[12] == PUBLICKEYBLOB && ReadInt32(snk, MagicPublicKeyOffset) == RSA1) // valid public key
+ {
+ return;
+ }
+ }
+
+ throw new InvalidOperationException("Invalid key file.");
+ }
+
+ private static int ReadInt32(byte[] array, int index)
+ {
+ return array[index] | array[index + 1] << 8 | array[index + 2] << 16 | array[index + 3] << 24;
+ }
+
+ private static ImmutableArray CreatePublicKey(byte version, uint exponent, byte[] modulus)
+ {
+ using (var ms = new MemoryStream(160))
+ using (var binaryWriter = new BinaryWriter(ms))
+ {
+ binaryWriter.Write(CALG_RSA_SIGN);
+ binaryWriter.Write(CALG_SHA);
+ // total size of the rest of the blob (20 - size of RSAPUBKEY)
+ binaryWriter.Write(modulus.Length + 20);
+ binaryWriter.Write(PUBLICKEYBLOB);
+ binaryWriter.Write(version);
+ binaryWriter.Write((ushort)0x00000000); // reserved
+ binaryWriter.Write(CALG_RSA_SIGN);
+ binaryWriter.Write(RSA1);
+ binaryWriter.Write(modulus.Length << 3);
+ binaryWriter.Write(exponent);
+ binaryWriter.Write(modulus);
+ return ImmutableArray.Create(ms.ToArray());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/StrongNameOptions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/StrongNameOptions.cs
new file mode 100644
index 0000000000..e079707018
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/StrongNameOptions.cs
@@ -0,0 +1,44 @@
+// 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.CommandLineUtils;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal
+{
+ public class StrongNameOptions
+ {
+ public static readonly string StrongNameKeyPath = "--key-file";
+ public static readonly string DelaySignTemplate = "--delay-sign";
+ public static readonly string PublicSignTemplate = "--public-sign";
+
+ public CommandOption KeyFileOption { get; set; }
+
+ public CommandOption DelaySignOption { get; private set; }
+
+ public CommandOption PublicSignOption { get; private set; }
+
+ public void Configure(CommandLineApplication app)
+ {
+ KeyFileOption = app.Option(
+ StrongNameKeyPath,
+ "Strong name key path",
+ CommandOptionType.SingleValue);
+
+ DelaySignOption = app.Option(
+ DelaySignTemplate,
+ "Determines if the precompiled view assembly is to be delay signed.",
+ CommandOptionType.NoValue);
+
+ PublicSignOption = app.Option(
+ PublicSignTemplate,
+ "Determines if the precompiled view assembly is to be public signed.",
+ CommandOptionType.NoValue);
+ }
+
+ public string KeyFile => KeyFileOption.Value();
+
+ public bool DelaySign => DelaySignOption.HasValue();
+
+ public bool PublicSign => PublicSignOption.HasValue();
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCollectionCodeGenerator.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCollectionCodeGenerator.cs
new file mode 100644
index 0000000000..54d642e1c3
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCollectionCodeGenerator.cs
@@ -0,0 +1,86 @@
+// 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.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Text;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
+using Microsoft.AspNetCore.Mvc.Core.ApplicationParts;
+using Microsoft.AspNetCore.Mvc.Razor.Internal;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal
+{
+ public class ViewCollectionCodeGenerator
+ {
+ public ViewCollectionCodeGenerator(
+ CSharpCompiler compiler,
+ CSharpCompilation compilation)
+ {
+ Compiler = compiler;
+ Compilation = compilation;
+ }
+
+ public CSharpCompiler Compiler { get; }
+
+ public CSharpCompilation Compilation { get; private set; }
+
+ public void AddViewFactory(IList result)
+ {
+ var precompiledViewsArray = new StringBuilder();
+ foreach (var item in result)
+ {
+ precompiledViewsArray.AppendLine(
+ $"new {typeof(PrecompiledViewInfo).FullName}(@\"{item.RelativeFileInfo.RelativePath}\", typeof({item.TypeName})),");
+ }
+
+ var factoryContent =
+ $@"
+namespace {AssemblyPart.ViewCollectionNamespace}
+{{
+ public class {AssemblyPart.ViewCollectionTypeName} : {typeof(PrecompiledViews).FullName}
+ {{
+ public {AssemblyPart.ViewCollectionTypeName}() : base(new[]
+ {{
+ {precompiledViewsArray}
+ }})
+ {{
+ }}
+ }}
+}}";
+ var syntaxTree = Compiler.CreateSyntaxTree(SourceText.From(factoryContent));
+ Compilation = Compilation.AddSyntaxTrees(syntaxTree);
+ }
+
+ public void AddAssemblyMetadata(
+ AssemblyName applicationAssemblyName,
+ StrongNameOptions strongNameOptions)
+ {
+ if (!string.IsNullOrEmpty(strongNameOptions.KeyFile))
+ {
+ var updatedOptions = Compilation.Options.WithStrongNameProvider(new DesktopStrongNameProvider());
+
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || strongNameOptions.PublicSign)
+ {
+ updatedOptions = updatedOptions.WithCryptoPublicKey(
+ SnkUtils.ExtractPublicKey(File.ReadAllBytes(strongNameOptions.KeyFile)));
+ }
+ else
+ {
+ updatedOptions = updatedOptions.WithCryptoKeyFile(strongNameOptions.KeyFile)
+ .WithDelaySign(strongNameOptions.DelaySign);
+ }
+
+ Compilation = Compilation.WithOptions(updatedOptions);
+ }
+
+ var assemblyVersionContent = $"[assembly:{typeof(AssemblyVersionAttribute).FullName}(\"{applicationAssemblyName.Version}\")]";
+ var syntaxTree = Compiler.CreateSyntaxTree(SourceText.From(assemblyVersionContent));
+ Compilation = Compilation.AddSyntaxTrees(syntaxTree);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCompilationInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCompilationInfo.cs
new file mode 100644
index 0000000000..f2ac3b44e3
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Internal/ViewCompilationInfo.cs
@@ -0,0 +1,25 @@
+// 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.AspNetCore.Mvc.Razor.Compilation;
+using Microsoft.AspNetCore.Razor.CodeGenerators;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal
+{
+ public class ViewCompilationInfo
+ {
+ public ViewCompilationInfo(
+ RelativeFileInfo relativeFileInfo,
+ GeneratorResults generatorResults)
+ {
+ RelativeFileInfo = relativeFileInfo;
+ GeneratorResults = generatorResults;
+ }
+
+ public RelativeFileInfo RelativeFileInfo { get; }
+
+ public GeneratorResults GeneratorResults { get; }
+
+ public string TypeName { get; set; }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.xproj b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.xproj
similarity index 55%
rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.xproj
rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.xproj
index 7a89944c5a..253d7491f8 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.xproj
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.xproj
@@ -1,20 +1,18 @@
-
+
- 14.0
+ 14.0.25420
$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
-
+
- a8aa326e-8ee8-4f11-b750-23028e0949d7
+ 4339fc9b-aec6-442a-b413-a41555ed76c7
.\obj
.\bin\
+
2.0
-
-
-
-
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Program.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Program.cs
new file mode 100644
index 0000000000..ee05cf6b9c
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Program.cs
@@ -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.Linq;
+using System.Reflection;
+using Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design
+{
+ public class Program
+ {
+ private readonly static Type ProgramType = typeof(Program);
+
+ public static int Main(string[] args)
+ {
+#if DEBUG
+ DebugHelper.HandleDebugSwitch(ref args);
+#endif
+
+ EnsureValidDispatchRecipient(ref args);
+
+ var app = new PrecompilationApplication(ProgramType);
+ new PrecompileRunCommand().Configure(app);
+ return app.Execute(args);
+ }
+
+ private static void EnsureValidDispatchRecipient(ref string[] args)
+ {
+ const string DispatcherVersionArgumentName = "--dispatcher-version";
+
+ if (!args.Contains(DispatcherVersionArgumentName, StringComparer.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ var dispatcherArgumentIndex = Array.FindIndex(
+ args,
+ (value) => string.Equals(value, DispatcherVersionArgumentName, StringComparison.OrdinalIgnoreCase));
+ var dispatcherArgumentValueIndex = dispatcherArgumentIndex + 1;
+ if (dispatcherArgumentValueIndex < args.Length)
+ {
+ var dispatcherVersion = args[dispatcherArgumentValueIndex];
+
+ var thisAssembly = ProgramType.GetTypeInfo().Assembly;
+ var version = thisAssembly.GetCustomAttribute()
+ ?.InformationalVersion
+ ?? thisAssembly.GetName().Version.ToString();
+
+ if (string.Equals(dispatcherVersion, version, StringComparison.Ordinal))
+ {
+ // Remove dispatcher arguments from
+ var preDispatcherArgument = args.Take(dispatcherArgumentIndex);
+ var postDispatcherArgument = args.Skip(dispatcherArgumentIndex + 2);
+ var newProgramArguments = preDispatcherArgument.Concat(postDispatcherArgument);
+ args = newProgramArguments.ToArray();
+ return;
+ }
+ }
+
+ var thisAssemblyName = typeof(Program).GetTypeInfo().Assembly.GetName().Name;
+
+ // Could not validate the dispatcher version.
+ throw new InvalidOperationException("Could not invoke tool");
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..2dc4003a17
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/Properties/AssemblyInfo.cs
@@ -0,0 +1,11 @@
+// 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 System.Resources;
+
+[assembly: AssemblyMetadata("Serviceable", "True")]
+[assembly: NeutralResourcesLanguage("en-us")]
+[assembly: AssemblyCompany("Microsoft Corporation.")]
+[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
+[assembly: AssemblyProduct("Microsoft ASP.NET Core")]
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/project.json b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/project.json
new file mode 100644
index 0000000000..26565cfcec
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design/project.json
@@ -0,0 +1,41 @@
+{
+ "version": "1.0.0-*",
+ "description": "Razor precompilation",
+ "packOptions": {
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/AspNet/Mvc"
+ },
+ "tags": [
+ "cshtml",
+ "razor",
+ "precompilation",
+ "aspnetcore"
+ ]
+ },
+ "buildOptions": {
+ "keyFile": "../../tools/Key.snk",
+ "warningsAsErrors": true,
+ "emitEntryPoint": true,
+ "nowarn": [
+ "CS1591"
+ ],
+ "xmlDoc": true
+ },
+ "dependencies": {
+ "Microsoft.AspNetCore.Hosting": "1.1.0-*",
+ "Microsoft.AspNetCore.Mvc.Razor": "1.1.0-*",
+ "Microsoft.Extensions.CommandLineUtils": "1.1.0-*"
+ },
+ "frameworks": {
+ "netcoreapp1.0": {
+ "dependencies": {
+ "Microsoft.NETCore.App": {
+ "type": "platform",
+ "version": "1.0.0-*"
+ }
+ }
+ },
+ "net451": {}
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs
new file mode 100644
index 0000000000..664cec4e17
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Internal/PrecompileDispatchCommand.cs
@@ -0,0 +1,183 @@
+// 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.IO;
+using System.Reflection;
+using Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design.Internal;
+using Microsoft.DotNet.InternalAbstractions;
+using Microsoft.DotNet.ProjectModel;
+using Microsoft.Extensions.CommandLineUtils;
+using Microsoft.Extensions.Internal;
+using NuGet.Frameworks;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal
+{
+ public class PrecompileDispatchCommand
+ {
+ private CommonOptions Options { get; } = new CommonOptions();
+
+ private CommandOption FrameworkOption { get; set; }
+
+ private CommandOption ConfigurationOption { get; set; }
+
+ private CommandOption OutputPathOption { get; set; }
+
+ private NuGetFramework TargetFramework { get; set; }
+
+ private CommandOption BuildBasePathOption { get; set; }
+
+ private string ProjectPath { get; set; }
+
+ private string Configuration { get; set; }
+
+ private string OutputPath { get; set; }
+
+ public void Configure(CommandLineApplication app)
+ {
+ Options.Configure(app);
+ FrameworkOption = app.Option(
+ "-f|--framework",
+ "Target Framework",
+ CommandOptionType.SingleValue);
+
+ ConfigurationOption = app.Option(
+ "-c|--configuration",
+ "Configuration",
+ CommandOptionType.SingleValue);
+
+ OutputPathOption = app.Option(
+ "-o|--output-path",
+ "Published path of the application.",
+ CommandOptionType.SingleValue);
+
+ app.OnExecute(() => Execute());
+ }
+
+ private int Execute()
+ {
+ ParseArguments();
+
+ var runtimeContext = GetRuntimeContext();
+
+ var outputPaths = runtimeContext.GetOutputPaths(Configuration);
+ var applicationName = Path.GetFileNameWithoutExtension(outputPaths.CompilationFiles.Assembly);
+ var dispatchArgs = new List
+ {
+#if DEBUG
+ "--debug",
+#endif
+ ProjectPath,
+ PrecompileRunCommand.ApplicationNameTemplate,
+ applicationName,
+ PrecompileRunCommand.OutputPathTemplate,
+ OutputPath,
+ CommonOptions.ContentRootTemplate,
+ Options.ContentRootOption.Value() ?? Directory.GetCurrentDirectory(),
+ };
+
+ if (Options.ConfigureCompilationType.HasValue())
+ {
+ dispatchArgs.Add(CommonOptions.ConfigureCompilationTypeTemplate);
+ dispatchArgs.Add(Options.ConfigureCompilationType.Value());
+ }
+
+ var compilerOptions = runtimeContext.ProjectFile.GetCompilerOptions(TargetFramework, Configuration);
+ if (!string.IsNullOrEmpty(compilerOptions.KeyFile))
+ {
+ dispatchArgs.Add(StrongNameOptions.StrongNameKeyPath);
+ var keyFilePath = Path.GetFullPath(Path.Combine(runtimeContext.ProjectDirectory, compilerOptions.KeyFile));
+ dispatchArgs.Add(keyFilePath);
+
+ if (compilerOptions.DelaySign ?? false)
+ {
+ dispatchArgs.Add(StrongNameOptions.DelaySignTemplate);
+ }
+
+ if (compilerOptions.PublicSign ?? false)
+ {
+ dispatchArgs.Add(StrongNameOptions.PublicSignTemplate);
+ }
+ }
+
+ var toolName = typeof(Design.Program).GetTypeInfo().Assembly.GetName().Name;
+ var dispatchCommand = DotnetToolDispatcher.CreateDispatchCommand(
+ dispatchArgs,
+ TargetFramework,
+ Configuration,
+ outputPath: outputPaths.RuntimeOutputPath,
+ buildBasePath: null,
+ projectDirectory: ProjectPath,
+ toolName: toolName);
+
+ var commandExitCode = dispatchCommand
+ .ForwardStdErr(Console.Error)
+ .ForwardStdOut(Console.Out)
+ .Execute()
+ .ExitCode;
+
+ return commandExitCode;
+ }
+
+ private void ParseArguments()
+ {
+ ProjectPath = GetProjectPath();
+ Configuration = ConfigurationOption.Value() ?? DotNet.Cli.Utils.Constants.DefaultConfiguration;
+
+ if (!FrameworkOption.HasValue())
+ {
+ throw new Exception($"Option {FrameworkOption.Template} does not have a value.");
+ }
+ TargetFramework = NuGetFramework.Parse(FrameworkOption.Value());
+
+ if (!OutputPathOption.HasValue())
+ {
+ throw new Exception($"Option {OutputPathOption.Template} does not have a value.");
+ }
+ OutputPath = OutputPathOption.Value();
+ }
+
+ private string GetProjectPath()
+ {
+ string projectPath;
+ if (!string.IsNullOrEmpty(Options.ProjectArgument.Value))
+ {
+ projectPath = Path.GetFullPath(Options.ProjectArgument.Value);
+ if (string.Equals(Path.GetFileName(ProjectPath), "project.json", StringComparison.OrdinalIgnoreCase))
+ {
+ projectPath = Path.GetDirectoryName(ProjectPath);
+ }
+
+ if (!Directory.Exists(projectPath))
+ {
+ throw new InvalidOperationException($"Could not find directory {projectPath}.");
+ }
+ }
+ else
+ {
+ projectPath = Directory.GetCurrentDirectory();
+ }
+
+ return projectPath;
+ }
+
+ private ProjectContext GetRuntimeContext()
+ {
+ var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment());
+
+ var projectContext = workspace.GetProjectContext(ProjectPath, TargetFramework);
+ if (projectContext == null)
+ {
+ Debug.Assert(FrameworkOption.HasValue());
+ throw new InvalidOperationException($"Project '{ProjectPath}' does not support framework: {FrameworkOption.Value()}");
+ }
+
+ var runtimeContext = workspace.GetRuntimeContext(
+ projectContext,
+ RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers());
+ return runtimeContext;
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.xproj b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj
similarity index 55%
rename from test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.xproj
rename to src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj
index 014f4d66cb..6b5961a59f 100644
--- a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.xproj
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools.xproj
@@ -1,20 +1,18 @@
-
+
- 14.0
+ 14.0.25420
$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
-
+
- 6bb4c20b-24c0-45d6-9e4c-c2620959bdd5
+ f8bf7d95-0633-407f-bb0b-02563f13c068
.\obj
.\bin\
+
2.0
-
-
-
-
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs
new file mode 100644
index 0000000000..3ca5029d70
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Program.cs
@@ -0,0 +1,22 @@
+// 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.AspNetCore.Mvc.Razor.Precompilation.Design.Internal;
+using Microsoft.AspNetCore.Mvc.Razor.Precompilation.Internal;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools
+{
+ public class Program
+ {
+ public static int Main(string[] args)
+ {
+#if DEBUG
+ DebugHelper.HandleDebugSwitch(ref args);
+#endif
+
+ var app = new PrecompilationApplication(typeof(Program));
+ new PrecompileDispatchCommand().Configure(app);
+ return app.Execute(args);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..2dc4003a17
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/Properties/AssemblyInfo.cs
@@ -0,0 +1,11 @@
+// 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 System.Resources;
+
+[assembly: AssemblyMetadata("Serviceable", "True")]
+[assembly: NeutralResourcesLanguage("en-us")]
+[assembly: AssemblyCompany("Microsoft Corporation.")]
+[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
+[assembly: AssemblyProduct("Microsoft ASP.NET Core")]
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json
new file mode 100644
index 0000000000..6612d92a65
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Precompilation.Tools/project.json
@@ -0,0 +1,50 @@
+{
+ "version": "1.0.0-*",
+ "description": "Razor precompilation",
+ "packOptions": {
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/AspNet/Mvc"
+ },
+ "tags": [
+ "cshtml",
+ "razor",
+ "precompilation",
+ "aspnetcore"
+ ]
+ },
+ "buildOptions": {
+ "outputName": "dotnet-razor-precompile",
+ "keyFile": "../../tools/Key.snk",
+ "warningsAsErrors": true,
+ "emitEntryPoint": true,
+ "nowarn": [
+ "CS1591"
+ ],
+
+ "xmlDoc": true
+ },
+ "dependencies": {
+ "Microsoft.AspNetCore.Mvc.Razor.Precompilation.Design": "1.0.0-*",
+ "Microsoft.DotNet.Cli.Utils": "1.0.0-*",
+ "Microsoft.Extensions.CommandLineUtils": "1.1.0-*",
+ "Microsoft.Extensions.DotnetToolDispatcher.Sources": {
+ "type": "build",
+ "version": "1.1.0-*"
+ }
+ },
+ "frameworks": {
+ "netcoreapp1.0": {
+ "dependencies": {
+ "Microsoft.DotNet.ProjectModel.Loader": "1.0.0-*",
+ "Microsoft.NETCore.App": {
+ "type": "platform",
+ "version": "1.0.0-*"
+ },
+ "System.Diagnostics.Process": "4.1.0-*",
+ "System.Runtime.Serialization.Primitives": "4.1.1-*"
+ }
+ },
+ "net451": {}
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeature.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeature.cs
new file mode 100644
index 0000000000..4efeb118fe
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeature.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 PrecompiledViewsFeature
+ {
+ public IDictionary PrecompiledViews { get; } =
+ new Dictionary(StringComparer.OrdinalIgnoreCase);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeatureProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeatureProvider.cs
new file mode 100644
index 0000000000..7f2b477bac
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/PrecompiledViewsFeatureProvider.cs
@@ -0,0 +1,28 @@
+// 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;
+using Microsoft.AspNetCore.Mvc.Core.ApplicationParts;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
+{
+ public class PrecompiledViewsFeatureProvider : IApplicationFeatureProvider
+ {
+ public void PopulateFeature(IEnumerable parts, PrecompiledViewsFeature feature)
+ {
+ foreach (var provider in parts.OfType())
+ {
+ var precompiledViews = provider.PrecompiledViews;
+ if (precompiledViews != null)
+ {
+ foreach (var viewInfo in precompiledViews)
+ {
+ feature.PrecompiledViews[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..ba80ddbb05 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 PrecompiledViewsFeatureProvider());
+ }
}
///
@@ -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..4fdb1e07e5
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs
@@ -0,0 +1,81 @@
+// 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 Microsoft.AspNetCore.Mvc.Razor.Compilation;
+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 Action _compilationCallback;
+ 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;
+ _compilationCallback = optionsAccessor.Value.CompilationCallback;
+ 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);
+ }
+
+ public CSharpCompilation ProcessCompilation(CSharpCompilation compilation)
+ {
+ var compilationContext = new RoslynCompilationContext(compilation);
+ Rewrite(compilationContext);
+ _compilationCallback(compilationContext);
+
+ return compilationContext.Compilation;
+ }
+
+ private void Rewrite(RoslynCompilationContext compilationContext)
+ {
+ var compilation = compilationContext.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);
+ }
+
+ compilationContext.Compilation = compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(rewrittenTrees);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultCompilerCacheProvider.cs
index f38ddd4457..aaa64d1745 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 PrecompiledViewsFeature();
+ applicationPartManager.PopulateFeature(feature);
+ Cache = new CompilerCache(fileProviderAccessor.FileProvider, feature.PrecompiledViews);
}
///
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs
index 833c541949..44d9c3d8bf 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs
@@ -8,17 +8,13 @@
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;
-using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{
@@ -33,59 +29,27 @@ 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;
///
/// Initalizes a new instance of the class.
///
- /// The .
- /// Accessor to .
+ /// The .
/// The .
/// The .
public DefaultRoslynCompilationService(
- ApplicationPartManager partManager,
- IOptions optionsAccessor,
+ CSharpCompiler compiler,
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
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 +64,15 @@ 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 syntaxTree = _compiler.CreateSyntaxTree(sourceText).WithFilePath(assemblyName);
+ var compilation = _compiler
+ .CreateCompilation(assemblyName)
+ .AddSyntaxTrees(syntaxTree);
+ compilation = _compiler.ProcessCompilation(compilation);
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,52 +105,6 @@ 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)
- {
- 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);
- }
-
// Internal for unit testing
internal CompilationResult GetCompilationFailedResult(
string relativePath,
@@ -265,6 +171,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/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/Internal/DefaultRoslynCompilationServiceTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs
index b880459544..fe691ccbf2 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,14 @@ 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),
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 bf8cf2e720..d1756d6a72 100644
--- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
@@ -209,7 +209,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]