[Fixes #5166] Support passing instance directly when invoking ViewComponents with single parameter
This commit is contained in:
parent
9ed753288f
commit
a6a4b5369a
|
|
@ -53,12 +53,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
|
||||
private static ViewComponentDescriptor CreateDescriptor(TypeInfo typeInfo)
|
||||
{
|
||||
var methodInfo = FindMethod(typeInfo.AsType());
|
||||
var candidate = new ViewComponentDescriptor
|
||||
{
|
||||
FullName = ViewComponentConventions.GetComponentFullName(typeInfo),
|
||||
ShortName = ViewComponentConventions.GetComponentName(typeInfo),
|
||||
TypeInfo = typeInfo,
|
||||
MethodInfo = FindMethod(typeInfo.AsType())
|
||||
MethodInfo = methodInfo,
|
||||
Parameters = methodInfo.GetParameters()
|
||||
};
|
||||
|
||||
return candidate;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
// 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 System.Runtime.CompilerServices;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
|
|
@ -130,19 +132,28 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
componentType.FullName));
|
||||
}
|
||||
|
||||
private async Task<IHtmlContent> InvokeCoreAsync(
|
||||
ViewComponentDescriptor descriptor,
|
||||
object arguments)
|
||||
// Internal for testing
|
||||
internal IDictionary<string, object> GetArgumentDictionary(ViewComponentDescriptor descriptor, object arguments)
|
||||
{
|
||||
if (descriptor.Parameters.Count == 1 && descriptor.Parameters[0].ParameterType.IsAssignableFrom(arguments.GetType()))
|
||||
{
|
||||
return new Dictionary<string, object>(capacity: 1, comparer: StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ descriptor.Parameters[0].Name, arguments }
|
||||
};
|
||||
}
|
||||
|
||||
return PropertyHelper.ObjectToDictionary(arguments);
|
||||
}
|
||||
|
||||
private async Task<IHtmlContent> InvokeCoreAsync(ViewComponentDescriptor descriptor, object arguments)
|
||||
{
|
||||
var argumentDictionary = GetArgumentDictionary(descriptor, arguments);
|
||||
|
||||
var viewBuffer = new ViewBuffer(_viewBufferScope, descriptor.FullName, ViewBuffer.ViewComponentPageSize);
|
||||
using (var writer = new ViewBufferTextWriter(viewBuffer, _viewContext.Writer.Encoding))
|
||||
{
|
||||
var context = new ViewComponentContext(
|
||||
descriptor,
|
||||
PropertyHelper.ObjectToDictionary(arguments),
|
||||
_htmlEncoder,
|
||||
_viewContext,
|
||||
writer);
|
||||
var context = new ViewComponentContext(descriptor, argumentDictionary, _htmlEncoder, _viewContext, writer);
|
||||
|
||||
var invoker = _invokerFactory.CreateInstance(context);
|
||||
if (invoker == null)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
|
|
@ -124,5 +126,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
/// Gets or sets the <see cref="System.Reflection.MethodInfo"/> to invoke.
|
||||
/// </summary>
|
||||
public MethodInfo MethodInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parameters associated with the method described by <see cref="MethodInfo"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ParameterInfo> Parameters { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -55,12 +55,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public async Task ExecuteAsync_ViewComponentResult_AllowsNullViewDataAndTempData()
|
||||
{
|
||||
// Arrange
|
||||
var methodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke));
|
||||
var descriptor = new ViewComponentDescriptor()
|
||||
{
|
||||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
TypeInfo = typeof(TextViewComponent).GetTypeInfo(),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
MethodInfo = methodInfo,
|
||||
Parameters = methodInfo.GetParameters(),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
@ -146,12 +148,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public async Task ExecuteResultAsync_ExecutesSyncViewComponent()
|
||||
{
|
||||
// Arrange
|
||||
var methodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke));
|
||||
var descriptor = new ViewComponentDescriptor()
|
||||
{
|
||||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
TypeInfo = typeof(TextViewComponent).GetTypeInfo(),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
MethodInfo = methodInfo,
|
||||
Parameters = methodInfo.GetParameters(),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
@ -175,12 +179,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public async Task ExecuteResultAsync_UsesDictionaryArguments()
|
||||
{
|
||||
// Arrange
|
||||
var methodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke));
|
||||
var descriptor = new ViewComponentDescriptor()
|
||||
{
|
||||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
TypeInfo = typeof(TextViewComponent).GetTypeInfo(),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
MethodInfo = methodInfo,
|
||||
Parameters = methodInfo.GetParameters(),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
@ -204,12 +210,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public async Task ExecuteResultAsync_ExecutesAsyncViewComponent()
|
||||
{
|
||||
// Arrange
|
||||
var methodInfo = typeof(AsyncTextViewComponent).GetMethod(nameof(AsyncTextViewComponent.InvokeAsync));
|
||||
var descriptor = new ViewComponentDescriptor()
|
||||
{
|
||||
FullName = "Full.Name.AsyncText",
|
||||
ShortName = "AsyncText",
|
||||
TypeInfo = typeof(AsyncTextViewComponent).GetTypeInfo(),
|
||||
MethodInfo = typeof(AsyncTextViewComponent).GetMethod(nameof(AsyncTextViewComponent.InvokeAsync)),
|
||||
MethodInfo = methodInfo,
|
||||
Parameters = methodInfo.GetParameters(),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
@ -233,12 +241,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public async Task ExecuteResultAsync_ExecutesViewComponent_AndWritesDiagnosticSource()
|
||||
{
|
||||
// Arrange
|
||||
var methodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke));
|
||||
var descriptor = new ViewComponentDescriptor()
|
||||
{
|
||||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
TypeInfo = typeof(TextViewComponent).GetTypeInfo(),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
MethodInfo = methodInfo,
|
||||
Parameters = methodInfo.GetParameters(),
|
||||
};
|
||||
|
||||
var adapter = new TestDiagnosticListener();
|
||||
|
|
@ -272,12 +282,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public async Task ExecuteResultAsync_ExecutesViewComponent_ByShortName()
|
||||
{
|
||||
// Arrange
|
||||
var methodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke));
|
||||
var descriptor = new ViewComponentDescriptor()
|
||||
{
|
||||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
TypeInfo = typeof(TextViewComponent).GetTypeInfo(),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
MethodInfo = methodInfo,
|
||||
Parameters = methodInfo.GetParameters(),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
@ -301,12 +313,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public async Task ExecuteResultAsync_ExecutesViewComponent_ByFullName()
|
||||
{
|
||||
// Arrange
|
||||
var methodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke));
|
||||
var descriptor = new ViewComponentDescriptor()
|
||||
{
|
||||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
TypeInfo = typeof(TextViewComponent).GetTypeInfo(),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
MethodInfo = methodInfo,
|
||||
Parameters = methodInfo.GetParameters(),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
@ -330,12 +344,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public async Task ExecuteResultAsync_ExecutesViewComponent_ByType()
|
||||
{
|
||||
// Arrange
|
||||
var methodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke));
|
||||
var descriptor = new ViewComponentDescriptor()
|
||||
{
|
||||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
TypeInfo = typeof(TextViewComponent).GetTypeInfo(),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
MethodInfo = methodInfo,
|
||||
Parameters = methodInfo.GetParameters(),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
@ -359,12 +375,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public async Task ExecuteResultAsync_SetsStatusCode()
|
||||
{
|
||||
// Arrange
|
||||
var methodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke));
|
||||
var descriptor = new ViewComponentDescriptor()
|
||||
{
|
||||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
TypeInfo = typeof(TextViewComponent).GetTypeInfo(),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke))
|
||||
MethodInfo = methodInfo,
|
||||
Parameters = methodInfo.GetParameters(),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
@ -417,12 +435,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
string expectedContentType)
|
||||
{
|
||||
// Arrange
|
||||
var methodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke));
|
||||
var descriptor = new ViewComponentDescriptor()
|
||||
{
|
||||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
TypeInfo = typeof(TextViewComponent).GetTypeInfo(),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
MethodInfo = methodInfo,
|
||||
Parameters = methodInfo.GetParameters(),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
@ -455,12 +475,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public async Task ViewComponentResult_SetsContentTypeHeader_OverrideResponseContentType()
|
||||
{
|
||||
// Arrange
|
||||
var methodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke));
|
||||
var descriptor = new ViewComponentDescriptor()
|
||||
{
|
||||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
TypeInfo = typeof(TextViewComponent).GetTypeInfo(),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
MethodInfo = methodInfo,
|
||||
Parameters = methodInfo.GetParameters(),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
@ -492,12 +514,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
string expectedContentType)
|
||||
{
|
||||
// Arrange
|
||||
var methodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke));
|
||||
var descriptor = new ViewComponentDescriptor()
|
||||
{
|
||||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
TypeInfo = typeof(TextViewComponent).GetTypeInfo(),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
MethodInfo = methodInfo,
|
||||
Parameters = methodInfo.GetParameters(),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,198 @@
|
|||
// 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.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||
using Microsoft.Extensions.WebEncoders.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||
{
|
||||
public class DefaultViewComponentHelperTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetArgumentDictionary_SupportsAnonymouslyTypedArguments()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var descriptor = CreateDescriptorForType(typeof(ViewComponentSingleParam));
|
||||
|
||||
// Act
|
||||
var argumentDictionary = helper.GetArgumentDictionary(descriptor, new { a = 0 });
|
||||
|
||||
// Assert
|
||||
Assert.Collection(argumentDictionary,
|
||||
item =>
|
||||
{
|
||||
Assert.Equal("a", item.Key);
|
||||
Assert.IsType(typeof(int), item.Value);
|
||||
Assert.Equal(0, item.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetArgumentDictionary_SingleParameter_DoesNotNeedAnonymouslyTypedArguments()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var descriptor = CreateDescriptorForType(typeof(ViewComponentSingleParam));
|
||||
|
||||
// Act
|
||||
var argumentDictionary = helper.GetArgumentDictionary(descriptor, 0);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(argumentDictionary,
|
||||
item =>
|
||||
{
|
||||
Assert.Equal("a", item.Key);
|
||||
Assert.IsType(typeof(int), item.Value);
|
||||
Assert.Equal(0, item.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetArgumentDictionary_MultipleParameters_NeedsAnonymouslyTypedArguments()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var descriptor = CreateDescriptorForType(typeof(ViewComponentMultipleParam));
|
||||
|
||||
// Act
|
||||
var argumentDictionary = helper.GetArgumentDictionary(descriptor, new { a = 0, b = "foo" });
|
||||
|
||||
// Assert
|
||||
Assert.Collection(argumentDictionary,
|
||||
item1 =>
|
||||
{
|
||||
Assert.Equal("a", item1.Key);
|
||||
Assert.IsType(typeof(int), item1.Value);
|
||||
Assert.Equal(0, item1.Value);
|
||||
},
|
||||
item2 =>
|
||||
{
|
||||
Assert.Equal("b", item2.Key);
|
||||
Assert.IsType(typeof(string), item2.Value);
|
||||
Assert.Equal("foo", item2.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetArgumentDictionary_SingleObjectParameter_DoesNotNeedAnonymouslyTypedArguments()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var descriptor = CreateDescriptorForType(typeof(ViewComponentObjectParam));
|
||||
var expectedValue = new object();
|
||||
|
||||
// Act
|
||||
var argumentDictionary = helper.GetArgumentDictionary(descriptor, expectedValue);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(argumentDictionary,
|
||||
item =>
|
||||
{
|
||||
Assert.Equal("o", item.Key);
|
||||
Assert.IsType(typeof(object), item.Value);
|
||||
Assert.Same(expectedValue, item.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetArgumentDictionary_SingleParameter_AcceptsDictionaryType()
|
||||
{
|
||||
// Arrange
|
||||
var helper = CreateHelper();
|
||||
var descriptor = CreateDescriptorForType(typeof(ViewComponentSingleParam));
|
||||
var arguments = new Dictionary<string, object>
|
||||
{
|
||||
{ "a", 10 }
|
||||
};
|
||||
|
||||
// Act
|
||||
var argumentDictionary = helper.GetArgumentDictionary(descriptor, arguments);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(argumentDictionary,
|
||||
item =>
|
||||
{
|
||||
Assert.Equal("a", item.Key);
|
||||
Assert.IsType(typeof(int), item.Value);
|
||||
Assert.Equal(10, item.Value);
|
||||
});
|
||||
}
|
||||
|
||||
private DefaultViewComponentHelper CreateHelper()
|
||||
{
|
||||
var descriptorCollectionProvider = Mock.Of<IViewComponentDescriptorCollectionProvider>();
|
||||
var selector = Mock.Of<IViewComponentSelector>();
|
||||
var invokerFactory = Mock.Of<IViewComponentInvokerFactory>();
|
||||
var viewBufferScope = Mock.Of<IViewBufferScope>();
|
||||
|
||||
return new DefaultViewComponentHelper(
|
||||
descriptorCollectionProvider,
|
||||
new HtmlTestEncoder(),
|
||||
selector,
|
||||
invokerFactory,
|
||||
viewBufferScope);
|
||||
}
|
||||
|
||||
private ViewComponentDescriptor CreateDescriptorForType(Type componentType)
|
||||
{
|
||||
var provider = CreateProvider(componentType);
|
||||
return provider.GetViewComponents().First();
|
||||
}
|
||||
|
||||
private class ViewComponentSingleParam
|
||||
{
|
||||
public IViewComponentResult Invoke(int a) => null;
|
||||
}
|
||||
|
||||
private class ViewComponentMultipleParam
|
||||
{
|
||||
public IViewComponentResult Invoke(int a, string b) => null;
|
||||
}
|
||||
|
||||
private class ViewComponentObjectParam
|
||||
{
|
||||
public IViewComponentResult Invoke(object o) => null;
|
||||
}
|
||||
|
||||
private DefaultViewComponentDescriptorProvider CreateProvider(Type componentType)
|
||||
{
|
||||
return new FilteredViewComponentDescriptorProvider(componentType);
|
||||
}
|
||||
|
||||
// This will only consider types nested inside this class as ViewComponent classes
|
||||
private class FilteredViewComponentDescriptorProvider : DefaultViewComponentDescriptorProvider
|
||||
{
|
||||
public FilteredViewComponentDescriptorProvider(params Type[] allowedTypes)
|
||||
: base(GetApplicationPartManager(allowedTypes.Select(t => t.GetTypeInfo())))
|
||||
{
|
||||
}
|
||||
|
||||
private static ApplicationPartManager GetApplicationPartManager(IEnumerable<TypeInfo> types)
|
||||
{
|
||||
var manager = new ApplicationPartManager();
|
||||
manager.ApplicationParts.Add(new TestApplicationPart(types));
|
||||
manager.FeatureProviders.Add(new TestFeatureProvider());
|
||||
return manager;
|
||||
}
|
||||
|
||||
private class TestFeatureProvider : IApplicationFeatureProvider<ViewComponentFeature>
|
||||
{
|
||||
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewComponentFeature feature)
|
||||
{
|
||||
foreach (var type in parts.OfType<IApplicationPartTypeProvider>().SelectMany(p => p.Types))
|
||||
{
|
||||
feature.ViewComponents.Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
@model Person
|
||||
<h1>@Model.Name</h1>
|
||||
@await Component.InvokeAsync("InheritingViewComponent", new { address = Model.Address })
|
||||
@await Component.InvokeAsync("InheritingViewComponent", Model.Address)
|
||||
|
|
@ -12,4 +12,4 @@
|
|||
}
|
||||
ViewWithRelativePath-content
|
||||
<partial>@await Html.PartialAsync("../Shared/_PartialThatSetsTitle.cshtml")</partial>
|
||||
@await Component.InvokeAsync("ComponentWithRelativePath", new { person })
|
||||
@await Component.InvokeAsync("ComponentWithRelativePath", person)
|
||||
Loading…
Reference in New Issue