Change ITypeInfo.IsTagHelper to ITypeInfo.ImplementsInterface(ITypeInfo)

Fixes #511
This commit is contained in:
Pranav K 2015-09-25 15:05:39 -07:00
parent d2a7a355cc
commit e87f10e8f8
13 changed files with 335 additions and 131 deletions

View File

@ -18,13 +18,8 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
[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 =
private static readonly System.Reflection.TypeInfo OpenGenericDictionaryTypeInfo =
typeof(IDictionary<,>).GetTypeInfo();
private static readonly System.Reflection.TypeInfo TagHelperTypeInfo =
typeof(ITagHelper).GetTypeInfo();
private readonly ITypeSymbol _type;
private readonly ITypeSymbol _underlyingType;
private string _fullName;
@ -120,13 +115,33 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
}
/// <inheritdoc />
public bool IsTagHelper
public bool ImplementsInterface(ITypeInfo interfaceTypeInfo)
{
get
if (interfaceTypeInfo == null)
{
return _type.AllInterfaces.Any(
implementedInterface => IsType(implementedInterface, TagHelperTypeInfo));
throw new ArgumentNullException(nameof(interfaceTypeInfo));
}
var runtimeTypeInfo = interfaceTypeInfo as RuntimeTypeInfo;
if (runtimeTypeInfo != null)
{
return runtimeTypeInfo.TypeInfo.IsInterface &&
_type.AllInterfaces.Any(implementedInterface => IsType(implementedInterface, runtimeTypeInfo.TypeInfo));
}
var codeAnalysisTypeInfo = interfaceTypeInfo as CodeAnalysisSymbolBasedTypeInfo;
if (codeAnalysisTypeInfo != null)
{
return codeAnalysisTypeInfo.TypeSymbol.TypeKind == TypeKind.Interface &&
_type.AllInterfaces.Any(
implementedInterface => implementedInterface == codeAnalysisTypeInfo.TypeSymbol);
}
throw new ArgumentException(
Resources.FormatArgumentMustBeAnInstanceOf(
typeof(RuntimeTypeInfo).FullName,
typeof(CodeAnalysisSymbolBasedTypeInfo).FullName),
nameof(interfaceTypeInfo));
}
private string SanitizedFullName
@ -193,8 +208,8 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
System.Reflection.TypeInfo targetTypeInfo)
{
return string.Equals(
targetTypeInfo.FullName,
GetFullName(sourceTypeSymbol),
RuntimeTypeInfo.SanitizeFullName(targetTypeInfo.FullName),
RuntimeTypeInfo.SanitizeFullName(GetFullName(sourceTypeSymbol)),
StringComparison.Ordinal);
}

View File

@ -10,6 +10,22 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNet.Razor.Runtime.Precompilation.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// Argument must be an instance of '{0}' or '{1}'.
/// </summary>
internal static string ArgumentMustBeAnInstanceOf
{
get { return GetString("ArgumentMustBeAnInstanceOf"); }
}
/// <summary>
/// Argument must be an instance of '{0}' or '{1}'.
/// </summary>
internal static string FormatArgumentMustBeAnInstanceOf(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentMustBeAnInstanceOf"), p0, p1);
}
/// <summary>
/// Unable to find a suitable constructor for type '{0}'.
/// </summary>

View File

@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ArgumentMustBeAnInstanceOf" xml:space="preserve">
<value>Argument must be an instance of '{0}' or '{1}'.</value>
</data>
<data name="CodeAnalysisConstructorNotFound" xml:space="preserve">
<value>Unable to find a suitable constructor for type '{0}'.</value>
</data>

View File

