Allow Invoke \ InvokeAsync methods for a ViewComponent to be defined in base types
Fixes https://github.com/aspnet/Mvc/issues/8397
This commit is contained in:
parent
060698a52f
commit
fc86cc3ca1
|
|
@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
|||
var descriptorBuilder = TagHelperDescriptorBuilder.Create(ViewComponentTagHelperConventions.Kind, typeName, assemblyName);
|
||||
descriptorBuilder.SetTypeName(typeName);
|
||||
descriptorBuilder.DisplayName = displayName;
|
||||
|
||||
|
||||
if (TryFindInvokeMethod(type, out var method, out var diagnostic))
|
||||
{
|
||||
var methodParameters = method.Parameters;
|
||||
|
|
@ -84,21 +84,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
|||
|
||||
private bool TryFindInvokeMethod(INamedTypeSymbol type, out IMethodSymbol method, out RazorDiagnostic diagnostic)
|
||||
{
|
||||
var methods = type.GetMembers()
|
||||
.OfType<IMethodSymbol>()
|
||||
.Where(m =>
|
||||
m.DeclaredAccessibility == Accessibility.Public &&
|
||||
(string.Equals(m.Name, ViewComponentTypes.AsyncMethodName, StringComparison.Ordinal) ||
|
||||
string.Equals(m.Name, ViewComponentTypes.SyncMethodName, StringComparison.Ordinal)))
|
||||
.ToArray();
|
||||
var methods = GetInvokeMethods(type);
|
||||
|
||||
if (methods.Length == 0)
|
||||
if (methods.Count == 0)
|
||||
{
|
||||
diagnostic = RazorExtensionsDiagnosticFactory.CreateViewComponent_CannotFindMethod(type.ToDisplayString(FullNameTypeDisplayFormat));
|
||||
diagnostic = RazorExtensionsDiagnosticFactory.CreateViewComponent_CannotFindMethod(type.ToDisplayString(FullNameTypeDisplayFormat));
|
||||
method = null;
|
||||
return false;
|
||||
}
|
||||
else if (methods.Length > 1)
|
||||
else if (methods.Count > 1)
|
||||
{
|
||||
diagnostic = RazorExtensionsDiagnosticFactory.CreateViewComponent_AmbiguousMethods(type.ToDisplayString(FullNameTypeDisplayFormat));
|
||||
method = null;
|
||||
|
|
@ -153,6 +147,27 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
|||
return true;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<IMethodSymbol> GetInvokeMethods(INamedTypeSymbol type)
|
||||
{
|
||||
var methods = new List<IMethodSymbol>();
|
||||
while (type != null)
|
||||
{
|
||||
var currentTypeMethods = type.GetMembers()
|
||||
.OfType<IMethodSymbol>()
|
||||
.Where(m =>
|
||||
m.DeclaredAccessibility == Accessibility.Public &&
|
||||
!m.IsStatic &&
|
||||
(string.Equals(m.Name, ViewComponentTypes.AsyncMethodName, StringComparison.Ordinal) ||
|
||||
string.Equals(m.Name, ViewComponentTypes.SyncMethodName, StringComparison.Ordinal)));
|
||||
|
||||
methods.AddRange(currentTypeMethods);
|
||||
|
||||
type = type.BaseType;
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
private void AddRequiredAttributes(ImmutableArray<IParameterSymbol> methodParameters, TagMatchingRuleDescriptorBuilder builder)
|
||||
{
|
||||
foreach (var parameter in methodParameters)
|
||||
|
|
@ -164,7 +179,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
|||
builder.Attribute(attributeBuilder =>
|
||||
{
|
||||
var lowerKebabName = HtmlConventions.ToHtmlCase(parameter.Name);
|
||||
attributeBuilder.Name =lowerKebabName;
|
||||
attributeBuilder.Name = lowerKebabName;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,32 +59,81 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
return false;
|
||||
}
|
||||
|
||||
return descriptorX != null &&
|
||||
string.Equals(descriptorX.Kind, descriptorY.Kind, StringComparison.Ordinal) &&
|
||||
string.Equals(descriptorX.AssemblyName, descriptorY.AssemblyName, StringComparison.Ordinal) &&
|
||||
string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.Ordinal) &&
|
||||
Enumerable.SequenceEqual(
|
||||
descriptorX.BoundAttributes.OrderBy(attribute => attribute.Name, _stringComparer),
|
||||
descriptorY.BoundAttributes.OrderBy(attribute => attribute.Name, _stringComparer),
|
||||
_boundAttributeComparer) &&
|
||||
Enumerable.SequenceEqual(
|
||||
descriptorX.TagMatchingRules.OrderBy(rule => rule.TagName, _stringComparer),
|
||||
descriptorY.TagMatchingRules.OrderBy(rule => rule.TagName, _stringComparer),
|
||||
_tagMatchingRuleComparer) &&
|
||||
(descriptorX.AllowedChildTags == descriptorY.AllowedChildTags ||
|
||||
if (descriptorX == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(descriptorX.Kind, descriptorY.Kind, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(descriptorX.AssemblyName, descriptorY.AssemblyName, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Enumerable.SequenceEqual(
|
||||
descriptorX.BoundAttributes.OrderBy(attribute => attribute.Name, _stringComparer),
|
||||
descriptorY.BoundAttributes.OrderBy(attribute => attribute.Name, _stringComparer),
|
||||
_boundAttributeComparer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Enumerable.SequenceEqual(
|
||||
descriptorX.TagMatchingRules.OrderBy(rule => rule.TagName, _stringComparer),
|
||||
descriptorY.TagMatchingRules.OrderBy(rule => rule.TagName, _stringComparer),
|
||||
_tagMatchingRuleComparer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(descriptorX.AllowedChildTags == descriptorY.AllowedChildTags ||
|
||||
(descriptorX.AllowedChildTags != null &&
|
||||
descriptorY.AllowedChildTags != null &&
|
||||
Enumerable.SequenceEqual(
|
||||
descriptorX.AllowedChildTags.OrderBy(childTag => childTag.Name, _stringComparer),
|
||||
descriptorY.AllowedChildTags.OrderBy(childTag => childTag.Name, _stringComparer),
|
||||
_AllowedChildTagDescriptorComparer))) &&
|
||||
string.Equals(descriptorX.Documentation, descriptorY.Documentation, StringComparison.Ordinal) &&
|
||||
string.Equals(descriptorX.DisplayName, descriptorY.DisplayName, StringComparison.Ordinal) &&
|
||||
string.Equals(descriptorX.TagOutputHint, descriptorY.TagOutputHint, _stringComparison) &&
|
||||
Enumerable.SequenceEqual(descriptorX.Diagnostics, descriptorY.Diagnostics) &&
|
||||
Enumerable.SequenceEqual(
|
||||
descriptorX.Metadata.OrderBy(metadataX => metadataX.Key, StringComparer.Ordinal),
|
||||
descriptorY.Metadata.OrderBy(metadataY => metadataY.Key, StringComparer.Ordinal));
|
||||
_AllowedChildTagDescriptorComparer))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(descriptorX.Documentation, descriptorY.Documentation, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(descriptorX.DisplayName, descriptorY.DisplayName, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(descriptorX.TagOutputHint, descriptorY.TagOutputHint, _stringComparison))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Enumerable.SequenceEqual(descriptorX.Diagnostics, descriptorY.Diagnostics))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Enumerable.SequenceEqual(
|
||||
descriptorX.Metadata.OrderBy(metadataX => metadataX.Key, StringComparer.Ordinal),
|
||||
descriptorY.Metadata.OrderBy(metadataY => metadataY.Key, StringComparer.Ordinal)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
|
|
@ -145,6 +145,74 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
|||
Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_ForSyncViewComponentWithInvokeInBaseType_Works()
|
||||
{
|
||||
// Arrange
|
||||
var testCompilation = TestCompilation.Create(_assembly);
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
|
||||
|
||||
var expectedDescriptor = TagHelperDescriptorBuilder.Create(
|
||||
ViewComponentTagHelperConventions.Kind,
|
||||
"__Generated__SyncDerivedViewComponentTagHelper",
|
||||
typeof(SyncDerivedViewComponent).GetTypeInfo().Assembly.GetName().Name)
|
||||
.TypeName("__Generated__SyncDerivedViewComponentTagHelper")
|
||||
.DisplayName("SyncDerivedViewComponentTagHelper")
|
||||
.TagMatchingRuleDescriptor(rule =>
|
||||
rule
|
||||
.RequireTagName("vc:sync-derived")
|
||||
.RequireAttributeDescriptor(attribute => attribute.Name("foo"))
|
||||
.RequireAttributeDescriptor(attribute => attribute.Name("bar")))
|
||||
.BoundAttributeDescriptor(attribute =>
|
||||
attribute
|
||||
.Name("foo")
|
||||
.PropertyName("foo")
|
||||
.TypeName(typeof(string).FullName)
|
||||
.DisplayName("string SyncDerivedViewComponentTagHelper.foo"))
|
||||
.BoundAttributeDescriptor(attribute =>
|
||||
attribute
|
||||
.Name("bar")
|
||||
.PropertyName("bar")
|
||||
.TypeName(typeof(string).FullName)
|
||||
.DisplayName("string SyncDerivedViewComponentTagHelper.bar"))
|
||||
.AddMetadata(ViewComponentTagHelperMetadata.Name, "SyncDerived")
|
||||
.Build();
|
||||
|
||||
var viewComponent = testCompilation.GetTypeByMetadataName(typeof(SyncDerivedViewComponent).FullName);
|
||||
|
||||
// Act
|
||||
var descriptor = factory.CreateDescriptor(viewComponent);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_ForAsyncViewComponentWithInvokeInBaseType_Works()
|
||||
{
|
||||
// Arrange
|
||||
var testCompilation = TestCompilation.Create(_assembly);
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
|
||||
|
||||
var expectedDescriptor = TagHelperDescriptorBuilder.Create(
|
||||
ViewComponentTagHelperConventions.Kind,
|
||||
"__Generated__AsyncDerivedViewComponentTagHelper",
|
||||
typeof(AsyncDerivedViewComponent).Assembly.GetName().Name)
|
||||
.TypeName("__Generated__AsyncDerivedViewComponentTagHelper")
|
||||
.DisplayName("AsyncDerivedViewComponentTagHelper")
|
||||
.TagMatchingRuleDescriptor(rule => rule.RequireTagName("vc:async-derived"))
|
||||
.AddMetadata(ViewComponentTagHelperMetadata.Name, "AsyncDerived")
|
||||
.Build();
|
||||
|
||||
var viewComponent = testCompilation.GetTypeByMetadataName(typeof(AsyncDerivedViewComponent).FullName);
|
||||
|
||||
// Act
|
||||
var descriptor = factory.CreateDescriptor(viewComponent);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_AddsDiagnostic_ForViewComponentWithNoInvokeMethod()
|
||||
{
|
||||
|
|
@ -162,6 +230,40 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
|||
Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_CannotFindMethod.Id, diagnostic.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_AddsDiagnostic_ForViewComponentWithNoInstanceInvokeMethod()
|
||||
{
|
||||
// Arrange
|
||||
var testCompilation = TestCompilation.Create(_assembly);
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
|
||||
|
||||
var viewComponent = testCompilation.GetTypeByMetadataName(typeof(StaticInvokeAsyncViewComponent).FullName);
|
||||
|
||||
// Act
|
||||
var descriptor = factory.CreateDescriptor(viewComponent);
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
|
||||
Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_CannotFindMethod.Id, diagnostic.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_AddsDiagnostic_ForViewComponentWithNoPublicInvokeMethod()
|
||||
{
|
||||
// Arrange
|
||||
var testCompilation = TestCompilation.Create(_assembly);
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
|
||||
|
||||
var viewComponent = testCompilation.GetTypeByMetadataName(typeof(NonPublicInvokeAsyncViewComponent).FullName);
|
||||
|
||||
// Act
|
||||
var descriptor = factory.CreateDescriptor(viewComponent);
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
|
||||
Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_CannotFindMethod.Id, diagnostic.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_ForViewComponentWithInvokeAsync_UnderstandsGenericTask()
|
||||
{
|
||||
|
|
@ -278,6 +380,23 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
|||
var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
|
||||
Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_SyncMethod_CannotReturnTask.Id, diagnostic.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_ForViewComponent_WithAmbiguousMethods()
|
||||
{
|
||||
// Arrange
|
||||
var testCompilation = TestCompilation.Create(_assembly);
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
|
||||
|
||||
var viewComponent = testCompilation.GetTypeByMetadataName(typeof(DerivedViewComponentWithAmbiguity).FullName);
|
||||
|
||||
// Act
|
||||
var descriptor = factory.CreateDescriptor(viewComponent);
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
|
||||
Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_AmbiguousMethods.Id, diagnostic.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public class StringParameterViewComponent
|
||||
|
|
@ -340,4 +459,27 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
|||
{
|
||||
public Task<string> Invoke() => null;
|
||||
}
|
||||
|
||||
public class SyncDerivedViewComponent : StringParameterViewComponent
|
||||
{
|
||||
}
|
||||
|
||||
public class AsyncDerivedViewComponent : AsyncViewComponentWithNonGenericTask
|
||||
{
|
||||
}
|
||||
|
||||
public class DerivedViewComponentWithAmbiguity : AsyncViewComponentWithNonGenericTask
|
||||
{
|
||||
public string Invoke() => null;
|
||||
}
|
||||
|
||||
public class StaticInvokeAsyncViewComponent
|
||||
{
|
||||
public static Task<string> InvokeAsync() => null;
|
||||
}
|
||||
|
||||
public class NonPublicInvokeAsyncViewComponent
|
||||
{
|
||||
protected Task<string> InvokeAsync() => null;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue