// 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", 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 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. 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 /// /// Initializes a new instance of with the specified . /// /// The path to the application base. // 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 /// /// Initializes a new instance of using the specified . /// /// An rooted at the application base path. public MvcRazorHost(IChunkTreeCache chunkTreeCache) : this(chunkTreeCache, new RazorPathNormalizer()) { } /// 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; } } /// /// Gets the model type used by default when no model is specified. /// /// This value is used as the generic type argument for the base type public virtual string DefaultModel { get { return "dynamic"; } } /// public string MainClassNamePrefix { get { return "ASPV_"; } } /// /// Gets the list of chunks that are injected by default by this host. /// public virtual IReadOnlyList DefaultInheritedChunks { get { return _defaultInheritedChunks; } } /// /// Gets or sets the name attribute that is used to decorate properties that are injected and need to be /// activated. /// public virtual string InjectAttribute { get { return "Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute"; } } /// /// Gets the type name used to represent model expression properties. /// public virtual string ModelExpressionType { get { return "Microsoft.AspNet.Mvc.Rendering.ModelExpression"; } } /// /// Gets the method name used to create model expressions. /// 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; } } /// /// Locates and parses _ViewImports.cshtml files applying to the given to /// create s. /// /// The path to a Razor file to locate _ViewImports.cshtml for. /// Inherited s. public IReadOnlyList 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); } /// 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); } /// 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); } /// public override ParserBase DecorateCodeParser(ParserBase incomingCodeParser) { if (incomingCodeParser == null) { throw new ArgumentNullException(nameof(incomingCodeParser)); } return new MvcRazorCodeParser(); } /// 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 GetInheritedChunkTrees(string sourceFileName) { var inheritedChunkTrees = GetInheritedChunkTreeResults(sourceFileName) .Select(result => result.ChunkTree) .ToList(); return inheritedChunkTrees; } } }