Produce ITypeInfo \ IPropertyInfo using CodeAnalysis symbol tree

This commit is contained in:
Pranav K 2015-08-26 19:21:38 -07:00
parent 231e8a9cf4
commit 9a7be69a0d
45 changed files with 3117 additions and 539 deletions

View File

@ -17,6 +17,12 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.Runt
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.Test.Sources", "src\Microsoft.AspNet.Razor.Test.Sources\Microsoft.AspNet.Razor.Test.Sources.xproj", "{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.Runtime.Precompilation", "src\Microsoft.AspNet.Razor.Runtime.Precompilation\Microsoft.AspNet.Razor.Runtime.Precompilation.xproj", "{3B7ECD22-4C02-45CF-92E8-98C074DD9D0E}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.Runtime.Precompilation.Test", "test\Microsoft.AspNet.Razor.Runtime.Precompilation.Test\Microsoft.AspNet.Razor.Runtime.Precompilation.Test.xproj", "{C626444C-5A63-42BC-ACAE-DBB2CD3EDE25}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.Runtime.Precompilation.Files", "test\Microsoft.AspNet.Razor.Runtime.Precompilation.Files\Microsoft.AspNet.Razor.Runtime.Precompilation.Files.xproj", "{125D4CC1-5317-495F-88B8-472DFD9C1097}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -43,6 +49,18 @@ Global
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Release|Any CPU.Build.0 = Release|Any CPU
{3B7ECD22-4C02-45CF-92E8-98C074DD9D0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B7ECD22-4C02-45CF-92E8-98C074DD9D0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B7ECD22-4C02-45CF-92E8-98C074DD9D0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B7ECD22-4C02-45CF-92E8-98C074DD9D0E}.Release|Any CPU.Build.0 = Release|Any CPU
{C626444C-5A63-42BC-ACAE-DBB2CD3EDE25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C626444C-5A63-42BC-ACAE-DBB2CD3EDE25}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C626444C-5A63-42BC-ACAE-DBB2CD3EDE25}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C626444C-5A63-42BC-ACAE-DBB2CD3EDE25}.Release|Any CPU.Build.0 = Release|Any CPU
{125D4CC1-5317-495F-88B8-472DFD9C1097}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{125D4CC1-5317-495F-88B8-472DFD9C1097}.Debug|Any CPU.Build.0 = Debug|Any CPU
{125D4CC1-5317-495F-88B8-472DFD9C1097}.Release|Any CPU.ActiveCfg = Release|Any CPU
{125D4CC1-5317-495F-88B8-472DFD9C1097}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -53,5 +71,8 @@ Global
{D0196096-1B01-4133-AACE-1A10A0F7247C} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{0535998A-E32C-4D1A-80D1-0B15A513C471} = {92463391-81BE-462B-AC3C-78C6C760741F}
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{3B7ECD22-4C02-45CF-92E8-98C074DD9D0E} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{C626444C-5A63-42BC-ACAE-DBB2CD3EDE25} = {92463391-81BE-462B-AC3C-78C6C760741F}
{125D4CC1-5317-495F-88B8-472DFD9C1097} = {92463391-81BE-462B-AC3C-78C6C760741F}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,232 @@
// 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.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
/// <summary>
/// Utilities to work with creating <see cref="Attribute"/> instances from <see cref="AttributeData"/>.
/// </summary>
public static class CodeAnalysisAttributeUtilities
{
private static readonly ConcurrentDictionary<ConstructorInfo, Func<object[], Attribute>> _constructorCache =
new ConcurrentDictionary<ConstructorInfo, Func<object[], Attribute>>();
/// <summary>
/// Gets the sequence of <see cref="Attribute"/>s of type <typeparamref name="TAttribute"/>
/// that are declared on the specified <paramref name="symbol"/>.
/// </summary>
/// <typeparam name="TAttribute">The <see cref="Attribute"/> type.</typeparam>
/// <param name="symbol">The <see cref="ISymbol"/> to find attributes on.</param>
/// <param name="symbolLookup">The <see cref="CodeAnalysisSymbolLookupCache"/>.</param>
/// <returns></returns>
public static IEnumerable<TAttribute> GetCustomAttributes<TAttribute>(
[NotNull] ISymbol symbol,
[NotNull] CodeAnalysisSymbolLookupCache symbolLookup)
where TAttribute : Attribute
{
var attributes = symbol.GetAttributes();
if (attributes.Length > 0)
{
var attributeSymbol = symbolLookup.GetSymbol(typeof(TAttribute).GetTypeInfo());
return attributes
.Where(attribute => attribute.AttributeClass == attributeSymbol)
.Select(attribute => CreateAttribute<TAttribute>(attribute, symbolLookup))
.ToArray();
}
return Enumerable.Empty<TAttribute>();
}
private static TAttribute CreateAttribute<TAttribute>(
AttributeData attributeData,
CodeAnalysisSymbolLookupCache symbolLookup)
where TAttribute : Attribute
{
TAttribute attribute;
var matchInfo = MatchConstructor(typeof(TAttribute), attributeData.ConstructorArguments, symbolLookup);
Func<object[], Attribute> constructorDelegate;
if (!_constructorCache.TryGetValue(matchInfo.Constructor, out constructorDelegate))
{
constructorDelegate = MakeFastConstructorInvoker(matchInfo);
_constructorCache[matchInfo.Constructor] = constructorDelegate;
}
attribute = (TAttribute)constructorDelegate(matchInfo.ArgumentValues);
if (attributeData.NamedArguments.Length > 0)
{
var helpers = PropertyHelper.GetVisibleProperties(attribute);
foreach (var item in attributeData.NamedArguments)
{
var helper = helpers.FirstOrDefault(
propertyHelper => string.Equals(propertyHelper.Name, item.Key, StringComparison.Ordinal));
if (helper == null)
{
throw new InvalidOperationException(
Resources.FormatCodeAnalysis_PropertyNotFound(item.Key, attribute.GetType().FullName));
}
var propertyValue = ConvertTypedConstantValue(
helper.Property.PropertyType,
item.Value,
symbolLookup);
helper.SetValue(attribute, propertyValue);
}
}
return attribute;
}
private static Func<object[], Attribute> MakeFastConstructorInvoker(ConstructorMatchInfo matchInfo)
{
var argsParameter = Expression.Parameter(typeof(object[]), "args");
var parameters = new Expression[matchInfo.ArgumentValues.Length];
for (var index = 0; index < matchInfo.ArgumentValues.Length; index++)
{
parameters[index] =
Expression.Convert(
Expression.ArrayIndex(
argsParameter,
Expression.Constant(index)),
matchInfo.ArgumentValues[index].GetType());
}
// () => new TAttribute(args)
var lambda =
Expression.Lambda<Func<object[], Attribute>>(
Expression.New(
matchInfo.Constructor,
parameters),
argsParameter);
return lambda.Compile();
}
private static ConstructorMatchInfo MatchConstructor(
Type type,
ImmutableArray<TypedConstant> symbolConstructorArguments,
CodeAnalysisSymbolLookupCache symbolLookup)
{
var constructor = FindConstructor(type, symbolConstructorArguments, symbolLookup);
var constructorParmaters = constructor.GetParameters();
var arguments = new object[symbolConstructorArguments.Length];
for (var i = 0; i < arguments.Length; i++)
{
var value = ConvertTypedConstantValue(
constructorParmaters[i].ParameterType,
symbolConstructorArguments[i],
symbolLookup);
arguments[i] = value;
}
return new ConstructorMatchInfo
{
Constructor = constructor,
ArgumentValues = arguments
};
}
private static ConstructorInfo FindConstructor(
Type type,
ImmutableArray<TypedConstant> symbolConstructorArguments,
CodeAnalysisSymbolLookupCache symbolLookup)
{
var constructors = type.GetConstructors();
foreach (var constructor in constructors)
{
var runtimeParameters = constructor.GetParameters();
if (runtimeParameters.Length != symbolConstructorArguments.Length)
{
continue;
}
var parametersMatched = true;
for (var index = 0; index < runtimeParameters.Length; index++)
{
var runtimeParameter = runtimeParameters[index].ParameterType;
if (symbolConstructorArguments[index].Kind == TypedConstantKind.Array &&
runtimeParameter.IsArray)
{
var arrayType = (IArrayTypeSymbol)symbolConstructorArguments[index].Type;
if (symbolLookup.GetSymbol(runtimeParameter.GetElementType().GetTypeInfo()) !=
arrayType.ElementType)
{
parametersMatched = false;
break;
}
}
else
{
var parameterSymbol = symbolLookup.GetSymbol(runtimeParameter.GetTypeInfo());
if (symbolConstructorArguments[index].Type != parameterSymbol)
{
parametersMatched = false;
break;
}
}
}
if (parametersMatched)
{
return constructor;
}
}
throw new InvalidOperationException(Resources.FormatCodeAnalysisConstructorNotFound(type.FullName));
}
private static object ConvertTypedConstantValue(
Type type,
TypedConstant constructorArgument,
CodeAnalysisSymbolLookupCache symbolLookup)
{
switch (constructorArgument.Kind)
{
case TypedConstantKind.Enum:
return Enum.ToObject(type, constructorArgument.Value);
case TypedConstantKind.Primitive:
return constructorArgument.Value;
case TypedConstantKind.Type:
var typeSymbol = (INamedTypeSymbol)constructorArgument.Value;
var typeName = CodeAnalysisSymbolBasedTypeInfo.GetAssemblyQualifiedName(typeSymbol);
return Type.GetType(typeName);
case TypedConstantKind.Array:
Debug.Assert(type.IsArray && constructorArgument.Values != null);
var elementType = type.GetElementType();
var values = Array.CreateInstance(elementType, constructorArgument.Values.Length);
for (var index = 0; index < values.Length; index++)
{
values.SetValue(
ConvertTypedConstantValue(elementType, constructorArgument.Values[index], symbolLookup),
index);
}
return values;
default:
throw new NotSupportedException(
Resources.FormatCodeAnalysis_TypeConstantKindNotSupported(constructorArgument.Kind));
}
}
private struct ConstructorMatchInfo
{
public ConstructorInfo Constructor;
public object[] ArgumentValues;
}
}
}

View File

@ -0,0 +1,69 @@
// 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.Diagnostics;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.CodeAnalysis;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
/// <summary>
/// <see cref="IPropertyInfo"/> implementation using Code Analysis symbols.
/// </summary>
[DebuggerDisplay("{Name, PropertyType}")]
public class CodeAnalysisSymbolBasedPropertyInfo : IPropertyInfo
{
private readonly IPropertySymbol _propertySymbol;
private readonly CodeAnalysisSymbolLookupCache _symbolLookup;
/// <summary>
/// Initializes a new instance of <see cref="CodeAnalysisSymbolBasedPropertyInfo"/>.
/// </summary>
/// <param name="propertySymbol">The <see cref="IPropertySymbol"/>.</param>
/// <param name="symbolLookup">The <see cref="CodeAnalysisSymbolLookupCache"/>.</param>
public CodeAnalysisSymbolBasedPropertyInfo(
[NotNull] IPropertySymbol propertySymbol,
[NotNull] CodeAnalysisSymbolLookupCache symbolLookup)
{
_symbolLookup = symbolLookup;
_propertySymbol = propertySymbol;
PropertyType = new CodeAnalysisSymbolBasedTypeInfo(_propertySymbol.Type, _symbolLookup);
}
/// <inheritdoc />
public bool HasPublicGetter
{
get
{
return _propertySymbol.GetMethod != null &&
_propertySymbol.GetMethod.DeclaredAccessibility == Accessibility.Public;
}
}
/// <inheritdoc />
public bool HasPublicSetter
{
get
{
return _propertySymbol.SetMethod != null &&
_propertySymbol.SetMethod.DeclaredAccessibility == Accessibility.Public;
}
}
/// <inheritdoc />
public string Name => _propertySymbol.MetadataName;
/// <inheritdoc />
public ITypeInfo PropertyType { get; }
/// <inheritdoc />
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>()
where TAttribute : Attribute
{
return CodeAnalysisAttributeUtilities.GetCustomAttributes<TAttribute>(_propertySymbol, _symbolLookup);
}
}
}

View File

