// 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.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Security.Cryptography; using System.Text; using System.Threading; using Microsoft.AspNet.Razor.Chunks.Generators; using Microsoft.AspNet.Razor.CodeGeneration; using Microsoft.AspNet.Razor.Parser; using Microsoft.AspNet.Razor.Text; using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Razor { /// /// Entry-point to the Razor Template Engine /// public class RazorTemplateEngine { private const int BufferSize = 1024; public static readonly string DefaultClassName = "Template"; public static readonly string DefaultNamespace = string.Empty; /// /// Constructs a new RazorTemplateEngine with the specified host /// /// /// The host which defines the environment in which the generated template code will live. /// public RazorTemplateEngine([NotNull] RazorEngineHost host) { Host = host; } /// /// The RazorEngineHost which defines the environment in which the generated template code will live /// public RazorEngineHost Host { get; } public ParserResults ParseTemplate([NotNull] ITextBuffer input) { return ParseTemplate(input, cancelToken: null); } /// /// Parses the template specified by the TextBuffer and returns it's result /// /// /// /// IMPORTANT: This does NOT need to be called before GeneratedCode! GenerateCode will automatically /// parse the document first. /// /// /// The cancel token provided can be used to cancel the parse. However, please note /// that the parse occurs _synchronously_, on the callers thread. This parameter is /// provided so that if the caller is in a background thread with a CancellationToken, /// it can pass it along to the parser. /// /// /// The input text to parse. /// A token used to cancel the parser. /// The resulting parse tree. [SuppressMessage( "Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Input object would be disposed if we dispose the wrapper. We don't own the input so " + "we don't want to dispose it")] public ParserResults ParseTemplate([NotNull] ITextBuffer input, CancellationToken? cancelToken) { return ParseTemplateCore(input.ToDocument(), sourceFileName: null, cancelToken: cancelToken); } // See ParseTemplate(ITextBuffer, CancellationToken?), // this overload simply wraps a TextReader in a TextBuffer (see ITextBuffer and BufferingTextReader) public ParserResults ParseTemplate([NotNull] TextReader input, string sourceFileName) { return ParseTemplateCore(new SeekableTextReader(input), sourceFileName, cancelToken: null); } [SuppressMessage( "Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Input object would be disposed if we dispose the wrapper. We don't own the input so " + "we don't want to dispose it")] public ParserResults ParseTemplate([NotNull] TextReader input, CancellationToken? cancelToken) { return ParseTemplateCore(new SeekableTextReader(input), sourceFileName: null, cancelToken: cancelToken); } protected internal virtual ParserResults ParseTemplateCore( [NotNull] ITextDocument input, string sourceFileName, CancellationToken? cancelToken) { // Construct the parser var parser = CreateParser(sourceFileName); Debug.Assert(parser != null); return parser.Parse(input); } public GeneratorResults GenerateCode([NotNull] ITextBuffer input) { return GenerateCode(input, className: null, rootNamespace: null, sourceFileName: null, cancelToken: null); } public GeneratorResults GenerateCode([NotNull] ITextBuffer input, CancellationToken? cancelToken) { return GenerateCode( input, className: null, rootNamespace: null, sourceFileName: null, cancelToken: cancelToken); } public GeneratorResults GenerateCode( [NotNull] ITextBuffer input, string className, string rootNamespace, string sourceFileName) { return GenerateCode(input, className, rootNamespace, sourceFileName, cancelToken: null); } /// /// Parses the template specified by the TextBuffer, generates code for it, and returns the constructed code. /// /// /// /// The cancel token provided can be used to cancel the parse. However, please note /// that the parse occurs _synchronously_, on the callers thread. This parameter is /// provided so that if the caller is in a background thread with a CancellationToken, /// it can pass it along to the parser. /// /// /// The className, rootNamespace and sourceFileName parameters are optional and override the default /// specified by the Host. For example, the WebPageRazorHost in System.Web.WebPages.Razor configures the /// Class Name, Root Namespace and Source File Name based on the virtual path of the page being compiled. /// However, the built-in RazorEngineHost class uses constant defaults, so the caller will likely want to /// change them using these parameters. /// /// /// The input text to parse. /// A token used to cancel the parser. /// /// The name of the generated class, overriding whatever is specified in the Host. The default value (defined /// in the Host) can be used by providing null for this argument. /// /// The namespace in which the generated class will reside, overriding whatever is /// specified in the Host. The default value (defined in the Host) can be used by providing null for this /// argument. /// /// /// The file name to use in line pragmas, usually the original Razor file, overriding whatever is specified in /// the Host. The default value (defined in the Host) can be used by providing null for this argument. /// /// The resulting parse tree AND generated code. [SuppressMessage( "Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Input object would be disposed if we dispose the wrapper. We don't own the input so " + "we don't want to dispose it")] public GeneratorResults GenerateCode( [NotNull] ITextBuffer input, string className, string rootNamespace, string sourceFileName, CancellationToken? cancelToken) { return GenerateCodeCore( input.ToDocument(), className, rootNamespace, sourceFileName, checksum: null, cancelToken: cancelToken); } // See GenerateCode override which takes ITextBuffer, and BufferingTextReader for details. public GeneratorResults GenerateCode([NotNull] TextReader input) { return GenerateCode(input, className: null, rootNamespace: null, sourceFileName: null, cancelToken: null); } public GeneratorResults GenerateCode([NotNull] TextReader input, CancellationToken? cancelToken) { return GenerateCode( input, className: null, rootNamespace: null, sourceFileName: null, cancelToken: cancelToken); } public GeneratorResults GenerateCode( [NotNull] TextReader input, string className, string rootNamespace, string sourceFileName) { return GenerateCode(input, className, rootNamespace, sourceFileName, cancelToken: null); } /// /// Parses the contents specified by the and returns the generated code. /// /// A that represents the contents to be parsed. /// The name of the generated class. When null, defaults to /// (Host.DefaultClassName). /// The namespace in which the generated class will reside. When null, /// defaults to (Host.DefaultNamespace). /// /// The file name to use in line pragmas, usually the original Razor file. /// /// A that represents the results of parsing the content. /// /// This overload calculates the checksum of the contents of prior to code /// generation. The checksum is used for producing the #pragma checksum line pragma required for /// debugging. /// public GeneratorResults GenerateCode( [NotNull] Stream inputStream, string className, string rootNamespace, string sourceFileName) { MemoryStream memoryStream = null; string checksum = null; try { if (!Host.DesignTimeMode) { // We don't need to calculate the checksum in design time. if (!inputStream.CanSeek) { memoryStream = new MemoryStream(); inputStream.CopyTo(memoryStream); // We don't have to dispose the input stream since it is owned externally. inputStream = memoryStream; } inputStream.Position = 0; checksum = ComputeChecksum(inputStream); inputStream.Position = 0; } using (var reader = new StreamReader( inputStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: BufferSize, leaveOpen: true)) { var seekableStream = new SeekableTextReader(reader); return GenerateCodeCore( seekableStream, className, rootNamespace, sourceFileName, checksum, cancelToken: null); } } finally { if (memoryStream != null) { memoryStream.Dispose(); } } } [SuppressMessage( "Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Input object would be disposed if we dispose the wrapper. We don't own the input so " + "we don't want to dispose it")] public GeneratorResults GenerateCode( [NotNull] TextReader input, string className, string rootNamespace, string sourceFileName, CancellationToken? cancelToken) { return GenerateCodeCore( new SeekableTextReader(input), className, rootNamespace, sourceFileName, checksum: null, cancelToken: cancelToken); } protected internal virtual GeneratorResults GenerateCodeCore( [NotNull] ITextDocument input, string className, string rootNamespace, string sourceFileName, string checksum, CancellationToken? cancelToken) { className = (className ?? Host.DefaultClassName) ?? DefaultClassName; rootNamespace = (rootNamespace ?? Host.DefaultNamespace) ?? DefaultNamespace; // Run the parser var parser = CreateParser(sourceFileName); Debug.Assert(parser != null); var results = parser.Parse(input); // Generate code var chunkGenerator = CreateChunkGenerator(className, rootNamespace, sourceFileName); chunkGenerator.DesignTimeMode = Host.DesignTimeMode; chunkGenerator.Visit(results); var codeGeneratorContext = new CodeGeneratorContext(chunkGenerator.Context, results.ErrorSink); codeGeneratorContext.Checksum = checksum; var codeGenerator = CreateCodeGenerator(codeGeneratorContext); var codeGeneratorResult = codeGenerator.Generate(); // Collect results and return return new GeneratorResults(results, codeGeneratorResult, codeGeneratorContext.ChunkTreeBuilder.ChunkTree); } protected internal virtual RazorChunkGenerator CreateChunkGenerator( string className, string rootNamespace, string sourceFileName) { return Host.DecorateChunkGenerator( Host.CodeLanguage.CreateChunkGenerator(className, rootNamespace, sourceFileName, Host)); } protected internal virtual RazorParser CreateParser(string sourceFileName) { var codeParser = Host.CodeLanguage.CreateCodeParser(); var markupParser = Host.CreateMarkupParser(); var parser = new RazorParser( Host.DecorateCodeParser(codeParser), Host.DecorateMarkupParser(markupParser), Host.TagHelperDescriptorResolver) { DesignTimeMode = Host.DesignTimeMode }; return Host.DecorateRazorParser(parser, sourceFileName); } protected internal virtual CodeGenerator CreateCodeGenerator(CodeGeneratorContext context) { return Host.DecorateCodeGenerator(Host.CodeLanguage.CreateCodeGenerator(context), context); } private static string ComputeChecksum(Stream inputStream) { byte[] hashedBytes; using (var hashAlgorithm = SHA1.Create()) { hashedBytes = hashAlgorithm.ComputeHash(inputStream); } var fileHashBuilder = new StringBuilder(hashedBytes.Length * 2); foreach (var value in hashedBytes) { fileHashBuilder.Append(value.ToString("x2")); } return fileHashBuilder.ToString(); } } }