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

Adding support for Razor precompilation #5160

Closed
wants to merge 1 commit 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
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,27 @@ 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,
IViewsProvider
{
/// <summary>
/// Gets the suffix for the view assembly.
/// </summary>
public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";

/// <summary>
/// Gets the namespace for the <see cref="ViewInfoContainer"/> type in the view assembly.
/// </summary>
public static readonly string ViewInfoContainerNamespace = "AspNetCore";

/// <summary>
/// Gets the type name for the view collection type in the view assembly.
/// </summary>
public static readonly string ViewInfoContainerTypeName = "__PrecompiledViewCollection";

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

/// <inheritdoc />
public IEnumerable<ViewInfo> Views
{
get
{
var precompiledAssemblyName = new AssemblyName(Assembly.FullName);
precompiledAssemblyName.Name = precompiledAssemblyName.Name + PrecompiledViewsAssemblySuffix;

var typeName = $"{ViewInfoContainerNamespace}.{ViewInfoContainerTypeName},{precompiledAssemblyName}";
var viewInfoContainerTypeName = Type.GetType(typeName);

if (viewInfoContainerTypeName == null)
{
return null;
}

var precompiledViews = (ViewInfoContainer)Activator.CreateInstance(viewInfoContainerTypeName);
return precompiledViews.ViewInfos;
}
}

/// <inheritdoc />
public IEnumerable<string> GetReferencePaths()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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.ApplicationParts
{
/// <summary>
/// Exposes a sequence of views associated with an <see cref="ApplicationPart"/> .
/// </summary>
public interface IViewsProvider
{
/// <summary>
/// Gets the sequence of <see cref="ViewInfo"/>.
/// </summary>
IEnumerable<ViewInfo> Views { get; }
}
}
34 changes: 34 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ViewInfo.cs
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.ApplicationParts
{
/// <summary>
/// Provides information for precompiled views.
/// </summary>
public class ViewInfo
{
/// <summary>
/// Creates a new instance of <see cref="ViewInfo" />.
/// </summary>
/// <param name="path">The path of the view.</param>
/// <param name="type">The view <see cref="System.Type"/>.</param>
public ViewInfo(string path, Type type)
{
Path = path;
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,27 @@
// 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.ApplicationParts
{
/// <summary>
/// A container for <see cref="ViewInfo"/> instances.
/// </summary>
public class ViewInfoContainer
Copy link
Contributor

Choose a reason for hiding this comment

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

Just double checking, this name change hasn't been updated in the other PR yet right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup.

{
/// <summary>
/// Initializes a new instance of <see cref="ViewInfos"/>.
/// </summary>
/// <param name="views">The sequence of <see cref="ViewInfo"/>.</param>
public ViewInfoContainer(IReadOnlyList<ViewInfo> views)
{
ViewInfos = views;
}

/// <summary>
/// The <see cref="IReadOnlyList{T}"/> of <see cref="ViewInfo"/>.
/// </summary>
public IReadOnlyList<ViewInfo> 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
{
/// <summary>
/// Configures the <see cref="IMvcBuilder"/>. Implement this interface to enable design-time configuration
Copy link
Contributor

Choose a reason for hiding this comment

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

We may want to be more specific here so people don't think this enables custom TagHelper resolution in their cshtml editors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, let's figure out the naming bit in a second iteration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cc @rynowak \ @Eilon - suggestions?

/// (for instance during pre-compilation of views) of <see cref="IMvcBuilder"/>.
/// </summary>
public interface IDesignTimeMvcBuilderConfiguration
{
/// <summary>
/// Configures the <see cref="IMvcBuilder"/>.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
void ConfigureMvc(IMvcBuilder builder);
}
}
14 changes: 14 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeature.cs
Original file line number Diff line number Diff line change
@@ -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 ViewsFeature
{
public IDictionary<string, Type> Views { get; } =
new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmmm, don't we use semantics equivalent to the current file system for view lookup? Wouldn't doing IgnoreCase here potentially break file systems that are case sensitive?

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 use case insensitive lookups in other view lookup places (for instance the CompilerCache). Sadly the case sensitivity here wouldn't make a big difference.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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;

namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
{
/// <summary>
/// An <see cref="IApplicationFeatureProvider{TFeature}"/> for <see cref="ViewsFeature"/>.
/// </summary>
public class ViewsFeatureProvider : IApplicationFeatureProvider<ViewsFeature>
{
/// <inheritdoc />
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature)
{
foreach (var provider in parts.OfType<IViewsProvider>())
{
var precompiledViews = provider.Views;
if (precompiledViews != null)
{
foreach (var viewInfo in precompiledViews)
{
feature.Views[viewInfo.Path] = viewInfo.Type;
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ private static void AddRazorViewEngineFeatureProviders(IMvcCoreBuilder builder)
{
builder.PartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider());
}

if (!builder.PartManager.FeatureProviders.OfType<ViewsFeatureProvider>().Any())
{
builder.PartManager.FeatureProviders.Add(new ViewsFeatureProvider());
}
}

/// <summary>
Expand Down Expand Up @@ -127,6 +132,8 @@ public static IMvcCoreBuilder InitializeTagHelper<TTagHelper>(
// Internal for testing.
internal static void AddRazorViewEngineServices(IServiceCollection services)
{
services.TryAddSingleton<CSharpCompiler>();
services.TryAddSingleton<RazorReferenceManager>();
// This caches compilation related details that are valid across the lifetime of the application.
services.TryAddSingleton<ICompilationService, DefaultRoslynCompilationService>();

Expand Down Expand Up @@ -165,7 +172,7 @@ internal static void AddRazorViewEngineServices(IServiceCollection services)
// creating the singleton RazorViewEngine instance.
services.TryAddTransient<IRazorPageFactoryProvider, DefaultRazorPageFactoryProvider>();
services.TryAddTransient<IRazorCompilationService, RazorCompilationService>();
services.TryAddTransient<IMvcRazorHost,MvcRazorHost>();
services.TryAddTransient<IMvcRazorHost, MvcRazorHost>();

// This caches Razor page activation details that are valid for the lifetime of the application.
services.TryAddSingleton<IRazorPageActivator, RazorPageActivator>();
Expand Down
51 changes: 51 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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.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 DebugInformationFormat _pdbFormat =
#if NET451
SymbolsUtility.SupportsFullPdbGeneration() ?
DebugInformationFormat.Pdb :
DebugInformationFormat.PortablePdb;
#else
DebugInformationFormat.PortablePdb;
#endif

public CSharpCompiler(RazorReferenceManager manager, IOptions<RazorViewEngineOptions> optionsAccessor)
{
_referenceManager = manager;
_compilationOptions = optionsAccessor.Value.CompilationOptions;
_parseOptions = optionsAccessor.Value.ParseOptions;
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);
}
}
}
14 changes: 7 additions & 7 deletions src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilerCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,22 @@ public CompilerCache(IFileProvider fileProvider)

/// <summary>
/// Initializes a new instance of <see cref="CompilerCache"/> populated with precompiled views
/// specified by <paramref name="precompiledViews"/>.
/// specified by <paramref name="views"/>.
/// </summary>
/// <param name="fileProvider"><see cref="IFileProvider"/> used to locate Razor views.</param>
/// <param name="precompiledViews">A mapping of application relative paths of view to the precompiled view
/// <see cref="Type"/>s.</param>
/// <param name="views">A mapping of application relative paths of view to <see cref="Type"/>s that
/// have already been compiled.</param>
public CompilerCache(
IFileProvider fileProvider,
IDictionary<string, Type> precompiledViews)
IDictionary<string, Type> views)
: this(fileProvider)
{
if (precompiledViews == null)
if (views == null)
{
throw new ArgumentNullException(nameof(precompiledViews));
throw new ArgumentNullException(nameof(views));
}

foreach (var item in precompiledViews)
foreach (var item in views)
{
var cacheEntry = new CompilerCacheResult(item.Key, new CompilationResult(item.Value));
_cache.Set(GetNormalizedPath(item.Key), Task.FromResult(cacheEntry));
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -13,10 +14,15 @@ public class DefaultCompilerCacheProvider : ICompilerCacheProvider
/// <summary>
/// Initializes a new instance of <see cref="DefaultCompilerCacheProvider"/>.
/// </summary>
/// <param name="applicationPartManager">The <see cref="ApplicationPartManager" /></param>
/// <param name="fileProviderAccessor">The <see cref="IRazorViewEngineFileProviderAccessor"/>.</param>
public DefaultCompilerCacheProvider(IRazorViewEngineFileProviderAccessor fileProviderAccessor)
public DefaultCompilerCacheProvider(
ApplicationPartManager applicationPartManager,
IRazorViewEngineFileProviderAccessor fileProviderAccessor)
{
Cache = new CompilerCache(fileProviderAccessor.FileProvider);
var feature = new ViewsFeature();
applicationPartManager.PopulateFeature(feature);
Cache = new CompilerCache(fileProviderAccessor.FileProvider, feature.Views);
}

/// <inheritdoc />
Expand Down
Loading