@ -0,0 +1,327 @@
// 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.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.CodeAnalysis;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
/// <summary>
/// <see cref="ITypeInfo"/> implementation using Code Analysis symbols.
/// </summary>
[DebuggerDisplay("{Name}")]
public class CodeAnalysisSymbolBasedTypeInfo : ITypeInfo
{
/// <summary>
/// The <see cref="System.Reflection.TypeInfo"/> for <see cref="IDictionary{TKey, TValue}"/>.
/// </summary>
public static readonly System.Reflection.TypeInfo OpenGenericDictionaryTypeInfo =
typeof(IDictionary<,>).GetTypeInfo();
private readonly CodeAnalysisSymbolLookupCache _symbolLookup;
private readonly ITypeSymbol _type;
private readonly ITypeSymbol _underlyingType;
private string _fullName;
private List<IPropertyInfo> _properties;
/// <summary>
/// Initializes a new instance of <see cref="CodeAnalysisSymbolBasedTypeInfo"/>.
/// </summary>
/// <param name="propertySymbol">The <see cref="IPropertySymbol"/>.</param>
/// <param name="symbolLookup">The <see cref="CodeAnalysisSymbolLookupCache"/>.</param>
public CodeAnalysisSymbolBasedTypeInfo(
[NotNull] ITypeSymbol type,
[NotNull] CodeAnalysisSymbolLookupCache symbolLookup)
{
_symbolLookup = symbolLookup;
_type = type;
_underlyingType = UnwrapArrayType(type);
}
/// <inheritdoc />
public string FullName
{
get
{
if (_fullName == null)
{
_fullName = GetFullName(_type);
}
return _fullName;
}
}
/// <summary>
/// The <see cref="ITypeSymbol"/> instance.
/// </summary>
public ITypeSymbol TypeSymbol => _type;
/// <inheritdoc />
public bool IsAbstract => _type.IsAbstract;
/// <inheritdoc />
public bool IsGenericType
{
get
{
return _type.Kind == SymbolKind.NamedType &&
((INamedTypeSymbol)_type).IsGenericType;
}
}
/// <inheritdoc />
public bool IsNested => _underlyingType.ContainingType != null;
/// <inheritdoc />
public bool IsPublic
{
get
{
return _type.DeclaredAccessibility == Accessibility.Public ||
_type.TypeKind == TypeKind.Array;
}
}
/// <inheritdoc />
public string Name
{
get
{
if (_type.TypeKind == TypeKind.Array)
{
return _underlyingType.MetadataName + "[]";
}
return _type.MetadataName;
}
}
/// <inheritdoc />
public IEnumerable<IPropertyInfo> Properties
{
get
{
if (_properties == null)
{
_properties = GetProperties(_type, _symbolLookup);
}
return _properties;
}
}
/// <inheritdoc />
public bool IsTagHelper
{
get
{
var interfaceSymbol = _symbolLookup.GetSymbol(typeof(ITagHelper).GetTypeInfo());
return _type.AllInterfaces.Any(implementedInterface => implementedInterface == interfaceSymbol);
}
}
/// <inheritdoc />
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>()
where TAttribute : Attribute
{
return CodeAnalysisAttributeUtilities.GetCustomAttributes<TAttribute>(_type, _symbolLookup);
}
/// <inheritdoc />
public ITypeInfo[] GetGenericDictionaryParameters()
{
var dictionarySymbol = _symbolLookup.GetSymbol(OpenGenericDictionaryTypeInfo);
INamedTypeSymbol dictionaryInterface;
if (_type.Kind == SymbolKind.NamedType &&
((INamedTypeSymbol)_type).ConstructedFrom == dictionarySymbol)
{
dictionaryInterface = (INamedTypeSymbol)_type;
}
else
{
dictionaryInterface = _type
.AllInterfaces
.FirstOrDefault(implementedInterface => implementedInterface.ConstructedFrom == dictionarySymbol);
}
if (dictionaryInterface != null)
{
Debug.Assert(dictionaryInterface.TypeArguments.Length == 2);
return new[]
{
new CodeAnalysisSymbolBasedTypeInfo(dictionaryInterface.TypeArguments[0], _symbolLookup),
new CodeAnalysisSymbolBasedTypeInfo(dictionaryInterface.TypeArguments[1], _symbolLookup),
};
}
return null;
}
/// <summary>
/// Gets the assembly qualified named of the specified <paramref name="symbol"/>.
/// </summary>
/// <param name="symbol">The <see cref="ITypeSymbol" /> to generate the name for.</param>
/// <returns>The assembly qualified name.</returns>
public static string GetAssemblyQualifiedName([NotNull] ITypeSymbol symbol)
{
var builder = new StringBuilder();
GetAssemblyQualifiedName(builder, symbol);
return builder.ToString();
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return Equals(obj as ITypeInfo);
}
/// <inheritdoc />
public bool Equals(ITypeInfo other)
{
if (other == null)
{
return false;
}
var otherSymbolBasedType = other as CodeAnalysisSymbolBasedTypeInfo;
if (otherSymbolBasedType != null)
{
return otherSymbolBasedType.TypeSymbol == TypeSymbol;
}
return string.Equals(FullName, other.FullName, StringComparison.Ordinal);
}
/// <inheritdoc />
public override int GetHashCode() => FullName.GetHashCode();
private static List<IPropertyInfo> GetProperties(
ITypeSymbol typeSymbol,
CodeAnalysisSymbolLookupCache symbolLookup)
{
var properties = new List<IPropertyInfo>();
var overridenProperties = new HashSet<IPropertySymbol>();
do
{
foreach (var member in typeSymbol.GetMembers().Where(m => m.Kind == SymbolKind.Property))
{
var propertySymbol = (IPropertySymbol)member;
if (!propertySymbol.IsIndexer && !overridenProperties.Contains(propertySymbol))
{
var propertyInfo = new CodeAnalysisSymbolBasedPropertyInfo(propertySymbol, symbolLookup);
properties.Add(propertyInfo);
}
if (propertySymbol.IsOverride)
{
overridenProperties.Add(propertySymbol.OverriddenProperty);
}
}
typeSymbol = typeSymbol.BaseType;
} while (typeSymbol != null);
return properties;
}
private static string GetFullName(ITypeSymbol typeSymbol)
{
var nameBuilder = new StringBuilder();
GetFullName(nameBuilder, typeSymbol);
return nameBuilder.Length == 0 ? null : nameBuilder.ToString();
}
private static void GetFullName(StringBuilder nameBuilder, ITypeSymbol typeSymbol)
{
if (typeSymbol.Kind == SymbolKind.TypeParameter)
{
return;
}
var insertIndex = nameBuilder.Length;
if (typeSymbol.TypeKind == TypeKind.Array)
{
var arrayType = (IArrayTypeSymbol)typeSymbol;
GetFullName(nameBuilder, arrayType.ElementType);
nameBuilder.Append("[]");
return;
}
nameBuilder.Append(typeSymbol.MetadataName);
if (typeSymbol.Kind == SymbolKind.NamedType)
{
var namedSymbol = (INamedTypeSymbol)typeSymbol;
// The symbol represents a generic but not open generic type
if (namedSymbol.IsGenericType &&
namedSymbol.ConstructedFrom != namedSymbol)
{
nameBuilder.Append('[');
foreach (var typeArgument in namedSymbol.TypeArguments)
{
nameBuilder.Append('[');
GetFullName(nameBuilder, typeArgument);
nameBuilder.Append("],");
}
// Removing trailing slash
Debug.Assert(nameBuilder.Length > 0 && nameBuilder[nameBuilder.Length - 1] == ',');
nameBuilder.Length--;
nameBuilder.Append("]");
}
}
var containingType = typeSymbol.ContainingType;
while (containingType != null)
{
nameBuilder
.Insert(insertIndex, '+')
.Insert(insertIndex, containingType.MetadataName);
containingType = containingType.ContainingType;
}
var containingNamespace = typeSymbol.ContainingNamespace;
while (!containingNamespace.IsGlobalNamespace)
{
nameBuilder
.Insert(insertIndex, '.')
.Insert(insertIndex, containingNamespace.MetadataName);
containingNamespace = containingNamespace.ContainingNamespace;
}
}
private static void GetAssemblyQualifiedName(StringBuilder builder, ITypeSymbol typeSymbol)
{
GetFullName(builder, typeSymbol);
typeSymbol = UnwrapArrayType(typeSymbol);
builder
.Append(", ")
.Append(typeSymbol.ContainingAssembly.Identity);
}
private static ITypeSymbol UnwrapArrayType(ITypeSymbol type)
{
if (type.TypeKind == TypeKind.Array)
{
return ((IArrayTypeSymbol)type).ElementType;
}
return type;
}
}
}

View File

@ -0,0 +1,62 @@
// 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 Microsoft.CodeAnalysis;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
/// <summary>
/// Caches mapping of <see cref="System.Reflection.TypeInfo"/> to <see cref="INamedTypeSymbol"/>.
/// </summary>
public class CodeAnalysisSymbolLookupCache
{
private readonly Dictionary<System.Reflection.TypeInfo, INamedTypeSymbol> _symbolLookup =
new Dictionary<System.Reflection.TypeInfo, INamedTypeSymbol>();
private readonly object _lookupLock = new object();
private readonly Compilation _compilation;
private INamedTypeSymbol _dictionarySymbol;
/// <summary>
/// Initialzes a new instance of <see cref="CodeAnalysisSymbolLookupCache"/>.
/// </summary>
/// <param name="compilation">The <see cref="Compilation"/> instance.</param>
public CodeAnalysisSymbolLookupCache([NotNull] Compilation compilation)
{
_compilation = compilation;
}
/// <summary>
/// Gets a <see cref="INamedTypeSymbol"/> that corresponds to <paramref name="typeInfo"/>.
/// </summary>
/// <param name="typeInfo">The <see cref="System.Reflection.TypeInfo"/> to lookup.</param>
/// <returns></returns>
public INamedTypeSymbol GetSymbol([NotNull] System.Reflection.TypeInfo typeInfo)
{
if (typeInfo == CodeAnalysisSymbolBasedTypeInfo.OpenGenericDictionaryTypeInfo)
{
if (_dictionarySymbol == null)
{
// Allow safe races
_dictionarySymbol = _compilation.GetTypeByMetadataName(
CodeAnalysisSymbolBasedTypeInfo.OpenGenericDictionaryTypeInfo.FullName);
}
return _dictionarySymbol;
}
lock (_lookupLock)
{
INamedTypeSymbol typeSymbol;
if (!_symbolLookup.TryGetValue(typeInfo, out typeSymbol))
{
typeSymbol = _compilation.GetTypeByMetadataName(typeInfo.FullName);
_symbolLookup[typeInfo] = typeSymbol;
}
return typeSymbol;
}
}
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>3b7ecd22-4c02-45cf-92e8-98c074dd9d0e</ProjectGuid>
<RootNamespace>Microsoft.AspNet.Razor.Runtime.Precompilation</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,110 @@
// 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.CodeAnalysis;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
/// <summary>
/// <see cref="TagHelperTypeResolver"/> used during Razor precompilation.
/// </summary>
public class PrecompilationTagHelperTypeResolver : TagHelperTypeResolver
{
private readonly object _assemblyLookupLock = new object();
private readonly Dictionary<string, IEnumerable<ITypeInfo>> _assemblyLookup
= new Dictionary<string, IEnumerable<ITypeInfo>>(StringComparer.Ordinal);
private readonly Compilation _compilation;
private readonly CodeAnalysisSymbolLookupCache _symbolLookup;
/// <summary>
/// Initializes a new instance of <see cref="PrecompilationTagHelperTypeResolver"/>.
/// </summary>
/// <param name="compilation">The <see cref="Compilation"/>.</param>
public PrecompilationTagHelperTypeResolver([NotNull] Compilation compilation)
{
_compilation = compilation;
_symbolLookup = new CodeAnalysisSymbolLookupCache(compilation);
}
/// <inheritdoc />
protected override IEnumerable<ITypeInfo> GetTopLevelExportedTypes([NotNull] AssemblyName assemblyName)
{
lock (_assemblyLookupLock)
{
IEnumerable<ITypeInfo> result;
if (!_assemblyLookup.TryGetValue(assemblyName.Name, out result))
{
result = GetExportedTypes(assemblyName.Name);
_assemblyLookup[assemblyName.Name] = result;
}
return result;
}
}
// Internal for unit testing
internal IEnumerable<ITypeInfo> GetExportedTypes(string assemblyName)
{
if (string.Equals(_compilation.AssemblyName, assemblyName, StringComparison.Ordinal))
{
return GetExportedTypes(_compilation.Assembly);
}
else
{
foreach (var reference in _compilation.References)
{
var compilationReference = reference as CompilationReference;
if (compilationReference != null &&
string.Equals(
compilationReference.Compilation.AssemblyName,
assemblyName,
StringComparison.Ordinal))
{
return GetExportedTypes(compilationReference.Compilation.Assembly);
}
var assemblySymbol = _compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (string.Equals(
assemblySymbol?.Identity.Name,
assemblyName,
StringComparison.Ordinal))
{
return GetExportedTypes(assemblySymbol);
}
}
}
throw new InvalidOperationException(
Resources.FormatCodeAnalysis_UnableToLoadAssemblyReference(assemblyName));
}
private List<ITypeInfo> GetExportedTypes(IAssemblySymbol assembly)
{
var exportedTypes = new List<ITypeInfo>();
GetExportedTypes(assembly.GlobalNamespace, exportedTypes);
return exportedTypes;
}
private void GetExportedTypes(INamespaceSymbol namespaceSymbol, List<ITypeInfo> exportedTypes)
{
foreach (var type in namespaceSymbol.GetTypeMembers())
{
if (type.TypeKind == TypeKind.Class &&
type.DeclaredAccessibility == Accessibility.Public)
{
exportedTypes.Add(new CodeAnalysisSymbolBasedTypeInfo(type, _symbolLookup));
}
}
foreach (var subNamespace in namespaceSymbol.GetNamespaceMembers())
{
GetExportedTypes(subNamespace, exportedTypes);
}
}
}
}