@ -442,6 +442,22 @@ namespace Microsoft.AspNet.Razor.Runtime
return GetString("TagHelperDescriptorFactory_ParentTag");
}
/// <summary>
/// Argument must be an instance of '{0}'.
/// </summary>
internal static string ArgumentMustBeAnInstanceOf
{
get { return GetString("ArgumentMustBeAnInstanceOf"); }
}
/// <summary>
/// Argument must be an instance of '{0}'.
/// </summary>
internal static string FormatArgumentMustBeAnInstanceOf(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentMustBeAnInstanceOf"), p0);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
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
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>
@ -26,36 +26,36 @@
<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
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
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
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
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
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
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
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@ -198,4 +198,7 @@
<data name="TagHelperDescriptorFactory_ParentTag" xml:space="preserve">
<value>Parent Tag</value>
</data>
<data name="ArgumentMustBeAnInstanceOf" xml:space="preserve">
<value>Argument must be an instance of '{0}'.</value>
</data>
</root>

View File

@ -49,9 +49,9 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
bool IsGenericType { get; }
/// <summary>
/// Gets a value indicating whether the type implements the <see cref="ITagHelper"/> interface.
/// Gets a value indicating whether the type implements the <param name="interfaceTypeInfo"/> interface.
/// </summary>
bool IsTagHelper { get; }
bool ImplementsInterface(ITypeInfo interfaceTypeInfo);
/// <summary>
/// Gets the <see cref="ITypeInfo[]"/> for the <c>TKey</c> and <c>TValue</c> parameters of

View File

@ -77,7 +77,23 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
}
/// <inheritdoc />
public bool IsTagHelper => TagHelperTypeInfo.IsAssignableFrom(TypeInfo);
public bool ImplementsInterface(ITypeInfo interfaceTypeInfo)
{
if (interfaceTypeInfo == null)
{
throw new ArgumentNullException(nameof(interfaceTypeInfo));
}
var runtimeTypeInfo = interfaceTypeInfo as RuntimeTypeInfo;
if (runtimeTypeInfo == null)
{
throw new ArgumentException(
Resources.FormatArgumentMustBeAnInstanceOf(typeof(RuntimeTypeInfo).FullName),
nameof(interfaceTypeInfo));
}
return runtimeTypeInfo.TypeInfo.IsInterface && runtimeTypeInfo.TypeInfo.IsAssignableFrom(TypeInfo);
}
private string SanitizedFullName
{

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// </summary>
public class TagHelperTypeResolver
{
private static readonly TypeInfo ITagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo();
private static readonly ITypeInfo ITagHelperTypeInfo = new RuntimeTypeInfo(typeof(ITagHelper).GetTypeInfo());
/// <summary>
/// Locates valid <see cref="ITagHelper"/> types from the <see cref="Assembly"/> named <paramref name="name"/>.
@ -109,7 +109,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
return typeInfo.IsPublic &&
!typeInfo.IsAbstract &&
!typeInfo.IsGenericType &&
typeInfo.IsTagHelper;
typeInfo.ImplementsInterface(ITagHelperTypeInfo);
}
}
}

View File

