// 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; using Microsoft.AspNet.Diagnostics; using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Razor; using Microsoft.AspNet.Razor.CodeGenerators; using Microsoft.Extensions.OptionsModel; namespace Microsoft.AspNet.Mvc.Razor.Compilation { /// /// Default implementation of . /// public class RazorCompilationService : IRazorCompilationService { private readonly ICompilationService _compilationService; private readonly IMvcRazorHost _razorHost; private readonly IFileProvider _fileProvider; /// /// Instantiates a new instance of the class. /// /// The to compile generated code. /// The to generate code from Razor files. /// /// The to read Razor files referenced in error messages. /// public RazorCompilationService( ICompilationService compilationService, IMvcRazorHost razorHost, IOptions viewEngineOptions) { _compilationService = compilationService; _razorHost = razorHost; _fileProvider = viewEngineOptions.Value.FileProvider; } /// public CompilationResult Compile(RelativeFileInfo file) { if (file == null) { throw new ArgumentNullException(nameof(file)); } GeneratorResults results; using (var inputStream = file.FileInfo.CreateReadStream()) { results = GenerateCode(file.RelativePath, inputStream); } if (!results.Success) { return GetCompilationFailedResult(file, results.ParserErrors); } return _compilationService.Compile(file, results.GeneratedCode); } /// /// Generate code for the Razor file at with content /// . /// /// /// The path of the Razor file relative to the root of the application. Used to generate line pragmas and /// calculate the class name of the generated type. /// /// A that contains the Razor content. /// A instance containing results of code generation. protected virtual GeneratorResults GenerateCode(string relativePath, Stream inputStream) { return _razorHost.GenerateCode(relativePath, inputStream); } // Internal for unit testing internal CompilationResult GetCompilationFailedResult(RelativeFileInfo file, IEnumerable errors) { // If a SourceLocation does not specify a file path, assume it is produced // from parsing the current file. var messageGroups = errors .GroupBy(razorError => razorError.Location.FilePath ?? file.RelativePath, StringComparer.Ordinal); var failures = new List(); foreach (var group in messageGroups) { var filePath = group.Key; var fileContent = ReadFileContentsSafely(filePath); var compilationFailure = new CompilationFailure( filePath, fileContent, compiledContent: string.Empty, messages: group.Select(parserError => CreateDiagnosticMessage(parserError, filePath))); failures.Add(compilationFailure); } return new CompilationResult(failures); } private DiagnosticMessage CreateDiagnosticMessage(RazorError error, string filePath) { return new DiagnosticMessage( message: error.Message, formattedMessage: $"{error} ({error.Location.LineIndex},{error.Location.CharacterIndex}) {error.Message}", filePath: filePath, startLine: error.Location.LineIndex + 1, startColumn: error.Location.CharacterIndex, endLine: error.Location.LineIndex + 1, endColumn: error.Location.CharacterIndex + error.Length); } private string ReadFileContentsSafely(string relativePath) { var fileInfo = _fileProvider.GetFileInfo(relativePath); if (fileInfo.Exists) { try { using (var reader = new StreamReader(fileInfo.CreateReadStream())) { return reader.ReadToEnd(); } } catch { // Ignore any failures } } return null; } } }