Create an abstraction model for TypeInfo for tag helper discovery.

This commit is contained in:
Pranav K 2015-08-10 16:33:04 -07:00
parent 08c8f9f7ba
commit 231e8a9cf4
15 changed files with 958 additions and 92 deletions

View File

@ -0,0 +1,30 @@
// 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;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
/// <summary>
/// Metadata common to types and properties.
/// </summary>
public interface IMemberInfo
{
/// <summary>
/// Gets the name.
/// </summary>
string Name { get; }
/// <summary>
/// Retrieves a collection of custom <see cref="Attribute"/>s of type <typeparamref name="TAttribute"/> applied
/// to this instance of <see cref="IMemberInfo"/>.
/// </summary>
/// <typeparam name="TAttribute">The type of <see cref="Attribute"/> to search for.</typeparam>
/// <returns>A sequence of custom <see cref="Attribute"/>s of type
/// <typeparamref name="TAttribute"/>.</returns>
/// <remarks>Result not include inherited <see cref="Attribute"/>s.</remarks>
IEnumerable<TAttribute> GetCustomAttributes<TAttribute>()
where TAttribute : Attribute;
}
}

View File

@ -0,0 +1,26 @@
// 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.
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
/// <summary>
/// Contains property metadata.
/// </summary>
public interface IPropertyInfo : IMemberInfo
{
/// <summary>
/// Gets a value indicating whether this property has a public getter.
/// </summary>
bool HasPublicGetter { get; }
/// <summary>
/// Gets a value indicating whether this property has a public setter.
/// </summary>
bool HasPublicSetter { get; }
/// <summary>
/// Gets the <see cref="ITypeInfo"/> of the property.
/// </summary>
ITypeInfo PropertyType { get; }
}
}

View File

@ -0,0 +1,61 @@
// 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;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
/// <summary>
/// Contains type metadata.
/// </summary>
public interface ITypeInfo : IMemberInfo
{
/// <summary>
/// Fully qualified name of the type.
/// </summary>
string FullName { get; }
/// <summary>
/// Gets <see cref="IPropertyInfo"/>s for all properties of the current type excluding indexers.
/// </summary>
/// <remarks>
/// Indexers in this context refer to the CLR notion of an indexer (<c>this [string name]</c>
/// and does not overlap with the semantics of
/// <see cref="Razor.TagHelpers.TagHelperAttributeDescriptor.IsIndexer"/>.
/// </remarks>
IEnumerable<IPropertyInfo> Properties { get; }
/// <summary>
/// Gets a value indicating whether the type is public.
/// </summary>
bool IsPublic { get; }
/// <summary>
/// Gets a value indicating whether the type is abstract or an interface.
/// </summary>
bool IsAbstract { get; }
/// <summary>
/// Gets a value indicating whether the type is generic.
/// </summary>
bool IsGenericType { get; }
/// <summary>
/// Gets a value indicating whether the type implements the <see cref="ITagHelper"/> interface.
/// </summary>
bool IsTagHelper { get; }
/// <summary>
/// Gets the full names of the parameter types if the type implements
/// <see cref="IDictionary{TKey, TValue}"/>.
/// </summary>
/// <returns>
/// The full type names (<seealso cref="System.Type.FullName"/>) of <c>TKey</c> and <c>TValue</c>
/// parameters if the type implements <see cref="IDictionary{TKey, TValue}"/>, otherwise <c>null</c>.
/// </returns>
/// <remarks>
/// For open generic types, full type names for generic type parameters is <c>null</c>.
/// </remarks>
string[] GetGenericDictionaryParameterNames();
}
}

View File

@ -0,0 +1,51 @@
// 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.Reflection;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Razor.Runtime
{
/// <summary>
/// <see cref="IPropertyInfo"/> adapter for <see cref="PropertyInfo"/> instances.
/// </summary>
public class RuntimePropertyInfo : IPropertyInfo
{
/// <summary>
/// Initializes a new instance of <see cref="RuntimePropertyInfo"/>.
/// </summary>
/// <param name="propertyInfo">The <see cref="PropertyInfo"/> instance to adapt.</param>
public RuntimePropertyInfo([NotNull] PropertyInfo propertyInfo)
{
Property = propertyInfo;
}
/// <summary>
/// The <see cref="PropertyInfo"/> instance.
/// </summary>
public PropertyInfo Property { get; }
/// <inheritdoc />
public bool HasPublicGetter => Property.GetMethod != null && Property.GetMethod.IsPublic;
/// <inheritdoc />
public bool HasPublicSetter => Property.SetMethod != null && Property.SetMethod.IsPublic;
/// <inheritdoc />
public string Name => Property.Name;
/// <inheritdoc />
public ITypeInfo PropertyType => new RuntimeTypeInfo(Property.PropertyType.GetTypeInfo());
/// <inheritdoc />
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>() where TAttribute : Attribute
=> Property.GetCustomAttributes<TAttribute>(inherit: false);
/// <inheritdoc />
public override string ToString() =>
Property.ToString();
}
}

View File