View File

@ -0,0 +1,6 @@
// 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNet.Razor.Runtime.Precompilation.Test")]

View File

@ -0,0 +1,94 @@
// <auto-generated />
namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNet.Razor.Runtime.Precompilation.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// Unable to find a suitable constructor for type '{0}'.
/// </summary>
internal static string CodeAnalysisConstructorNotFound
{
get { return GetString("CodeAnalysisConstructorNotFound"); }
}
/// <summary>
/// Unable to find a suitable constructor for type '{0}'.
/// </summary>
internal static string FormatCodeAnalysisConstructorNotFound(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("CodeAnalysisConstructorNotFound"), p0);
}
/// <summary>
/// Unable to find property {0} on type {1}.
/// </summary>
internal static string CodeAnalysis_PropertyNotFound
{
get { return GetString("CodeAnalysis_PropertyNotFound"); }
}
/// <summary>
/// Unable to find property {0} on type {1}.
/// </summary>
internal static string FormatCodeAnalysis_PropertyNotFound(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("CodeAnalysis_PropertyNotFound"), p0, p1);
}
/// <summary>
/// The type constant kind '{0}' is not supported.
/// </summary>
internal static string CodeAnalysis_TypeConstantKindNotSupported
{
get { return GetString("CodeAnalysis_TypeConstantKindNotSupported"); }
}
/// <summary>
/// The type constant kind '{0}' is not supported.
/// </summary>
internal static string FormatCodeAnalysis_TypeConstantKindNotSupported(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("CodeAnalysis_TypeConstantKindNotSupported"), p0);
}
/// <summary>
/// Unable to load assembly reference '{0}'.
/// </summary>
internal static string CodeAnalysis_UnableToLoadAssemblyReference
{
get { return GetString("CodeAnalysis_UnableToLoadAssemblyReference"); }
}
/// <summary>
/// Unable to load assembly reference '{0}'.
/// </summary>
internal static string FormatCodeAnalysis_UnableToLoadAssemblyReference(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("CodeAnalysis_UnableToLoadAssemblyReference"), p0);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CodeAnalysisConstructorNotFound" xml:space="preserve">
<value>Unable to find a suitable constructor for type '{0}'.</value>
</data>
<data name="CodeAnalysis_PropertyNotFound" xml:space="preserve">
<value>Unable to find property {0} on type {1}.</value>
</data>
<data name="CodeAnalysis_TypeConstantKindNotSupported" xml:space="preserve">
<value>The type constant kind '{0}' is not supported.</value>
</data>
<data name="CodeAnalysis_UnableToLoadAssemblyReference" xml:space="preserve">
<value>Unable to load assembly reference '{0}'.</value>
</data>
</root>

View File

@ -0,0 +1,40 @@
{
"description": "Supports tag helper resolution during precompilation.",
"version": "4.0.0-*",
"repository": {
"type": "git",
"url": "git://github.com/aspnet/razor"
},
"compilationOptions": {
"warningsAsErrors": true
},
"dependencies": {
"Microsoft.Framework.NotNullAttribute.Sources": {
"version": "1.0.0-*",
"type": "build"
},
"Microsoft.Framework.PropertyActivator.Sources": {
"version": "1.0.0-*",
"type": "build"
},
"Microsoft.Framework.PropertyHelper.Sources": {
"version": "1.0.0-*",
"type": "build"
},
"Microsoft.AspNet.Razor.Runtime": "4.0.0-*",
"Microsoft.Dnx.Compilation.CSharp.Abstractions": "1.0.0-*"
},
"frameworks": {
"dnx451": {
"frameworkAssemblies": {
"System.Runtime": "4.0.0.0"
}
},
"dnxcore50": {
"dependencies": {
"System.Collections.Concurrent": "4.0.11-*",
"System.Linq.Expressions": "4.0.11-*"
}
}
}
}

View File

@ -46,16 +46,16 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
bool IsTagHelper { get; }
/// <summary>
/// Gets the full names of the parameter types if the type implements
/// Gets the <see cref="ITypeInfo[]"/> for the <c>TKey</c> and <c>TValue</c> parameters of
/// <see cref="IDictionary{TKey, TValue}"/>.
/// </summary>
/// <returns>
/// The full type names (<seealso cref="System.Type.FullName"/>) of <c>TKey</c> and <c>TValue</c>
/// The <see cref="ITypeInfo"/> 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>.
/// For open generic types, <see cref="ITypeInfo" /> for generic type parameters is <c>null</c>.
/// </remarks>
string[] GetGenericDictionaryParameterNames();
ITypeInfo[] GetGenericDictionaryParameters();
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
@ -14,6 +15,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// </summary>
public class RuntimeTypeInfo : ITypeInfo
{
private static readonly Regex _fullNameSanitizer = new Regex(
@", [A-Za-z\.]+, Version=\d+\.\d+\.\d+\.\d+, Culture=neutral, PublicKeyToken=\w+",
RegexOptions.ExplicitCapture,
matchTimeout: TimeSpan.FromSeconds(10));
private static readonly TypeInfo TagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo();
private IEnumerable<IPropertyInfo> _properties;
@ -35,7 +41,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
public string Name => TypeInfo.Name;
/// <inheritdoc />
public string FullName => TypeInfo.FullName;
public string FullName => SanitizeFullName(TypeInfo.FullName);
/// <inheritdoc />
public bool IsAbstract => TypeInfo.IsAbstract;
@ -72,17 +78,53 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
TypeInfo.GetCustomAttributes<TAttribute>(inherit: false);
/// <inheritdoc />
public string[] GetGenericDictionaryParameterNames()
public ITypeInfo[] GetGenericDictionaryParameters()
{
return ClosedGenericMatcher.ExtractGenericInterface(
TypeInfo.AsType(),
typeof(IDictionary<,>))
?.GenericTypeArguments
.Select(type => type.FullName)
.Select(type => type.IsGenericParameter ? null : new RuntimeTypeInfo(type.GetTypeInfo()))
.ToArray();
}
/// <inheritdoc />
public override string ToString() => TypeInfo.ToString();
/// <inheritdoc />
public override bool Equals(object obj)
{
return Equals(obj as ITypeInfo);
}
/// <inheritdoc />
public bool Equals(ITypeInfo other)
{
if (other == null)
{
return false;
}
var otherRuntimeType = other as RuntimeTypeInfo;
if (otherRuntimeType != null)
{
return otherRuntimeType.TypeInfo == TypeInfo;
}
return string.Equals(FullName, other.FullName, StringComparison.Ordinal);
}
/// <inheritdoc />
public override int GetHashCode() => FullName.GetHashCode();
// Internal for unit testing
internal static string SanitizeFullName(string fullName)
{
// In CoreCLR, some types (such as System.String) are type forwarded from System.Runtime
// to mscorlib at runtime. Type names of generic type parameters includes the assembly qualified name;
// consequently the type name generated at precompilation differs from the one at runtime. We'll
// avoid dealing with these inconsistencies by removing assembly information from TypeInfo.FullName.
return _fullNameSanitizer.Replace(fullName, string.Empty);
}
}
}

View File

@ -5,6 +5,7 @@ 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;
@ -27,6 +28,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
private static readonly Regex HtmlCaseRegex =
new Regex("(?<!^)((?<=[a-zA-Z0-9])[A-Z][a-z])|((?<=[a-z])[A-Z])", RegexOptions.None);
private static readonly ITypeInfo StringTypeInfo = new RuntimeTypeInfo(typeof(string).GetTypeInfo());
// TODO: Investigate if we should cache TagHelperDescriptors for types:
// https://github.com/aspnet/Razor/issues/165
@ -573,8 +576,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
isInvalid = false;
var hasPublicSetter = property.HasPublicSetter;
var dictionaryTypeArguments = property.PropertyType.GetGenericDictionaryParameterNames();
if (dictionaryTypeArguments?[0] != typeof(string).FullName)
var dictionaryTypeArguments = property.PropertyType.GetGenericDictionaryParameters();
if (!StringTypeInfo.Equals(dictionaryTypeArguments?[0]))
{
if (attributeNameAttribute?.DictionaryAttributePrefix != null)
{
@ -641,7 +644,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
return ToAttributeDescriptor(
property,
attributeName: prefix,
typeName: dictionaryTypeArguments[1],
typeName: dictionaryTypeArguments[1].FullName,
isIndexer: true,
designTime: designTime);
}

View File

@ -0,0 +1,36 @@
// 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.AspNet.Razor.Runtime.Precompilation
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class AttributesWithArrayConstructorArgumentsAttribute : Attribute
{
public AttributesWithArrayConstructorArgumentsAttribute(string[] stringArgs, int[] intArgs)
{
StringArgs = stringArgs;
IntArgs = intArgs;
}
public AttributesWithArrayConstructorArgumentsAttribute(string[] stringArgs, Type[] typeArgs, int[] intArgs)
{
StringArgs = stringArgs;
TypeArgs = typeArgs;
IntArgs = intArgs;
}
public AttributesWithArrayConstructorArgumentsAttribute(int[] intArgs, Type[] typeArgs)
{
IntArgs = intArgs;
TypeArgs = typeArgs;
}
public string[] StringArgs { get; }
public Type[] TypeArgs { get; }
public int[] IntArgs { get; }
}
}

View File

@ -0,0 +1,16 @@
// 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.AspNet.Razor.Runtime.Precompilation
{
public class ArrayPropertiesAttribute : Attribute
{
public Type[] ArrayOfTypes { get; set; }
public int[] ArrayOfInts { get; set; }
public DayOfWeek[] Days { get; set; }
}
}

View File

@ -0,0 +1,28 @@
// 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.Precompilation
{
internal class InternalType
{
}
public class PublicType
{
private class NestedPrivateType
{
}
}
public class ContainerType
{
public class NestedType
{
}
}
public class GenericType<TVal>
{
}
}

View File

@ -0,0 +1,33 @@
// 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.Precompilation
{
public struct MyStruct
{
}
public enum MyEnum
{
}
public interface MyInterface
{
}
public abstract class MyAbstractClass
{
}
public partial class MyPartialClass
{
}
public partial class MyPartialClass
{
}
public sealed class MySealedClass
{
}
}

View File

@ -0,0 +1,12 @@
// 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.AspNet.Razor.Runtime.Precompilation
{
public class EnumPropertyAttribute : Attribute
{
public DayOfWeek DayOfWeek { get; set; }
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>125d4cc1-5317-495f-88b8-472dfd9c1097</ProjectGuid>
<RootNamespace>Microsoft.AspNet.Razor.Runtime.Precompilation.Files</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,24 @@
// 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.AspNet.Razor.Runtime.Precompilation
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class MultipleConstructorArgumentsAttribute : Attribute
{
public MultipleConstructorArgumentsAttribute(string firstArgument, string secondArgument, int thirdArgument)
{
FirstArgument = firstArgument;
SecondArgument = secondArgument;
ThirdArgument = thirdArgument;
}
public string FirstArgument { get; }
public string SecondArgument { get; }
public int ThirdArgument { get; }
}
}

View File

@ -0,0 +1,21 @@
// 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.Precompilation
{
public class TypeWithArrayProperties
{
public MyAbstractClass[] AbstractArray { get; set; }
public IDisposable[] DisposableArray { get; }
public ContainerType.NestedType[] NestedArrayType { get; }
internal InternalType[] InternalArray { get; set; }
public ICollection<IDictionary<string, IList<object[]>>>[] GenericArray { get; set; }
}
}

View File

@ -0,0 +1,52 @@
// 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.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Resources;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
[TargetElement("img", Attributes = AppendVersionAttributeName + "," + SrcAttributeName)]
[TargetElement("image", Attributes = SrcAttributeName)]
[EditorBrowsable(EditorBrowsableState.Never)]
[EnumProperty(DayOfWeek = DayOfWeek.Friday)]
[CustomValidation(typeof(Validator), "ValidationMethod", ErrorMessageResourceType = typeof(ResourceManager))]
[RestrictChildren("ol", "ul", "li", "dl", "dd")]
[ArrayProperties(
ArrayOfInts = new[] { 7, 8, 9 },
ArrayOfTypes = new[] { typeof(ITagHelper), typeof(Guid) },
Days = new[] { DayOfWeek.Saturday })]
public class TypeWithAttributes
{
private const string AppendVersionAttributeName = "asp-append-version";
private const string SrcAttributeName = "src";
[HtmlAttributeName(SrcAttributeName)]
[Required(AllowEmptyStrings = true)]
public string Src { get; set; }
[HtmlAttributeName(AppendVersionAttributeName, DictionaryAttributePrefix = "prefix")]
[HtmlAttributeNotBound]
public bool AppendVersion { get; set; }
[Required]
[EditorBrowsable(EditorBrowsableState.Advanced)]
[ArrayProperties(
ArrayOfInts = new int[0],
ArrayOfTypes = new[] { typeof(TypeWithAttributes) },
Days = new[] { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Wednesday, DayOfWeek.Sunday })]
public ViewContext ViewContext { get; set; }
[AttributesWithArrayConstructorArguments(new[] { 1, 2 }, new[] { typeof(Uri), typeof(IList<>) })]
[AttributesWithArrayConstructorArguments(new[] { "Hello", "world" }, new[] { typeof(List<Guid>) }, new int[0])]
[AttributesWithArrayConstructorArguments(
new[] { "world", "Hello" },
new[] { typeof(IDictionary<string, object>) },
new[] { 1 })]
protected IEditableObject HostingEnvironment { get; }
}
}

