diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs index 7b78770d54..ee6d981fb9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { GenerateVCTHClass(visitor.Class, tagHelper.Value); - var tagHelperTypeName = tagHelper.Value.Metadata[TagHelperDescriptorBuilder.TypeNameKey]; + var tagHelperTypeName = tagHelper.Value.GetTypeName(); if (visitor.Fields.UsedTagHelperTypeNames.Remove(tagHelperTypeName)) { visitor.Fields.UsedTagHelperTypeNames.Add(GetVCTHFullName(visitor.Namespace, visitor.Class, tagHelper.Value)); @@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions foreach (var attribute in descriptor.BoundAttributes) { writer.WriteAutoPropertyDeclaration( - "public", attribute.TypeName, attribute.Metadata[TagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]); + "public", attribute.TypeName, attribute.GetPropertyName()); if (attribute.IndexerTypeName != null) { @@ -203,7 +203,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions private string[] GetMethodParameters(TagHelperDescriptor descriptor) { var propertyNames = descriptor.BoundAttributes.Select( - attribute => attribute.Metadata[TagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]); + attribute => attribute.GetPropertyName()); var joinedPropertyNames = string.Join(", ", propertyNames); var parametersString = $"new {{ { joinedPropertyNames } }}"; diff --git a/src/Microsoft.AspNetCore.Razor.Language/BoundAttributeDescriptorExtensions.cs b/src/Microsoft.AspNetCore.Razor.Language/BoundAttributeDescriptorExtensions.cs new file mode 100644 index 0000000000..597ab40bee --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/BoundAttributeDescriptorExtensions.cs @@ -0,0 +1,20 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public static class BoundAttributeDescriptorExtensions + { + public static string GetPropertyName(this BoundAttributeDescriptor descriptor) + { + descriptor.Metadata.TryGetValue(TagHelperBoundAttributeDescriptorBuilder.PropertyNameKey, out var propertyName); + + return propertyName; + } + + public static bool IsDefaultKind(this BoundAttributeDescriptor descriptor) + => string.Equals(descriptor.Kind, TagHelperBoundAttributeDescriptorBuilder.DescriptorKind, StringComparison.Ordinal); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DesignTimeTagHelperWriter.cs b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DesignTimeTagHelperWriter.cs index 0f1af3feae..3eeda1ddf7 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DesignTimeTagHelperWriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DesignTimeTagHelperWriter.cs @@ -196,7 +196,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration string attributeName, BoundAttributeDescriptor descriptor) { - var propertyAccessor = $"{tagHelperVariableName}.{descriptor.Metadata[TagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]}"; + var propertyAccessor = $"{tagHelperVariableName}.{descriptor.GetPropertyName()}"; if (isIndexerNameMatch) { diff --git a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/PreallocatedAttributeTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/PreallocatedAttributeTargetExtension.cs index 27a1372beb..43c01077a5 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/PreallocatedAttributeTargetExtension.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/PreallocatedAttributeTargetExtension.cs @@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration string attributeName, BoundAttributeDescriptor descriptor) { - var propertyAccessor = $"{tagHelperVariableName}.{descriptor.Metadata[TagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]}"; + var propertyAccessor = $"{tagHelperVariableName}.{descriptor.GetPropertyName()}"; if (isIndexerNameMatch) { diff --git a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeTagHelperWriter.cs b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeTagHelperWriter.cs index d0e8d4ec14..354f38fc94 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeTagHelperWriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeTagHelperWriter.cs @@ -310,7 +310,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration { var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); var tagHelperRenderingContext = context.TagHelperRenderingContext; - var propertyName = node.Descriptor.Metadata[TagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]; + var propertyName = node.Descriptor.GetPropertyName(); // Ensure that the property we're trying to set has initialized its dictionary bound properties. if (node.IsIndexerNameMatch && @@ -453,7 +453,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration string attributeName, BoundAttributeDescriptor descriptor) { - var propertyAccessor = $"{tagHelperVariableName}.{descriptor.Metadata[TagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]}"; + var propertyAccessor = $"{tagHelperVariableName}.{descriptor.GetPropertyName()}"; if (isIndexerNameMatch) { diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIRLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIRLoweringPhase.cs index ae0e4e5ff1..38d21b575f 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIRLoweringPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIRLoweringPhase.cs @@ -533,7 +533,7 @@ namespace Microsoft.AspNetCore.Razor.Language foreach (var descriptor in block.Binding.Descriptors) { - var typeName = descriptor.Metadata[TagHelperDescriptorBuilder.TypeNameKey]; + var typeName = descriptor.GetTypeName(); _tagHelperFields.UsedTagHelperTypeNames.Add(typeName); } } @@ -543,7 +543,7 @@ namespace Microsoft.AspNetCore.Razor.Language var descriptors = tagHelperBinding.Descriptors; foreach (var descriptor in descriptors) { - var typeName = descriptor.Metadata[TagHelperDescriptorBuilder.TypeNameKey]; + var typeName = descriptor.GetTypeName(); var createTagHelper = new CreateTagHelperIRNode() { TagHelperTypeName = typeName, @@ -577,8 +577,8 @@ namespace Microsoft.AspNetCore.Razor.Language { var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First( attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, attributeDescriptor)); - var tagHelperTypeName = associatedDescriptor.Metadata[TagHelperDescriptorBuilder.TypeNameKey]; - var attributePropertyName = associatedAttributeDescriptor.Metadata[TagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]; + var tagHelperTypeName = associatedDescriptor.GetTypeName(); + var attributePropertyName = associatedAttributeDescriptor.GetPropertyName(); var setTagHelperProperty = new SetTagHelperPropertyIRNode() { diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs index a692fba5ee..4970a761fe 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs @@ -267,13 +267,13 @@ namespace Microsoft.AspNetCore.Razor.Language return false; } - if (descriptor.Kind != TagHelperDescriptorBuilder.DescriptorKind) + if (!descriptor.IsDefaultKind()) { - // We only understand TagHelperDescriptors generated from ITagHelpers. + // We only understand default TagHelperDescriptors. return false; } - var descriptorTypeName = descriptor.Metadata[TagHelperDescriptorBuilder.TypeNameKey]; + var descriptorTypeName = descriptor.GetTypeName(); if (lookupInfo.TypePattern.EndsWith("*", StringComparison.Ordinal)) { diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperParseTreeRewriter.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperParseTreeRewriter.cs index f904f31dac..959980e326 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperParseTreeRewriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/TagHelperParseTreeRewriter.cs @@ -273,7 +273,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy if (invalidRule != null) { - var typeName = descriptor.Metadata[TagHelperDescriptorBuilder.TypeNameKey]; + var typeName = descriptor.GetTypeName(); // End tag TagHelper that states it shouldn't have an end tag. errorSink.OnError( @@ -564,8 +564,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy // Can't have a set of TagHelpers that expect different structures. if (baseStructure.HasValue && baseStructure != rule.TagStructure) { - var baseDescriptorTypeName = baseDescriptor.Metadata[TagHelperDescriptorBuilder.TypeNameKey]; - var descriptorTypeName = descriptor.Metadata[TagHelperDescriptorBuilder.TypeNameKey]; + var baseDescriptorTypeName = baseDescriptor.GetTypeName(); + var descriptorTypeName = descriptor.GetTypeName(); errorSink.OnError( tagBlock.Start, LegacyResources.FormatTagHelperParseTreeRewriter_InconsistentTagStructure( diff --git a/src/Microsoft.AspNetCore.Razor.Language/TagHelperBoundAttributeDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/TagHelperBoundAttributeDescriptorBuilder.cs index 47597715c6..a0f60e8767 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/TagHelperBoundAttributeDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/TagHelperBoundAttributeDescriptorBuilder.cs @@ -9,8 +9,8 @@ namespace Microsoft.AspNetCore.Razor.Language { public sealed class TagHelperBoundAttributeDescriptorBuilder { - public static readonly string DescriptorKind = "ITagHelper"; - public static readonly string PropertyNameKey = "ITagHelper.PropertyName"; + internal const string DescriptorKind = "ITagHelper"; + internal const string PropertyNameKey = "ITagHelper.PropertyName"; private static readonly IReadOnlyDictionary PrimitiveDisplayTypeNameLookups = new Dictionary(StringComparer.Ordinal) { diff --git a/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorBuilder.cs index 6303493387..c1fcb74b09 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorBuilder.cs @@ -9,8 +9,8 @@ namespace Microsoft.AspNetCore.Razor.Language { public sealed class TagHelperDescriptorBuilder { - public static readonly string DescriptorKind = "ITagHelper"; - public static readonly string TypeNameKey = "ITagHelper.TypeName"; + internal const string DescriptorKind = "ITagHelper"; + internal const string TypeNameKey = "ITagHelper.TypeName"; private static ICollection InvalidNonWhitespaceAllowedChildCharacters { get; } = new HashSet( new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' }); diff --git a/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorExtensions.cs b/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorExtensions.cs new file mode 100644 index 0000000000..ead9d6ca62 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorExtensions.cs @@ -0,0 +1,20 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public static class TagHelperDescriptorExtensions + { + public static string GetTypeName(this TagHelperDescriptor descriptor) + { + descriptor.Metadata.TryGetValue(TagHelperDescriptorBuilder.TypeNameKey, out var typeName); + + return typeName; + } + + public static bool IsDefaultKind(this TagHelperDescriptor descriptor) + => string.Equals(descriptor.Kind, TagHelperDescriptorBuilder.DescriptorKind, StringComparison.Ordinal); + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/BoundAttributeDescriptorExtensionsTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/BoundAttributeDescriptorExtensionsTest.cs new file mode 100644 index 0000000000..b07afaca35 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/BoundAttributeDescriptorExtensionsTest.cs @@ -0,0 +1,81 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public class BoundAttributeDescriptorExtensionsTest + { + [Fact] + public void GetPropertyName_ReturnsPropertyName() + { + // Arrange + var expectedPropertyName = "IntProperty"; + var descriptor = TagHelperBoundAttributeDescriptorBuilder.Create("TestTagHelper") + .Name("test") + .PropertyName(expectedPropertyName) + .TypeName(typeof(int).FullName) + .Build(); + + // Act + var propertyName = descriptor.GetPropertyName(); + + // Assert + Assert.Equal(expectedPropertyName, propertyName); + } + + [Fact] + public void GetPropertyName_ReturnsNullIfNoPropertyName() + { + // Arrange + var descriptor = TagHelperBoundAttributeDescriptorBuilder.Create("TestTagHelper") + .Name("test") + .TypeName(typeof(int).FullName) + .Build(); + + // Act + var propertyName = descriptor.GetPropertyName(); + + // Assert + Assert.Null(propertyName); + } + + [Fact] + public void IsDefaultKind_ReturnsTrueIfFromDefaultBuilder() + { + // Arrange + var descriptor = TagHelperBoundAttributeDescriptorBuilder.Create("TestTagHelper") + .Name("test") + .PropertyName("IntProperty") + .TypeName(typeof(int).FullName) + .Build(); + + // Act + var isDefault = descriptor.IsDefaultKind(); + + // Assert + Assert.True(isDefault); + } + + [Fact] + public void IsDefaultKind_ReturnsFalseIfFromCustomBuilder() + { + // Arrange + var descriptor = new CustomBoundAttributeDescriptor(); + + // Act + var isDefault = descriptor.IsDefaultKind(); + + // Assert + Assert.False(isDefault); + } + + private class CustomBoundAttributeDescriptor : BoundAttributeDescriptor + { + public CustomBoundAttributeDescriptor() : base("custom") + { + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TagHelperDescriptorExtensionsTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/TagHelperDescriptorExtensionsTest.cs new file mode 100644 index 0000000000..ac55dff8ac --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TagHelperDescriptorExtensionsTest.cs @@ -0,0 +1,72 @@ +// 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.Collections.Generic; +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public class TagHelperDescriptorExtensionsTest + { + [Fact] + public void GetTypeName_ReturnsTypeName() + { + // Arrange + var expectedTypeName = "TestTagHelper"; + var descriptor = TagHelperDescriptorBuilder.Create(expectedTypeName, "TestAssembly").Build(); + + // Act + var typeName = descriptor.GetTypeName(); + + // Assert + Assert.Equal(expectedTypeName, typeName); + } + + [Fact] + public void GetTypeName_ReturnsNullIfNoTypeName() + { + // Arrange + var descriptor = new CustomTagHelperDescriptor(); + + // Act + var typeName = descriptor.GetTypeName(); + + // Assert + Assert.Null(typeName); + } + + [Fact] + public void IsDefaultKind_ReturnsTrueIfFromDefaultBuilder() + { + // Arrange + var descriptor = TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly").Build(); + + // Act + var isDefault = descriptor.IsDefaultKind(); + + // Assert + Assert.True(isDefault); + } + + [Fact] + public void IsDefaultKind_ReturnsFalseIfFromCustomBuilder() + { + // Arrange + var descriptor = new CustomTagHelperDescriptor(); + + // Act + var isDefault = descriptor.IsDefaultKind(); + + // Assert + Assert.False(isDefault); + } + + private class CustomTagHelperDescriptor : TagHelperDescriptor + { + public CustomTagHelperDescriptor() : base("custom") + { + Metadata = new Dictionary(); + } + } + } +} diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/TagHelperViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/TagHelperViewModel.cs index 8b619dd15f..c63d2e5c8a 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/TagHelperViewModel.cs +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/TagHelperViewModel.cs @@ -20,7 +20,7 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo public string TargetElement => string.Join(", ", _descriptor.TagMatchingRules.Select(rule => rule.TagName)); - public string TypeName => _descriptor.Metadata[TagHelperDescriptorBuilder.TypeNameKey]; + public string TypeName => _descriptor.GetTypeName(); } } #endif \ No newline at end of file