[Fixes #5216] Make generic parameters work with ViewComponent tag helpers
This commit is contained in:
parent
ea701f2cd6
commit
c28ad48e98
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Host;
|
||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
|
|
@ -56,7 +57,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
StringComparison.Ordinal));
|
||||
|
||||
var tagHelperDescriptors = viewComponentDescriptors
|
||||
.Where(descriptor => !descriptor.Parameters.Any(parameter => parameter.ParameterType.GetTypeInfo().IsGenericType))
|
||||
.Where(d => !d.Parameters.Any(p => p.ParameterType.GetTypeInfo().ContainsGenericParameters))
|
||||
.Select(viewComponentDescriptor => CreateDescriptor(viewComponentDescriptor));
|
||||
|
||||
return tagHelperDescriptors;
|
||||
|
|
@ -92,11 +93,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
foreach (var parameter in methodParameters)
|
||||
{
|
||||
var lowerKebabName = TagHelperDescriptorFactory.ToHtmlCase(parameter.Name);
|
||||
var typeName = GetCSharpTypeName(parameter.ParameterType);
|
||||
var descriptor = new TagHelperAttributeDescriptor
|
||||
{
|
||||
Name = lowerKebabName,
|
||||
PropertyName = parameter.Name,
|
||||
TypeName = parameter.ParameterType.FullName
|
||||
TypeName = typeName
|
||||
};
|
||||
|
||||
descriptor.IsEnum = parameter.ParameterType.GetTypeInfo().IsEnum;
|
||||
|
|
@ -113,7 +115,110 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
});
|
||||
}
|
||||
|
||||
private string GetTagName(ViewComponentDescriptor descriptor) =>
|
||||
$"vc:{TagHelperDescriptorFactory.ToHtmlCase(descriptor.ShortName)}";
|
||||
private string GetTagName(ViewComponentDescriptor descriptor)
|
||||
{
|
||||
return $"vc:{TagHelperDescriptorFactory.ToHtmlCase(descriptor.ShortName)}";
|
||||
}
|
||||
|
||||
// Internal for testing.
|
||||
internal static string GetCSharpTypeName(Type type)
|
||||
{
|
||||
var outputBuilder = new StringBuilder();
|
||||
WriteCSharpTypeName(type, outputBuilder);
|
||||
|
||||
var typeName = outputBuilder.ToString();
|
||||
|
||||
// We don't want to add global:: to the top level type because Razor does that for us.
|
||||
return typeName.Substring("global::".Length);
|
||||
}
|
||||
|
||||
private static void WriteCSharpTypeName(Type type, StringBuilder outputBuilder)
|
||||
{
|
||||
var typeInfo = type.GetTypeInfo();
|
||||
if (typeInfo.IsByRef)
|
||||
{
|
||||
WriteCSharpTypeName(typeInfo.GetElementType(), outputBuilder);
|
||||
}
|
||||
else if (typeInfo.IsNested)
|
||||
{
|
||||
WriteNestedTypes(type, outputBuilder);
|
||||
}
|
||||
else if (typeInfo.IsGenericType)
|
||||
{
|
||||
outputBuilder.Append("global::");
|
||||
var part = type.FullName.Substring(0, type.FullName.IndexOf('`'));
|
||||
outputBuilder.Append(part);
|
||||
|
||||
var genericArguments = type.GenericTypeArguments;
|
||||
WriteGenericArguments(genericArguments, 0, genericArguments.Length, outputBuilder);
|
||||
}
|
||||
else
|
||||
{
|
||||
outputBuilder.Append("global::");
|
||||
outputBuilder.Append(type.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteNestedTypes(Type type, StringBuilder outputBuilder)
|
||||
{
|
||||
var nestedTypes = new List<Type>();
|
||||
var currentType = type;
|
||||
do
|
||||
{
|
||||
nestedTypes.Insert(0, currentType);
|
||||
currentType = currentType.DeclaringType;
|
||||
} while (currentType.IsNested);
|
||||
|
||||
nestedTypes.Insert(0, currentType);
|
||||
|
||||
outputBuilder.Append("global::");
|
||||
outputBuilder.Append(currentType.Namespace);
|
||||
outputBuilder.Append(".");
|
||||
|
||||
var typeArgumentIndex = 0;
|
||||
for (var i = 0; i < nestedTypes.Count; i++)
|
||||
{
|
||||
var nestedType = nestedTypes[i];
|
||||
var arityIndex = nestedType.Name.IndexOf('`');
|
||||
if (arityIndex >= 0)
|
||||
{
|
||||
var part = nestedType.Name.Substring(0, arityIndex);
|
||||
outputBuilder.Append(part);
|
||||
|
||||
var genericArguments = type.GenericTypeArguments;
|
||||
var typeArgumentCount = nestedType.IsConstructedGenericType ?
|
||||
nestedType.GenericTypeArguments.Length : nestedType.GetTypeInfo().GenericTypeParameters.Length;
|
||||
|
||||
WriteGenericArguments(genericArguments, typeArgumentIndex, typeArgumentCount, outputBuilder);
|
||||
|
||||
typeArgumentIndex = typeArgumentCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
outputBuilder.Append(nestedType.Name);
|
||||
}
|
||||
|
||||
if (i + 1 < nestedTypes.Count)
|
||||
{
|
||||
outputBuilder.Append(".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteGenericArguments(Type[] genericArguments, int startIndex, int length, StringBuilder outputBuilder)
|
||||
{
|
||||
outputBuilder.Append("<");
|
||||
|
||||
for (var i = startIndex; i < length; i++)
|
||||
{
|
||||
WriteCSharpTypeName(genericArguments[i], outputBuilder);
|
||||
if (i + 1 < length)
|
||||
{
|
||||
outputBuilder.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
outputBuilder.Append(">");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,7 @@
|
|||
// 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;
|
||||
|
|
@ -27,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.Internal
|
|||
return new TheoryData<string, IEnumerable<TagHelperDescriptor>>
|
||||
{
|
||||
{ assemblyOne, new [] { provider.GetTagHelperDescriptorOne() } },
|
||||
{ assemblyTwo, new [] { provider.GetTagHelperDescriptorTwo() } },
|
||||
{ assemblyTwo, new [] { provider.GetTagHelperDescriptorTwo(), provider.GetTagHelperDescriptorGeneric() } },
|
||||
{ assemblyNone, Enumerable.Empty<TagHelperDescriptor>() }
|
||||
};
|
||||
}
|
||||
|
|
@ -50,6 +51,48 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.Internal
|
|||
Assert.Equal(expectedDescriptors, descriptors, TagHelperDescriptorComparer.Default);
|
||||
}
|
||||
|
||||
public static TheoryData TypeData
|
||||
{
|
||||
get
|
||||
{
|
||||
var outParamType = typeof(ViewComponentTagHelperDescriptorFactoryTest).GetMethod("MethodWithOutParam").GetParameters().First().ParameterType;
|
||||
var refParamType = typeof(ViewComponentTagHelperDescriptorFactoryTest).GetMethod("MethodWithRefParam").GetParameters().First().ParameterType;
|
||||
|
||||
return new TheoryData<Type, string>
|
||||
{
|
||||
{ typeof(string), "System.String" },
|
||||
{ typeof(string[,]), "System.String[,]" },
|
||||
{ typeof(List<int*[]>), "System.Collections.Generic.List<global::System.Int32*[]>" },
|
||||
{ typeof(List<string[,,]>), "System.Collections.Generic.List<global::System.String[,,]>" },
|
||||
{ typeof(Dictionary<string[], List<string>>),
|
||||
"System.Collections.Generic.Dictionary<global::System.String[], global::System.Collections.Generic.List<global::System.String>>" },
|
||||
{ typeof(Dictionary<string, List<string[,]>>),
|
||||
"System.Collections.Generic.Dictionary<global::System.String, global::System.Collections.Generic.List<global::System.String[,]>>" },
|
||||
{ outParamType, "System.Collections.Generic.List<global::System.Char*[]>" },
|
||||
{ refParamType, "System.String[]" },
|
||||
{ typeof(NonGeneric.Nested1<bool, string>.Nested2),
|
||||
"Microsoft.AspNetCore.Mvc.Razor.Test.Internal.ViewComponentTagHelperDescriptorFactoryTest.NonGeneric.Nested1<global::System.Boolean, global::System.String>.Nested2" },
|
||||
{ typeof(GenericType<string, int>.GenericNestedType<bool, string>),
|
||||
"Microsoft.AspNetCore.Mvc.Razor.Test.Internal.ViewComponentTagHelperDescriptorFactoryTest.GenericType<global::System.String, global::System.Int32>.GenericNestedType<global::System.Boolean, global::System.String>" },
|
||||
{ typeof(GenericType<string, int>.NonGenericNested.MultiNestedType<bool, string>),
|
||||
"Microsoft.AspNetCore.Mvc.Razor.Test.Internal.ViewComponentTagHelperDescriptorFactoryTest.GenericType<global::System.String, global::System.Int32>.NonGenericNested.MultiNestedType<global::System.Boolean, global::System.String>" },
|
||||
{ typeof(Dictionary<GenericType<string, int>.NonGenericNested.MultiNestedType<bool, string>, List<string[]>>),
|
||||
"System.Collections.Generic.Dictionary<global::Microsoft.AspNetCore.Mvc.Razor.Test.Internal.ViewComponentTagHelperDescriptorFactoryTest.GenericType<global::System.String, global::System.Int32>.NonGenericNested.MultiNestedType<global::System.Boolean, global::System.String>, global::System.Collections.Generic.List<global::System.String[]>>" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TypeData))]
|
||||
public void GetCSharpTypeName_ReturnsCorrectTypeNames(Type type, string expected)
|
||||
{
|
||||
// Act
|
||||
var typeName = ViewComponentTagHelperDescriptorFactory.GetCSharpTypeName(type);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, typeName);
|
||||
}
|
||||
|
||||
// Test invokes are needed for method creation in TestViewComponentDescriptorProvider.
|
||||
public enum TestEnum
|
||||
{
|
||||
|
|
@ -70,6 +113,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.Internal
|
|||
{
|
||||
}
|
||||
|
||||
public void InvokeWithOpenGeneric<T>(List<T> baz)
|
||||
{
|
||||
}
|
||||
|
||||
private class TestViewComponentDescriptorProvider : IViewComponentDescriptorProvider
|
||||
{
|
||||
private readonly ViewComponentDescriptor _viewComponentDescriptorOne = new ViewComponentDescriptor
|
||||
|
|
@ -108,6 +155,18 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.Internal
|
|||
.GetMethod(nameof(ViewComponentTagHelperDescriptorFactoryTest.InvokeWithGenericParams)).GetParameters()
|
||||
};
|
||||
|
||||
private readonly ViewComponentDescriptor _viewComponentDescriptorOpenGeneric = new ViewComponentDescriptor
|
||||
{
|
||||
DisplayName = "OpenGenericDisplayName",
|
||||
FullName = "OpenGenericViewComponent",
|
||||
ShortName = "OpenGeneric",
|
||||
MethodInfo = typeof(ViewComponentTagHelperDescriptorFactoryTest)
|
||||
.GetMethod(nameof(ViewComponentTagHelperDescriptorFactoryTest.InvokeWithOpenGeneric)),
|
||||
TypeInfo = typeof(ViewComponentTagHelperDescriptorFactoryTest).GetTypeInfo(),
|
||||
Parameters = typeof(ViewComponentTagHelperDescriptorFactoryTest)
|
||||
.GetMethod(nameof(ViewComponentTagHelperDescriptorFactoryTest.InvokeWithOpenGeneric)).GetParameters()
|
||||
};
|
||||
|
||||
public TagHelperDescriptor GetTagHelperDescriptorOne()
|
||||
{
|
||||
var descriptor = new TagHelperDescriptor
|
||||
|
|
@ -201,15 +260,91 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.Internal
|
|||
return descriptor;
|
||||
}
|
||||
|
||||
public TagHelperDescriptor GetTagHelperDescriptorGeneric()
|
||||
{
|
||||
var descriptor = new TagHelperDescriptor
|
||||
{
|
||||
TagName = "vc:generic",
|
||||
TypeName = "__Generated__GenericViewComponentTagHelper",
|
||||
AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Test",
|
||||
Attributes = new List<TagHelperAttributeDescriptor>
|
||||
{
|
||||
new TagHelperAttributeDescriptor
|
||||
{
|
||||
Name = "foo",
|
||||
PropertyName = "Foo",
|
||||
TypeName = "System.Collections.Generic.List<global::System.String>"
|
||||
},
|
||||
|
||||
new TagHelperAttributeDescriptor
|
||||
{
|
||||
Name = "bar",
|
||||
PropertyName = "Bar",
|
||||
TypeName = "System.Collections.Generic.Dictionary<global::System.String, global::System.Int32>"
|
||||
}
|
||||
},
|
||||
RequiredAttributes = new List<TagHelperRequiredAttributeDescriptor>
|
||||
{
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "foo"
|
||||
},
|
||||
|
||||
new TagHelperRequiredAttributeDescriptor
|
||||
{
|
||||
Name = "bar"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
descriptor.PropertyBag.Add(ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey, "Generic");
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public IEnumerable<ViewComponentDescriptor> GetViewComponents()
|
||||
{
|
||||
return new List<ViewComponentDescriptor>
|
||||
{
|
||||
_viewComponentDescriptorOne,
|
||||
_viewComponentDescriptorTwo,
|
||||
_viewComponentDescriptorGeneric
|
||||
_viewComponentDescriptorGeneric,
|
||||
_viewComponentDescriptorOpenGeneric
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void MethodWithOutParam(out List<char*[]> foo)
|
||||
{
|
||||
foo = null;
|
||||
}
|
||||
|
||||
public void MethodWithRefParam(ref string[] bar)
|
||||
{
|
||||
}
|
||||
|
||||
private class GenericType<T1, T2>
|
||||
{
|
||||
public class GenericNestedType<T3, T4>
|
||||
{
|
||||
}
|
||||
|
||||
public class NonGenericNested
|
||||
{
|
||||
public class MultiNestedType<T5, T6>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NonGeneric
|
||||
{
|
||||
public class Nested1<T1, T2>
|
||||
{
|
||||
public class Nested2
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace TagHelpersWebSite.Components
|
||||
{
|
||||
public class GenericViewComponent : ViewComponent
|
||||
{
|
||||
public IViewComponentResult Invoke(Dictionary<string, List<string>> items)
|
||||
{
|
||||
return View(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
@addTagHelper "*, TagHelpersWebSite"
|
||||
@{
|
||||
var year = 2016;
|
||||
var dict = new Dictionary<string, List<string>>();
|
||||
var items = new List<string>() { "One", "Two", "Three" };
|
||||
dict.Add("Foo", items);
|
||||
}
|
||||
|
||||
<vc:generic items="dict"></vc:generic>
|
||||
<vc:dan jacket-color="Green" /><br />
|
||||
<div>
|
||||
<vc:copyright website="example.com" year="year" bold></vc:copyright>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
@model Dictionary<string, List<string>>
|
||||
|
||||
<div>Items: </div>
|
||||
<div>
|
||||
@foreach (var item in Model)
|
||||
{
|
||||
<strong>@item.Key</strong><br/>
|
||||
@foreach (var value in Model[item.Key])
|
||||
{
|
||||
<span>@value</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
Loading…
Reference in New Issue