View File

@ -0,0 +1,23 @@
// 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.AspNet.Razor.Runtime.TagHelpers;
namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
public class TypeWithComplexPropertyFullNames
{
public int Property1 { get; set; }
public int[] Property2 { get; set; }
public List<long> Property3 { get; set; }
public List<Tuple<string, DateTimeOffset>> Property4 { get; }
public IDictionary<ILookup<string, TagHelper>, IList<Comparer<byte[]>>> Property5 { get; }
}
}

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;
using System.Collections.Generic;
namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
public class TypeWithDictionaryProperties
{
public IDictionary<string, string> RouteValues1 { get; set; } =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public Dictionary<int, string> RouteValues2 { get; set; } =
new Dictionary<int, string>();
public Dictionary<List<string>, float> RouteValues3 { get; set; } =
new Dictionary<List<string>, float>();
public IDictionary<string, ParserResults> CustomDictionary { get; set; } =
new Dictionary<string, ParserResults>();
public IDictionary NonGenericDictionary { get; set; } =
new Dictionary<string, string>();
public object ObjectType { get; set; } =
new Dictionary<string, string>();
}
}

View File

@ -0,0 +1,12 @@
// 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.Precompilation
{
[MultipleConstructorArguments(firstArgument: "First1", secondArgument: "Second1", thirdArgument: 31)]
[MultipleConstructorArguments(secondArgument: "Second2", firstArgument: "First2", thirdArgument: 32)]
[MultipleConstructorArguments(thirdArgument: 33, secondArgument: "Second3", firstArgument: "First3")]
public class TypeWithNamedAttributes
{
}
}

View File

@ -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.
namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
public class Animal
{
public virtual string Name { get; set; }
}
public class Mammal : Animal
{
public override string Name { get; set; }
}
public class Dog : Mammal
{
public override string Name { get; set; }
}
}

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.Runtime.Precompilation
{
public class ViewContext
{
}
}

View File

@ -0,0 +1,18 @@
{
"version": "1.0.0-*",
"dependencies": {
"Microsoft.AspNet.Razor.Runtime.Precompilation": "4.0.0-*"
},
"frameworks": {
"dnx451": {
"frameworkAssemblies": {
"System.ComponentModel.DataAnnotations": "4.0.0.0"
}
},
"dnxcore50": {
"dependencies": {
"System.ComponentModel.Annotations": "4.0.11-*"
}
}
}
}

View File

@ -0,0 +1,18 @@
// 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.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
namespace Microsoft.AspNet.Mvc.Razor.Precompilation
{
[RestrictChildren(Never)]
[CustomValidation(typeof(TypeDoesNotExist)]
[TargetElement("img"
public class TypeWithMalformedAttribute
{
}
}

View File

@ -0,0 +1,16 @@
// 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.AspNet.Mvc.Razor.Precompilation
{
public class TypeWithMalformedProperties
{
public DateTime DateTime { get }
public int DateTime2 => "Hello world";
public string CustomOrder { get; set; }
}
}

View File

@ -0,0 +1,10 @@
// 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.Mvc.Razor.Precompilation
{
public class TypeWithMissingReferences : ITagHelper
{
}
}

View File

@ -0,0 +1,86 @@
// 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.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Dnx.Compilation;
using Microsoft.Dnx.Compilation.CSharp;
using Microsoft.Dnx.Runtime.Infrastructure;
namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
public static class CompilationUtility
{
private static readonly ConcurrentDictionary<string, AssemblyMetadata> _metadataCache =
new ConcurrentDictionary<string, AssemblyMetadata>(StringComparer.Ordinal);
private static readonly Assembly ExecutingAssembly = typeof(CompilationUtility).GetTypeInfo().Assembly;
public static readonly string GeneratedAssemblyName = Path.GetRandomFileName() + "." + Path.GetRandomFileName();
public static Compilation GetCompilation(params string[] resourceFiles)
{
var assemblyVersion = ExecutingAssembly.GetName().Version;
var syntaxTrees = new List<SyntaxTree>
{
CSharpSyntaxTree.ParseText(
$"[assembly: {typeof(AssemblyVersionAttribute).FullName}(\"{assemblyVersion}\")]")
};
foreach (var resourceFile in resourceFiles)
{
var resourceContent = ReadManifestResource(resourceFile);
syntaxTrees.Add(CSharpSyntaxTree.ParseText(resourceContent));
}
var libraryExporter = (ILibraryExporter)CallContextServiceLocator
.Locator
.ServiceProvider
.GetService(typeof(ILibraryExporter));
var applicationName = ExecutingAssembly.GetName().Name;
var libraryExport = libraryExporter.GetExport(applicationName);
var references = new List<MetadataReference>();
var roslynReference = libraryExport.MetadataReferences[0] as IRoslynMetadataReference;
var compilationReference = roslynReference?.MetadataReference as CompilationReference;
if (compilationReference != null)
{
references.AddRange(compilationReference.Compilation.References);
references.Add(roslynReference.MetadataReference);
}
else
{
var export = libraryExporter.GetAllExports(applicationName);
foreach (var metadataReference in export.MetadataReferences)
{
var reference = metadataReference.ConvertMetadataReference(
fileReference => _metadataCache.GetOrAdd(
fileReference.Path,
_ => fileReference.CreateAssemblyMetadata()));
references.Add(reference);
}
}
return CSharpCompilation.Create(
GeneratedAssemblyName,
syntaxTrees,
references);
}
private static string ReadManifestResource(string path)
{
path = $"{ExecutingAssembly.GetName().Name}.{path}.cs";
using (var contentStream = ExecutingAssembly.GetManifestResourceStream(path))
{
using (var reader = new StreamReader(contentStream))
{
return reader.ReadToEnd();
}
}
}
}
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>c626444c-5a63-42bc-acae-dbb2cd3ede25</ProjectGuid>
<RootNamespace>Microsoft.AspNet.Razor.Runtime.Precompilation.Test</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,31 @@
// 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.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.AspNet.Razor.Test.Internal;
using Xunit;
namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
public class PrecompilationTagHelperDescriptorFactoryTest : TagHelperDescriptorFactoryTest
{
public override ITypeInfo GetTypeInfo(Type tagHelperType)
{
var paths = new[]
{
$"TagHelperDescriptorFactoryTagHelpers",
$"CommonTagHelpers",
};
var compilation = CompilationUtility.GetCompilation(paths);
var typeResolver = new PrecompilationTagHelperTypeResolver(compilation);
return Assert.Single(typeResolver.GetExportedTypes(CompilationUtility.GeneratedAssemblyName),
generatedType => string.Equals(generatedType.FullName, tagHelperType.FullName, StringComparison.Ordinal));
}
}
}

View File

@ -0,0 +1,554 @@
// 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.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
public class PrecompilationTagHelperTypeResolverTest
{
private static readonly TypeInfo TagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo();
[Theory]
[InlineData(typeof(TypeDerivingFromITagHelper))]
[InlineData(typeof(AttributeTargetingTagHelper))]
[InlineData(typeof(TagHelperInGlobalNamespace))]
public void TypesReturnedFromGetTopLevelExportedTypes_ReturnsTopLevelTypeInfo(Type expected)
{
// Arrange
var compilation = CompilationUtility.GetCompilation("TagHelperDescriptorFactoryTagHelpers");
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var actual = Assert.Single(exportedTypes, type => type.FullName == expected.FullName);
AssertEqual(expected, actual);
}
[Fact]
public void PropertyNamesForComplexPropertiesAreGeneratedCorrectly()
{
// Arrange
var expectedType = typeof(TypeWithComplexPropertyFullNames);
var compilation = CompilationUtility.GetCompilation(expectedType.Name);
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var actual = Assert.Single(exportedTypes);
AssertEqual(expectedType, actual);
}
[Fact]
public void PropertyNamesForArrayPropertiesAreGeneratedCorrectly()
{
// Arrange
var expectedType = typeof(TypeWithArrayProperties);
var compilation = CompilationUtility.GetCompilation(expectedType.Name);
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var actual = Assert.Single(exportedTypes);
AssertEqual(expectedType, actual);
}
[Fact]
public void GetTopLevelExportedTypes_DoesNotReturnNonPublicOrNestedTypes()
{
// Arrange
var compilation = CompilationUtility.GetCompilation("AssemblyWithNonPublicTypes");
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
Assert.Collection(exportedTypes,
typeInfo =>
{
AssertEqual(typeof(PublicType), typeInfo);
},
typeInfo =>
{
AssertEqual(typeof(ContainerType), typeInfo);
},
typeInfo =>
{
AssertEqual(typeof(GenericType<>), typeInfo);
});
}
[Fact]
public void GetTopLevelExportedTypes_DoesNotReturnValueTypesOrInterfaces()
{
// Arrange
var compilation = CompilationUtility.GetCompilation("AssemblyWithNonTypes");
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
Assert.Collection(exportedTypes,
typeInfo =>
{
AssertEqual(typeof(MyAbstractClass), typeInfo);
},
typeInfo =>
{
AssertEqual(typeof(MyPartialClass), typeInfo);
},
typeInfo =>
{
AssertEqual(typeof(MySealedClass), typeInfo);
});
}
[Fact]
public void GetExportedTypes_PopulatesAttributes()
{
// Arrange
var expected = typeof(TypeWithAttributes).GetTypeInfo();
var compilation = CompilationUtility.GetCompilation(expected.Name);
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var actual = Assert.Single(exportedTypes);
AssertEqual(expected.AsType(), actual);
AssertAttributes<TargetElementAttribute>(
expected,
actual,
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(expectedAttribute.Tag, actualAttribute.Tag);
Assert.Equal(expectedAttribute.Attributes, actualAttribute.Attributes);
});
// Verify if enum in attribute constructors works.
AssertAttributes<EditorBrowsableAttribute>(
expected,
actual,
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(expectedAttribute.State, actualAttribute.State);
});
// Verify if enum in attribute property works.
AssertAttributes<EnumPropertyAttribute>(
expected,
actual,
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(expectedAttribute.DayOfWeek, actualAttribute.DayOfWeek);
});
// Verify if Type in attribute constructor and property works.
AssertAttributes<CustomValidationAttribute>(
expected,
actual,
(expectedAttribute, actualAttribute) =>
{
Assert.Same(expectedAttribute.ValidatorType, actualAttribute.ValidatorType);
Assert.Equal(expectedAttribute.Method, actualAttribute.Method);
Assert.Same(
expectedAttribute.ErrorMessageResourceType,
actualAttribute.ErrorMessageResourceType);
});
// Verify if array arguments work in constructor.
AssertAttributes<RestrictChildrenAttribute>(
expected,
actual,
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(
expectedAttribute.ChildTagNames,
actualAttribute.ChildTagNames);
});
// Complex array bindings
AssertAttributes<ArrayPropertiesAttribute>(
expected,
actual,
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(expectedAttribute.ArrayOfTypes, actualAttribute.ArrayOfTypes);
Assert.Equal(expectedAttribute.ArrayOfInts, actualAttribute.ArrayOfInts);
Assert.Equal(expectedAttribute.Days, actualAttribute.Days);
});
var expectedProperties = expected.DeclaredProperties;
Assert.Collection(actual.Properties,
property =>
{
Assert.Equal(nameof(TypeWithAttributes.Src), property.Name);
var expectedProperty = Assert.Single(expectedProperties, p => p.Name == property.Name);
AssertAttributes<HtmlAttributeNameAttribute>(
expectedProperty,
property,
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(expectedAttribute.Name, actualAttribute.Name);
Assert.Equal(expectedAttribute.DictionaryAttributePrefix, actualAttribute.DictionaryAttributePrefix);
});
// Verify boolean values bind.
AssertAttributes<RequiredAttribute>(
expectedProperty,
property,
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(expectedAttribute.AllowEmptyStrings, actualAttribute.AllowEmptyStrings);
});
},
property =>
{
Assert.Equal(nameof(TypeWithAttributes.AppendVersion), property.Name);
var expectedProperty = Assert.Single(expectedProperties, p => p.Name == property.Name);
AssertAttributes<HtmlAttributeNameAttribute>(
expectedProperty,
property,
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(expectedAttribute.Name, actualAttribute.Name);
Assert.Equal(expectedAttribute.DictionaryAttributePrefix, actualAttribute.DictionaryAttributePrefix);
});
// Attribute without constructor arguments or properties.
Assert.Single(expectedProperty.GetCustomAttributes<HtmlAttributeNotBoundAttribute>());
Assert.Single(property.GetCustomAttributes<HtmlAttributeNotBoundAttribute>());
},
property =>
{
Assert.Equal(nameof(TypeWithAttributes.ViewContext), property.Name);
var expectedProperty = Assert.Single(expectedProperties, p => p.Name == property.Name);
AssertAttributes<EditorBrowsableAttribute>(
expectedProperty,
property,
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(expectedAttribute.State, actualAttribute.State);
});
// Complex array bindings in properties
AssertAttributes<ArrayPropertiesAttribute>(
expected,
actual,
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(expectedAttribute.ArrayOfTypes, actualAttribute.ArrayOfTypes);
Assert.Equal(expectedAttribute.ArrayOfInts, actualAttribute.ArrayOfInts);
Assert.Equal(expectedAttribute.Days, actualAttribute.Days);
});
},
property =>
{
Assert.Equal("HostingEnvironment", property.Name);
Assert.Single(expectedProperties, p => p.Name == property.Name);
// Complex array bindings in constructor arguments
AssertAttributes<AttributesWithArrayConstructorArgumentsAttribute>(
expected,
actual,
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(expectedAttribute.StringArgs, actualAttribute.StringArgs);
Assert.Equal(expectedAttribute.IntArgs, actualAttribute.IntArgs);
Assert.Equal(expectedAttribute.TypeArgs, actualAttribute.TypeArgs);
});
});
}
[Fact]
public void GetExportedTypes_WithDerivedAttributes()
{
// Arrange
var expected = typeof(DerivedTagHelper);
var compilation = CompilationUtility.GetCompilation("TagHelperDescriptorFactoryTagHelpers");
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var actual = Assert.Single(exportedTypes, type => type.Name == expected.Name);
AssertEqual(expected, actual);
var expectedProperties = expected.GetProperties();
AssertAttributes<BaseAttribute>(
expectedProperties.First(p => p.Name == nameof(DerivedTagHelper.DerivedProperty)),
actual.Properties.First(p => p.Name == nameof(DerivedTagHelper.DerivedProperty)),
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(expectedAttribute.BaseProperty, actualAttribute.BaseProperty);
});
AssertAttributes<HtmlAttributeNameAttribute>(
expectedProperties.First(p => p.Name == nameof(DerivedTagHelper.NewProperty) &&
p.PropertyType == typeof(Type)),
actual.Properties.First(p => p.Name == nameof(DerivedTagHelper.NewProperty) &&
p.PropertyType.Name == typeof(Type).Name),
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(expectedAttribute.Name, actualAttribute.Name);
Assert.Equal(
expectedAttribute.DictionaryAttributePrefix,
actualAttribute.DictionaryAttributePrefix);
});
var expectedVirtualProperty = expectedProperties.First(
p => p.Name == nameof(DerivedTagHelper.VirtualProperty));
var actualVirtualProperty = actual.Properties.First(
p => p.Name == nameof(DerivedTagHelper.VirtualProperty));
Assert.Empty(expectedVirtualProperty.GetCustomAttributes<HtmlAttributeNotBoundAttribute>());
Assert.Empty(actualVirtualProperty.GetCustomAttributes<HtmlAttributeNotBoundAttribute>());
}
[Fact]
public void GetExportedTypes_CorrectlyIdentifiesIfTypeDerivesFromDictionary()
{
// Arrange
var compilation = CompilationUtility.GetCompilation(nameof(TypeWithDictionaryProperties));
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var exportedType = Assert.Single(exportedTypes);
AssertEqual(typeof(TypeWithDictionaryProperties), exportedType);
}
[Fact]
public void GetExportedTypes_WorksCorrectlyForOverridenProperties()
{
// Arrange
var compilation = CompilationUtility.GetCompilation("TypesWithInheritedProperties");
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
Assert.Collection(exportedTypes,
typeInfo =>
{
AssertEqual(typeof(Animal), typeInfo);
},
typeInfo =>
{
AssertEqual(typeof(Mammal), typeInfo);
},
typeInfo =>
{
AssertEqual(typeof(Dog), typeInfo);
});
}
[Fact]
public void GetExportedTypes_WorksIfAttributeConstructorArgumentsAreOutOfOrder()
{
// Arrange
var expectedType = typeof(TypeWithNamedAttributes);
var compilation = CompilationUtility.GetCompilation(expectedType.Name);
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var exportedType = Assert.Single(exportedTypes);
AssertEqual(expectedType, exportedType);
var attributes = exportedType.GetCustomAttributes<MultipleConstructorArgumentsAttribute>();
AssertAttributes<MultipleConstructorArgumentsAttribute>(
expectedType.GetTypeInfo(),
exportedType,
(expectedAttribute, actualAttribute) =>
{
Assert.Equal(expectedAttribute.FirstArgument, actualAttribute.FirstArgument);
Assert.Equal(expectedAttribute.SecondArgument, actualAttribute.SecondArgument);
Assert.Equal(expectedAttribute.ThirdArgument, actualAttribute.ThirdArgument);
});
}
[Fact]
public void GetExportedTypes_WorksIfAttributesAreMalformedErrors()
{
// Arrange
var compilation = CompilationUtility.GetCompilation("BadFiles.TypeWithMalformedAttribute");
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var actual = Assert.Single(exportedTypes);
var targetElementAttribute = Assert.Single(actual.GetCustomAttributes<TargetElementAttribute>());
Assert.Equal("img", targetElementAttribute.Tag);
Assert.Null(targetElementAttribute.Attributes);
Assert.Empty(actual.GetCustomAttributes<EditorBrowsableAttribute>());
var ex = Assert.Throws<InvalidOperationException>(
() => actual.GetCustomAttributes<CustomValidationAttribute>());
Assert.Equal($"Unable to find a suitable constructor for type '{typeof(CustomValidationAttribute).FullName}'.",
ex.Message);
ex = Assert.Throws<InvalidOperationException>(
() => actual.GetCustomAttributes<RestrictChildrenAttribute>());
Assert.Equal($"Unable to find a suitable constructor for type '{typeof(RestrictChildrenAttribute).FullName}'.",
ex.Message);
}
[Fact]
public void GetExportedTypes_WorksForPropertiesWithErrors()
{
// Arrange
var compilation = CompilationUtility.GetCompilation("BadFiles.TypeWithMalformedProperties");
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var actual = Assert.Single(exportedTypes);
Assert.Collection(actual.Properties,
property =>
{
Assert.Equal("DateTime", property.Name);
Assert.Equal(typeof(DateTime).Name, property.PropertyType.Name);
Assert.True(property.HasPublicGetter);
Assert.False(property.HasPublicSetter);
},
property =>
{
Assert.Equal("DateTime2", property.Name);
Assert.Equal(typeof(int).Name, property.PropertyType.Name);
Assert.True(property.HasPublicGetter);
Assert.False(property.HasPublicSetter);
},
property =>
{
Assert.Equal("CustomOrder", property.Name);
Assert.Equal(typeof(string).Name, property.PropertyType.Name);
Assert.True(property.HasPublicGetter);
Assert.True(property.HasPublicSetter);
});
}
[Fact]
public void GetExportedTypes_WorksForTypesWithErrors()
{
// Arrange
var compilation = CompilationUtility.GetCompilation("BadFiles.TypeWithMissingReferences");
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var type = Assert.Single(exportedTypes);
Assert.False(type.IsTagHelper);
}
private static void AssertAttributes<TAttribute>(
MemberInfo expected,
IMemberInfo actual,
Action<TAttribute, TAttribute> assertItem)
where TAttribute : Attribute
{
Assert.Equal(
expected.GetCustomAttributes<TAttribute>(),
actual.GetCustomAttributes<TAttribute>(),
new DelegateAssertion<TAttribute>(assertItem));
}
private static void AssertEqual(Type expected, ITypeInfo actual)
{
AssertEqual(new RuntimeTypeInfo(expected.GetTypeInfo()), actual, assertProperties: true);
}
private static void AssertEqual(ITypeInfo expected, ITypeInfo actual, bool assertProperties)
{
var runtimeType = Assert.IsType<RuntimeTypeInfo>(expected);
var actualFullName = actual.FullName.Replace(
CompilationUtility.GeneratedAssemblyName,
runtimeType.TypeInfo.Assembly.GetName().Name);
Assert.Equal(expected.Name, actual.Name);
if (expected.FullName != actualFullName)
{
Console.WriteLine("!!!");
Console.WriteLine(runtimeType.TypeInfo.FullName);
Console.WriteLine(actualFullName);
}
Assert.Equal(expected.FullName, actualFullName);
Assert.Equal(expected.IsPublic, actual.IsPublic);
Assert.Equal(expected.IsAbstract, actual.IsAbstract);
Assert.Equal(expected.IsGenericType, actual.IsGenericType);
Assert.Equal(expected.IsTagHelper, actual.IsTagHelper);
Assert.Equal(
expected.GetGenericDictionaryParameters(),
actual.GetGenericDictionaryParameters(),
new DelegateAssertion<ITypeInfo>((x, y) => AssertEqual(x, y, assertProperties: false)));
if (assertProperties)
{
Assert.Equal(
expected.Properties.OrderBy(p => p.Name),
actual.Properties.OrderBy(p => p.Name),
new DelegateAssertion<IPropertyInfo>((x, y) => AssertEqual(x, y)));
}
}
private static void AssertEqual(IPropertyInfo expected, IPropertyInfo actual)
{
Assert.Equal(expected.Name, actual.Name);
Assert.Equal(expected.HasPublicGetter, actual.HasPublicGetter);
Assert.Equal(expected.HasPublicSetter, actual.HasPublicSetter);
AssertEqual(expected.PropertyType, actual.PropertyType, assertProperties: false);
}
private class DelegateAssertion<T> : IEqualityComparer<T>
{
private readonly Action<T, T> _assert;
public DelegateAssertion(Action<T, T> assert)
{
_assert = assert;
}
public bool Equals(T x, T y)
{
_assert(x, y);
return true;
}
public int GetHashCode(T obj) => 0;
}
}
}

