File: PageDirectiveFeature.cs
Web Access
Project: src\src\Mvc\Mvc.Razor.RuntimeCompilation\src\Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.csproj (Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation;
 
internal static partial class PageDirectiveFeature
{
    private static readonly RazorProjectEngine PageDirectiveEngine = RazorProjectEngine.Create(RazorConfiguration.Default, new EmptyRazorProjectFileSystem(), builder =>
    {
        for (var i = builder.Phases.Count - 1; i >= 0; i--)
        {
            var phase = builder.Phases[i];
            builder.Phases.RemoveAt(i);
            if (phase is IRazorDocumentClassifierPhase)
            {
                break;
            }
        }
 
        RazorExtensions.Register(builder);
        builder.Features.Add(new PageDirectiveParserOptionsFeature());
    });
 
    public static bool TryGetPageDirective(ILogger logger, RazorProjectItem projectItem, [NotNullWhen(true)] out string? template)
    {
        ArgumentNullException.ThrowIfNull(projectItem);
 
        var codeDocument = PageDirectiveEngine.Process(projectItem);
 
        var documentIRNode = codeDocument.GetDocumentIntermediateNode();
        if (PageDirective.TryGetPageDirective(documentIRNode, out var pageDirective))
        {
            if (pageDirective.DirectiveNode is MalformedDirectiveIntermediateNode malformedNode)
            {
                Log.MalformedPageDirective(logger, projectItem.FilePath, malformedNode.Diagnostics);
            }
 
            template = pageDirective.RouteTemplate;
            return true;
        }
 
        template = null;
        return false;
    }
 
    private sealed class PageDirectiveParserOptionsFeature : RazorEngineFeatureBase, IConfigureRazorParserOptionsFeature
    {
        public int Order { get; }
 
        public void Configure(RazorParserOptionsBuilder options)
        {
            options.ParseLeadingDirectives = true;
        }
    }
 
    private sealed class EmptyRazorProjectFileSystem : RazorProjectFileSystem
    {
        public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
        {
            return Enumerable.Empty<RazorProjectItem>();
        }
 
        public override IEnumerable<RazorProjectItem> FindHierarchicalItems(string basePath, string path, string fileName)
        {
            return Enumerable.Empty<RazorProjectItem>();
        }
 
        public override RazorProjectItem GetItem(string path)
        {
            return GetItem(path, fileKind: null);
        }
 
        public override RazorProjectItem GetItem(string path, string? fileKind)
        {
            return new NotFoundProjectItem(string.Empty, path, fileKind);
        }
 
        private sealed class NotFoundProjectItem : RazorProjectItem
        {
            public NotFoundProjectItem(string basePath, string path, string? fileKind)
            {
                BasePath = basePath;
                FilePath = path;
                FileKind = fileKind ?? FileKinds.GetFileKindFromFilePath(FilePath);
            }
 
            /// <inheritdoc />
            public override string BasePath { get; }
 
            /// <inheritdoc />
            public override string FilePath { get; }
 
            /// <inheritdoc />
            public override string FileKind { get; }
 
            /// <inheritdoc />
            public override bool Exists => false;
 
            /// <inheritdoc />
            public override string PhysicalPath => throw new NotSupportedException();
 
            /// <inheritdoc />
            public override Stream Read() => throw new NotSupportedException();
        }
    }
 
    private static partial class Log
    {
        [LoggerMessage(104, LogLevel.Warning, "The page directive at '{FilePath}' is malformed. Please fix the following issues: {Diagnostics}", EventName = "MalformedPageDirective", SkipEnabledCheck = true)]
        private static partial void MalformedPageDirective(ILogger logger, string filePath, string[] diagnostics);
 
        public static void MalformedPageDirective(ILogger logger, string filePath, IList<RazorDiagnostic> diagnostics)
        {
            if (logger.IsEnabled(LogLevel.Warning))
            {
                var messages = new string[diagnostics.Count];
                for (var i = 0; i < diagnostics.Count; i++)
                {
                    messages[i] = diagnostics[i].GetMessage(CultureInfo.CurrentCulture);
                }
 
                MalformedPageDirective(logger, filePath, messages);
            }
        }
    }
}