Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Add dotnet-razor-precompile tool #5087

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion Mvc.NoFun.sln
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Copy link
Member

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?

Copy link
Contributor Author

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.


/// <summary>
/// Initalizes a new <see cref="AssemblyPart"/> instance.
/// </summary>
Expand Down Expand Up @@ -41,6 +50,27 @@ public AssemblyPart(Assembly assembly)
/// <inheritdoc />
public IEnumerable<TypeInfo> Types => Assembly.DefinedTypes;

/// <inheritdoc />
public IReadOnlyCollection<PrecompiledViewInfo> PrecompiledViews
Copy link
Member

@rynowak rynowak Aug 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we do IEnumerable or IReadOnlyList here? We don't really use IReadOnlyCollection anywhere else, and there isn't a reason to prefer it over IEnumerable.

I'd lean towards IEnumerable

{
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;
Copy link
Member

Choose a reason for hiding this comment

The 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()
{
Expand Down
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop the .Core

{
/// <summary>
/// Exposes a sequence of precompiled views associated with an <see cref="ApplicationPart"/> .
/// </summary>
public interface IPrecompiledViewsProvider
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we drop the term precompiled? That really only makes sense to us. I think a lot of these APIs would be cleaner and more general without precompiled in the names.

{
/// <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
Copy link
Member

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The 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 CompileOutput API. Only difference is once has the assembly factory and one has the assembly.

{
/// <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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should null check params

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get rid of .Core

{
/// <summary>
/// A container for <see cref="PrecompiledViewInfo"/> instances.
/// </summary>
public abstract class PrecompiledViews
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming seems bad. should this be something ViewFeature

{
/// <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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure another namespace is justified.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we name the type IMvcDesignTimeBuilderConfiguration in that case? Would be nice to call out that the interface is specifically meant for design time.

{
/// <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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the equivalent for IMvcCoreBuilder?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or does that get too complex.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might not be entirely too difficult. We could prefer types implementing IMvcBuilderConfiguration over IMvcCoreBuilderConfiguration.

{
/// <summary>
/// Configures the <see cref="IMvcBuilder"/>.
/// </summary>
/// <param name="builder"></param>
void ConfigureMvc(IMvcBuilder builder);
Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 ConfigureServices and move it into this one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept expecting it play...

Choose a reason for hiding this comment

The 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))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (string.IsNullOrEmpty(configureCompilationType))
{
    return null;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to fill in the else eventually.

Choose a reason for hiding this comment

The 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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @davidfowl - this is what we discussed a while ago about our AddXyZ(...) methods not being self-sufficient. This is necessary every where we need to use MVC with DI, but not with hosting.

}
}
}
Loading