View File

@ -0,0 +1,33 @@
{
"version": "1.0.0",
"dependencies": {
"Microsoft.AspNet.Razor.Runtime.Precompilation.Files": "1.0.0-*",
"Microsoft.AspNet.Razor.Runtime.Test": "1.0.0-*",
"Microsoft.AspNet.Razor.Test.Sources": {
"version": "4.0.0-*",
"type": "build"
},
"Microsoft.Dnx.Compilation.CSharp.Common": "1.0.0-*",
"Microsoft.Framework.HashCodeCombiner.Sources": {
"type": "build",
"version": "1.0.0-*"
}
},
"resource": [
"../Microsoft.AspNet.Razor.Runtime.Precompilation.Files/*.cs",
"../Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TestTagHelpers/*.cs",
"BadFiles/*.cs"
],
"commands": {
"test": "xunit.runner.aspnet"
},
"frameworks": {
"dnx451": {
"dependencies": {
"Moq": "4.2.1312.1622"
}
},
"dnxcore50": {
}
}
}

View File

@ -0,0 +1,123 @@
// 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.AspNet.Razor.TagHelpers;
using Microsoft.AspNet.Razor.Test.Internal;
using Xunit;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
public class RuntimeTagHelperDescriptorFactoryTest : TagHelperDescriptorFactoryTest
{
public override ITypeInfo GetTypeInfo(Type tagHelperType) =>
new RuntimeTypeInfo(tagHelperType.GetTypeInfo());
[Fact]
public void CreateDescriptors_BuildsDescriptorsFromSimpleTypes()
{
// Arrange
var errorSink = new ErrorSink();
var objectAssemblyName = typeof(object).GetTypeInfo().Assembly.GetName().Name;
var expectedDescriptor =
CreateTagHelperDescriptor("object", "System.Object", objectAssemblyName);
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
objectAssemblyName,
GetTypeInfo(typeof(object)),
designTime: false,
errorSink: errorSink);
// Assert
Assert.Empty(errorSink.Errors);
var descriptor = Assert.Single(descriptors);
Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default);
}
// TagHelperDesignTimeDescriptors are not created in CoreCLR.
#if !DNXCORE50
public static TheoryData OutputElementHintData
{
get
{
// tagHelperType, expectedDescriptors
return new TheoryData<Type, TagHelperDescriptor[]>
{
{
typeof(OutputElementHintTagHelper),
new[]
{
new TagHelperDescriptor
{
TagName = "output-element-hint",
TypeName = typeof(OutputElementHintTagHelper).FullName,
AssemblyName = AssemblyName,
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
{
OutputElementHint = "strong"
}
}
}
},
{
typeof(MulitpleDescriptorTagHelperWithOutputElementHint),
new[]
{
new TagHelperDescriptor
{
TagName = "a",
TypeName = typeof(MulitpleDescriptorTagHelperWithOutputElementHint).FullName,
AssemblyName = AssemblyName,
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
{
OutputElementHint = "div"
}
},
new TagHelperDescriptor
{
TagName = "p",
TypeName = typeof(MulitpleDescriptorTagHelperWithOutputElementHint).FullName,
AssemblyName = AssemblyName,
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
{
OutputElementHint = "div"
}
}
}
}
};
}
}
[Theory]
[MemberData(nameof(OutputElementHintData))]
public void CreateDescriptors_CreatesDesignTimeDescriptorsWithOutputElementHint(
Type tagHelperType,
TagHelperDescriptor[] expectedDescriptors)
{
// Arrange
var errorSink = new ErrorSink();
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
GetTypeInfo(tagHelperType),
designTime: true,
errorSink: errorSink);
// Assert
Assert.Empty(errorSink.Errors);
// We don't care about order. Mono returns reflected attributes differently so we need to ensure order
// doesn't matter by sorting.
descriptors = descriptors.OrderBy(descriptor => descriptor.TagName);
Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default);
}
#endif
}
}

