aspnetcore/src/Microsoft.AspNet.Mvc.Razor..../MvcRazorHost.cs

336 lines
14 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if NET45
using Microsoft.AspNet.FileProviders;
#endif
using Microsoft.AspNet.Mvc.Razor.Directives;
using Microsoft.AspNet.Mvc.Razor.Internal;
using Microsoft.AspNet.Razor;
using Microsoft.AspNet.Razor.Chunks;
using Microsoft.AspNet.Razor.CodeGenerators;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Razor.TagHelpers;
namespace Microsoft.AspNet.Mvc.Razor
{
public class MvcRazorHost : RazorEngineHost, IMvcRazorHost
{
private const string BaseType = "Microsoft.AspNet.Mvc.Razor.RazorPage";
private const string HtmlHelperPropertyName = "Html";
private static readonly string[] _defaultNamespaces = new[]
{
"System",
"System.Linq",
"System.Collections.Generic",
"Microsoft.AspNet.Mvc",
"Microsoft.AspNet.Mvc.Rendering",
};
private static readonly Chunk[] _defaultInheritedChunks = new Chunk[]
{
new InjectChunk("Microsoft.AspNet.Mvc.Rendering.IHtmlHelper<TModel>", HtmlHelperPropertyName),
new InjectChunk("Microsoft.AspNet.Mvc.Rendering.IJsonHelper", "Json"),
new InjectChunk("Microsoft.AspNet.Mvc.IViewComponentHelper", "Component"),
new InjectChunk("Microsoft.AspNet.Mvc.IUrlHelper", "Url"),
new AddTagHelperChunk
{
LookupText = "Microsoft.AspNet.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNet.Mvc.Razor"
},
new SetBaseTypeChunk
{
// Microsoft.Aspnet.Mvc.Razor.RazorPage<TModel>
TypeName = BaseType + ChunkHelper.TModelToken,
// Set the Start to Undefined to prevent Razor design time code generation from rendering a line mapping
// for this chunk.
Start = SourceLocation.Undefined
}
};
// CodeGenerationContext.DefaultBaseClass is set to MyBaseType<dynamic>.
private readonly IChunkTreeCache _chunkTreeCache;
private readonly RazorPathNormalizer _pathNormalizer;
private ChunkInheritanceUtility _chunkInheritanceUtility;
private ITagHelperDescriptorResolver _tagHelperDescriptorResolver;
internal MvcRazorHost(IChunkTreeCache chunkTreeCache, RazorPathNormalizer pathNormalizer)
: base(new CSharpRazorCodeLanguage())
{
_pathNormalizer = pathNormalizer;
_chunkTreeCache = chunkTreeCache;
DefaultBaseClass = BaseType + ChunkHelper.TModelToken;
DefaultNamespace = "Asp";
// Enable instrumentation by default to allow precompiled views to work with BrowserLink.
EnableInstrumentation = true;
GeneratedClassContext = new GeneratedClassContext(
executeMethodName: "ExecuteAsync",
writeMethodName: "Write",
writeLiteralMethodName: "WriteLiteral",
writeToMethodName: "WriteTo",
writeLiteralToMethodName: "WriteLiteralTo",
templateTypeName: "Microsoft.AspNet.Mvc.Razor.HelperResult",
defineSectionMethodName: "DefineSection",
generatedTagHelperContext: new GeneratedTagHelperContext
{
ExecutionContextTypeName = typeof(TagHelperExecutionContext).FullName,
ExecutionContextAddMethodName = nameof(TagHelperExecutionContext.Add),
ExecutionContextAddTagHelperAttributeMethodName =
nameof(TagHelperExecutionContext.AddTagHelperAttribute),
ExecutionContextAddHtmlAttributeMethodName = nameof(TagHelperExecutionContext.AddHtmlAttribute),
ExecutionContextAddMinimizedHtmlAttributeMethodName =
nameof(TagHelperExecutionContext.AddMinimizedHtmlAttribute),
ExecutionContextOutputPropertyName = nameof(TagHelperExecutionContext.Output),
RunnerTypeName = typeof(TagHelperRunner).FullName,
RunnerRunAsyncMethodName = nameof(TagHelperRunner.RunAsync),
ScopeManagerTypeName = typeof(TagHelperScopeManager).FullName,
ScopeManagerBeginMethodName = nameof(TagHelperScopeManager.Begin),
ScopeManagerEndMethodName = nameof(TagHelperScopeManager.End),
TagHelperContentTypeName = nameof(TagHelperContent),
// Can't use nameof because RazorPage is not accessible here.
CreateTagHelperMethodName = "CreateTagHelper",
FormatInvalidIndexerAssignmentMethodName = "InvalidTagHelperIndexerAssignment",
StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope",
EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope",
WriteTagHelperAsyncMethodName = "WriteTagHelperAsync",
WriteTagHelperToAsyncMethodName = "WriteTagHelperToAsync",
// Can't use nameof because IHtmlHelper is (also) not accessible here.
MarkAsHtmlEncodedMethodName = HtmlHelperPropertyName + ".Raw",
AddHtmlAttributeValuesMethodName = "AddHtmlAttributeValues",
HtmlEncoderPropertyName = "HtmlEncoder",
TagHelperContentGetContentMethodName = nameof(TagHelperContent.GetContent),
})
{
BeginContextMethodName = "BeginContext",
EndContextMethodName = "EndContext"
};
foreach (var ns in _defaultNamespaces)
{
NamespaceImports.Add(ns);
}
}
#if NET45
/// <summary>
/// Initializes a new instance of <see cref="MvcRazorHost"/> with the specified <paramref name="root"/>.
/// </summary>
/// <param name="root">The path to the application base.</param>
// Note: This constructor is used by tooling and is created once for each
// Razor page that is loaded. Consequently, each loaded page has its own copy of
// the ChunkTreeCache, but this ok - having a shared ChunkTreeCache per application in tooling
// is problematic to manage.
public MvcRazorHost(string root)
: this(new DefaultChunkTreeCache(new PhysicalFileProvider(root)), new DesignTimeRazorPathNormalizer(root))
{
}
#endif
/// <summary>
/// Initializes a new instance of <see cref="MvcRazorHost"/> using the specified <paramref name="chunkTreeCache"/>.
/// </summary>
/// <param name="chunkTreeCache">An <see cref="IChunkTreeCache"/> rooted at the application base path.</param>
public MvcRazorHost(IChunkTreeCache chunkTreeCache)
: this(chunkTreeCache, new RazorPathNormalizer())
{
}
/// <inheritdoc />
public override ITagHelperDescriptorResolver TagHelperDescriptorResolver
{
get
{
// The initialization of the _tagHelperDescriptorResolver needs to be lazy to allow for the setting
// of DesignTimeMode.
if (_tagHelperDescriptorResolver == null)
{
_tagHelperDescriptorResolver = new TagHelperDescriptorResolver(DesignTimeMode);
}
return _tagHelperDescriptorResolver;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_tagHelperDescriptorResolver = value;
}
}
/// <summary>
/// Gets the model type used by default when no model is specified.
/// </summary>
/// <remarks>This value is used as the generic type argument for the base type </remarks>
public virtual string DefaultModel
{
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>
public virtual IReadOnlyList<Chunk> DefaultInheritedChunks
{
get { return _defaultInheritedChunks; }
}
/// <summary>
/// Gets or sets the name attribute that is used to decorate properties that are injected and need to be
/// activated.
/// </summary>
public virtual string InjectAttribute
{
get { return "Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute"; }
}
/// <summary>
/// Gets the type name used to represent <see cref="ITagHelper"/> model expression properties.
/// </summary>
public virtual string ModelExpressionType
{
get { return "Microsoft.AspNet.Mvc.Rendering.ModelExpression"; }
}
/// <summary>
/// Gets the method name used to create model expressions.
/// </summary>
public virtual string CreateModelExpressionMethod
{
get { return "CreateModelExpression"; }
}
// Internal for testing
internal ChunkInheritanceUtility ChunkInheritanceUtility
{
get
{
if (_chunkInheritanceUtility == null)
{
// This needs to be lazily evaluated to support DefaultInheritedChunks being virtual.
_chunkInheritanceUtility = new ChunkInheritanceUtility(this, _chunkTreeCache, DefaultInheritedChunks);
}
return _chunkInheritanceUtility;
}
set
{
_chunkInheritanceUtility = value;
}
}
/// <summary>
/// Locates and parses _ViewImports.cshtml files applying to the given <paramref name="sourceFileName"/> to
/// create <see cref="ChunkTreeResult"/>s.
/// </summary>
/// <param name="sourceFileName">The path to a Razor file to locate _ViewImports.cshtml for.</param>
/// <returns>Inherited <see cref="ChunkTreeResult"/>s.</returns>
public IReadOnlyList<ChunkTreeResult> GetInheritedChunkTreeResults(string sourceFileName)
{
if (sourceFileName == null)
{
throw new ArgumentNullException(nameof(sourceFileName));
}
// Need the normalized path to resolve inherited chunks only. Full paths are needed for generated Razor
// files checksum and line pragmas to enable DesignTime debugging.
var normalizedPath = _pathNormalizer.NormalizePath(sourceFileName);
return ChunkInheritanceUtility.GetInheritedChunkTreeResults(normalizedPath);
}
/// <inheritdoc />
public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream)
{
// Adding a prefix so that the main view class can be easily identified.
var className = MainClassNamePrefix + ParserHelpers.SanitizeClassName(rootRelativePath);
var engine = new RazorTemplateEngine(this);
return engine.GenerateCode(inputStream, className, DefaultNamespace, rootRelativePath);
}
/// <inheritdoc />
public override RazorParser DecorateRazorParser(RazorParser razorParser, string sourceFileName)
{
if (razorParser == null)
{
throw new ArgumentNullException(nameof(razorParser));
}
var inheritedChunkTrees = GetInheritedChunkTrees(sourceFileName);
return new MvcRazorParser(razorParser, inheritedChunkTrees, DefaultInheritedChunks, ModelExpressionType);
}
/// <inheritdoc />
public override ParserBase DecorateCodeParser(ParserBase incomingCodeParser)
{
if (incomingCodeParser == null)
{
throw new ArgumentNullException(nameof(incomingCodeParser));
}
return new MvcRazorCodeParser();
}
/// <inheritdoc />
public override CodeGenerator DecorateCodeGenerator(
CodeGenerator incomingGenerator,
CodeGeneratorContext context)
{
if (incomingGenerator == null)
{
throw new ArgumentNullException(nameof(incomingGenerator));
}
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var inheritedChunkTrees = GetInheritedChunkTrees(context.SourceFile);
ChunkInheritanceUtility.MergeInheritedChunkTrees(
context.ChunkTreeBuilder.ChunkTree,
inheritedChunkTrees,
DefaultModel);
return new MvcCSharpCodeGenerator(
context,
DefaultModel,
InjectAttribute,
new GeneratedTagHelperAttributeContext
{
ModelExpressionTypeName = ModelExpressionType,
CreateModelExpressionMethodName = CreateModelExpressionMethod
});
}
private IReadOnlyList<ChunkTree> GetInheritedChunkTrees(string sourceFileName)
{
var inheritedChunkTrees = GetInheritedChunkTreeResults(sourceFileName)
.Select(result => result.ChunkTree)
.ToList();
return inheritedChunkTrees;
}
}
}