@ -0,0 +1,88 @@
// 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 System.Reflection;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
/// <summary>
/// <see cref="ITypeInfo"/> adapter for <see cref="System.Reflection.TypeInfo"/> instances.
/// </summary>
public class RuntimeTypeInfo : ITypeInfo
{
private static readonly TypeInfo TagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo();
private IEnumerable<IPropertyInfo> _properties;
/// <summary>
/// Initializes a new instance of <see cref="RuntimeTypeInfo"/>
/// </summary>
/// <param name="propertyInfo">The <see cref="System.Reflection.TypeInfo"/> instance to adapt.</param>
public RuntimeTypeInfo([NotNull] TypeInfo typeInfo)
{
TypeInfo = typeInfo;
}
/// <summary>
/// The <see cref="System.Reflection.TypeInfo"/> instance.
/// </summary>
public TypeInfo TypeInfo { get; }
/// <inheritdoc />
public string Name => TypeInfo.Name;
/// <inheritdoc />
public string FullName => TypeInfo.FullName;
/// <inheritdoc />
public bool IsAbstract => TypeInfo.IsAbstract;
/// <inheritdoc />
public bool IsGenericType => TypeInfo.IsGenericType;
/// <inheritdoc />
public bool IsPublic => TypeInfo.IsPublic;
/// <inheritdoc />
public IEnumerable<IPropertyInfo> Properties
{
get
{
if (_properties == null)
{
_properties = TypeInfo
.AsType()
.GetRuntimeProperties()
.Where(property => property.GetIndexParameters().Length == 0)
.Select(property => new RuntimePropertyInfo(property));
}
return _properties;
}
}
/// <inheritdoc />
public bool IsTagHelper => TagHelperTypeInfo.IsAssignableFrom(TypeInfo);
/// <inheritdoc />
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>() where TAttribute : Attribute =>
TypeInfo.GetCustomAttributes<TAttribute>(inherit: false);
/// <inheritdoc />
public string[] GetGenericDictionaryParameterNames()
{
return ClosedGenericMatcher.ExtractGenericInterface(
TypeInfo.AsType(),
typeof(IDictionary<,>))
?.GenericTypeArguments
.Select(type => type.FullName)
.ToArray();
}
/// <inheritdoc />
public override string ToString() => TypeInfo.ToString();
}
}

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.Framework.Internal;
@ -13,7 +12,7 @@ using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
/// <summary>
/// Factory for <see cref="TagHelperDescriptor"/>s from <see cref="Type"/>s.
/// Factory for <see cref="TagHelperDescriptor"/>s from <see cref="ITypeInfo"/>s.
/// </summary>
public static class TagHelperDescriptorFactory
{
@ -35,31 +34,30 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' });
/// <summary>
/// Creates a <see cref="TagHelperDescriptor"/> from the given <paramref name="type"/>.
/// Creates a <see cref="TagHelperDescriptor"/> from the given <paramref name="typeInfo"/>.
/// </summary>
/// <param name="assemblyName">The assembly name that contains <paramref name="type"/>.</param>
/// <param name="type">The type to create a <see cref="TagHelperDescriptor"/> from.</param>
/// <param name="typeInfo">The <see cref="ITypeInfo"/> to create a <see cref="TagHelperDescriptor"/> from.
/// </param>
/// <param name="designTime">Indicates if the returned <see cref="TagHelperDescriptor"/>s should include
/// design time specific information.</param>
/// <param name="errorSink">The <see cref="ErrorSink"/> used to collect <see cref="RazorError"/>s encountered
/// when creating <see cref="TagHelperDescriptor"/>s for the given <paramref name="type"/>.</param>
/// when creating <see cref="TagHelperDescriptor"/>s for the given <paramref name="typeInfo"/>.</param>
/// <returns>
/// A collection of <see cref="TagHelperDescriptor"/>s that describe the given <paramref name="type"/>.
/// A collection of <see cref="TagHelperDescriptor"/>s that describe the given <paramref name="typeInfo"/>.
/// </returns>
public static IEnumerable<TagHelperDescriptor> CreateDescriptors(
string assemblyName,
[NotNull] Type type,
[NotNull] ITypeInfo typeInfo,
bool designTime,
[NotNull] ErrorSink errorSink)
{
var typeInfo = type.GetTypeInfo();
if (ShouldSkipDescriptorCreation(designTime, typeInfo))
{
return Enumerable.Empty<TagHelperDescriptor>();
}
var attributeDescriptors = GetAttributeDescriptors(type, designTime, errorSink);
var attributeDescriptors = GetAttributeDescriptors(typeInfo, designTime, errorSink);
var targetElementAttributes = GetValidTargetElementAttributes(typeInfo, errorSink);
var allowedChildren = GetAllowedChildren(typeInfo, errorSink);
@ -76,16 +74,16 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
}
private static IEnumerable<TargetElementAttribute> GetValidTargetElementAttributes(
TypeInfo typeInfo,
ITypeInfo typeInfo,
ErrorSink errorSink)
{
var targetElementAttributes = typeInfo.GetCustomAttributes<TargetElementAttribute>(inherit: false);
var targetElementAttributes = typeInfo.GetCustomAttributes<TargetElementAttribute>();
return targetElementAttributes.Where(attribute => ValidTargetElementAttributeNames(attribute, errorSink));
}
private static IEnumerable<TagHelperDescriptor> BuildTagHelperDescriptors(
TypeInfo typeInfo,
ITypeInfo typeInfo,
string assemblyName,
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
IEnumerable<TargetElementAttribute> targetElementAttributes,
@ -97,7 +95,12 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
#if !DNXCORE50
if (designTime)
{
typeDesignTimeDescriptor = TagHelperDesignTimeDescriptorFactory.CreateDescriptor(typeInfo.AsType());
var runtimeTypeInfo = typeInfo as RuntimeTypeInfo;
if (runtimeTypeInfo != null)
{
typeDesignTimeDescriptor =
TagHelperDesignTimeDescriptorFactory.CreateDescriptor(runtimeTypeInfo.TypeInfo.AsType());
}
}
#endif
@ -138,9 +141,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
typeDesignTimeDescriptor));
}
private static IEnumerable<string> GetAllowedChildren(TypeInfo typeInfo, ErrorSink errorSink)
private static IEnumerable<string> GetAllowedChildren(ITypeInfo typeInfo, ErrorSink errorSink)
{
var restrictChildrenAttribute = typeInfo.GetCustomAttribute<RestrictChildrenAttribute>(inherit: false);
var restrictChildrenAttribute = typeInfo
.GetCustomAttributes<RestrictChildrenAttribute>()
.FirstOrDefault();
if (restrictChildrenAttribute == null)
{
return null;
@ -338,7 +343,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
}
private static IEnumerable<TagHelperAttributeDescriptor> GetAttributeDescriptors(
Type type,
ITypeInfo type,
bool designTime,
ErrorSink errorSink)
{
@ -347,7 +352,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Keep indexer descriptors separate to avoid sorting the combined list later.
var indexerDescriptors = new List<TagHelperAttributeDescriptor>();
var accessibleProperties = type.GetRuntimeProperties().Where(IsAccessibleProperty);
var accessibleProperties = type.Properties.Where(IsAccessibleProperty);
foreach (var property in accessibleProperties)
{
if (ShouldSkipDescriptorCreation(designTime, property))
@ -355,13 +360,15 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
continue;
}
var attributeNameAttribute = property.GetCustomAttribute<HtmlAttributeNameAttribute>(inherit: false);
var attributeNameAttribute = property
.GetCustomAttributes<HtmlAttributeNameAttribute>()
.FirstOrDefault();
var hasExplicitName =
attributeNameAttribute != null && !string.IsNullOrEmpty(attributeNameAttribute.Name);
var attributeName = hasExplicitName ? attributeNameAttribute.Name : ToHtmlCase(property.Name);
TagHelperAttributeDescriptor mainDescriptor = null;
if (property.SetMethod?.IsPublic == true)
if (property.HasPublicSetter)
{
mainDescriptor = ToAttributeDescriptor(property, attributeName, designTime);
if (!ValidateTagHelperAttributeDescriptor(mainDescriptor, type, errorSink))
@ -425,7 +432,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Internal for testing.
internal static bool ValidateTagHelperAttributeDescriptor(
TagHelperAttributeDescriptor attributeDescriptor,
Type parentType,
ITypeInfo parentType,
ErrorSink errorSink)
{
string nameOrPrefix;
@ -457,13 +464,16 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
nameOrPrefix);
}
private static bool ShouldSkipDescriptorCreation(bool designTime, MemberInfo memberInfo)
private static bool ShouldSkipDescriptorCreation(bool designTime, IMemberInfo memberInfo)
{
if (designTime)
{
var editorBrowsableAttribute = memberInfo.GetCustomAttribute<EditorBrowsableAttribute>(inherit: false);
var editorBrowsableAttribute = memberInfo
.GetCustomAttributes<EditorBrowsableAttribute>()
.FirstOrDefault();
return editorBrowsableAttribute != null && editorBrowsableAttribute.State == EditorBrowsableState.Never;
return editorBrowsableAttribute != null &&
editorBrowsableAttribute.State == EditorBrowsableState.Never;
}
return false;
@ -471,7 +481,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
private static bool ValidateTagHelperAttributeNameOrPrefix(
string attributeNameOrPrefix,
Type parentType,
ITypeInfo parentType,
string propertyName,
ErrorSink errorSink,
string nameOrPrefix)
@ -540,7 +550,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
}
private static TagHelperAttributeDescriptor ToAttributeDescriptor(
PropertyInfo property,
IPropertyInfo property,
string attributeName,
bool designTime)
{
@ -553,21 +563,18 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
}
private static TagHelperAttributeDescriptor ToIndexerAttributeDescriptor(
PropertyInfo property,
IPropertyInfo property,
HtmlAttributeNameAttribute attributeNameAttribute,
Type parentType,
ITypeInfo parentType,
ErrorSink errorSink,
string defaultPrefix,
bool designTime,
out bool isInvalid)
{
isInvalid = false;
var hasPublicSetter = property.SetMethod?.IsPublic == true;
var dictionaryTypeArguments = ClosedGenericMatcher.ExtractGenericInterface(
property.PropertyType,
typeof(IDictionary<,>))
?.GenericTypeArguments;
if (dictionaryTypeArguments?[0] != typeof(string))
var hasPublicSetter = property.HasPublicSetter;
var dictionaryTypeArguments = property.PropertyType.GetGenericDictionaryParameterNames();
if (dictionaryTypeArguments?[0] != typeof(string).FullName)
{
if (attributeNameAttribute?.DictionaryAttributePrefix != null)
{
@ -634,13 +641,13 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
return ToAttributeDescriptor(
property,
attributeName: prefix,
typeName: dictionaryTypeArguments[1].FullName,
typeName: dictionaryTypeArguments[1],
isIndexer: true,
designTime: designTime);
}
private static TagHelperAttributeDescriptor ToAttributeDescriptor(
PropertyInfo property,
IPropertyInfo property,
string attributeName,
string typeName,
bool isIndexer,
@ -651,8 +658,12 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
#if !DNXCORE50
if (designTime)
{
propertyDesignTimeDescriptor =
TagHelperDesignTimeDescriptorFactory.CreateAttributeDescriptor(property);
var runtimeProperty = property as RuntimePropertyInfo;
if (runtimeProperty != null)
{
propertyDesignTimeDescriptor =
TagHelperDesignTimeDescriptorFactory.CreateAttributeDescriptor(runtimeProperty.Property);
}
}
#endif
@ -666,11 +677,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
};
}
private static bool IsAccessibleProperty(PropertyInfo property)
private static bool IsAccessibleProperty(IPropertyInfo property)
{
// Accessible properties are those with public getters and without [HtmlAttributeNotBound].
return property.GetMethod?.IsPublic == true &&
property.GetCustomAttribute<HtmlAttributeNotBoundAttribute>(inherit: false) == null;
return property.HasPublicGetter &&
property.GetCustomAttributes<HtmlAttributeNotBoundAttribute>().FirstOrDefault() == null;
}
/// <summary>

View File

@ -17,25 +17,16 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
private static readonly TypeInfo ITagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo();
/// <summary>
/// Instantiates a new instance of the <see cref="TagHelperTypeResolver"/> class.
/// </summary>
public TagHelperTypeResolver()
{
}
/// <summary>
/// Loads an <see cref="Assembly"/> using the given <paramref name="name"/> and resolves
/// all valid <see cref="ITagHelper"/> <see cref="Type"/>s.
/// Locates valid <see cref="ITagHelper"/> types from the <see cref="Assembly"/> named <paramref name="name"/>.
/// </summary>
/// <param name="name">The name of an <see cref="Assembly"/> to search.</param>
/// <param name="documentLocation">The <see cref="SourceLocation"/> of the associated
/// <see cref="Parser.SyntaxTree.SyntaxTreeNode"/> responsible for the current <see cref="Resolve"/> call.
/// </param>
/// <param name="errorSink">The <see cref="ErrorSink"/> used to record errors found when resolving
/// <see cref="ITagHelper"/> <see cref="Type"/>s.</param>
/// <returns>An <see cref="IEnumerable{Type}"/> of valid <see cref="ITagHelper"/> <see cref="Type"/>s.
/// </returns>
public IEnumerable<Type> Resolve(
/// <see cref="ITagHelper"/> types.</param>
/// <returns>An <see cref="IEnumerable{ITypeInfo}"/> of valid <see cref="ITagHelper"/> types.</returns>
public IEnumerable<ITypeInfo> Resolve(
string name,
SourceLocation documentLocation,
[NotNull] ErrorSink errorSink)
@ -48,15 +39,15 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull,
errorLength);
return Type.EmptyTypes;
return Enumerable.Empty<ITypeInfo>();
}
var assemblyName = new AssemblyName(name);
IEnumerable<TypeInfo> libraryTypes;
IEnumerable<ITypeInfo> libraryTypes;
try
{
libraryTypes = GetExportedTypes(assemblyName);
libraryTypes = GetTopLevelExportedTypes(assemblyName);
}
catch (Exception ex)
{
@ -67,13 +58,26 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
ex.Message),
name.Length);
return Type.EmptyTypes;
return Enumerable.Empty<ITypeInfo>();
}
var validTagHelpers = libraryTypes.Where(IsTagHelper);
return libraryTypes.Where(IsTagHelper);
}
// Convert from TypeInfo[] to Type[]
return validTagHelpers.Select(type => type.AsType());
/// <summary>
/// Returns all non-nested exported types from the given <paramref name="assemblyName"/>
/// </summary>
/// <param name="assemblyName">The <see cref="AssemblyName"/> to get <see cref="ITypeInfo"/>s from.</param>
/// <returns>
/// An <see cref="IEnumerable{ITypeInfo}"/> of types exported from the given <paramref name="assemblyName"/>.
/// </returns>
protected virtual IEnumerable<ITypeInfo> GetTopLevelExportedTypes([NotNull] AssemblyName assemblyName)
{
var exportedTypeInfos = GetExportedTypes(assemblyName);
return exportedTypeInfos
.Where(typeInfo => !typeInfo.IsNested)
.Select(typeInfo => new RuntimeTypeInfo(typeInfo));
}
/// <summary>
@ -91,13 +95,12 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
}
// Internal for testing.
internal virtual bool IsTagHelper(TypeInfo typeInfo)
internal virtual bool IsTagHelper(ITypeInfo typeInfo)
{
return typeInfo.IsPublic &&
!typeInfo.IsAbstract &&
!typeInfo.IsGenericType &&
!typeInfo.IsNested &&
ITagHelperTypeInfo.IsAssignableFrom(typeInfo);
typeInfo.IsTagHelper;
}
}
}

