-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Add dotnet-razor-precompile tool #5087
Changes from all commits
1c5840a
5c66e06
23e6c4d
b2be0d4
ab983af
c4d5e70
0b72b38
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,15 +5,24 @@ | |
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 | ||
{ | ||
/// <summary> | ||
/// An <see cref="ApplicationPart"/> backed by an <see cref="Assembly"/>. | ||
/// </summary> | ||
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"; | ||
|
||
/// <summary> | ||
/// Initalizes a new <see cref="AssemblyPart"/> instance. | ||
/// </summary> | ||
|
@@ -41,6 +50,27 @@ public AssemblyPart(Assembly assembly) | |
/// <inheritdoc /> | ||
public IEnumerable<TypeInfo> Types => Assembly.DefinedTypes; | ||
|
||
/// <inheritdoc /> | ||
public IReadOnlyCollection<PrecompiledViewInfo> PrecompiledViews | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we do I'd lean towards |
||
{ | ||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like this should be a c-style cast. You want to get an exception if this fails. |
||
return precompiledViews?.ViewInfos; | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public IEnumerable<string> GetReferencePaths() | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Drop the |
||
{ | ||
/// <summary> | ||
/// Exposes a sequence of precompiled views associated with an <see cref="ApplicationPart"/> . | ||
/// </summary> | ||
public interface IPrecompiledViewsProvider | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we drop the term |
||
{ | ||
/// <summary> | ||
/// Gets the sequence of <see cref="PrecompiledViewInfo"/>. | ||
/// </summary> | ||
IReadOnlyCollection<PrecompiledViewInfo> PrecompiledViews { get; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Drop the core |
||
{ | ||
/// <summary> | ||
/// Provides information for precompiled views. | ||
/// </summary> | ||
public class PrecompiledViewInfo | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a note, not sure if there's anything that can be done but this is darn close to the |
||
{ | ||
/// <summary> | ||
/// Creates a new instance of <see cref="PrecompiledViewInfo" />. | ||
/// </summary> | ||
/// <param name="path">The path of the view.</param> | ||
/// <param name="type">The view <see cref="System.Type"/>.</param> | ||
public PrecompiledViewInfo(string path, Type type) | ||
{ | ||
Path = path; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should null check params There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment probably applies everywhere in this PR |
||
Type = type; | ||
} | ||
|
||
/// <summary> | ||
/// The path of the view. | ||
/// </summary> | ||
public string Path { get; } | ||
|
||
/// <summary> | ||
/// The view <see cref="System.Type"/>. | ||
/// </summary> | ||
public Type Type { get; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Get rid of |
||
{ | ||
/// <summary> | ||
/// A container for <see cref="PrecompiledViewInfo"/> instances. | ||
/// </summary> | ||
public abstract class PrecompiledViews | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Naming seems bad. should this be something |
||
{ | ||
/// <summary> | ||
/// Initializes a new instance of <see cref="ViewInfos"/>. | ||
/// </summary> | ||
/// <param name="precompiledViews">The sequence of <see cref="PrecompiledViewInfo"/>.</param> | ||
protected PrecompiledViews(IReadOnlyCollection<PrecompiledViewInfo> precompiledViews) | ||
{ | ||
ViewInfos = precompiledViews; | ||
} | ||
|
||
public IReadOnlyCollection<PrecompiledViewInfo> ViewInfos { get; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure another namespace is justified. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we name the type |
||
{ | ||
/// <summary> | ||
/// Configures the <see cref="IMvcBuilder"/>. Implement this interface to enable design-time configuration | ||
/// (for instance during pre-compilation of views) of <see cref="IMvcBuilder"/>. | ||
/// </summary> | ||
public interface IMvcBuilderConfiguration | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need the equivalent for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or does that get too complex. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might not be entirely too difficult. We could prefer types implementing |
||
{ | ||
/// <summary> | ||
/// Configures the <see cref="IMvcBuilder"/>. | ||
/// </summary> | ||
/// <param name="builder"></param> | ||
void ConfigureMvc(IMvcBuilder builder); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is an interface method, is the idea that you'd put this on your startup class? Or do you do another class? Thinking about code sharing between designtime and runtime There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, in my sample, I added it to Startup. It made it more natural to take the code you had in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm largely ignoring the stuff to do with the command line application - just FYI |
||
{ | ||
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); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kept expecting it play... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can second that. |
||
{ | ||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<IMvcRazorHost>(); | ||
Compiler = serviceProvider.GetRequiredService<CSharpCompiler>(); | ||
|
||
FileProvider = serviceProvider.GetRequiredService<IRazorViewEngineFileProviderAccessor>().FileProvider; | ||
} | ||
|
||
public IMvcRazorHost Host { get; } | ||
|
||
public CSharpCompiler Compiler { get; } | ||
|
||
public IFileProvider FileProvider { get; } | ||
|
||
private IMvcBuilderConfiguration GetConfigureCompilationAction(string configureCompilationType) | ||
{ | ||
Type type; | ||
if (!string.IsNullOrEmpty(configureCompilationType)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if (string.IsNullOrEmpty(configureCompilationType))
{
return null;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to fill in the else eventually. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fair enough |
||
{ | ||
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<IHostingEnvironment>(hostingEnvironment) | ||
.AddSingleton<DiagnosticSource>(diagnosticSource) | ||
.AddLogging() | ||
.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>(); | ||
|
||
var mvcCoreBuilder = services | ||
.AddMvcCore() | ||
.AddRazorViewEngine(); | ||
|
||
var mvcBuilder = new MvcBuilder(mvcCoreBuilder.Services, mvcCoreBuilder.PartManager); | ||
mvcBuilderConfiguration?.ConfigureMvc(mvcBuilder); | ||
|
||
return mvcBuilder.Services.BuildServiceProvider(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. /cc @davidfowl - this is what we discussed a while ago about our |
||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason not to just make these two properties this a single string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use these in codegen, so they have to separated.