@ -0,0 +1,38 @@
// 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
{
public class DerivingFromIList : IReadOnlyList<string>
{
public string this[int index]
{
get
{
throw new NotImplementedException();
}
}
public int Count
{
get
{
throw new NotImplementedException();
}
}
public IEnumerator<string> GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNet.Razor.Runtime
{
public class DerivingFromList : List<string>
{
}
}

View File

@ -8,6 +8,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Testing;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
@ -15,7 +16,7 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{
public class PrecompilationTagHelperTypeResolverTest
{
private static readonly TypeInfo TagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo();
private static readonly ITypeInfo TagHelperTypeInfo = new RuntimeTypeInfo(typeof(ITagHelper).GetTypeInfo());
[Theory]
[InlineData(typeof(TypeDerivingFromITagHelper))]
@ -35,6 +36,60 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
AssertEqual(expected, actual);
}
[Fact]
public void ImplementsInterface_ThrowsIfPassedInParameterIsNotRuntimeTypeOrCodeAnalysisBasedTypeInfo()
{
// Arrange
var interfaceType = new TestTypeInfo();
var compilation = CompilationUtility.GetCompilation(nameof(DerivingFromList));
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var actual = Assert.Single(exportedTypes);
ExceptionAssert.ThrowsArgument(() => actual.ImplementsInterface(interfaceType),
"interfaceTypeInfo",
$"Argument must be an instance of '{typeof(RuntimeTypeInfo)}' or '{typeof(CodeAnalysisSymbolBasedTypeInfo)}'.");
}
[Theory]
[InlineData(typeof(DerivingFromList))]
[InlineData(typeof(DerivingFromIList))]
public void ImplementsInterface_ReturnsTrueIfTypeDerivesFromInterface(Type expected)
{
// Arrange
var interfaceType = new RuntimeTypeInfo(typeof(IEnumerable<string>).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);
Assert.True(actual.ImplementsInterface(interfaceType));
}
[Theory]
[InlineData(typeof(TagHelper))]
[InlineData(typeof(IEnumerable<string>))]
public void ImplementsInterface_ReturnsFalseIfTypeDoesNotDerivesFromInterface(Type interfaceType)
{
// Arrange
var interfaceTypeInfo = new RuntimeTypeInfo(interfaceType.GetTypeInfo());
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 == nameof(RequiredParentTagHelper));
Assert.False(actual.ImplementsInterface(interfaceTypeInfo));
}
[Fact]
public void PropertyNamesForComplexPropertiesAreGeneratedCorrectly()
{
@ -472,7 +527,7 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
// Assert
var type = Assert.Single(exportedTypes);
Assert.False(type.IsTagHelper);
Assert.False(type.ImplementsInterface(TagHelperTypeInfo));
}
private static void AssertAttributes<TAttribute>(
@ -481,7 +536,7 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
Action<TAttribute, TAttribute> assertItem)
where TAttribute : Attribute
{
AssertAttributes<TAttribute>(expected, actual, assertItem, attributes => attributes);
AssertAttributes(expected, actual, assertItem, attributes => attributes);
}
private static void AssertAttributes<TAttribute>(
@ -519,7 +574,9 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
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.ImplementsInterface(TagHelperTypeInfo),
actual.ImplementsInterface(TagHelperTypeInfo));
Assert.Equal(
expected.GetGenericDictionaryParameters(),
actual.GetGenericDictionaryParameters(),

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Testing;
using Xunit;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
@ -110,35 +111,58 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
Assert.Empty(actual);
}
[Theory]
[InlineData(typeof(ITagHelper))]
[InlineData(typeof(TagHelper))]
[InlineData(typeof(ImplementsITagHelper))]
[InlineData(typeof(DerivesFromTagHelper))]
[InlineData(typeof(Fake.ImplementsRealITagHelper))]
public void IsTagHelper_ReturnsTrueIfTypeImplementsTagHelper(Type type)
[Fact]
public void ImplementsInterface_ThrowsIfArgumentIsNotRuntimeType()
{
// Arrange
var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo());
var typeInfo = new RuntimeTypeInfo(typeof(object).GetTypeInfo());
var interfaceTypeInfo = new TestTypeInfo();
// Act and Assert
ExceptionAssert.ThrowsArgument(() => typeInfo.ImplementsInterface(interfaceTypeInfo),
"interfaceTypeInfo",
$"Argument must be an instance of '{typeof(RuntimeTypeInfo)}'.");
}
[Theory]
[InlineData(typeof(ITagHelper), typeof(ITagHelper))]
[InlineData(typeof(TagHelper), typeof(ITagHelper))]
[InlineData(typeof(ImplementsITagHelper), typeof(ITagHelper))]
[InlineData(typeof(DerivesFromTagHelper), typeof(ITagHelper))]
[InlineData(typeof(Fake.ImplementsRealITagHelper), typeof(ITagHelper))]
[InlineData(typeof(string), typeof(IEnumerable<char>))]
[InlineData(typeof(Dictionary<string, string>), typeof(IDictionary<string, string>))]
[InlineData(typeof(List<string>), typeof(IList<string>))]
[InlineData(typeof(IList<string>), typeof(IEnumerable<string>))]
public void ImplementsInterface_ReturnsTrueIfTypeImplementsInterface(Type runtimeType, Type interfaceType)
{
// Arrange
var runtimeTypeInfo = new RuntimeTypeInfo(runtimeType.GetTypeInfo());
var interfaceTypeInfo = new RuntimeTypeInfo(interfaceType.GetTypeInfo());
// Act
var result = runtimeTypeInfo.IsTagHelper;
var result = runtimeTypeInfo.ImplementsInterface(interfaceTypeInfo);
// Assert
Assert.True(result);
}
[Theory]
[InlineData(typeof(string))]
[InlineData(typeof(SubType))]
[InlineData(typeof(Fake.DoesNotImplementRealITagHelper))]
public void IsTagHelper_ReturnsFalseIfTypeDoesNotImplementTagHelper(Type type)
[InlineData(typeof(string), typeof(object))]
[InlineData(typeof(DerivesFromTagHelper), typeof(TagHelper))]
[InlineData(typeof(string), typeof(ITagHelper))]
[InlineData(typeof(string), typeof(IList<char>))]
[InlineData(typeof(SubType), typeof(ITagHelper))]
[InlineData(typeof(Fake.DoesNotImplementRealITagHelper), typeof(ITagHelper))]
[InlineData(typeof(IDictionary<,>), typeof(IDictionary<string, string>))]
public void ImplementsInterface_ReturnsTrueIfTypeDoesNotImplementInterface(Type runtimeType, Type interfaceType)
{
// Arrange
var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo());
var runtimeTypeInfo = new RuntimeTypeInfo(runtimeType.GetTypeInfo());
var interfaceTypeInfo = new RuntimeTypeInfo(interfaceType.GetTypeInfo());
// Act
var result = runtimeTypeInfo.IsTagHelper;
var result = runtimeTypeInfo.ImplementsInterface(interfaceTypeInfo);
// Assert
Assert.False(result);
@ -502,73 +526,5 @@ 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 bool Equals(ITypeInfo other)
{
throw new NotImplementedException();
}
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>() where TAttribute : Attribute
{
throw new NotImplementedException();
}
public ITypeInfo[] GetGenericDictionaryParameters()
{
throw new NotImplementedException();
}
}
}
}

View File

@ -0,0 +1,73 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
public 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 ImplementsInterface(ITypeInfo other)
{
throw new NotImplementedException();
}
public string Name
{
get
{
throw new NotImplementedException();
}
}
public IEnumerable<IPropertyInfo> Properties
{
get
{
throw new NotImplementedException();
}
}
public bool Equals(ITypeInfo other)
{
throw new NotImplementedException();
}
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>() where TAttribute : Attribute
{
throw new NotImplementedException();
}
public ITypeInfo[] GetGenericDictionaryParameters()
{
throw new NotImplementedException();
}
}
}