View File

@ -0,0 +1,9 @@
// 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.
namespace Microsoft.AspNet.Razor.Fake
{
public class DoesNotImplementRealITagHelper : Microsoft.AspNet.Razor.Fake.ITagHelper
{
}
}

View File

@ -0,0 +1,14 @@
// 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.Threading.Tasks;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
namespace Microsoft.AspNet.Razor.Fake
{
public interface ITagHelper
{
}
}

View File

@ -0,0 +1,26 @@
// 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.Threading.Tasks;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
namespace Microsoft.AspNet.Razor.Fake
{
public class ImplementsRealITagHelper : Microsoft.AspNet.Razor.Runtime.TagHelpers.ITagHelper
{
public int Order
{
get
{
throw new NotImplementedException();
}
}
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,159 @@
// 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.Linq;
using System.Reflection;
using Xunit;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
public class RuntimePropertyInfoTest
{
[Fact]
public void PropertyInfo_ReturnsMetadataOfAdaptingProperty()
{
// Arrange
var property = GetPropertyInfo(nameof(TestType.Property));
var runtimePropertyInfo = new RuntimePropertyInfo(property);
// Act
var actual = runtimePropertyInfo.Property;
// Assert
Assert.Same(property, actual);
var runtimeTypeInfo = Assert.IsType<RuntimeTypeInfo>(runtimePropertyInfo.PropertyType);
Assert.Same(property.PropertyType, runtimeTypeInfo.TypeInfo);
}
[Theory]
[InlineData(nameof(TestType.Property))]
[InlineData(nameof(TestType.PrivateSetter))]
[InlineData(nameof(TestType.PropertyWithoutSetter))]
public void HasPublicGetter_ReturnsTrueIfGetterExistsAndIsPublic(string propertyName)
{
// Arrange
var property = GetPropertyInfo(propertyName);
var runtimePropertyInfo = new RuntimePropertyInfo(property);
// Act
var result = runtimePropertyInfo.HasPublicGetter;
// Assert
Assert.True(result);
}
[Theory]
[InlineData(nameof(TestType.PrivateGetter))]
[InlineData(nameof(TestType.PropertyWithoutGetter))]
[InlineData("ProtectedProperty")]
public void HasPublicGetter_ReturnsFalseIfGetterDoesNotExistOrIsNonPublic(string propertyName)
{
// Arrange
var property = GetPropertyInfo(propertyName);
var runtimePropertyInfo = new RuntimePropertyInfo(property);
// Act
var result = runtimePropertyInfo.HasPublicGetter;
// Assert
Assert.False(result);
}
[Theory]
[InlineData(nameof(TestType.Property))]
[InlineData(nameof(TestType.PrivateGetter))]
[InlineData(nameof(TestType.PropertyWithoutGetter))]
public void HasPublicSetter_ReturnsTrueIfSetterExistsAndIsPublic(string propertyName)
{
// Arrange
var property = GetPropertyInfo(propertyName);
var runtimePropertyInfo = new RuntimePropertyInfo(property);
// Act
var result = runtimePropertyInfo.HasPublicSetter;
// Assert
Assert.True(result);
}
[Theory]
[InlineData(nameof(TestType.PrivateSetter))]
[InlineData(nameof(TestType.PropertyWithoutSetter))]
[InlineData("ProtectedProperty")]
public void HasPublicSetter_ReturnsFalseIfGetterDoesNotExistOrIsNonPublic(string propertyName)
{
// Arrange
var property = GetPropertyInfo(propertyName);
var runtimePropertyInfo = new RuntimePropertyInfo(property);
// Act
var result = runtimePropertyInfo.HasPublicSetter;
// Assert
Assert.False(result);
}
[Fact]
public void GetAttributes_ReturnsCustomAttributesOfSpecifiedType()
{
// Arrange
var property = GetPropertyInfo(nameof(TestType.PropertyWithAttributes));
var runtimeProperty = new RuntimePropertyInfo(property);
// Act
var attributes = property.GetCustomAttributes<HtmlAttributeNameAttribute>();
// Assert
var htmlAttributeName = Assert.Single(attributes);
Assert.Equal("somename", htmlAttributeName.Name);
}
[Fact]
public void GetAttributes_DoesNotInheritAttributes()
{
// Arrange
var property = GetPropertyInfo(nameof(TestType.PropertyWithAttributes));
var runtimeProperty = new RuntimePropertyInfo(property);
// Act
var attributes = property.GetCustomAttributes<HtmlAttributeNotBoundAttribute>();
// Assert
Assert.Empty(attributes);
}
private static PropertyInfo GetPropertyInfo(string propertyName)
{
return typeof(TestType).GetRuntimeProperties()
.FirstOrDefault(p => p.Name == propertyName);
}
public class BaseType
{
[HtmlAttributeNotBound]
public virtual string PropertyWithAttributes { get; }
}
public class TestType : BaseType
{
public string Property { get; set; }
public int PrivateSetter { get; private set; }
public object PrivateGetter { private get; set; }
protected DateTimeOffset ProtectedProperty { get; set; }
public string PropertyWithoutGetter
{
set { }
}
public int PropertyWithoutSetter => 0;
[HtmlAttributeName("somename")]
public override string PropertyWithAttributes { get; }
}
}
}

View File

@ -0,0 +1,375 @@
// 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;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Xunit;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
public class RuntimeTypeInfoTest
{
[Theory]
[InlineData(typeof(int))]
[InlineData(typeof(string))]
[InlineData(typeof(Tuple<,>))]
[InlineData(typeof(IDictionary<string, string>))]
[InlineData(typeof(IDictionary<string, IDictionary<string, CustomType>>))]
[InlineData(typeof(AbstractType))]
[InlineData(typeof(PrivateType))]
[InlineData(typeof(KnownKeyDictionary<>))]
[InlineData(typeof(KnownKeyDictionary<string>))]
public void RuntimeTypeInfo_ReturnsMetadataOfAdaptingType(Type type)
{
// Arrange
var typeInfo = type.GetTypeInfo();
var runtimeTypeInfo = new RuntimeTypeInfo(typeInfo);
// Act and Assert
Assert.Same(runtimeTypeInfo.TypeInfo, typeInfo);
Assert.Equal(runtimeTypeInfo.Name, typeInfo.Name);
Assert.Equal(runtimeTypeInfo.FullName, typeInfo.FullName);
Assert.Equal(runtimeTypeInfo.IsAbstract, typeInfo.IsAbstract);
Assert.Equal(runtimeTypeInfo.IsGenericType, typeInfo.IsGenericType);
Assert.Equal(runtimeTypeInfo.IsPublic, typeInfo.IsPublic);
}
[Fact]
public void Properties_ReturnsPublicPropertiesOfAdaptingType()
{
// Arrange
var typeInfo = typeof(SubType).GetTypeInfo();
var runtimeTypeInfo = new RuntimeTypeInfo(typeInfo);
// Act and Assert
Assert.Collection(runtimeTypeInfo.Properties,
property =>
{
Assert.IsType<RuntimePropertyInfo>(property);
Assert.Equal("Property1", property.Name);
},
property =>
{
Assert.IsType<RuntimePropertyInfo>(property);
Assert.Equal("Property2", property.Name);
},
property =>
{
Assert.IsType<RuntimePropertyInfo>(property);
Assert.Equal("Property3", property.Name);
},
property =>
{
Assert.IsType<RuntimePropertyInfo>(property);
Assert.Equal("Property4", property.Name);
},
property =>
{
Assert.IsType<RuntimePropertyInfo>(property);
Assert.Equal("BaseTypeProperty", property.Name);
},
property =>
{
Assert.IsType<RuntimePropertyInfo>(property);
Assert.Equal("ProtectedProperty", property.Name);
});
}
[Fact]
public void GetCustomAttributes_ReturnsAllAttributesOfType()
{
// Arrange
var typeInfo = typeof(TypeWithAttributes).GetTypeInfo();
var runtimeTypeInfo = new RuntimeTypeInfo(typeInfo);
var expected = typeInfo.GetCustomAttributes<TargetElementAttribute>();
// Act
var actual = runtimeTypeInfo.GetCustomAttributes<TargetElementAttribute>();
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void GetCustomAttributes_DoesNotInheritAttributesFromBaseType()
{
// Arrange
var typeInfo = typeof(SubType).GetTypeInfo();
var runtimeTypeInfo = new RuntimeTypeInfo(typeInfo);
// Act
var actual = runtimeTypeInfo.GetCustomAttributes<EditorBrowsableAttribute>();
// Assert
Assert.Empty(actual);
}
[Theory]
[InlineData(typeof(ITagHelper))]
[InlineData(typeof(TagHelper))]
[InlineData(typeof(ImplementsITagHelper))]
[InlineData(typeof(DerivesFromTagHelper))]
[InlineData(typeof(Fake.ImplementsRealITagHelper))]
public void IsTagHelper_ReturnsTrueIfTypeImplementsTagHelper(Type type)
{
// Arrange
var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo());
// Act
var result = runtimeTypeInfo.IsTagHelper;
// Assert
Assert.True(result);
}
[Theory]
[InlineData(typeof(string))]
[InlineData(typeof(SubType))]
[InlineData(typeof(Fake.DoesNotImplementRealITagHelper))]
public void IsTagHelper_ReturnsFalseIfTypeDoesNotImplementTagHelper(Type type)
{
// Arrange
var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo());
// Act
var result = runtimeTypeInfo.IsTagHelper;
// Assert
Assert.False(result);
}
[Theory]
[InlineData(typeof(Dictionary<string, string>), new[] { typeof(string), typeof(string) })]
[InlineData(typeof(DerivesFromDictionary), new[] { typeof(int), typeof(object) })]
[InlineData(typeof(ImplementsIDictionary), new[] { typeof(List<string>), typeof(string) })]
[InlineData(typeof(IDictionary<string, IDictionary<string, CustomType>>),
new[] { typeof(string), typeof(IDictionary<string, CustomType>) })]
[InlineData(typeof(Dictionary<,>), new Type[] { null, null })]
[InlineData(typeof(KnownKeyDictionary<>), new[] { typeof(string), null })]
[InlineData(typeof(KnownKeyDictionary<ImplementsIDictionary>),
new[] { typeof(string), typeof(ImplementsIDictionary) })]
public void GetGenericDictionaryParameterNames_ReturnsKeyAndValueParameterTypeNames(
Type type,
Type[] expectedTypes)
{
// Arrange
var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo());
var expected = expectedTypes.Select(t => t?.FullName);
// Act
var actual = runtimeTypeInfo.GetGenericDictionaryParameterNames();
// Assert
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(typeof(int))]
[InlineData(typeof(string))]
[InlineData(typeof(List<string>))]
[InlineData(typeof(IDictionary))]
[InlineData(typeof(ITagHelper))]
public void GetGenericDictionaryParameterNames_ReturnsNullIfTypeDoesNotImplementGenericDictionary(Type type)
{
// Arrange
var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo());
// Act
var actual = runtimeTypeInfo.GetGenericDictionaryParameterNames();
// Assert
Assert.Null(actual);
}
public class AbstractType
{
}
internal class InternalType
{
}
private class PrivateType
{
}
[EditorBrowsable(EditorBrowsableState.Never)]
private class BaseType
{
public string this[string key]
{
get { return ""; }
set { }
}
public string BaseTypeProperty { get; set; }
protected int ProtectedProperty { get; set; }
}
private class SubType : BaseType
{
public string Property1 { get; set; }
public int Property2 { get; }
public object Property3 { private get; set; }
private int Property4 { get; set; }
}
[TargetElement("test1")]
[TargetElement("test2")]
private class TypeWithAttributes
{
}
private class DerivesFromTagHelper : TagHelper
{
}
private class ImplementsITagHelper : ITagHelper
{
public int Order { get; } = 0;
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
throw new NotImplementedException();
}
}
private class DerivesFromDictionary : Dictionary<int, object>
{
}
private class ImplementsIDictionary : IDictionary<List<string>, string>, IReadOnlyList<int>
{
public int this[int index]
{
get
{
throw new NotImplementedException();
}
}
public string this[List<string> key]
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public int Count
{
get
{
throw new NotImplementedException();
}
}
public bool IsReadOnly
{
get
{
throw new NotImplementedException();
}
}
public ICollection<List<string>> Keys
{
get
{
throw new NotImplementedException();
}
}
public ICollection<string> Values
{
get
{
throw new NotImplementedException();
}
}
public void Add(KeyValuePair<List<string>, string> item)
{
throw new NotImplementedException();
}
public void Add(List<string> key, string value)
{
throw new NotImplementedException();
}
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(KeyValuePair<List<string>, string> item)
{
throw new NotImplementedException();
}
public bool ContainsKey(List<string> key)
{
throw new NotImplementedException();
}
public void CopyTo(KeyValuePair<List<string>, string>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public IEnumerator<KeyValuePair<List<string>, string>> GetEnumerator()
{
throw new NotImplementedException();
}
public bool Remove(KeyValuePair<List<string>, string> item)
{
throw new NotImplementedException();
}
public bool Remove(List<string> key)
{
throw new NotImplementedException();
}
public bool TryGetValue(List<string> key, out string value)
{
throw new NotImplementedException();
}
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
public class KnownKeyDictionary<TValue> : Dictionary<string, TValue>
{
}
private class CustomType
{
}
}
}

