Create a pre compilation module and apis to allow meta programming
to precompile razor pages. This is limited to sites where the .cshtml are still deployed. It's current purpose is to speed up startup. Deploying without the razor files is a separate feature.
This commit is contained in:
parent
43c7ddb9b7
commit
6600e68fc0
|
|
@ -9,5 +9,15 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public interface IMvcRazorHost
|
||||
{
|
||||
GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream);
|
||||
|
||||
/// <summary>
|
||||
/// Represent the prefix off the main entry class in the view.
|
||||
/// </summary>
|
||||
string MainClassNamePrefix { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Represent the namespace the main entry class in the view.
|
||||
/// </summary>
|
||||
string DefaultNamespace { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public MvcRazorHost(string root) :
|
||||
this(new PhysicalFileSystem(root))
|
||||
{
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -101,6 +100,12 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
get { return "dynamic"; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string MainClassNamePrefix
|
||||
{
|
||||
get { return "ASPV_"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of chunks that are injected by default by this host.
|
||||
/// </summary>
|
||||
|
|
@ -121,7 +126,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <inheritdoc />
|
||||
public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream)
|
||||
{
|
||||
var className = ParserHelpers.SanitizeClassName(rootRelativePath);
|
||||
// Adding a prefix so that the main view class can be easily identified.
|
||||
var className = MainClassNamePrefix + ParserHelpers.SanitizeClassName(rootRelativePath);
|
||||
using (var reader = new StreamReader(inputStream))
|
||||
{
|
||||
var engine = new RazorTemplateEngine(this);
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
catch (Exception)
|
||||
{
|
||||
// Don't throw if reading the file fails.
|
||||
return string.Empty;
|
||||
|
|
|
|||
|
|
@ -3,34 +3,110 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class CompilerCache
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, Type> _cache;
|
||||
private readonly ConcurrentDictionary<string, CompilerCacheEntry> _cache;
|
||||
private static readonly Type[] EmptyType = new Type[0];
|
||||
|
||||
public CompilerCache()
|
||||
public CompilerCache([NotNull] IEnumerable<Assembly> assemblies)
|
||||
: this(GetFileInfos(assemblies))
|
||||
{
|
||||
_cache = new ConcurrentDictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public CompilationResult GetOrAdd(IFileInfo file, Func<CompilationResult> compile)
|
||||
internal CompilerCache(IEnumerable<RazorFileInfoCollection> viewCollections) : this()
|
||||
{
|
||||
// Generate a content id
|
||||
var contentId = file.PhysicalPath + '|' + file.LastModified.Ticks;
|
||||
|
||||
Type compiledType;
|
||||
if (!_cache.TryGetValue(contentId, out compiledType))
|
||||
foreach (var viewCollection in viewCollections)
|
||||
{
|
||||
var result = compile();
|
||||
_cache.TryAdd(contentId, result.CompiledType);
|
||||
foreach (var fileInfo in viewCollection.FileInfos)
|
||||
{
|
||||
var containingAssembly = viewCollection.GetType().GetTypeInfo().Assembly;
|
||||
var viewType = containingAssembly.GetType(fileInfo.FullTypeName);
|
||||
var cacheEntry = new CompilerCacheEntry(fileInfo, viewType);
|
||||
|
||||
return result;
|
||||
// There shouldn't be any duplicates and if there are any the first will win.
|
||||
// If the result doesn't match the one on disk its going to recompile anyways.
|
||||
_cache.TryAdd(fileInfo.RelativePath, cacheEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal CompilerCache()
|
||||
{
|
||||
_cache = new ConcurrentDictionary<string, CompilerCacheEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
internal static IEnumerable<RazorFileInfoCollection>
|
||||
GetFileInfos(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
return assemblies.SelectMany(a => a.ExportedTypes)
|
||||
.Where(Match)
|
||||
.Select(c => (RazorFileInfoCollection)Activator.CreateInstance(c));
|
||||
}
|
||||
|
||||
private static bool Match(Type t)
|
||||
{
|
||||
var inAssemblyType = typeof(RazorFileInfoCollection);
|
||||
if (inAssemblyType.IsAssignableFrom(t))
|
||||
{
|
||||
var hasParameterlessConstructor = t.GetConstructor(EmptyType) != null;
|
||||
|
||||
return hasParameterlessConstructor
|
||||
&& !t.GetTypeInfo().IsAbstract
|
||||
&& !t.GetTypeInfo().ContainsGenericParameters;
|
||||
}
|
||||
|
||||
return CompilationResult.Successful(compiledType);
|
||||
return false;
|
||||
}
|
||||
|
||||
public CompilationResult GetOrAdd(RelativeFileInfo fileInfo, Func<CompilationResult> compile)
|
||||
{
|
||||
if (!_cache.TryGetValue(fileInfo.RelativePath, out var cacheEntry))
|
||||
{
|
||||
return OnCacheMiss(fileInfo, compile);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cacheEntry.Length != fileInfo.FileInfo.Length)
|
||||
{
|
||||
// it's not a match, recompile
|
||||
return OnCacheMiss(fileInfo, compile);
|
||||
}
|
||||
|
||||
if (cacheEntry.LastModified == fileInfo.FileInfo.LastModified)
|
||||
{
|
||||
// Match, not update needed
|
||||
return CompilationResult.Successful(cacheEntry.ViewType);
|
||||
}
|
||||
|
||||
var hash = RazorFileHash.GetHash(fileInfo.FileInfo);
|
||||
|
||||
// Timestamp doesn't match but it might be because of deployment, compare the hash.
|
||||
if (cacheEntry.IsPreCompiled &&
|
||||
string.Equals(cacheEntry.Hash, hash, StringComparison.Ordinal))
|
||||
{
|
||||
// Cache hit, but we need to update the entry
|
||||
return OnCacheMiss(fileInfo, () => CompilationResult.Successful(cacheEntry.ViewType));
|
||||
}
|
||||
|
||||
// it's not a match, recompile
|
||||
return OnCacheMiss(fileInfo, compile);
|
||||
}
|
||||
}
|
||||
|
||||
private CompilationResult OnCacheMiss(RelativeFileInfo file, Func<CompilationResult> compile)
|
||||
{
|
||||
var result = compile();
|
||||
|
||||
var cacheEntry = new CompilerCacheEntry(file, result.CompiledType);
|
||||
_cache.AddOrUpdate(file.RelativePath, cacheEntry, (a, b) => cacheEntry);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class CompilerCacheEntry
|
||||
{
|
||||
public CompilerCacheEntry([NotNull] RazorFileInfo info, [NotNull] Type viewType)
|
||||
{
|
||||
ViewType = viewType;
|
||||
RelativePath = info.RelativePath;
|
||||
Length = info.Length;
|
||||
LastModified = info.LastModified;
|
||||
Hash = info.Hash;
|
||||
}
|
||||
|
||||
public CompilerCacheEntry([NotNull] RelativeFileInfo info, [NotNull] Type viewType)
|
||||
{
|
||||
ViewType = viewType;
|
||||
RelativePath = info.RelativePath;
|
||||
Length = info.FileInfo.Length;
|
||||
LastModified = info.FileInfo.LastModified;
|
||||
}
|
||||
|
||||
public Type ViewType { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public long Length { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The file hash, should only be available for pre compiled files.
|
||||
/// </summary>
|
||||
public string Hash { get; set; }
|
||||
|
||||
public bool IsPreCompiled { get { return Hash != null; } }
|
||||
}
|
||||
}
|
||||
|
|
@ -7,12 +7,10 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Emit;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Framework.Runtime;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
||||
|
|
@ -31,27 +29,31 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
|
||||
private readonly Lazy<List<MetadataReference>> _applicationReferences;
|
||||
|
||||
private readonly string _classPrefix;
|
||||
|
||||
/// <summary>
|
||||
/// Initalizes a new instance of the <see cref="RoslynCompilationService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="environment">The environment for the executing application.</param>
|
||||
/// <param name="loaderEngine">The loader used to load compiled assemblies.</param>
|
||||
/// <param name="libraryManager">The library manager that provides export and reference information.</param>
|
||||
/// <param name="host">The <see cref="IMvcRazorHost"/> that was used to generate the code.</param>
|
||||
public RoslynCompilationService(IApplicationEnvironment environment,
|
||||
IAssemblyLoaderEngine loaderEngine,
|
||||
ILibraryManager libraryManager)
|
||||
ILibraryManager libraryManager,
|
||||
IMvcRazorHost host)
|
||||
{
|
||||
_environment = environment;
|
||||
_loader = loaderEngine;
|
||||
_libraryManager = libraryManager;
|
||||
_applicationReferences = new Lazy<List<MetadataReference>>(GetApplicationReferences);
|
||||
_classPrefix = host.MainClassNamePrefix;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public CompilationResult Compile(IFileInfo fileInfo, string compilationContent)
|
||||
{
|
||||
var sourceText = SourceText.From(compilationContent, Encoding.UTF8);
|
||||
var syntaxTrees = new[] { CSharpSyntaxTree.ParseText(sourceText, path: fileInfo.PhysicalPath) };
|
||||
var syntaxTrees = new[] { SyntaxTreeGenerator.Generate(compilationContent, fileInfo.PhysicalPath) };
|
||||
|
||||
var references = _applicationReferences.Value;
|
||||
|
||||
|
|
@ -103,9 +105,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
}
|
||||
|
||||
var type = assembly.GetExportedTypes()
|
||||
.First();
|
||||
.First(t => t.Name.
|
||||
StartsWith(_classPrefix, StringComparison.Ordinal));
|
||||
|
||||
return UncachedCompilationResult.Successful(type, compilationContent);
|
||||
return UncachedCompilationResult.Successful(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public static class SyntaxTreeGenerator
|
||||
{
|
||||
private static CSharpParseOptions DefaultOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return CSharpParseOptions.Default
|
||||
.WithLanguageVersion(LanguageVersion.CSharp6);
|
||||
}
|
||||
}
|
||||
|
||||
public static SyntaxTree Generate([NotNull] string text, [NotNull] string path)
|
||||
{
|
||||
return GenerateCore(text, path, DefaultOptions);
|
||||
}
|
||||
|
||||
public static SyntaxTree Generate([NotNull] string text,
|
||||
[NotNull] string path,
|
||||
[NotNull] CSharpParseOptions options)
|
||||
{
|
||||
return GenerateCore(text, path, options);
|
||||
}
|
||||
|
||||
public static SyntaxTree GenerateCore([NotNull] string text,
|
||||
[NotNull] string path,
|
||||
[NotNull] CSharpParseOptions options)
|
||||
{
|
||||
var sourceText = SourceText.From(text, Encoding.UTF8);
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(sourceText,
|
||||
path: path,
|
||||
options: options);
|
||||
|
||||
return syntaxTree;
|
||||
}
|
||||
|
||||
public static CSharpParseOptions GetParseOptions(CSharpCompilation compilation)
|
||||
{
|
||||
return CSharpParseOptions.Default
|
||||
.WithLanguageVersion(compilation.LanguageVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
}
|
||||
|
||||
public string RazorFileContent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="UncachedCompilationResult"/> that represents a success in compilation.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <summary>
|
||||
/// Creates a <see cref="IRazorPage"/> for the specified path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to locate the page.</param>
|
||||
/// <param name="relativePath">The path to locate the page.</param>
|
||||
/// <returns>The IRazorPage instance if it exists, null otherwise.</returns>
|
||||
IRazorPage CreateInstance(string path);
|
||||
IRazorPage CreateInstance(string relativePath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public interface IRazorCompilationService
|
||||
{
|
||||
CompilationResult Compile(IFileInfo fileInfo);
|
||||
CompilationResult Compile(RelativeFileInfo fileInfo);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public static class GeneratorResultExtensions
|
||||
{
|
||||
public static string GetMainClassName([NotNull] this GeneratorResults results,
|
||||
[NotNull] IMvcRazorHost host,
|
||||
[NotNull] SyntaxTree syntaxTree)
|
||||
{
|
||||
// The mainClass name should return directly from the generator results.
|
||||
var classes = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>();
|
||||
var mainClass = classes.FirstOrDefault(c =>
|
||||
c.Identifier.ValueText.StartsWith(host.MainClassNamePrefix, StringComparison.Ordinal));
|
||||
|
||||
if (mainClass != null)
|
||||
{
|
||||
var typeName = mainClass.Identifier.ValueText;
|
||||
|
||||
if (!string.IsNullOrEmpty(host.DefaultNamespace))
|
||||
{
|
||||
typeName = host.DefaultNamespace + "." + typeName;
|
||||
}
|
||||
|
||||
return typeName;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class RazorFileInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Type name including namespace.
|
||||
/// </summary>
|
||||
public string FullTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Last modified at compilation time.
|
||||
/// </summary>
|
||||
public DateTime LastModified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of the file in bytes.
|
||||
/// </summary>
|
||||
public long Length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to to the file relative to the application base.
|
||||
/// </summary>
|
||||
public string RelativePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A hash of the file content.
|
||||
/// </summary>
|
||||
public string Hash { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public abstract class RazorFileInfoCollection
|
||||
{
|
||||
public IReadOnlyList<RazorFileInfo> FileInfos { get; protected set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class RazorFileInfoCollectionGenerator
|
||||
{
|
||||
private string _fileFormat;
|
||||
|
||||
protected IReadOnlyList<RazorFileInfo> FileInfos { get; private set; }
|
||||
protected CSharpParseOptions Options { get; private set; }
|
||||
|
||||
public RazorFileInfoCollectionGenerator([NotNull] IReadOnlyList<RazorFileInfo> fileInfos,
|
||||
[NotNull] CSharpParseOptions options)
|
||||
{
|
||||
FileInfos = fileInfos;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public virtual SyntaxTree GenerateCollection()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(Top);
|
||||
|
||||
foreach (var fileInfo in FileInfos)
|
||||
{
|
||||
var perFileEntry = GenerateFile(fileInfo);
|
||||
builder.Append(perFileEntry);
|
||||
}
|
||||
|
||||
builder.Append(Bottom);
|
||||
|
||||
// TODO: consider saving the file for debuggability
|
||||
var sourceCode = builder.ToString();
|
||||
var syntaxTree = SyntaxTreeGenerator.Generate(sourceCode,
|
||||
"__AUTO__GeneratedViewsCollection.cs",
|
||||
Options);
|
||||
|
||||
return syntaxTree;
|
||||
}
|
||||
|
||||
|
||||
protected virtual string GenerateFile([NotNull] RazorFileInfo fileInfo)
|
||||
{
|
||||
return string.Format(FileFormat,
|
||||
fileInfo.LastModified.ToFileTimeUtc(),
|
||||
fileInfo.Length,
|
||||
fileInfo.RelativePath,
|
||||
fileInfo.FullTypeName,
|
||||
fileInfo.Hash);
|
||||
}
|
||||
|
||||
protected virtual string Top
|
||||
{
|
||||
get
|
||||
{
|
||||
return
|
||||
@"using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Mvc.Razor;
|
||||
|
||||
namespace __ASP_ASSEMBLY
|
||||
{
|
||||
public class __PreGeneratedViewCollection : " + nameof(RazorFileInfoCollection) + @"
|
||||
{
|
||||
public __PreGeneratedViewCollection()
|
||||
{
|
||||
var fileInfos = new List<" + nameof(RazorFileInfo) + @">();
|
||||
" + nameof(RazorFileInfoCollection.FileInfos) + @" = fileInfos;
|
||||
" + nameof(RazorFileInfo) + @" info;
|
||||
|
||||
";
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual string Bottom
|
||||
{
|
||||
get
|
||||
{
|
||||
return
|
||||
@" }
|
||||
}
|
||||
}
|
||||
";
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual string FileFormat
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fileFormat == null)
|
||||
{
|
||||
_fileFormat =
|
||||
" info = new "
|
||||
+ nameof(RazorFileInfo) + @"
|
||||
{{
|
||||
" + nameof(RazorFileInfo.LastModified) + @" = DateTime.FromFileTimeUtc({0:D}),
|
||||
" + nameof(RazorFileInfo.Length) + @" = {1:D},
|
||||
" + nameof(RazorFileInfo.RelativePath) + @" = @""{2}"",
|
||||
" + nameof(RazorFileInfo.FullTypeName) + @" = @""{3}"",
|
||||
" + nameof(RazorFileInfo.Hash) + @" = @""{4}"",
|
||||
}};
|
||||
fileInfos.Add(info);
|
||||
";
|
||||
}
|
||||
|
||||
return _fileFormat;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Runtime;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class RazorPreCompiler
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IMvcRazorHost _host;
|
||||
|
||||
protected virtual string FileExtension
|
||||
{
|
||||
get
|
||||
{
|
||||
return ".cshtml";
|
||||
}
|
||||
}
|
||||
|
||||
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider) :
|
||||
this(designTimeServiceProvider, designTimeServiceProvider.GetService<IMvcRazorHost>())
|
||||
{
|
||||
}
|
||||
|
||||
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
|
||||
[NotNull] IMvcRazorHost host)
|
||||
{
|
||||
_serviceProvider = designTimeServiceProvider;
|
||||
_host = host;
|
||||
|
||||
var appEnv = _serviceProvider.GetService<IApplicationEnvironment>();
|
||||
_fileSystem = new PhysicalFileSystem(appEnv.ApplicationBasePath);
|
||||
}
|
||||
|
||||
public virtual void CompileViews([NotNull] IBeforeCompileContext context)
|
||||
{
|
||||
var descriptors = CreateCompilationDescriptors(context);
|
||||
var collectionGenerator = new RazorFileInfoCollectionGenerator(
|
||||
descriptors,
|
||||
SyntaxTreeGenerator.GetParseOptions(context.CSharpCompilation));
|
||||
|
||||
var tree = collectionGenerator.GenerateCollection();
|
||||
context.CSharpCompilation = context.CSharpCompilation.AddSyntaxTrees(tree);
|
||||
}
|
||||
|
||||
protected virtual IReadOnlyList<RazorFileInfo> CreateCompilationDescriptors(
|
||||
[NotNull] IBeforeCompileContext context)
|
||||
{
|
||||
var options = SyntaxTreeGenerator.GetParseOptions(context.CSharpCompilation);
|
||||
var list = new List<RazorFileInfo>();
|
||||
|
||||
foreach (var info in GetFileInfosRecursive(string.Empty))
|
||||
{
|
||||
var descriptor = ParseView(info,
|
||||
context,
|
||||
options);
|
||||
|
||||
if (descriptor != null)
|
||||
{
|
||||
list.Add(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private IEnumerable<RelativeFileInfo> GetFileInfosRecursive(string currentPath)
|
||||
{
|
||||
IEnumerable<IFileInfo> fileInfos;
|
||||
string path = currentPath;
|
||||
|
||||
if (!_fileSystem.TryGetDirectoryContents(path, out fileInfos))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var fileInfo in fileInfos)
|
||||
{
|
||||
if (fileInfo.IsDirectory)
|
||||
{
|
||||
var subPath = Path.Combine(path, fileInfo.Name);
|
||||
|
||||
foreach (var info in GetFileInfosRecursive(subPath))
|
||||
{
|
||||
yield return info;
|
||||
}
|
||||
}
|
||||
else if (Path.GetExtension(fileInfo.Name)
|
||||
.Equals(FileExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var info = new RelativeFileInfo()
|
||||
{
|
||||
FileInfo = fileInfo,
|
||||
RelativePath = Path.Combine(currentPath, fileInfo.Name),
|
||||
};
|
||||
|
||||
yield return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual RazorFileInfo ParseView([NotNull] RelativeFileInfo fileInfo,
|
||||
[NotNull] IBeforeCompileContext context,
|
||||
[NotNull] CSharpParseOptions options)
|
||||
{
|
||||
using (var stream = fileInfo.FileInfo.CreateReadStream())
|
||||
{
|
||||
var results = _host.GenerateCode(fileInfo.RelativePath, stream);
|
||||
if (results.Success)
|
||||
{
|
||||
var syntaxTree = SyntaxTreeGenerator.Generate(results.GeneratedCode, fileInfo.FileInfo.PhysicalPath, options);
|
||||
var fullTypeName = results.GetMainClassName(_host, syntaxTree);
|
||||
|
||||
if (fullTypeName != null)
|
||||
{
|
||||
context.CSharpCompilation = context.CSharpCompilation.AddSyntaxTrees(syntaxTree);
|
||||
|
||||
var hash = RazorFileHash.GetHash(fileInfo.FileInfo);
|
||||
|
||||
return new RazorFileInfo()
|
||||
{
|
||||
FullTypeName = fullTypeName,
|
||||
RelativePath = fileInfo.RelativePath,
|
||||
LastModified = fileInfo.FileInfo.LastModified,
|
||||
Length = fileInfo.FileInfo.Length,
|
||||
Hash = hash,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add diagnostics when view parsing/code generation failed.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Microsoft.Framework.Runtime
|
||||
{
|
||||
[AssemblyNeutral]
|
||||
public interface IBeforeCompileContext
|
||||
{
|
||||
CSharpCompilation CSharpCompilation { get; set; }
|
||||
|
||||
IList<ResourceDescription> Resources { get; }
|
||||
|
||||
IList<Diagnostic> Diagnostics { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class RelativeFileInfo
|
||||
{
|
||||
public IFileInfo FileInfo { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +1,48 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.Framework.Runtime;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class RazorCompilationService : IRazorCompilationService
|
||||
{
|
||||
// This class must be registered as a singleton service for the caching to work.
|
||||
private readonly CompilerCache _cache = new CompilerCache();
|
||||
private readonly IApplicationEnvironment _environment;
|
||||
private readonly CompilerCache _cache;
|
||||
private readonly ICompilationService _baseCompilationService;
|
||||
private readonly IMvcRazorHost _razorHost;
|
||||
private readonly string _appRoot;
|
||||
|
||||
public RazorCompilationService(IApplicationEnvironment environment,
|
||||
ICompilationService compilationService,
|
||||
public RazorCompilationService(ICompilationService compilationService,
|
||||
IControllerAssemblyProvider _controllerAssemblyProvider,
|
||||
IMvcRazorHost razorHost)
|
||||
{
|
||||
_environment = environment;
|
||||
_baseCompilationService = compilationService;
|
||||
_razorHost = razorHost;
|
||||
_appRoot = EnsureTrailingSlash(environment.ApplicationBasePath);
|
||||
_cache = new CompilerCache(_controllerAssemblyProvider.CandidateAssemblies);
|
||||
}
|
||||
|
||||
public CompilationResult Compile([NotNull] IFileInfo file)
|
||||
public CompilationResult Compile([NotNull] RelativeFileInfo file)
|
||||
{
|
||||
return _cache.GetOrAdd(file, () => CompileCore(file));
|
||||
}
|
||||
|
||||
internal CompilationResult CompileCore(IFileInfo file)
|
||||
internal CompilationResult CompileCore(RelativeFileInfo file)
|
||||
{
|
||||
GeneratorResults results;
|
||||
using (var inputStream = file.CreateReadStream())
|
||||
using (var inputStream = file.FileInfo.CreateReadStream())
|
||||
{
|
||||
Contract.Assert(file.PhysicalPath.StartsWith(_appRoot, StringComparison.OrdinalIgnoreCase));
|
||||
var rootRelativePath = file.PhysicalPath.Substring(_appRoot.Length);
|
||||
results = _razorHost.GenerateCode(rootRelativePath, inputStream);
|
||||
results = _razorHost.GenerateCode(
|
||||
file.RelativePath, inputStream);
|
||||
}
|
||||
|
||||
if (!results.Success)
|
||||
{
|
||||
var messages = results.ParserErrors.Select(e => new CompilationMessage(e.Message));
|
||||
return CompilationResult.Failed(file, results.GeneratedCode, messages);
|
||||
return CompilationResult.Failed(file.FileInfo, results.GeneratedCode, messages);
|
||||
}
|
||||
|
||||
return _baseCompilationService.Compile(file, results.GeneratedCode);
|
||||
}
|
||||
|
||||
private static string EnsureTrailingSlash([NotNull]string path)
|
||||
{
|
||||
if (!path.EndsWith(Path.DirectorySeparatorChar.ToString()))
|
||||
{
|
||||
path += Path.DirectorySeparatorChar;
|
||||
}
|
||||
return path;
|
||||
return _baseCompilationService.Compile(file.FileInfo, results.GeneratedCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public static class RazorFileHash
|
||||
{
|
||||
public static string GetHash([NotNull] IFileInfo file)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = file.CreateReadStream())
|
||||
{
|
||||
return GetHash(stream);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Don't throw if reading the file fails.
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetHash(Stream stream)
|
||||
{
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
return BitConverter.ToString(md5.ComputeHash(stream));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,15 +30,22 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRazorPage CreateInstance([NotNull] string path)
|
||||
public IRazorPage CreateInstance([NotNull] string relativePath)
|
||||
{
|
||||
var fileInfo = _fileInfoCache.GetFileInfo(path);
|
||||
var fileInfo = _fileInfoCache.GetFileInfo(relativePath);
|
||||
|
||||
if (fileInfo != null)
|
||||
{
|
||||
var result = _compilationService.Compile(fileInfo);
|
||||
var relativeFileInfo = new RelativeFileInfo()
|
||||
{
|
||||
FileInfo = fileInfo,
|
||||
RelativePath = relativePath,
|
||||
};
|
||||
|
||||
var result = _compilationService.Compile(relativeFileInfo);
|
||||
var page = (IRazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType);
|
||||
page.Path = path;
|
||||
page.Path = relativePath;
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
"System.Collections": "4.0.10.0",
|
||||
"System.Collections.Concurrent": "4.0.0.0",
|
||||
"System.ComponentModel": "4.0.0.0",
|
||||
"System.Security.Cryptography.Hashing.Algorithms": "4.0.0.0",
|
||||
"System.Diagnostics.Contracts": "4.0.0.0",
|
||||
"System.Diagnostics.Debug": "4.0.10.0",
|
||||
"System.Diagnostics.Tools": "4.0.0.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Mvc.Razor;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.Framework.Runtime;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.DependencyInjection.Fallback;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public abstract class RazorPreCompileModule : ICompileModule
|
||||
{
|
||||
private readonly IServiceProvider _appServices;
|
||||
|
||||
public RazorPreCompileModule(IServiceProvider services)
|
||||
{
|
||||
_appServices = services;
|
||||
}
|
||||
|
||||
public virtual void BeforeCompile(IBeforeCompileContext context)
|
||||
{
|
||||
var sc = new ServiceCollection();
|
||||
sc.Add(MvcServices.GetDefaultServices());
|
||||
var sp = sc.BuildServiceProvider(_appServices);
|
||||
|
||||
var viewCompiler = new RazorPreCompiler(sp);
|
||||
viewCompiler.CompileViews(context);
|
||||
}
|
||||
|
||||
public void AfterCompile(IAfterCompileContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Microsoft.Framework.Runtime
|
||||
{
|
||||
[AssemblyNeutral]
|
||||
public interface ICompileModule
|
||||
{
|
||||
void BeforeCompile(IBeforeCompileContext context);
|
||||
|
||||
void AfterCompile(IAfterCompileContext context);
|
||||
}
|
||||
|
||||
[AssemblyNeutral]
|
||||
public interface IAfterCompileContext
|
||||
{
|
||||
CSharpCompilation CSharpCompilation { get; set; }
|
||||
|
||||
IList<Diagnostic> Diagnostics { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -28,8 +28,6 @@ namespace Microsoft.AspNet.Mvc.Filters
|
|||
|
||||
var provider = CreateProvider();
|
||||
|
||||
//System.Diagnostics.Debugger.Launch();
|
||||
//System.Diagnostics.Debugger.Break();
|
||||
// Act
|
||||
provider.Invoke(context, () => { });
|
||||
var results = context.Results;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
// 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.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -15,12 +18,23 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
{
|
||||
// Arrange
|
||||
var cache = new CompilerCache();
|
||||
var fileInfo = Mock.Of<IFileInfo>();
|
||||
var fileInfo = new Mock<IFileInfo>();
|
||||
|
||||
fileInfo
|
||||
.SetupGet(i => i.LastModified)
|
||||
.Returns(DateTime.FromFileTimeUtc(10000));
|
||||
|
||||
var type = GetType();
|
||||
var expected = UncachedCompilationResult.Successful(type, "hello world");
|
||||
|
||||
var runtimeFileInfo = new RelativeFileInfo()
|
||||
{
|
||||
FileInfo = fileInfo.Object,
|
||||
RelativePath = "ab",
|
||||
};
|
||||
|
||||
// Act
|
||||
var actual = cache.GetOrAdd(fileInfo, () => expected);
|
||||
var actual = cache.GetOrAdd(runtimeFileInfo, () => expected);
|
||||
|
||||
// Assert
|
||||
Assert.Same(expected, actual);
|
||||
|
|
@ -28,6 +42,117 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
Assert.Same(type, actual.CompiledType);
|
||||
}
|
||||
|
||||
private abstract class View
|
||||
{
|
||||
public abstract string Content { get; }
|
||||
}
|
||||
|
||||
private class PreCompile : View
|
||||
{
|
||||
public override string Content { get { return "Hello World it's @DateTime.Now"; } }
|
||||
}
|
||||
|
||||
private class RuntimeCompileIdentical : View
|
||||
{
|
||||
public override string Content { get { return new PreCompile().Content; } }
|
||||
}
|
||||
|
||||
private class RuntimeCompileDifferent : View
|
||||
{
|
||||
public override string Content { get { return new PreCompile().Content.Substring(1) + " "; } }
|
||||
}
|
||||
|
||||
private class RuntimeCompileDifferentLength : View
|
||||
{
|
||||
public override string Content
|
||||
{
|
||||
get
|
||||
{
|
||||
return new PreCompile().Content + " longer because it was modified at runtime";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewCollection : RazorFileInfoCollection
|
||||
{
|
||||
public ViewCollection()
|
||||
{
|
||||
var fileInfos = new List<RazorFileInfo>();
|
||||
FileInfos = fileInfos;
|
||||
|
||||
var content = new PreCompile().Content;
|
||||
var length = Encoding.UTF8.GetByteCount(content);
|
||||
|
||||
fileInfos.Add(new RazorFileInfo()
|
||||
{
|
||||
FullTypeName = typeof(PreCompile).FullName,
|
||||
Hash = RazorFileHash.GetHash(GetMemoryStream(content)),
|
||||
LastModified = DateTime.FromFileTimeUtc(10000),
|
||||
Length = length,
|
||||
RelativePath = "ab",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream GetMemoryStream(string content)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(content);
|
||||
|
||||
return new MemoryStream(bytes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(RuntimeCompileIdentical), 10000, false)]
|
||||
[InlineData(typeof(RuntimeCompileIdentical), 11000, false)]
|
||||
[InlineData(typeof(RuntimeCompileDifferent), 10000, false)] // expected failure: same time and length
|
||||
[InlineData(typeof(RuntimeCompileDifferent), 11000, true)]
|
||||
[InlineData(typeof(RuntimeCompileDifferentLength), 10000, true)]
|
||||
[InlineData(typeof(RuntimeCompileDifferentLength), 10000, true)]
|
||||
public void FileWithTheSameLengthAndDifferentTime_DoesNot_OverridesPrecompilation(
|
||||
Type resultViewType,
|
||||
long fileTimeUTC,
|
||||
bool swapsPreCompile)
|
||||
{
|
||||
// Arrange
|
||||
var instance = (View)Activator.CreateInstance(resultViewType);
|
||||
var length = Encoding.UTF8.GetByteCount(instance.Content);
|
||||
|
||||
var collection = new ViewCollection();
|
||||
var cache = new CompilerCache(new[] { new ViewCollection() });
|
||||
|
||||
var fileInfo = new Mock<IFileInfo>();
|
||||
fileInfo
|
||||
.SetupGet(i => i.Length)
|
||||
.Returns(length);
|
||||
fileInfo
|
||||
.SetupGet(i => i.LastModified)
|
||||
.Returns(DateTime.FromFileTimeUtc(fileTimeUTC));
|
||||
fileInfo.Setup(i => i.CreateReadStream())
|
||||
.Returns(GetMemoryStream(instance.Content));
|
||||
|
||||
var preCompileType = typeof(PreCompile);
|
||||
|
||||
var runtimeFileInfo = new RelativeFileInfo()
|
||||
{
|
||||
FileInfo = fileInfo.Object,
|
||||
RelativePath = "ab",
|
||||
};
|
||||
|
||||
// Act
|
||||
var actual = cache.GetOrAdd(runtimeFileInfo,
|
||||
() => CompilationResult.Successful(resultViewType));
|
||||
|
||||
// Assert
|
||||
if (swapsPreCompile)
|
||||
{
|
||||
Assert.Equal(actual.CompiledType, resultViewType);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(actual.CompiledType, typeof(PreCompile));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_DoesNotCacheCompiledContent_OnCallsAfterInitial()
|
||||
{
|
||||
|
|
@ -42,10 +167,16 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
var type = GetType();
|
||||
var uncachedResult = UncachedCompilationResult.Successful(type, "hello world");
|
||||
|
||||
var runtimeFileInfo = new RelativeFileInfo()
|
||||
{
|
||||
FileInfo = fileInfo.Object,
|
||||
RelativePath = "test",
|
||||
};
|
||||
|
||||
// Act
|
||||
cache.GetOrAdd(fileInfo.Object, () => uncachedResult);
|
||||
var actual1 = cache.GetOrAdd(fileInfo.Object, () => uncachedResult);
|
||||
var actual2 = cache.GetOrAdd(fileInfo.Object, () => uncachedResult);
|
||||
cache.GetOrAdd(runtimeFileInfo, () => uncachedResult);
|
||||
var actual1 = cache.GetOrAdd(runtimeFileInfo, () => uncachedResult);
|
||||
var actual2 = cache.GetOrAdd(runtimeFileInfo, () => uncachedResult);
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(uncachedResult, actual1);
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.Framework.Runtime;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -20,24 +21,32 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
public void CompileCoreCalculatesRootRelativePath(string appPath, string viewPath)
|
||||
{
|
||||
// Arrange
|
||||
var env = new Mock<IApplicationEnvironment>();
|
||||
env.SetupGet(e => e.ApplicationName).Returns("MyTestApplication");
|
||||
env.SetupGet(e => e.ApplicationBasePath).Returns(appPath);
|
||||
var host = new Mock<IMvcRazorHost>();
|
||||
host.Setup(h => h.GenerateCode(@"views\index\home.cshtml", It.IsAny<Stream>()))
|
||||
.Returns(new GeneratorResults(new Block(new BlockBuilder { Type = BlockType.Comment }), new RazorError[0], new CodeBuilderResult("", new LineMapping[0])))
|
||||
.Verifiable();
|
||||
|
||||
var ap = new Mock<IControllerAssemblyProvider>();
|
||||
ap.SetupGet(e => e.CandidateAssemblies)
|
||||
.Returns(Enumerable.Empty<Assembly>())
|
||||
.Verifiable();
|
||||
|
||||
var fileInfo = new Mock<IFileInfo>();
|
||||
fileInfo.Setup(f => f.PhysicalPath).Returns(viewPath);
|
||||
fileInfo.Setup(f => f.CreateReadStream()).Returns(Stream.Null);
|
||||
var compiler = new Mock<ICompilationService>();
|
||||
compiler.Setup(c => c.Compile(fileInfo.Object, It.IsAny<string>()))
|
||||
.Returns(CompilationResult.Successful(typeof(RazorCompilationServiceTest)));
|
||||
var razorService = new RazorCompilationService(env.Object, compiler.Object, host.Object);
|
||||
var razorService = new RazorCompilationService(compiler.Object, ap.Object, host.Object);
|
||||
|
||||
var relativeFileInfo = new RelativeFileInfo()
|
||||
{
|
||||
FileInfo = fileInfo.Object,
|
||||
RelativePath = @"views\index\home.cshtml",
|
||||
};
|
||||
|
||||
// Act
|
||||
razorService.CompileCore(fileInfo.Object);
|
||||
razorService.CompileCore(relativeFileInfo);
|
||||
|
||||
// Assert
|
||||
host.Verify();
|
||||
|
|
|
|||
Loading…
Reference in New Issue