View File

@ -5,39 +5,64 @@ 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
{
private static readonly string StringFullName = typeof(string).FullName;
private static readonly string CollectionsNamespace = typeof(IDictionary<,>).Namespace;
public static TheoryData RuntimeTypeInfo_ReturnsMetadataOfAdaptingTypeData =>
new TheoryData<Type, string>
{
{ typeof(int), typeof(int).FullName },
{ typeof(string), typeof(string).FullName },
{ typeof(Tuple<>), typeof(Tuple<>).FullName },
{ typeof(Tuple<,>), typeof(Tuple<,>).FullName },
{
typeof(IDictionary<string, string>),
$"{typeof(IDictionary<,>).FullName}[[{StringFullName}],[{StringFullName}]]"
},
{
typeof(IDictionary<string, IDictionary<string, CustomType>>),
$"{typeof(IDictionary<,>).FullName}[[{StringFullName}],[{typeof(IDictionary<,>).FullName}" +
$"[[{StringFullName}],[{typeof(CustomType).FullName}]]]]"
},
{
typeof(IList<IReadOnlyList<IDictionary<List<string>, Tuple<CustomType, object[]>>>>),
$"{typeof(IList<>).FullName}[[{typeof(IReadOnlyList<>).FullName}[[{typeof(IDictionary<,>).FullName}[[" +
$"{typeof(List<>).FullName}[[{StringFullName}]]],[{typeof(Tuple<,>).FullName}[[{typeof(CustomType).FullName}]," +
$"[{typeof(object).FullName}[]]]]]]]]]"
},
{ typeof(AbstractType), typeof(AbstractType).FullName },
{ typeof(PrivateType), typeof(PrivateType).FullName },
{ typeof(KnownKeyDictionary<>), typeof(KnownKeyDictionary<>).FullName },
{
typeof(KnownKeyDictionary<string>),
$"{typeof(KnownKeyDictionary<>).Namespace}" +
$".RuntimeTypeInfoTest+KnownKeyDictionary`1[[{StringFullName}]]"
}
};
[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)
[MemberData(nameof(RuntimeTypeInfo_ReturnsMetadataOfAdaptingTypeData))]
public void RuntimeTypeInfo_ReturnsMetadataOfAdaptingType(Type type, string expectedFullName)
{
// 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);
Assert.Same(typeInfo, runtimeTypeInfo.TypeInfo);
Assert.Equal(typeInfo.Name, runtimeTypeInfo.Name);
Assert.Equal(expectedFullName, runtimeTypeInfo.FullName);
Assert.Equal(typeInfo.IsAbstract, runtimeTypeInfo.IsAbstract);
Assert.Equal(typeInfo.IsGenericType, runtimeTypeInfo.IsGenericType);
Assert.Equal(typeInfo.IsPublic, runtimeTypeInfo.IsPublic);
}
[Fact]
@ -150,23 +175,62 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
[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(
public void GetGenericDictionaryParameters_ReturnsKeyAndValueParameterTypeNames(
Type type,
Type[] expectedTypes)
{
// Arrange
var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo());
var expected = expectedTypes.Select(t => t?.FullName);
// Act
var actual = runtimeTypeInfo.GetGenericDictionaryParameterNames();
var actual = runtimeTypeInfo.GetGenericDictionaryParameters();
// Assert
Assert.Equal(expected, actual);
Assert.Collection(actual,
keyType =>
{
Assert.Equal(new RuntimeTypeInfo(expectedTypes[0].GetTypeInfo()), keyType);
},
valueType =>
{
Assert.Equal(new RuntimeTypeInfo(expectedTypes[1].GetTypeInfo()), valueType);
});
}
[Fact]
public void GetGenericDictionaryParameters_WorksWhenValueParameterIsOpen()
{
// Arrange
var runtimeTypeInfo = new RuntimeTypeInfo(typeof(KnownKeyDictionary<>).GetTypeInfo());
// Act
var actual = runtimeTypeInfo.GetGenericDictionaryParameters();
// Assert
Assert.Collection(actual,
keyType =>
{
Assert.Equal(new RuntimeTypeInfo(typeof(string).GetTypeInfo()), keyType);
},
valueType =>
{
Assert.Null(valueType);
});
}
[Fact]
public void GetGenericDictionaryParameters_WorksWhenKeyAndValueParametersAreOpen()
{
// Arrange
var runtimeTypeInfo = new RuntimeTypeInfo(typeof(Dictionary<,>).GetTypeInfo());
// Act
var actual = runtimeTypeInfo.GetGenericDictionaryParameters();
// Assert
Assert.Equal(new RuntimeTypeInfo[] { null, null }, actual);
}
[Theory]
@ -181,12 +245,104 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo());
// Act
var actual = runtimeTypeInfo.GetGenericDictionaryParameterNames();
var actual = runtimeTypeInfo.GetGenericDictionaryParameters();
// Assert
Assert.Null(actual);
}
[Theory]
[InlineData(typeof(string))]
[InlineData(typeof(IDictionary<,>))]
[InlineData(typeof(ITagHelper))]
[InlineData(typeof(TagHelper))]
public void Equals_ReturnsTrueIfTypeInfosAreIdentical(Type type)
{
// Arrange
var typeA = new RuntimeTypeInfo(type.GetTypeInfo());
var typeB = new RuntimeTypeInfo(type.GetTypeInfo());
// Act
var equals = typeA.Equals(typeB);
var hashCodeA = typeA.GetHashCode();
var hashCodeB = typeB.GetHashCode();
// Assert
Assert.True(equals);
Assert.Equal(hashCodeA, hashCodeB);
}
public static TheoryData Equals_ReturnsTrueIfTypeFullNamesAreIdenticalData =>
new TheoryData<Type, string>
{
{ typeof(string), typeof(string).FullName },
{ typeof(ITagHelper), typeof(ITagHelper).FullName },
{ typeof(TagHelper), typeof(TagHelper).FullName },
{ typeof(TagHelper), typeof(TagHelper).FullName },
{ typeof(IDictionary<,>), typeof(IDictionary<,>).FullName },
{
typeof(IDictionary<string,string>),
RuntimeTypeInfo.SanitizeFullName(typeof(IDictionary<string, string>).FullName)
},
};
[Theory]
[MemberData(nameof(Equals_ReturnsTrueIfTypeFullNamesAreIdenticalData))]
public void Equals_ReturnsTrueIfTypeInfoNamesAreIdentical(Type type, string fullName)
{
// Arrange
var typeA = new RuntimeTypeInfo(type.GetTypeInfo());
var typeB = new TestTypeInfo
{
FullName = fullName
};
// Act
var equals = typeA.Equals(typeB);
// Assert
Assert.True(equals);
}
[Theory]
[InlineData(typeof(string), typeof(object))]
[InlineData(typeof(IDictionary<,>), typeof(IDictionary<string, string>))]
[InlineData(typeof(KnownKeyDictionary<string>), typeof(IDictionary<string, string>))]
[InlineData(typeof(ITagHelper), typeof(TagHelper))]
public void Equals_ReturnsFalseIfTypeInfosAreDifferent(Type typeA, Type typeB)
{
// Arrange
var typeAInfo = new RuntimeTypeInfo(typeA.GetTypeInfo());
var typeBInfo = new RuntimeTypeInfo(typeB.GetTypeInfo());
// Act
var equals = typeAInfo.Equals(typeBInfo);
var hashCodeA = typeAInfo.GetHashCode();
var hashCodeB = typeBInfo.GetHashCode();
// Assert
Assert.False(equals);
Assert.NotEqual(hashCodeA, hashCodeB);
}
[Theory]
[MemberData(nameof(Equals_ReturnsTrueIfTypeFullNamesAreIdenticalData))]
public void Equals_ReturnsFalseIfTypeInfoNamesAreDifferent(Type type, string fullName)
{
// Arrange
var typeA = new RuntimeTypeInfo(type.GetTypeInfo());
var typeB = new TestTypeInfo
{
FullName = "Different" + fullName
};
// Act
var equals = typeA.Equals(typeB);
// Assert
Assert.False(equals);
}
public class AbstractType
{
}
@ -371,5 +527,68 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
private class CustomType
{
}
private class TestTypeInfo : ITypeInfo
{
public string FullName { get; set; }
public bool IsAbstract
{
get
{
throw new NotImplementedException();
}
}
public bool IsGenericType
{
get
{
throw new NotImplementedException();
}
}
public bool IsPublic
{
get
{
throw new NotImplementedException();
}
}
public bool IsTagHelper
{
get
{
throw new NotImplementedException();
}
}
public string Name
{
get
{
throw new NotImplementedException();
}
}
public IEnumerable<IPropertyInfo> Properties
{
get
{
throw new NotImplementedException();
}
}
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>() where TAttribute : Attribute
{
throw new NotImplementedException();
}
public ITypeInfo[] GetGenericDictionaryParameters()
{
throw new NotImplementedException();
}
}
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Razor.TagHelpers;
@ -12,10 +11,12 @@ using Xunit;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
public class TagHelperDescriptorFactoryTest
public abstract class TagHelperDescriptorFactoryTest
{
private static readonly string AssemblyName =
typeof(TagHelperDescriptorFactoryTest).GetTypeInfo().Assembly.GetName().Name;
protected static readonly AssemblyName TagHelperDescriptorFactoryTestAssembly =
typeof(TagHelperDescriptorFactoryTest).GetTypeInfo().Assembly.GetName();
protected static readonly string AssemblyName = TagHelperDescriptorFactoryTestAssembly.Name;
public static TheoryData RestrictChildrenData
{
@ -74,6 +75,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
}
}
public abstract ITypeInfo GetTypeInfo(Type tagHelperType);
[Theory]
[MemberData(nameof(RestrictChildrenData))]
public void CreateDescriptors_CreatesDescriptorsWithAllowedChildren(
@ -189,88 +192,6 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default);
}
// TagHelperDesignTimeDescriptors are not created in CoreCLR.
#if !DNXCORE50
public static TheoryData OutputElementHintData
{
get
{
// tagHelperType, expectedDescriptors
return new TheoryData<Type, TagHelperDescriptor[]>
{
{
typeof(OutputElementHintTagHelper),
new[]
{
new TagHelperDescriptor
{
TagName = "output-element-hint",
TypeName = typeof(OutputElementHintTagHelper).FullName,
AssemblyName = AssemblyName,
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
{
OutputElementHint = "strong"
}
}
}
},
{
typeof(MulitpleDescriptorTagHelperWithOutputElementHint),
new[]
{
new TagHelperDescriptor
{
TagName = "a",
TypeName = typeof(MulitpleDescriptorTagHelperWithOutputElementHint).FullName,
AssemblyName = AssemblyName,
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
{
OutputElementHint = "div"
}
},
new TagHelperDescriptor
{
TagName = "p",
TypeName = typeof(MulitpleDescriptorTagHelperWithOutputElementHint).FullName,
AssemblyName = AssemblyName,
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
{
OutputElementHint = "div"
}
}
}
}
};
}
}
[Theory]
[MemberData(nameof(OutputElementHintData))]
public void CreateDescriptors_CreatesDesignTimeDescriptorsWithOutputElementHint(
Type tagHelperType,
TagHelperDescriptor[] expectedDescriptors)
{
// Arrange
var errorSink = new ErrorSink();
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
GetTypeInfo(tagHelperType),
designTime: true,
errorSink: errorSink);
// Assert
Assert.Empty(errorSink.Errors);
// We don't care about order. Mono returns reflected attributes differently so we need to ensure order
// doesn't matter by sorting.
descriptors = descriptors.OrderBy(descriptor => descriptor.TagName);
Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default);
}
#endif
public static TheoryData EditorBrowsableData
{
get
@ -852,34 +773,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
GetTypeInfo(typeof(InheritedNotOverriddenAttributeTagHelper)),
designTime: false,
errorSink: errorSink);
// Assert
Assert.Empty(errorSink.Errors);
Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default);
}
[Fact]
public void CreateDescriptors_BuildsDescriptorsFromSimpleTypes()
{
// Arrange
var errorSink = new ErrorSink();
var objectAssemblyName = typeof(object).GetTypeInfo().Assembly.GetName().Name;
var expectedDescriptor =
CreateTagHelperDescriptor("object", "System.Object", objectAssemblyName);
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
objectAssemblyName,
GetTypeInfo(typeof(object)),
designTime: false,
errorSink: errorSink);
// Assert
Assert.Empty(errorSink.Errors);
var descriptor = Assert.Single(descriptors);
Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default);
}
[Fact]
public void CreateDescriptors_BuildsDescriptorsWithInheritedProperties()
{
@ -1411,7 +1309,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
Name = "dictionary-property",
PropertyName = nameof(DefaultValidHtmlAttributePrefix.DictionaryProperty),
TypeName = typeof(IDictionary<string, string>).FullName
TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(IDictionary<string, string>).FullName)
},
new TagHelperAttributeDescriptor
{
@ -1431,7 +1329,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
Name = "valid-name",
PropertyName = nameof(SingleValidHtmlAttributePrefix.DictionaryProperty),
TypeName = typeof(IDictionary<string, string>).FullName
TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(IDictionary<string, string>).FullName)
},
new TagHelperAttributeDescriptor
{
@ -1451,31 +1349,31 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
Name = "valid-name1",
PropertyName = nameof(MultipleValidHtmlAttributePrefix.DictionaryProperty),
TypeName = typeof(Dictionary<string, object>).FullName
TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(Dictionary<string, object>).FullName)
},
new TagHelperAttributeDescriptor
{
Name = "valid-name2",
PropertyName = nameof(MultipleValidHtmlAttributePrefix.DictionarySubclassProperty),
TypeName = typeof(DictionarySubclass).FullName
TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(DictionarySubclass).FullName)
},
new TagHelperAttributeDescriptor
{
Name = "valid-name3",
PropertyName = nameof(MultipleValidHtmlAttributePrefix.DictionaryWithoutParameterlessConstructorProperty),
TypeName = typeof(DictionaryWithoutParameterlessConstructor).FullName
TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(DictionaryWithoutParameterlessConstructor).FullName)
},
new TagHelperAttributeDescriptor
{
Name = "valid-name4",
PropertyName = nameof(MultipleValidHtmlAttributePrefix.GenericDictionarySubclassProperty),
TypeName = typeof(GenericDictionarySubclass<object>).FullName
TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(GenericDictionarySubclass<object>).FullName)
},
new TagHelperAttributeDescriptor
{
Name = "valid-name5",
PropertyName = nameof(MultipleValidHtmlAttributePrefix.SortedDictionaryProperty),
TypeName = typeof(SortedDictionary<string, int>).FullName
TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(SortedDictionary<string, int>).FullName)
},
new TagHelperAttributeDescriptor
{
@ -2072,7 +1970,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
return data;
}
private static TagHelperDescriptor CreateTagHelperDescriptor(
protected static TagHelperDescriptor CreateTagHelperDescriptor(
string tagName,
string typeName,
string assemblyName,
@ -2100,392 +1998,5 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
TypeName = propertyInfo.PropertyType.FullName
};
}
private static ITypeInfo GetTypeInfo(Type tagHelperType) =>
new RuntimeTypeInfo(tagHelperType.GetTypeInfo());
[RestrictChildren("p")]
private class RestrictChildrenTagHelper
{
}
[RestrictChildren("p", "strong")]
private class DoubleRestrictChildrenTagHelper
{
}
[TargetElement("p")]
[TargetElement("div")]
[RestrictChildren("p", "strong")]
private class MultiTargetRestrictChildrenTagHelper
{
}
[TargetElement("input", TagStructure = TagStructure.WithoutEndTag)]
private class TagStructureTagHelper : TagHelper
{
}
[TargetElement("p", TagStructure = TagStructure.NormalOrSelfClosing)]
[TargetElement("input", TagStructure = TagStructure.WithoutEndTag)]
private class MultiSpecifiedTagStructureTagHelper : TagHelper
{
}
[TargetElement("p")]
[TargetElement("input", TagStructure = TagStructure.WithoutEndTag)]
private class MultiWithUnspecifiedTagStructureTagHelper : TagHelper
{
}
[EditorBrowsable(EditorBrowsableState.Always)]
private class DefaultEditorBrowsableTagHelper : TagHelper
{
[EditorBrowsable(EditorBrowsableState.Always)]
public int Property { get; set; }
}
private class HiddenPropertyEditorBrowsableTagHelper : TagHelper
{
[EditorBrowsable(EditorBrowsableState.Never)]
public int Property { get; set; }
}
private class MultiPropertyEditorBrowsableTagHelper : TagHelper
{
[EditorBrowsable(EditorBrowsableState.Never)]
public int Property { get; set; }
public virtual int Property2 { get; set; }
}
private class OverriddenPropertyEditorBrowsableTagHelper : MultiPropertyEditorBrowsableTagHelper
{
[EditorBrowsable(EditorBrowsableState.Never)]
public override int Property2 { get; set; }
}
[EditorBrowsable(EditorBrowsableState.Never)]
private class EditorBrowsableTagHelper : TagHelper
{
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual int Property { get; set; }
}
private class InheritedEditorBrowsableTagHelper : EditorBrowsableTagHelper
{
public override int Property { get; set; }
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
private class OverriddenEditorBrowsableTagHelper : EditorBrowsableTagHelper
{
[EditorBrowsable(EditorBrowsableState.Advanced)]
public override int Property { get; set; }
}
[TargetElement("p")]
[TargetElement("div")]
[EditorBrowsable(EditorBrowsableState.Never)]
private class MultiEditorBrowsableTagHelper : TagHelper
{
}
[TargetElement(Attributes = "class*")]
private class AttributeWildcardTargetingTagHelper : TagHelper
{
}
[TargetElement(Attributes = "class*,style*")]
private class MultiAttributeWildcardTargetingTagHelper : TagHelper
{
}
[TargetElement(Attributes = "class")]
private class AttributeTargetingTagHelper : TagHelper
{
}
[TargetElement(Attributes = "class,style")]
private class MultiAttributeTargetingTagHelper : TagHelper
{
}
[TargetElement(Attributes = "custom")]
[TargetElement(Attributes = "class,style")]
private class MultiAttributeAttributeTargetingTagHelper : TagHelper
{
}
[TargetElement(Attributes = "style")]
private class InheritedAttributeTargetingTagHelper : AttributeTargetingTagHelper
{
}
[TargetElement("input", Attributes = "class")]
private class RequiredAttributeTagHelper : TagHelper
{
}
[TargetElement("div", Attributes = "class")]
private class InheritedRequiredAttributeTagHelper : RequiredAttributeTagHelper
{
}
[TargetElement("div", Attributes = "class")]
[TargetElement("input", Attributes = "class")]
private class MultiAttributeRequiredAttributeTagHelper : TagHelper
{
}
[TargetElement("input", Attributes = "style")]
[TargetElement("input", Attributes = "class")]
private class MultiAttributeSameTagRequiredAttributeTagHelper : TagHelper
{
}
[TargetElement("input", Attributes = "class,style")]
private class MultiRequiredAttributeTagHelper : TagHelper
{
}
[TargetElement("div", Attributes = "style")]
private class InheritedMultiRequiredAttributeTagHelper : MultiRequiredAttributeTagHelper
{
}
[TargetElement("div", Attributes = "class,style")]
[TargetElement("input", Attributes = "class,style")]
private class MultiTagMultiRequiredAttributeTagHelper : TagHelper
{
}
[TargetElement("p")]
[TargetElement("div")]
private class MultiTagTagHelper
{
public string ValidAttribute { get; set; }
}
private class InheritedMultiTagTagHelper : MultiTagTagHelper
{
}
[TargetElement("p")]
[TargetElement("p")]
[TargetElement("div")]
[TargetElement("div")]
private class DuplicateTagNameTagHelper
{
}
[TargetElement("data-condition")]
private class OverrideNameTagHelper
{
}
private class InheritedSingleAttributeTagHelper : SingleAttributeTagHelper
{
}
private class DuplicateAttributeNameTagHelper
{
public string MyNameIsLegion { get; set; }
[HtmlAttributeName("my-name-is-legion")]
public string Fred { get; set; }
}
private class NotBoundAttributeTagHelper
{
public object BoundProperty { get; set; }
[HtmlAttributeNotBound]
public string NotBoundProperty { get; set; }
[HtmlAttributeName("unused")]
[HtmlAttributeNotBound]
public string NamedNotBoundProperty { get; set; }
}
private class OverriddenAttributeTagHelper
{
[HtmlAttributeName("SomethingElse")]
public virtual string ValidAttribute1 { get; set; }
[HtmlAttributeName("Something-Else")]
public string ValidAttribute2 { get; set; }
}
private class InheritedOverriddenAttributeTagHelper : OverriddenAttributeTagHelper
{
public override string ValidAttribute1 { get; set; }
}
private class InheritedNotOverriddenAttributeTagHelper : OverriddenAttributeTagHelper
{
}
private class ALLCAPSTAGHELPER : TagHelper
{
public int ALLCAPSATTRIBUTE { get; set; }
}
private class CAPSOnOUTSIDETagHelper : TagHelper
{
public int CAPSOnOUTSIDEATTRIBUTE { get; set; }
}
private class capsONInsideTagHelper : TagHelper
{
public int capsONInsideattribute { get; set; }
}
private class One1Two2Three3TagHelper : TagHelper
{
public int One1Two2Three3Attribute { get; set; }
}
private class ONE1TWO2THREE3TagHelper : TagHelper
{
public int ONE1TWO2THREE3Attribute { get; set; }
}
private class First_Second_ThirdHiTagHelper : TagHelper
{
public int First_Second_ThirdAttribute { get; set; }
}
private class UNSuffixedCLASS : TagHelper
{
public int UNSuffixedATTRIBUTE { get; set; }
}
private class InvalidBoundAttribute : TagHelper
{
public string DataSomething { get; set; }
}
private class InvalidBoundAttributeWithValid : SingleAttributeTagHelper
{
public string DataSomething { get; set; }
}
private class OverriddenInvalidBoundAttributeWithValid : TagHelper
{
[HtmlAttributeName("valid-something")]
public string DataSomething { get; set; }
}
private class OverriddenValidBoundAttributeWithInvalid : TagHelper
{
[HtmlAttributeName("data-something")]
public string ValidSomething { get; set; }
}
private class OverriddenValidBoundAttributeWithInvalidUpperCase : TagHelper
{
[HtmlAttributeName("DATA-SOMETHING")]
public string ValidSomething { get; set; }
}
private class DefaultValidHtmlAttributePrefix : TagHelper
{
public IDictionary<string, string> DictionaryProperty { get; set; }
}
private class SingleValidHtmlAttributePrefix : TagHelper
{
[HtmlAttributeName("valid-name")]
public IDictionary<string, string> DictionaryProperty { get; set; }
}
private class MultipleValidHtmlAttributePrefix : TagHelper
{
[HtmlAttributeName("valid-name1", DictionaryAttributePrefix = "valid-prefix1-")]
public Dictionary<string, object> DictionaryProperty { get; set; }
[HtmlAttributeName("valid-name2", DictionaryAttributePrefix = "valid-prefix2-")]
public DictionarySubclass DictionarySubclassProperty { get; set; }
[HtmlAttributeName("valid-name3", DictionaryAttributePrefix = "valid-prefix3-")]
public DictionaryWithoutParameterlessConstructor DictionaryWithoutParameterlessConstructorProperty { get; set; }
[HtmlAttributeName("valid-name4", DictionaryAttributePrefix = "valid-prefix4-")]
public GenericDictionarySubclass<object> GenericDictionarySubclassProperty { get; set; }
[HtmlAttributeName("valid-name5", DictionaryAttributePrefix = "valid-prefix5-")]
public SortedDictionary<string, int> SortedDictionaryProperty { get; set; }
[HtmlAttributeName("valid-name6")]
public string StringProperty { get; set; }
public IDictionary<string, int> GetOnlyDictionaryProperty { get; }
[HtmlAttributeName(DictionaryAttributePrefix = "valid-prefix6")]
public IDictionary<string, string> GetOnlyDictionaryPropertyWithAttributePrefix { get; }
}
private class SingleInvalidHtmlAttributePrefix : TagHelper
{
[HtmlAttributeName("valid-name", DictionaryAttributePrefix = "valid-prefix")]
public string StringProperty { get; set; }
}
private class MultipleInvalidHtmlAttributePrefix : TagHelper
{
[HtmlAttributeName("valid-name1")]
public long LongProperty { get; set; }
[HtmlAttributeName("valid-name2", DictionaryAttributePrefix = "valid-prefix2-")]
public Dictionary<int, string> DictionaryOfIntProperty { get; set; }
[HtmlAttributeName("valid-name3", DictionaryAttributePrefix = "valid-prefix3-")]
public IReadOnlyDictionary<string, object> ReadOnlyDictionaryProperty { get; set; }
[HtmlAttributeName("valid-name4", DictionaryAttributePrefix = "valid-prefix4-")]
public int IntProperty { get; set; }
[HtmlAttributeName("valid-name5", DictionaryAttributePrefix = "valid-prefix5-")]
public DictionaryOfIntSubclass DictionaryOfIntSubclassProperty { get; set; }
[HtmlAttributeName(DictionaryAttributePrefix = "valid-prefix6")]
public IDictionary<int, string> GetOnlyDictionaryAttributePrefix { get; }
[HtmlAttributeName("invalid-name7")]
public IDictionary<string, object> GetOnlyDictionaryPropertyWithAttributeName { get; }
}
private class DictionarySubclass : Dictionary<string, string>
{
}
private class DictionaryWithoutParameterlessConstructor : Dictionary<string, string>
{
public DictionaryWithoutParameterlessConstructor(int count)
: base()
{
}
}
private class DictionaryOfIntSubclass : Dictionary<int, string>
{
}
private class GenericDictionarySubclass<TValue> : Dictionary<string, TValue>
{
}
[OutputElementHint("strong")]
private class OutputElementHintTagHelper : TagHelper
{
}
[TargetElement("a")]
[TargetElement("p")]
[OutputElementHint("div")]
private class MulitpleDescriptorTagHelperWithOutputElementHint : TagHelper
{
}
}
}

