// 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.Linq;
using Microsoft.AspNetCore.Mvc.Razor.Host;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Chunks;
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
using Microsoft.AspNetCore.Razor.Parser;
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
using Microsoft.AspNetCore.Razor.Parser.TagHelpers;
namespace Microsoft.AspNetCore.Mvc.Razor
{
///
/// A subtype of that uses to support inheritance of tag
/// helpers from _ViewImports files.
///
public class MvcRazorParser : RazorParser
{
private readonly IEnumerable _viewImportsDirectiveDescriptors;
private readonly string _modelExpressionTypeName;
///
/// Initializes a new instance of .
///
/// The to copy properties from.
/// The s that are inherited
/// from parsed pages from _ViewImports files.
/// The inherited by
/// default by all Razor pages in the application.
/// The full name of the model expression .
public MvcRazorParser(
RazorParser parser,
IReadOnlyList inheritedChunkTrees,
IReadOnlyList defaultInheritedChunks,
string modelExpressionTypeName)
: base(parser)
{
if (parser == null)
{
throw new ArgumentNullException(nameof(parser));
}
if (inheritedChunkTrees == null)
{
throw new ArgumentNullException(nameof(inheritedChunkTrees));
}
if (defaultInheritedChunks == null)
{
throw new ArgumentNullException(nameof(defaultInheritedChunks));
}
if (modelExpressionTypeName == null)
{
throw new ArgumentNullException(nameof(modelExpressionTypeName));
}
// Construct tag helper descriptors from @addTagHelper, @removeTagHelper and @tagHelperPrefix chunks
_viewImportsDirectiveDescriptors = GetTagHelperDirectiveDescriptors(
inheritedChunkTrees,
defaultInheritedChunks);
_modelExpressionTypeName = modelExpressionTypeName;
}
///
protected override IEnumerable GetTagHelperDescriptors(
Block documentRoot,
ErrorSink errorSink)
{
if (documentRoot == null)
{
throw new ArgumentNullException(nameof(documentRoot));
}
if (errorSink == null)
{
throw new ArgumentNullException(nameof(errorSink));
}
var visitor = new ViewImportsTagHelperDirectiveSpanVisitor(
TagHelperDescriptorResolver,
_viewImportsDirectiveDescriptors,
errorSink);
var descriptors = visitor.GetDescriptors(documentRoot);
foreach (var descriptor in descriptors)
{
foreach (var attributeDescriptor in descriptor.Attributes)
{
if (attributeDescriptor.IsIndexer &&
string.Equals(
attributeDescriptor.TypeName,
_modelExpressionTypeName,
StringComparison.Ordinal))
{
errorSink.OnError(
SourceLocation.Undefined,
Resources.FormatMvcRazorParser_InvalidPropertyType(
descriptor.TypeName,
attributeDescriptor.Name,
_modelExpressionTypeName),
length: 0);
}
}
}
return descriptors;
}
private static IEnumerable GetTagHelperDirectiveDescriptors(
IReadOnlyList inheritedChunkTrees,
IReadOnlyList defaultInheritedChunks)
{
var descriptors = new List();
var inheritedChunks = defaultInheritedChunks.Concat(inheritedChunkTrees.SelectMany(tree => tree.Children));
foreach (var chunk in inheritedChunks)
{
// All TagHelperDirectiveDescriptors created here have undefined source locations because the source
// that created them is not in the same file.
var addTagHelperChunk = chunk as AddTagHelperChunk;
if (addTagHelperChunk != null)
{
var descriptor = new TagHelperDirectiveDescriptor
{
DirectiveText = addTagHelperChunk.LookupText,
Location = chunk.Start,
DirectiveType = TagHelperDirectiveType.AddTagHelper
};
descriptors.Add(descriptor);
continue;
}
var removeTagHelperChunk = chunk as RemoveTagHelperChunk;
if (removeTagHelperChunk != null)
{
var descriptor = new TagHelperDirectiveDescriptor
{
DirectiveText = removeTagHelperChunk.LookupText,
Location = chunk.Start,
DirectiveType = TagHelperDirectiveType.RemoveTagHelper
};
descriptors.Add(descriptor);
continue;
}
var tagHelperPrefixDirectiveChunk = chunk as TagHelperPrefixDirectiveChunk;
if (tagHelperPrefixDirectiveChunk != null)
{
var descriptor = new TagHelperDirectiveDescriptor
{
DirectiveText = tagHelperPrefixDirectiveChunk.Prefix,
Location = chunk.Start,
DirectiveType = TagHelperDirectiveType.TagHelperPrefix
};
descriptors.Add(descriptor);
}
}
return descriptors;
}
private class ViewImportsTagHelperDirectiveSpanVisitor : TagHelperDirectiveSpanVisitor
{
private readonly IEnumerable _viewImportsDirectiveDescriptors;
public ViewImportsTagHelperDirectiveSpanVisitor(
ITagHelperDescriptorResolver descriptorResolver,
IEnumerable viewImportsDirectiveDescriptors,
ErrorSink errorSink)
: base(descriptorResolver, errorSink)
{
_viewImportsDirectiveDescriptors = viewImportsDirectiveDescriptors;
}
protected override TagHelperDescriptorResolutionContext GetTagHelperDescriptorResolutionContext(
IEnumerable descriptors,
ErrorSink errorSink)
{
var directivesToImport = MergeDirectiveDescriptors(descriptors, _viewImportsDirectiveDescriptors);
return base.GetTagHelperDescriptorResolutionContext(directivesToImport, errorSink);
}
private static IEnumerable MergeDirectiveDescriptors(
IEnumerable descriptors,
IEnumerable inheritedDescriptors)
{
var mergedDescriptors = new List();
TagHelperDirectiveDescriptor prefixDirectiveDescriptor = null;
foreach (var descriptor in inheritedDescriptors)
{
if (descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix)
{
// Always take the latest @tagHelperPrefix descriptor. Can only have 1 per page.
prefixDirectiveDescriptor = descriptor;
}
else
{
mergedDescriptors.Add(descriptor);
}
}
// We need to see if the provided descriptors contain a @tagHelperPrefix directive. If so, it
// takes precedence and overrides any provided by the inheritedDescriptors. If not we need to add the
// inherited @tagHelperPrefix directive back into the merged list.
if (prefixDirectiveDescriptor != null &&
!descriptors.Any(descriptor => descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix))
{
mergedDescriptors.Add(prefixDirectiveDescriptor);
}
mergedDescriptors.AddRange(descriptors);
return mergedDescriptors;
}
}
}
}