View File

@ -86,7 +86,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
tagHelperType,
GetTypeInfo(tagHelperType),
designTime: false,
errorSink: errorSink);
@ -175,7 +175,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
tagHelperType,
GetTypeInfo(tagHelperType),
designTime: false,
errorSink: errorSink);
@ -256,7 +256,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
tagHelperType,
GetTypeInfo(tagHelperType),
designTime: true,
errorSink: errorSink);
@ -493,7 +493,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
tagHelperType,
GetTypeInfo(tagHelperType),
designTime,
errorSink);
@ -695,7 +695,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
tagHelperType,
GetTypeInfo(tagHelperType),
designTime: false,
errorSink: errorSink);
@ -744,7 +744,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
tagHelperType,
GetTypeInfo(tagHelperType),
designTime: false,
errorSink: errorSink);
@ -781,7 +781,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(OverriddenAttributeTagHelper),
GetTypeInfo(typeof(OverriddenAttributeTagHelper)),
designTime: false,
errorSink: errorSink);
@ -815,7 +815,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(InheritedOverriddenAttributeTagHelper),
GetTypeInfo(typeof(InheritedOverriddenAttributeTagHelper)),
designTime: false,
errorSink: errorSink);
@ -849,7 +849,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(InheritedNotOverriddenAttributeTagHelper),
GetTypeInfo(typeof(InheritedNotOverriddenAttributeTagHelper)),
designTime: false,
errorSink: errorSink);
@ -870,7 +870,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
objectAssemblyName,
typeof(object),
GetTypeInfo(typeof(object)),
designTime: false,
errorSink: errorSink);
@ -902,7 +902,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(InheritedSingleAttributeTagHelper),
GetTypeInfo(typeof(InheritedSingleAttributeTagHelper)),
designTime: false,
errorSink: errorSink);
@ -930,7 +930,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(SingleAttributeTagHelper),
GetTypeInfo(typeof(SingleAttributeTagHelper)),
designTime: false,
errorSink: new ErrorSink());
@ -959,7 +959,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(MissingAccessorTagHelper),
GetTypeInfo(typeof(MissingAccessorTagHelper)),
designTime: false,
errorSink: errorSink);
@ -988,7 +988,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(NonPublicAccessorTagHelper),
GetTypeInfo(typeof(NonPublicAccessorTagHelper)),
designTime: false,
errorSink: errorSink);
@ -1020,7 +1020,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(NotBoundAttributeTagHelper),
GetTypeInfo(typeof(NotBoundAttributeTagHelper)),
designTime: false,
errorSink: errorSink);
@ -1039,7 +1039,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(DuplicateAttributeNameTagHelper),
GetTypeInfo(typeof(DuplicateAttributeNameTagHelper)),
designTime: false,
errorSink: errorSink);
@ -1086,7 +1086,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(MultiTagTagHelper),
GetTypeInfo(typeof(MultiTagTagHelper)),
designTime: false,
errorSink: errorSink);
@ -1118,7 +1118,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(InheritedMultiTagTagHelper),
GetTypeInfo(typeof(InheritedMultiTagTagHelper)),
designTime: false,
errorSink: errorSink);
@ -1148,7 +1148,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(DuplicateTagNameTagHelper),
GetTypeInfo(typeof(DuplicateTagNameTagHelper)),
designTime: false,
errorSink: errorSink);
@ -1178,7 +1178,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(OverrideNameTagHelper),
GetTypeInfo(typeof(OverrideNameTagHelper)),
designTime: false,
errorSink: errorSink);
@ -1366,7 +1366,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
type,
GetTypeInfo(type),
designTime: false,
errorSink: errorSink);
@ -1597,7 +1597,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
tagHelperType,
GetTypeInfo(tagHelperType),
designTime: false,
errorSink: errorSink);
@ -1651,7 +1651,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
descriptor,
typeof(MultiTagTagHelper),
GetTypeInfo(typeof(MultiTagTagHelper)),
errorSink);
// Assert
@ -1693,7 +1693,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
descriptor,
typeof(MultiTagTagHelper),
GetTypeInfo(typeof(MultiTagTagHelper)),
errorSink);
// Assert
@ -1739,7 +1739,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
descriptor,
typeof(MultiTagTagHelper),
GetTypeInfo(typeof(MultiTagTagHelper)),
errorSink);
// Assert
@ -1794,7 +1794,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
descriptor,
typeof(MultiTagTagHelper),
GetTypeInfo(typeof(MultiTagTagHelper)),
errorSink);
// Assert
@ -2101,6 +2101,9 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
};
}
private static ITypeInfo GetTypeInfo(Type tagHelperType) =>
new RuntimeTypeInfo(tagHelperType.GetTypeInfo());
[RestrictChildren("p")]
private class RestrictChildrenTagHelper
{

View File

@ -1575,7 +1575,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
return types?.Select(type => type.GetTypeInfo()) ?? Enumerable.Empty<TypeInfo>();
}
internal override bool IsTagHelper(TypeInfo typeInfo)
internal override bool IsTagHelper(ITypeInfo typeInfo)
{
return true;
}

View File

@ -62,7 +62,17 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
var types = tagHelperTypeResolver.Resolve("Foo", SourceLocation.Zero, new ErrorSink());
// Assert
Assert.Equal(ValidTestableTagHelpers, types);
Assert.Collection(types,
type =>
{
var typeInfo = Assert.IsType<RuntimeTypeInfo>(type);
Assert.Equal(typeof(Valid_PlainTagHelper).GetTypeInfo(), typeInfo.TypeInfo);
},
type =>
{
var typeInfo = Assert.IsType<RuntimeTypeInfo>(type);
Assert.Equal(typeof(Valid_InheritedTagHelper).GetTypeInfo(), typeInfo.TypeInfo);
});
}
[Fact]