View File

@ -0,0 +1,446 @@
// 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.ComponentModel;
using System.Threading.Tasks;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
[RestrictChildren("p")]
public class RestrictChildrenTagHelper
{
}
[RestrictChildren("p", "strong")]
public class DoubleRestrictChildrenTagHelper
{
}
[TargetElement("p")]
[TargetElement("div")]
[RestrictChildren("p", "strong")]
public class MultiTargetRestrictChildrenTagHelper
{
}
[TargetElement("input", TagStructure = TagStructure.WithoutEndTag)]
public class TagStructureTagHelper : TagHelper
{
}
[TargetElement("p", TagStructure = TagStructure.NormalOrSelfClosing)]
[TargetElement("input", TagStructure = TagStructure.WithoutEndTag)]
public class MultiSpecifiedTagStructureTagHelper : TagHelper
{
}
[TargetElement("p")]
[TargetElement("input", TagStructure = TagStructure.WithoutEndTag)]
public class MultiWithUnspecifiedTagStructureTagHelper : TagHelper
{
}
[EditorBrowsable(EditorBrowsableState.Always)]
public class DefaultEditorBrowsableTagHelper : TagHelper
{
[EditorBrowsable(EditorBrowsableState.Always)]
public int Property { get; set; }
}
public class HiddenPropertyEditorBrowsableTagHelper : TagHelper
{
[EditorBrowsable(EditorBrowsableState.Never)]
public int Property { get; set; }
}
public class MultiPropertyEditorBrowsableTagHelper : TagHelper
{
[EditorBrowsable(EditorBrowsableState.Never)]
public int Property { get; set; }
public virtual int Property2 { get; set; }
}
public class OverriddenPropertyEditorBrowsableTagHelper : MultiPropertyEditorBrowsableTagHelper
{
[EditorBrowsable(EditorBrowsableState.Never)]
public override int Property2 { get; set; }
}
[EditorBrowsable(EditorBrowsableState.Never)]
public class EditorBrowsableTagHelper : TagHelper
{
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual int Property { get; set; }
}
public class InheritedEditorBrowsableTagHelper : EditorBrowsableTagHelper
{
public override int Property { get; set; }
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
public class OverriddenEditorBrowsableTagHelper : EditorBrowsableTagHelper
{
[EditorBrowsable(EditorBrowsableState.Advanced)]
public override int Property { get; set; }
}
[TargetElement("p")]
[TargetElement("div")]
[EditorBrowsable(EditorBrowsableState.Never)]
public class MultiEditorBrowsableTagHelper : TagHelper
{
}
[TargetElement(Attributes = "class*")]
public class AttributeWildcardTargetingTagHelper : TagHelper
{
}
[TargetElement(Attributes = "class*,style*")]
public class MultiAttributeWildcardTargetingTagHelper : TagHelper
{
}
[TargetElement(Attributes = "class")]
public class AttributeTargetingTagHelper : TagHelper
{
}
[TargetElement(Attributes = "class,style")]
public class MultiAttributeTargetingTagHelper : TagHelper
{
}
[TargetElement(Attributes = "custom")]
[TargetElement(Attributes = "class,style")]
public class MultiAttributeAttributeTargetingTagHelper : TagHelper
{
}
[TargetElement(Attributes = "style")]
public class InheritedAttributeTargetingTagHelper : AttributeTargetingTagHelper
{
}
[TargetElement("input", Attributes = "class")]
public class RequiredAttributeTagHelper : TagHelper
{
}
[TargetElement("div", Attributes = "class")]
public class InheritedRequiredAttributeTagHelper : RequiredAttributeTagHelper
{
}
[TargetElement("div", Attributes = "class")]
[TargetElement("input", Attributes = "class")]
public class MultiAttributeRequiredAttributeTagHelper : TagHelper
{
}
[TargetElement("input", Attributes = "style")]
[TargetElement("input", Attributes = "class")]
public class MultiAttributeSameTagRequiredAttributeTagHelper : TagHelper
{
}
[TargetElement("input", Attributes = "class,style")]
public class MultiRequiredAttributeTagHelper : TagHelper
{
}
[TargetElement("div", Attributes = "style")]
public class InheritedMultiRequiredAttributeTagHelper : MultiRequiredAttributeTagHelper
{
}
[TargetElement("div", Attributes = "class,style")]
[TargetElement("input", Attributes = "class,style")]
public class MultiTagMultiRequiredAttributeTagHelper : TagHelper
{
}
[TargetElement("p")]
[TargetElement("div")]
public class MultiTagTagHelper
{
public string ValidAttribute { get; set; }
}
public class InheritedMultiTagTagHelper : MultiTagTagHelper
{
}
[TargetElement("p")]
[TargetElement("p")]
[TargetElement("div")]
[TargetElement("div")]
public class DuplicateTagNameTagHelper
{
}
[TargetElement("data-condition")]
public class OverrideNameTagHelper
{
}
public class InheritedSingleAttributeTagHelper : SingleAttributeTagHelper
{
}
public class DuplicateAttributeNameTagHelper
{
public string MyNameIsLegion { get; set; }
[HtmlAttributeName("my-name-is-legion")]
public string Fred { get; set; }
}
public class NotBoundAttributeTagHelper
{
public object BoundProperty { get; set; }
[HtmlAttributeNotBound]
public string NotBoundProperty { get; set; }
[HtmlAttributeName("unused")]
[HtmlAttributeNotBound]
public string NamedNotBoundProperty { get; set; }
}
public class OverriddenAttributeTagHelper
{
[HtmlAttributeName("SomethingElse")]
public virtual string ValidAttribute1 { get; set; }
[HtmlAttributeName("Something-Else")]
public string ValidAttribute2 { get; set; }
}
public class InheritedOverriddenAttributeTagHelper : OverriddenAttributeTagHelper
{
public override string ValidAttribute1 { get; set; }
}
public class InheritedNotOverriddenAttributeTagHelper : OverriddenAttributeTagHelper
{
}
public class ALLCAPSTAGHELPER : TagHelper
{
public int ALLCAPSATTRIBUTE { get; set; }
}
public class CAPSOnOUTSIDETagHelper : TagHelper
{
public int CAPSOnOUTSIDEATTRIBUTE { get; set; }
}
public class capsONInsideTagHelper : TagHelper
{
public int capsONInsideattribute { get; set; }
}
public class One1Two2Three3TagHelper : TagHelper
{
public int One1Two2Three3Attribute { get; set; }
}
public class ONE1TWO2THREE3TagHelper : TagHelper
{
public int ONE1TWO2THREE3Attribute { get; set; }
}
public class First_Second_ThirdHiTagHelper : TagHelper
{
public int First_Second_ThirdAttribute { get; set; }
}
public class UNSuffixedCLASS : TagHelper
{
public int UNSuffixedATTRIBUTE { get; set; }
}
public class InvalidBoundAttribute : TagHelper
{
public string DataSomething { get; set; }
}
public class InvalidBoundAttributeWithValid : SingleAttributeTagHelper
{
public string DataSomething { get; set; }
}
public class OverriddenInvalidBoundAttributeWithValid : TagHelper
{
[HtmlAttributeName("valid-something")]
public string DataSomething { get; set; }
}
public class OverriddenValidBoundAttributeWithInvalid : TagHelper
{
[HtmlAttributeName("data-something")]
public string ValidSomething { get; set; }
}
public class OverriddenValidBoundAttributeWithInvalidUpperCase : TagHelper
{
[HtmlAttributeName("DATA-SOMETHING")]
public string ValidSomething { get; set; }
}
public class DefaultValidHtmlAttributePrefix : TagHelper
{
public IDictionary<string, string> DictionaryProperty { get; set; }
}
public class SingleValidHtmlAttributePrefix : TagHelper
{
[HtmlAttributeName("valid-name")]
public IDictionary<string, string> DictionaryProperty { get; set; }
}
public class MultipleValidHtmlAttributePrefix : TagHelper
{
[HtmlAttributeName("valid-name1", DictionaryAttributePrefix = "valid-prefix1-")]
public Dictionary<string, object> DictionaryProperty { get; set; }
[HtmlAttributeName("valid-name2", DictionaryAttributePrefix = "valid-prefix2-")]
public DictionarySubclass DictionarySubclassProperty { get; set; }
[HtmlAttributeName("valid-name3", DictionaryAttributePrefix = "valid-prefix3-")]
public DictionaryWithoutParameterlessConstructor DictionaryWithoutParameterlessConstructorProperty { get; set; }
[HtmlAttributeName("valid-name4", DictionaryAttributePrefix = "valid-prefix4-")]
public GenericDictionarySubclass<object> GenericDictionarySubclassProperty { get; set; }
[HtmlAttributeName("valid-name5", DictionaryAttributePrefix = "valid-prefix5-")]
public SortedDictionary<string, int> SortedDictionaryProperty { get; set; }
[HtmlAttributeName("valid-name6")]
public string StringProperty { get; set; }
public IDictionary<string, int> GetOnlyDictionaryProperty { get; }
[HtmlAttributeName(DictionaryAttributePrefix = "valid-prefix6")]
public IDictionary<string, string> GetOnlyDictionaryPropertyWithAttributePrefix { get; }
}
public class SingleInvalidHtmlAttributePrefix : TagHelper
{
[HtmlAttributeName("valid-name", DictionaryAttributePrefix = "valid-prefix")]
public string StringProperty { get; set; }
}
public class MultipleInvalidHtmlAttributePrefix : TagHelper
{
[HtmlAttributeName("valid-name1")]
public long LongProperty { get; set; }
[HtmlAttributeName("valid-name2", DictionaryAttributePrefix = "valid-prefix2-")]
public Dictionary<int, string> DictionaryOfIntProperty { get; set; }
[HtmlAttributeName("valid-name3", DictionaryAttributePrefix = "valid-prefix3-")]
public IReadOnlyDictionary<string, object> ReadOnlyDictionaryProperty { get; set; }
[HtmlAttributeName("valid-name4", DictionaryAttributePrefix = "valid-prefix4-")]
public int IntProperty { get; set; }
[HtmlAttributeName("valid-name5", DictionaryAttributePrefix = "valid-prefix5-")]
public DictionaryOfIntSubclass DictionaryOfIntSubclassProperty { get; set; }
[HtmlAttributeName(DictionaryAttributePrefix = "valid-prefix6")]
public IDictionary<int, string> GetOnlyDictionaryAttributePrefix { get; }
[HtmlAttributeName("invalid-name7")]
public IDictionary<string, object> GetOnlyDictionaryPropertyWithAttributeName { get; }
}
public class DictionarySubclass : Dictionary<string, string>
{
}
public class DictionaryWithoutParameterlessConstructor : Dictionary<string, string>
{
public DictionaryWithoutParameterlessConstructor(int count)
: base()
{
}
}
public class DictionaryOfIntSubclass : Dictionary<int, string>
{
}
public class GenericDictionarySubclass<TValue> : Dictionary<string, TValue>
{
}
[OutputElementHint("strong")]
public class OutputElementHintTagHelper : TagHelper
{
}
[TargetElement("a")]
[TargetElement("p")]
[OutputElementHint("div")]
public class MulitpleDescriptorTagHelperWithOutputElementHint : TagHelper
{
}
public class TypeDerivingFromITagHelper : ITagHelper
{
public int Order { get; } = 0;
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
throw new NotImplementedException();
}
}
public class BaseAttribute : Attribute
{
public string BaseProperty { get; set; }
}
public class DerivedAttribute : BaseAttribute
{
public string DerivedProperty { get; set; }
}
public class BaseTagHelper : TagHelper
{
[Derived(DerivedProperty = "DerivedPropertyValue")]
public string BaseProperty { get; set; }
[HtmlAttributeNotBound]
public virtual string VirtualProperty { get; set; }
public int NewProperty { get; set; }
public virtual new string Order { get; set; }
}
public class DerivedTagHelper : BaseTagHelper
{
public override string VirtualProperty { get; set; }
[Base(BaseProperty = "BaseValue")]
public string DerivedProperty { get; set; }
[HtmlAttributeName("new-property")]
public new Type NewProperty { get; set; }
public override string Order { get; set; }
}
}
public class TagHelperInGlobalNamespace : TagHelper
{
}