Modify IViewComponentHelper to remove method selection ambiguity.
Fixes #612
This commit is contained in:
parent
ce0e35ff75
commit
399e516065
|
|
@ -38,18 +38,5 @@ namespace TagHelperSample.Web.Components
|
||||||
|
|
||||||
return View(movies);
|
return View(movies);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IViewComponentResult Invoke(string movieName)
|
|
||||||
{
|
|
||||||
string quote;
|
|
||||||
if (!_cache.TryGetValue(movieName, out quote))
|
|
||||||
{
|
|
||||||
IChangeToken expirationToken;
|
|
||||||
quote = _moviesService.GetCriticsQuote(out expirationToken);
|
|
||||||
_cache.Set(movieName, quote, new MemoryCacheEntryOptions().AddExpirationToken(expirationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Content(quote);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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 Microsoft.AspNet.Mvc;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using TagHelperSample.Web.Services;
|
||||||
|
|
||||||
|
namespace TagHelperSample.Web.Components
|
||||||
|
{
|
||||||
|
[ViewComponent(Name = "Movies")]
|
||||||
|
public class MoviesComponent : ViewComponent
|
||||||
|
{
|
||||||
|
private readonly IMemoryCache _cache;
|
||||||
|
private readonly MoviesService _moviesService;
|
||||||
|
|
||||||
|
public MoviesComponent(MoviesService moviesService, IMemoryCache cache)
|
||||||
|
{
|
||||||
|
_moviesService = moviesService;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IViewComponentResult Invoke(string movieName)
|
||||||
|
{
|
||||||
|
string quote;
|
||||||
|
if (!_cache.TryGetValue(movieName, out quote))
|
||||||
|
{
|
||||||
|
IChangeToken expirationToken;
|
||||||
|
quote = _moviesService.GetCriticsQuote(out expirationToken);
|
||||||
|
_cache.Set(movieName, quote, new MemoryCacheEntryOptions().AddExpirationToken(expirationToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Content(quote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<cache expires-after="TimeSpan.FromMinutes(20)">
|
<cache expires-after="TimeSpan.FromMinutes(20)">
|
||||||
@Component.Invoke("FeaturedMovies")
|
@await Component.InvokeAsync("FeaturedMovies")
|
||||||
</cache>
|
</cache>
|
||||||
</div>
|
</div>
|
||||||
<div style="clear: left"></div>
|
<div style="clear: left"></div>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
</div>
|
</div>
|
||||||
<em>Critics say:</em>
|
<em>Critics say:</em>
|
||||||
<cache vary-by="@movie.Name">
|
<cache vary-by="@movie.Name">
|
||||||
@Component.Invoke("FeaturedMovies", movie.Name)
|
@await Component.InvokeAsync("Movies", new { movieName = movie.Name })
|
||||||
</cache>
|
</cache>
|
||||||
</dd>
|
</dd>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,22 +7,27 @@ using Microsoft.AspNet.Html;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Supports the rendering of view components in a view.
|
||||||
|
/// </summary>
|
||||||
public interface IViewComponentHelper
|
public interface IViewComponentHelper
|
||||||
{
|
{
|
||||||
IHtmlContent Invoke(string name, params object[] args);
|
/// <summary>
|
||||||
|
/// Invokes a view component with the specified <paramref name="name"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the view component.</param>
|
||||||
|
/// <param name="arguments">Arguments to be passed to the invoked view component method.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that on completion returns the rendered <see cref="IHtmlContent" />.
|
||||||
|
/// </returns>
|
||||||
|
Task<IHtmlContent> InvokeAsync(string name, object arguments);
|
||||||
|
|
||||||
IHtmlContent Invoke(Type componentType, params object[] args);
|
/// <summary>
|
||||||
|
/// Invokes a view component of type <paramref name="componentType" />.
|
||||||
void RenderInvoke(string name, params object[] args);
|
/// </summary>
|
||||||
|
/// <param name="componentType">The view component <see cref="Type"/>.</param>
|
||||||
void RenderInvoke(Type componentType, params object[] args);
|
/// <param name="arguments">Arguments to be passed to the invoked view component method.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that on completion returns the rendered <see cref="IHtmlContent" />.
|
||||||
Task<IHtmlContent> InvokeAsync(string name, params object[] args);
|
/// </returns>
|
||||||
|
Task<IHtmlContent> InvokeAsync(Type componentType, object arguments);
|
||||||
Task<IHtmlContent> InvokeAsync(Type componentType, params object[] args);
|
|
||||||
|
|
||||||
Task RenderInvokeAsync(string name, params object[] args);
|
|
||||||
|
|
||||||
Task RenderInvokeAsync(Type componentType, params object[] args);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,21 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Logging
|
||||||
{
|
{
|
||||||
public static class DefaultViewComponentInvokerLoggerExtensions
|
public static class DefaultViewComponentInvokerLoggerExtensions
|
||||||
{
|
{
|
||||||
private static readonly Action<ILogger, string, Exception> _viewComponentExecuting;
|
private static readonly string[] EmptyArguments =
|
||||||
|
#if NET451
|
||||||
|
new string[0];
|
||||||
|
#else
|
||||||
|
Array.Empty<string>();
|
||||||
|
#endif
|
||||||
|
private static readonly Action<ILogger, string, string[], Exception> _viewComponentExecuting;
|
||||||
private static readonly Action<ILogger, string, double, string, Exception> _viewComponentExecuted;
|
private static readonly Action<ILogger, string, double, string, Exception> _viewComponentExecuted;
|
||||||
|
|
||||||
static DefaultViewComponentInvokerLoggerExtensions()
|
static DefaultViewComponentInvokerLoggerExtensions()
|
||||||
{
|
{
|
||||||
_viewComponentExecuting = LoggerMessage.Define<string>(
|
_viewComponentExecuting = LoggerMessage.Define<string, string[]>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
1,
|
1,
|
||||||
"Executing view component {ViewComponentName}");
|
"Executing view component {ViewComponentName} with arguments ({Arguments}).");
|
||||||
|
|
||||||
_viewComponentExecuted = LoggerMessage.Define<string, double, string>(
|
_viewComponentExecuted = LoggerMessage.Define<string, double, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
|
|
@ -32,9 +38,29 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Logging
|
||||||
return logger.BeginScopeImpl(new ViewComponentLogScope(context.ViewComponentDescriptor));
|
return logger.BeginScopeImpl(new ViewComponentLogScope(context.ViewComponentDescriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ViewComponentExecuting(this ILogger logger, ViewComponentContext context)
|
public static void ViewComponentExecuting(
|
||||||
|
this ILogger logger,
|
||||||
|
ViewComponentContext context,
|
||||||
|
object[] arguments)
|
||||||
{
|
{
|
||||||
_viewComponentExecuting(logger, context.ViewComponentDescriptor.DisplayName, null);
|
var formattedArguments = GetFormattedArguments(arguments);
|
||||||
|
_viewComponentExecuting(logger, context.ViewComponentDescriptor.DisplayName, formattedArguments, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] GetFormattedArguments(object[] arguments)
|
||||||
|
{
|
||||||
|
if (arguments == null || arguments.Length == 0)
|
||||||
|
{
|
||||||
|
return EmptyArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
var formattedArguments = new string[arguments.Length];
|
||||||
|
for (var i = 0; i < formattedArguments.Length; i++)
|
||||||
|
{
|
||||||
|
formattedArguments[i] = Convert.ToString(arguments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedArguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ViewComponentExecuted(
|
public static void ViewComponentExecuted(
|
||||||
|
|
|
||||||
|
|
@ -8,41 +8,29 @@ namespace Microsoft.AspNet.Mvc.Logging
|
||||||
{
|
{
|
||||||
public static class ViewComponentResultLoggerExtensions
|
public static class ViewComponentResultLoggerExtensions
|
||||||
{
|
{
|
||||||
private static readonly Action<ILogger, string, string[], Exception> _viewComponentResultExecuting;
|
private static readonly Action<ILogger, string, Exception> _viewComponentResultExecuting;
|
||||||
|
|
||||||
static ViewComponentResultLoggerExtensions()
|
static ViewComponentResultLoggerExtensions()
|
||||||
{
|
{
|
||||||
_viewComponentResultExecuting = LoggerMessage.Define<string, string[]>(
|
_viewComponentResultExecuting = LoggerMessage.Define<string>(
|
||||||
LogLevel.Information,
|
LogLevel.Information,
|
||||||
1,
|
1,
|
||||||
"Executing ViewComponentResult, running {ViewComponentName} with arguments ({Arguments}).");
|
"Executing ViewComponentResult, running {ViewComponentName}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ViewComponentResultExecuting(this ILogger logger, string viewComponentName, object[] arguments)
|
public static void ViewComponentResultExecuting(this ILogger logger, string viewComponentName)
|
||||||
{
|
{
|
||||||
if (logger.IsEnabled(LogLevel.Information))
|
if (logger.IsEnabled(LogLevel.Information))
|
||||||
{
|
{
|
||||||
var formattedArguments = new string[arguments.Length];
|
_viewComponentResultExecuting(logger, viewComponentName, null);
|
||||||
for (var i = 0; i < arguments.Length; i++)
|
|
||||||
{
|
|
||||||
formattedArguments[i] = Convert.ToString(arguments[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
_viewComponentResultExecuting(logger, viewComponentName, formattedArguments, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ViewComponentResultExecuting(this ILogger logger, Type viewComponentType, object[] arguments)
|
public static void ViewComponentResultExecuting(this ILogger logger, Type viewComponentType)
|
||||||
{
|
{
|
||||||
if (logger.IsEnabled(LogLevel.Information))
|
if (logger.IsEnabled(LogLevel.Information))
|
||||||
{
|
{
|
||||||
var formattedArguments = new string[arguments.Length];
|
_viewComponentResultExecuting(logger, viewComponentType.Name, null);
|
||||||
for (var i = 0; i < arguments.Length; i++)
|
|
||||||
{
|
|
||||||
formattedArguments[i] = Convert.ToString(arguments[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
_viewComponentResultExecuting(logger, viewComponentType.Name, formattedArguments, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
<DisabledCustomTools>.resx</DisabledCustomTools>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||||
<PropertyGroup Label="Globals">
|
<PropertyGroup Label="Globals">
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The async view component method '{0}' should be declared to return Task<T>.
|
/// Method '{0}' of view component '{1}' should be declared to return {2}<T>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string ViewComponent_AsyncMethod_ShouldReturnTask
|
internal static string ViewComponent_AsyncMethod_ShouldReturnTask
|
||||||
{
|
{
|
||||||
|
|
@ -35,11 +35,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The async view component method '{0}' should be declared to return Task<T>.
|
/// Method '{0}' of view component '{1}' should be declared to return {2}<T>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatViewComponent_AsyncMethod_ShouldReturnTask(object p0)
|
internal static string FormatViewComponent_AsyncMethod_ShouldReturnTask(object p0, object p1, object p2)
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AsyncMethod_ShouldReturnTask"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AsyncMethod_ShouldReturnTask"), p0, p1, p2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The view component method '{0}' should be declared to return a value.
|
/// Method '{0}' of view component '{1}' should be declared to return a value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string ViewComponent_SyncMethod_ShouldReturnValue
|
internal static string ViewComponent_SyncMethod_ShouldReturnValue
|
||||||
{
|
{
|
||||||
|
|
@ -67,11 +67,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The view component method '{0}' should be declared to return a value.
|
/// Method '{0}' of view component '{1}' should be declared to return a value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatViewComponent_SyncMethod_ShouldReturnValue(object p0)
|
internal static string FormatViewComponent_SyncMethod_ShouldReturnValue(object p0, object p1)
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_ShouldReturnValue"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_ShouldReturnValue"), p0, p1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -107,7 +107,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Could not find an '{0}' method matching the parameters.
|
/// Could not find an '{0}' or '{1}' method for the view component '{2}'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string ViewComponent_CannotFindMethod
|
internal static string ViewComponent_CannotFindMethod
|
||||||
{
|
{
|
||||||
|
|
@ -115,27 +115,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Could not find an '{0}' method matching the parameters.
|
/// Could not find an '{0}' or '{1}' method for the view component '{2}'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatViewComponent_CannotFindMethod(object p0)
|
internal static string FormatViewComponent_CannotFindMethod(object p0, object p1, object p2)
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod"), p0, p1, p2);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Could not find an '{0}' or '{1}' method matching the parameters.
|
|
||||||
/// </summary>
|
|
||||||
internal static string ViewComponent_CannotFindMethod_WithFallback
|
|
||||||
{
|
|
||||||
get { return GetString("ViewComponent_CannotFindMethod_WithFallback"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Could not find an '{0}' or '{1}' method matching the parameters.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatViewComponent_CannotFindMethod_WithFallback(object p0, object p1)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod_WithFallback"), p0, p1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -859,7 +843,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The view component method '{0}' cannot return a {1}.
|
/// Method '{0}' of view component '{1}' cannot return a {2}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string ViewComponent_SyncMethod_CannotReturnTask
|
internal static string ViewComponent_SyncMethod_CannotReturnTask
|
||||||
{
|
{
|
||||||
|
|
@ -867,11 +851,27 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The view component method '{0}' cannot return a {1}.
|
/// Method '{0}' of view component '{1}' cannot return a {2}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatViewComponent_SyncMethod_CannotReturnTask(object p0, object p1)
|
internal static string FormatViewComponent_SyncMethod_CannotReturnTask(object p0, object p1, object p2)
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_CannotReturnTask"), p0, p1);
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_CannotReturnTask"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// View component '{0}' must have exactly one public method named '{1}' or '{2}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ViewComponent_AmbiguousMethods
|
||||||
|
{
|
||||||
|
get { return GetString("ViewComponent_AmbiguousMethods"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// View component '{0}' must have exactly one public method named '{1}' or '{2}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatViewComponent_AmbiguousMethods(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AmbiguousMethods"), p0, p1, p2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
|
|
|
||||||
|
|
@ -7,48 +7,76 @@ using Microsoft.AspNet.Html;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Rendering
|
namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="IViewComponentHelper"/>.
|
||||||
|
/// </summary>
|
||||||
public static class ViewComponentHelperExtensions
|
public static class ViewComponentHelperExtensions
|
||||||
{
|
{
|
||||||
public static IHtmlContent Invoke<TComponent>(this IViewComponentHelper helper, params object[] args)
|
/// <summary>
|
||||||
|
/// Invokes a view component with the specified <paramref name="name"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the view component.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that on completion returns the rendered <see cref="IHtmlContent" />.
|
||||||
|
/// </returns>
|
||||||
|
public static Task<IHtmlContent> InvokeAsync(this IViewComponentHelper helper, string name)
|
||||||
{
|
{
|
||||||
if (helper == null)
|
if (helper == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(helper));
|
throw new ArgumentNullException(nameof(helper));
|
||||||
}
|
}
|
||||||
|
|
||||||
return helper.Invoke(typeof(TComponent), args);
|
return helper.InvokeAsync(name, arguments: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RenderInvoke<TComponent>(this IViewComponentHelper helper, params object[] args)
|
/// <summary>
|
||||||
|
/// Invokes a view component of type <paramref name="componentType" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="componentType">The view component <see cref="Type"/>.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that on completion returns the rendered <see cref="IHtmlContent" />.
|
||||||
|
/// </returns>
|
||||||
|
public static Task<IHtmlContent> InvokeAsync(this IViewComponentHelper helper, Type componentType)
|
||||||
{
|
{
|
||||||
if (helper == null)
|
if (helper == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(helper));
|
throw new ArgumentNullException(nameof(helper));
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.RenderInvoke(typeof(TComponent), args);
|
return helper.InvokeAsync(componentType, arguments: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<IHtmlContent> InvokeAsync<TComponent>(
|
/// <summary>
|
||||||
this IViewComponentHelper helper,
|
/// Invokes a view component of type <typeparam name="TComponent"/>.
|
||||||
params object[] args)
|
/// </summary>
|
||||||
|
/// <param name="helper">The <see cref="IViewComponentHelper"/>.</param>
|
||||||
|
/// <param name="arguments">Arguments to be passed to the invoked view component method.</param>
|
||||||
|
/// <typeparam name="TComponent">The <see cref="Type"/> of the view component.</typeparam>
|
||||||
|
/// <returns>A <see cref="Task"/> that on completion returns the rendered <see cref="IHtmlContent" />.
|
||||||
|
/// </returns>
|
||||||
|
public static Task<IHtmlContent> InvokeAsync<TComponent>(this IViewComponentHelper helper, object arguments)
|
||||||
{
|
{
|
||||||
if (helper == null)
|
if (helper == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(helper));
|
throw new ArgumentNullException(nameof(helper));
|
||||||
}
|
}
|
||||||
|
|
||||||
return helper.InvokeAsync(typeof(TComponent), args);
|
return helper.InvokeAsync(typeof(TComponent), arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task RenderInvokeAsync<TComponent>(this IViewComponentHelper helper, params object[] args)
|
/// <summary>
|
||||||
|
/// Invokes a view component of type <typeparam name="TComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="helper">The <see cref="IViewComponentHelper"/>.</param>
|
||||||
|
/// <typeparam name="TComponent">The <see cref="Type"/> of the view component.</typeparam>
|
||||||
|
/// <returns>A <see cref="Task"/> that on completion returns the rendered <see cref="IHtmlContent" />.
|
||||||
|
/// </returns>
|
||||||
|
public static Task<IHtmlContent> InvokeAsync<TComponent>(this IViewComponentHelper helper)
|
||||||
{
|
{
|
||||||
if (helper == null)
|
if (helper == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(helper));
|
throw new ArgumentNullException(nameof(helper));
|
||||||
}
|
}
|
||||||
|
|
||||||
return helper.RenderInvokeAsync(typeof(TComponent), args);
|
return helper.InvokeAsync(typeof(TComponent), arguments: null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
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
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
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
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
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
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -122,13 +122,13 @@
|
||||||
<comment>{1} is the newline character</comment>
|
<comment>{1} is the newline character</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="ViewComponent_AsyncMethod_ShouldReturnTask" xml:space="preserve">
|
<data name="ViewComponent_AsyncMethod_ShouldReturnTask" xml:space="preserve">
|
||||||
<value>The async view component method '{0}' should be declared to return Task<T>.</value>
|
<value>Method '{0}' of view component '{1}' should be declared to return {2}<T>.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ViewComponent_MustReturnValue" xml:space="preserve">
|
<data name="ViewComponent_MustReturnValue" xml:space="preserve">
|
||||||
<value>A view component must return a non-null value.</value>
|
<value>A view component must return a non-null value.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ViewComponent_SyncMethod_ShouldReturnValue" xml:space="preserve">
|
<data name="ViewComponent_SyncMethod_ShouldReturnValue" xml:space="preserve">
|
||||||
<value>The view component method '{0}' should be declared to return a value.</value>
|
<value>Method '{0}' of view component '{1}' should be declared to return a value.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ViewComponent_CannotFindComponent" xml:space="preserve">
|
<data name="ViewComponent_CannotFindComponent" xml:space="preserve">
|
||||||
<value>A view component named '{0}' could not be found.</value>
|
<value>A view component named '{0}' could not be found.</value>
|
||||||
|
|
@ -137,10 +137,7 @@
|
||||||
<value>An invoker could not be created for the view component '{0}'.</value>
|
<value>An invoker could not be created for the view component '{0}'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ViewComponent_CannotFindMethod" xml:space="preserve">
|
<data name="ViewComponent_CannotFindMethod" xml:space="preserve">
|
||||||
<value>Could not find an '{0}' method matching the parameters.</value>
|
<value>Could not find an '{0}' or '{1}' method for the view component '{2}'.</value>
|
||||||
</data>
|
|
||||||
<data name="ViewComponent_CannotFindMethod_WithFallback" xml:space="preserve">
|
|
||||||
<value>Could not find an '{0}' or '{1}' method matching the parameters.</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="ViewComponent_InvalidReturnValue" xml:space="preserve">
|
<data name="ViewComponent_InvalidReturnValue" xml:space="preserve">
|
||||||
<value>View components only support returning {0}, {1} or {2}.</value>
|
<value>View components only support returning {0}, {1} or {2}.</value>
|
||||||
|
|
@ -278,6 +275,9 @@
|
||||||
<value>The collection already contains an entry with key '{0}'.</value>
|
<value>The collection already contains an entry with key '{0}'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ViewComponent_SyncMethod_CannotReturnTask" xml:space="preserve">
|
<data name="ViewComponent_SyncMethod_CannotReturnTask" xml:space="preserve">
|
||||||
<value>The view component method '{0}' cannot return a {1}.</value>
|
<value>Method '{0}' of view component '{1}' cannot return a {2}.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewComponent_AmbiguousMethods" xml:space="preserve">
|
||||||
|
<value>View component '{0}' must have exactly one public method named '{1}' or '{2}'.</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Html;
|
||||||
using Microsoft.AspNet.Mvc.Internal;
|
using Microsoft.AspNet.Mvc.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Logging;
|
using Microsoft.AspNet.Mvc.Logging;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
@ -26,7 +28,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the arguments provided to the view component.
|
/// Gets or sets the arguments provided to the view component.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public object[] Arguments { get; set; }
|
public object Arguments { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response.
|
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response.
|
||||||
|
|
@ -77,6 +79,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||||
var logger = loggerFactory.CreateLogger<ViewComponentResult>();
|
var logger = loggerFactory.CreateLogger<ViewComponentResult>();
|
||||||
|
var htmlEncoder = services.GetRequiredService<HtmlEncoder>();
|
||||||
|
|
||||||
var viewData = ViewData;
|
var viewData = ViewData;
|
||||||
if (viewData == null)
|
if (viewData == null)
|
||||||
|
|
@ -119,23 +122,29 @@ namespace Microsoft.AspNet.Mvc
|
||||||
htmlHelperOptions);
|
htmlHelperOptions);
|
||||||
|
|
||||||
(viewComponentHelper as ICanHasViewContext)?.Contextualize(viewContext);
|
(viewComponentHelper as ICanHasViewContext)?.Contextualize(viewContext);
|
||||||
|
var result = await GetViewComponentResult(viewComponentHelper, logger);
|
||||||
|
|
||||||
if (ViewComponentType == null && ViewComponentName == null)
|
result.WriteTo(writer, htmlEncoder);
|
||||||
{
|
}
|
||||||
throw new InvalidOperationException(Resources.FormatViewComponentResult_NameOrTypeMustBeSet(
|
}
|
||||||
nameof(ViewComponentName),
|
|
||||||
nameof(ViewComponentType)));
|
private Task<IHtmlContent> GetViewComponentResult(IViewComponentHelper viewComponentHelper, ILogger logger)
|
||||||
}
|
{
|
||||||
else if (ViewComponentType == null)
|
if (ViewComponentType == null && ViewComponentName == null)
|
||||||
{
|
{
|
||||||
logger.ViewComponentResultExecuting(ViewComponentName, Arguments);
|
throw new InvalidOperationException(Resources.FormatViewComponentResult_NameOrTypeMustBeSet(
|
||||||
await viewComponentHelper.RenderInvokeAsync(ViewComponentName, Arguments);
|
nameof(ViewComponentName),
|
||||||
}
|
nameof(ViewComponentType)));
|
||||||
else
|
}
|
||||||
{
|
else if (ViewComponentType == null)
|
||||||
logger.ViewComponentResultExecuting(ViewComponentType, Arguments);
|
{
|
||||||
await viewComponentHelper.RenderInvokeAsync(ViewComponentType, Arguments);
|
logger.ViewComponentResultExecuting(ViewComponentName);
|
||||||
}
|
return viewComponentHelper.InvokeAsync(ViewComponentName, Arguments);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.ViewComponentResultExecuting(ViewComponentType);
|
||||||
|
return viewComponentHelper.InvokeAsync(ViewComponentType, Arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc.Infrastructure;
|
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||||
|
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
{
|
{
|
||||||
|
|
@ -14,6 +16,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DefaultViewComponentDescriptorProvider : IViewComponentDescriptorProvider
|
public class DefaultViewComponentDescriptorProvider : IViewComponentDescriptorProvider
|
||||||
{
|
{
|
||||||
|
private const string AsyncMethodName = "InvokeAsync";
|
||||||
|
private const string SyncMethodName = "Invoke";
|
||||||
private readonly IAssemblyProvider _assemblyProvider;
|
private readonly IAssemblyProvider _assemblyProvider;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -32,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
|
|
||||||
return types
|
return types
|
||||||
.Where(IsViewComponentType)
|
.Where(IsViewComponentType)
|
||||||
.Select(CreateCandidate);
|
.Select(CreateDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -47,11 +51,11 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether or not the given <see cref="TypeInfo"/> is a View Component class.
|
/// Determines whether or not the given <see cref="TypeInfo"/> is a view component class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="typeInfo">The <see cref="TypeInfo"/>.</param>
|
/// <param name="typeInfo">The <see cref="TypeInfo"/>.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// <c>true</c> if <paramref name="typeInfo"/>represents a View Component class, otherwise <c>false</c>.
|
/// <c>true</c> if <paramref name="typeInfo"/>represents a view component class, otherwise <c>false</c>.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
protected virtual bool IsViewComponentType(TypeInfo typeInfo)
|
protected virtual bool IsViewComponentType(TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
|
|
@ -63,16 +67,70 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
return ViewComponentConventions.IsComponent(typeInfo);
|
return ViewComponentConventions.IsComponent(typeInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ViewComponentDescriptor CreateCandidate(TypeInfo typeInfo)
|
private static ViewComponentDescriptor CreateDescriptor(TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
var candidate = new ViewComponentDescriptor()
|
var type = typeInfo.AsType();
|
||||||
|
var candidate = new ViewComponentDescriptor
|
||||||
{
|
{
|
||||||
FullName = ViewComponentConventions.GetComponentFullName(typeInfo),
|
FullName = ViewComponentConventions.GetComponentFullName(typeInfo),
|
||||||
ShortName = ViewComponentConventions.GetComponentName(typeInfo),
|
ShortName = ViewComponentConventions.GetComponentName(typeInfo),
|
||||||
Type = typeInfo.AsType(),
|
Type = type,
|
||||||
|
MethodInfo = FindMethod(type)
|
||||||
};
|
};
|
||||||
|
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static MethodInfo FindMethod(Type componentType)
|
||||||
|
{
|
||||||
|
var componentName = componentType.FullName;
|
||||||
|
var methods = componentType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||||
|
.Where(method =>
|
||||||
|
string.Equals(method.Name, AsyncMethodName, StringComparison.Ordinal) ||
|
||||||
|
string.Equals(method.Name, SyncMethodName, StringComparison.Ordinal))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (methods.Length == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatViewComponent_CannotFindMethod(SyncMethodName, AsyncMethodName, componentName));
|
||||||
|
}
|
||||||
|
else if (methods.Length > 1)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatViewComponent_AmbiguousMethods(componentName, AsyncMethodName, SyncMethodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedMethod = methods[0];
|
||||||
|
if (string.Equals(selectedMethod.Name, AsyncMethodName, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
if (!selectedMethod.ReturnType.GetTypeInfo().IsGenericType ||
|
||||||
|
selectedMethod.ReturnType.GetGenericTypeDefinition() != typeof(Task<>))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatViewComponent_AsyncMethod_ShouldReturnTask(
|
||||||
|
AsyncMethodName,
|
||||||
|
componentName,
|
||||||
|
nameof(Task)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (selectedMethod.ReturnType == typeof(void))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatViewComponent_SyncMethod_ShouldReturnValue(
|
||||||
|
SyncMethodName,
|
||||||
|
componentName));
|
||||||
|
}
|
||||||
|
else if (selectedMethod.ReturnType.IsAssignableFrom(typeof(Task)))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatViewComponent_SyncMethod_CannotReturnTask(
|
||||||
|
SyncMethodName,
|
||||||
|
componentName,
|
||||||
|
nameof(Task)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedMethod;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Html;
|
using Microsoft.AspNet.Html;
|
||||||
|
|
@ -10,9 +9,13 @@ using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||||
using Microsoft.AspNet.Mvc.ViewFeatures.Buffer;
|
using Microsoft.AspNet.Mvc.ViewFeatures.Buffer;
|
||||||
using Microsoft.AspNet.Mvc.ViewFeatures.Internal;
|
using Microsoft.AspNet.Mvc.ViewFeatures.Internal;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default implementation for <see cref="IViewComponentHelper"/>.
|
||||||
|
/// </summary>
|
||||||
public class DefaultViewComponentHelper : IViewComponentHelper, ICanHasViewContext
|
public class DefaultViewComponentHelper : IViewComponentHelper, ICanHasViewContext
|
||||||
{
|
{
|
||||||
private readonly IViewComponentDescriptorCollectionProvider _descriptorProvider;
|
private readonly IViewComponentDescriptorCollectionProvider _descriptorProvider;
|
||||||
|
|
@ -22,6 +25,16 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
private readonly IViewBufferScope _viewBufferScope;
|
private readonly IViewBufferScope _viewBufferScope;
|
||||||
private ViewContext _viewContext;
|
private ViewContext _viewContext;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="DefaultViewComponentHelper"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptorProvider">The <see cref="IViewComponentDescriptorCollectionProvider"/>
|
||||||
|
/// used to locate view components.</param>
|
||||||
|
/// <param name="htmlEncoder">The <see cref="HtmlEncoder"/>.</param>
|
||||||
|
/// <param name="selector">The <see cref="IViewComponentSelector"/>.</param>
|
||||||
|
/// <param name="invokerFactory">The <see cref="IViewComponentInvokerFactory"/>.</param>
|
||||||
|
/// <param name="viewBufferScope">The <see cref="IViewBufferScope"/> that manages the lifetime of
|
||||||
|
/// <see cref="ViewBuffer"/> instances.</param>
|
||||||
public DefaultViewComponentHelper(
|
public DefaultViewComponentHelper(
|
||||||
IViewComponentDescriptorCollectionProvider descriptorProvider,
|
IViewComponentDescriptorCollectionProvider descriptorProvider,
|
||||||
HtmlEncoder htmlEncoder,
|
HtmlEncoder htmlEncoder,
|
||||||
|
|
@ -61,6 +74,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
_viewBufferScope = viewBufferScope;
|
_viewBufferScope = viewBufferScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void Contextualize(ViewContext viewContext)
|
public void Contextualize(ViewContext viewContext)
|
||||||
{
|
{
|
||||||
if (viewContext == null)
|
if (viewContext == null)
|
||||||
|
|
@ -71,133 +85,41 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
_viewContext = viewContext;
|
_viewContext = viewContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHtmlContent Invoke(string name, params object[] arguments)
|
/// <inheritdoc />
|
||||||
|
public Task<IHtmlContent> InvokeAsync(string name, object arguments)
|
||||||
{
|
{
|
||||||
if (name == null)
|
if (name == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(name));
|
throw new ArgumentNullException(nameof(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
var descriptor = SelectComponent(name);
|
|
||||||
|
|
||||||
var viewBuffer = new ViewBuffer(_viewBufferScope, name);
|
|
||||||
using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding))
|
|
||||||
{
|
|
||||||
InvokeCore(writer, descriptor, arguments);
|
|
||||||
return writer.ContentBuilder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IHtmlContent Invoke(Type componentType, params object[] arguments)
|
|
||||||
{
|
|
||||||
if (componentType == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(componentType));
|
|
||||||
}
|
|
||||||
|
|
||||||
var descriptor = SelectComponent(componentType);
|
|
||||||
var viewBuffer = new ViewBuffer(_viewBufferScope, componentType.Name);
|
|
||||||
using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding))
|
|
||||||
{
|
|
||||||
InvokeCore(writer, descriptor, arguments);
|
|
||||||
return writer.ContentBuilder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RenderInvoke(string name, params object[] arguments)
|
|
||||||
{
|
|
||||||
if (name == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
var descriptor = SelectComponent(name);
|
|
||||||
InvokeCore(_viewContext.Writer, descriptor, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RenderInvoke(Type componentType, params object[] arguments)
|
|
||||||
{
|
|
||||||
if (componentType == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(componentType));
|
|
||||||
}
|
|
||||||
|
|
||||||
var descriptor = SelectComponent(componentType);
|
|
||||||
InvokeCore(_viewContext.Writer, descriptor, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IHtmlContent> InvokeAsync(string name, params object[] arguments)
|
|
||||||
{
|
|
||||||
if (name == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
var descriptor = SelectComponent(name);
|
|
||||||
|
|
||||||
var viewBuffer = new ViewBuffer(_viewBufferScope, name);
|
|
||||||
using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding))
|
|
||||||
{
|
|
||||||
await InvokeCoreAsync(writer, descriptor, arguments);
|
|
||||||
return writer.ContentBuilder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IHtmlContent> InvokeAsync(Type componentType, params object[] arguments)
|
|
||||||
{
|
|
||||||
if (componentType == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(componentType));
|
|
||||||
}
|
|
||||||
|
|
||||||
var descriptor = SelectComponent(componentType);
|
|
||||||
|
|
||||||
var viewBuffer = new ViewBuffer(_viewBufferScope, componentType.Name);
|
|
||||||
using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding))
|
|
||||||
{
|
|
||||||
await InvokeCoreAsync(writer, descriptor, arguments);
|
|
||||||
return writer.ContentBuilder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task RenderInvokeAsync(string name, params object[] arguments)
|
|
||||||
{
|
|
||||||
if (name == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
var descriptor = SelectComponent(name);
|
|
||||||
return InvokeCoreAsync(_viewContext.Writer, descriptor, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task RenderInvokeAsync(Type componentType, params object[] arguments)
|
|
||||||
{
|
|
||||||
if (componentType == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(componentType));
|
|
||||||
}
|
|
||||||
|
|
||||||
var descriptor = SelectComponent(componentType);
|
|
||||||
return InvokeCoreAsync(_viewContext.Writer, descriptor, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ViewComponentDescriptor SelectComponent(string name)
|
|
||||||
{
|
|
||||||
var descriptor = _selector.SelectComponent(name);
|
var descriptor = _selector.SelectComponent(name);
|
||||||
if (descriptor == null)
|
if (descriptor == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(Resources.FormatViewComponent_CannotFindComponent(name));
|
throw new InvalidOperationException(Resources.FormatViewComponent_CannotFindComponent(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
return descriptor;
|
return InvokeCoreAsync(descriptor, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<IHtmlContent> InvokeAsync(Type componentType, object arguments)
|
||||||
|
{
|
||||||
|
if (componentType == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(componentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
var descriptor = SelectComponent(componentType);
|
||||||
|
return InvokeCoreAsync(descriptor, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ViewComponentDescriptor SelectComponent(Type componentType)
|
private ViewComponentDescriptor SelectComponent(Type componentType)
|
||||||
{
|
{
|
||||||
var descriptors = _descriptorProvider.ViewComponents;
|
var descriptors = _descriptorProvider.ViewComponents;
|
||||||
foreach (var descriptor in descriptors.Items)
|
for (var i = 0; i < descriptors.Items.Count; i++)
|
||||||
{
|
{
|
||||||
|
var descriptor = descriptors.Items[i];
|
||||||
if (descriptor.Type == componentType)
|
if (descriptor.Type == componentType)
|
||||||
{
|
{
|
||||||
return descriptor;
|
return descriptor;
|
||||||
|
|
@ -208,58 +130,30 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
componentType.FullName));
|
componentType.FullName));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task InvokeCoreAsync(
|
private async Task<IHtmlContent> InvokeCoreAsync(
|
||||||
TextWriter writer,
|
|
||||||
ViewComponentDescriptor descriptor,
|
ViewComponentDescriptor descriptor,
|
||||||
object[] arguments)
|
object arguments)
|
||||||
{
|
{
|
||||||
if (writer == null)
|
var viewBuffer = new ViewBuffer(_viewBufferScope, descriptor.FullName);
|
||||||
|
using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(writer));
|
var context = new ViewComponentContext(
|
||||||
|
descriptor,
|
||||||
|
PropertyHelper.ObjectToDictionary(arguments),
|
||||||
|
_htmlEncoder,
|
||||||
|
_viewContext,
|
||||||
|
writer);
|
||||||
|
|
||||||
|
var invoker = _invokerFactory.CreateInstance(context);
|
||||||
|
if (invoker == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatViewComponent_IViewComponentFactory_ReturnedNull(descriptor.FullName));
|
||||||
|
}
|
||||||
|
|
||||||
|
await invoker.InvokeAsync(context);
|
||||||
|
return writer.ContentBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (descriptor == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(descriptor));
|
|
||||||
}
|
|
||||||
|
|
||||||
var context = new ViewComponentContext(descriptor, arguments, _htmlEncoder, _viewContext, writer);
|
|
||||||
|
|
||||||
var invoker = _invokerFactory.CreateInstance(context);
|
|
||||||
if (invoker == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
Resources.FormatViewComponent_IViewComponentFactory_ReturnedNull(descriptor.Type.FullName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return invoker.InvokeAsync(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InvokeCore(
|
|
||||||
TextWriter writer,
|
|
||||||
ViewComponentDescriptor descriptor,
|
|
||||||
object[] arguments)
|
|
||||||
{
|
|
||||||
if (writer == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(writer));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (descriptor == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(descriptor));
|
|
||||||
}
|
|
||||||
|
|
||||||
var context = new ViewComponentContext(descriptor, arguments, _htmlEncoder, _viewContext, writer);
|
|
||||||
|
|
||||||
var invoker = _invokerFactory.CreateInstance(context);
|
|
||||||
if (invoker == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
Resources.FormatViewComponent_IViewComponentFactory_ReturnedNull(descriptor.Type.FullName));
|
|
||||||
}
|
|
||||||
|
|
||||||
invoker.Invoke(context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default implementation for <see cref="IViewComponentInvoker"/>.
|
||||||
|
/// </summary>
|
||||||
public class DefaultViewComponentInvoker : IViewComponentInvoker
|
public class DefaultViewComponentInvoker : IViewComponentInvoker
|
||||||
{
|
{
|
||||||
private readonly ITypeActivatorCache _typeActivatorCache;
|
private readonly ITypeActivatorCache _typeActivatorCache;
|
||||||
|
|
@ -23,6 +26,13 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
private readonly DiagnosticSource _diagnosticSource;
|
private readonly DiagnosticSource _diagnosticSource;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="DefaultViewComponentInvoker"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="typeActivatorCache">Caches factories for instantiating view component instances.</param>
|
||||||
|
/// <param name="viewComponentActivator">The <see cref="IViewComponentActivator"/>.</param>
|
||||||
|
/// <param name="diagnosticSource">The <see cref="DiagnosticSource"/>.</param>
|
||||||
|
/// <param name="logger">The <see cref="ILogger"/>.</param>
|
||||||
public DefaultViewComponentInvoker(
|
public DefaultViewComponentInvoker(
|
||||||
ITypeActivatorCache typeActivatorCache,
|
ITypeActivatorCache typeActivatorCache,
|
||||||
IViewComponentActivator viewComponentActivator,
|
IViewComponentActivator viewComponentActivator,
|
||||||
|
|
@ -55,27 +65,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Invoke(ViewComponentContext context)
|
/// <inheritdoc />
|
||||||
{
|
|
||||||
if (context == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
var method = ViewComponentMethodSelector.FindSyncMethod(
|
|
||||||
context.ViewComponentDescriptor.Type.GetTypeInfo(),
|
|
||||||
context.Arguments);
|
|
||||||
if (method == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
Resources.FormatViewComponent_CannotFindMethod(ViewComponentMethodSelector.SyncMethodName));
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = InvokeSyncCore(method, context);
|
|
||||||
|
|
||||||
result.Execute(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InvokeAsync(ViewComponentContext context)
|
public async Task InvokeAsync(ViewComponentContext context)
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
|
|
@ -83,32 +73,25 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
IViewComponentResult result;
|
var methodInfo = context.ViewComponentDescriptor?.MethodInfo;
|
||||||
|
if (methodInfo == null)
|
||||||
var asyncMethod = ViewComponentMethodSelector.FindAsyncMethod(
|
|
||||||
context.ViewComponentDescriptor.Type.GetTypeInfo(),
|
|
||||||
context.Arguments);
|
|
||||||
if (asyncMethod == null)
|
|
||||||
{
|
{
|
||||||
// We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still
|
throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(
|
||||||
// execute the IViewResult asynchronously.
|
nameof(ViewComponentDescriptor.MethodInfo),
|
||||||
var syncMethod = ViewComponentMethodSelector.FindSyncMethod(
|
nameof(ViewComponentDescriptor)));
|
||||||
context.ViewComponentDescriptor.Type.GetTypeInfo(),
|
}
|
||||||
context.Arguments);
|
|
||||||
if (syncMethod == null)
|
var isAsync = typeof(Task).GetTypeInfo().IsAssignableFrom(methodInfo.ReturnType.GetTypeInfo());
|
||||||
{
|
IViewComponentResult result;
|
||||||
throw new InvalidOperationException(
|
if (isAsync)
|
||||||
Resources.FormatViewComponent_CannotFindMethod_WithFallback(
|
{
|
||||||
ViewComponentMethodSelector.SyncMethodName, ViewComponentMethodSelector.AsyncMethodName));
|
result = await InvokeAsyncCore(context);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = InvokeSyncCore(syncMethod, context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = await InvokeAsyncCore(asyncMethod, context);
|
// We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still
|
||||||
|
// execute the IViewResult asynchronously.
|
||||||
|
result = InvokeSyncCore(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
await result.ExecuteAsync(context);
|
await result.ExecuteAsync(context);
|
||||||
|
|
@ -129,29 +112,20 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IViewComponentResult> InvokeAsyncCore(
|
private async Task<IViewComponentResult> InvokeAsyncCore(ViewComponentContext context)
|
||||||
MethodInfo method,
|
|
||||||
ViewComponentContext context)
|
|
||||||
{
|
{
|
||||||
if (method == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(method));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
var component = CreateComponent(context);
|
var component = CreateComponent(context);
|
||||||
|
|
||||||
using (_logger.ViewComponentScope(context))
|
using (_logger.ViewComponentScope(context))
|
||||||
{
|
{
|
||||||
|
var method = context.ViewComponentDescriptor.MethodInfo;
|
||||||
|
var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, method.GetParameters());
|
||||||
|
|
||||||
_diagnosticSource.BeforeViewComponent(context, component);
|
_diagnosticSource.BeforeViewComponent(context, component);
|
||||||
_logger.ViewComponentExecuting(context);
|
_logger.ViewComponentExecuting(context, arguments);
|
||||||
|
|
||||||
var startTime = Environment.TickCount;
|
var startTime = Environment.TickCount;
|
||||||
var result = await ControllerActionExecutor.ExecuteAsync(method, component, context.Arguments);
|
var result = await ControllerActionExecutor.ExecuteAsync(method, component, arguments);
|
||||||
|
|
||||||
var viewComponentResult = CoerceToViewComponentResult(result);
|
var viewComponentResult = CoerceToViewComponentResult(result);
|
||||||
_logger.ViewComponentExecuted(context, startTime, viewComponentResult);
|
_logger.ViewComponentExecuted(context, startTime, viewComponentResult);
|
||||||
|
|
@ -161,35 +135,25 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IViewComponentResult InvokeSyncCore(MethodInfo method, ViewComponentContext context)
|
private IViewComponentResult InvokeSyncCore(ViewComponentContext context)
|
||||||
{
|
{
|
||||||
if (method == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(method));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
var component = CreateComponent(context);
|
var component = CreateComponent(context);
|
||||||
|
|
||||||
using (_logger.ViewComponentScope(context))
|
using (_logger.ViewComponentScope(context))
|
||||||
{
|
{
|
||||||
_diagnosticSource.BeforeViewComponent(context, component);
|
var method = context.ViewComponentDescriptor.MethodInfo;
|
||||||
_logger.ViewComponentExecuting(context);
|
var arguments = ControllerActionExecutor.PrepareArguments(
|
||||||
|
context.Arguments,
|
||||||
|
method.GetParameters());
|
||||||
|
|
||||||
|
_diagnosticSource.BeforeViewComponent(context, component);
|
||||||
|
_logger.ViewComponentExecuting(context, arguments);
|
||||||
|
|
||||||
|
var startTime = Environment.TickCount;
|
||||||
|
object result;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var startTime = Environment.TickCount;
|
result = method.Invoke(component, arguments);
|
||||||
var result = method.Invoke(component, context.Arguments);
|
|
||||||
|
|
||||||
var viewComponentResult = CoerceToViewComponentResult(result);
|
|
||||||
_logger.ViewComponentExecuted(context, startTime, viewComponentResult);
|
|
||||||
_diagnosticSource.AfterViewComponent(context, viewComponentResult, component);
|
|
||||||
|
|
||||||
return viewComponentResult;
|
|
||||||
}
|
}
|
||||||
catch (TargetInvocationException ex)
|
catch (TargetInvocationException ex)
|
||||||
{
|
{
|
||||||
|
|
@ -198,6 +162,12 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
exceptionInfo.Throw();
|
exceptionInfo.Throw();
|
||||||
return null; // Unreachable
|
return null; // Unreachable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var viewComponentResult = CoerceToViewComponentResult(result);
|
||||||
|
_logger.ViewComponentExecuted(context, startTime, viewComponentResult);
|
||||||
|
_diagnosticSource.AfterViewComponent(context, viewComponentResult, component);
|
||||||
|
|
||||||
|
return viewComponentResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Discovers the View Components in the application.
|
/// Discovers the view components in the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IViewComponentDescriptorProvider
|
public interface IViewComponentDescriptorProvider
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,17 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the contract for execution of a view component.
|
||||||
|
/// </summary>
|
||||||
public interface IViewComponentInvoker
|
public interface IViewComponentInvoker
|
||||||
{
|
{
|
||||||
void Invoke(ViewComponentContext context);
|
/// <summary>
|
||||||
|
/// Executes the view component specified by <see cref="ViewComponentContext.ViewComponentDescriptor"/>
|
||||||
|
/// of <paramref name="context"/> and writes the result to <see cref="ViewComponentContext.Writer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The <see cref="ViewComponentContext"/>.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that represents the asynchronous operation of execution.</returns>
|
||||||
Task InvokeAsync(ViewComponentContext context);
|
Task InvokeAsync(ViewComponentContext context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@
|
||||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Selects a View Component based on a View Component name.
|
/// Selects a view component based on a view component name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IViewComponentSelector
|
public interface IViewComponentSelector
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Selects a View Component based on <paramref name="componentName"/>.
|
/// Selects a view component based on <paramref name="componentName"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="componentName">The View Component name.</param>
|
/// <param name="componentName">The view component name.</param>
|
||||||
/// <returns>A <see cref="ViewComponentDescriptor"/>, or <c>null</c> if no match is found.</returns>
|
/// <returns>A <see cref="ViewComponentDescriptor"/>, or <c>null</c> if no match is found.</returns>
|
||||||
ViewComponentDescriptor SelectComponent(string componentName);
|
ViewComponentDescriptor SelectComponent(string componentName);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
|
@ -10,7 +11,7 @@ using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A context for View Components.
|
/// A context for view components.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ViewComponentContext
|
public class ViewComponentContext
|
||||||
{
|
{
|
||||||
|
|
@ -23,7 +24,6 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
public ViewComponentContext()
|
public ViewComponentContext()
|
||||||
{
|
{
|
||||||
ViewComponentDescriptor = new ViewComponentDescriptor();
|
ViewComponentDescriptor = new ViewComponentDescriptor();
|
||||||
Arguments = new object[0];
|
|
||||||
ViewContext = new ViewContext();
|
ViewContext = new ViewContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,14 +31,14 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
/// Creates a new <see cref="ViewComponentContext"/>.
|
/// Creates a new <see cref="ViewComponentContext"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="viewComponentDescriptor">
|
/// <param name="viewComponentDescriptor">
|
||||||
/// The <see cref="ViewComponentContext"/> for the View Component being invoked.
|
/// The <see cref="ViewComponentContext"/> for the view component being invoked.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="arguments">The View Component arguments.</param>
|
/// <param name="arguments">The view component arguments.</param>
|
||||||
/// <param name="viewContext">The <see cref="ViewContext"/>.</param>
|
/// <param name="viewContext">The <see cref="ViewContext"/>.</param>
|
||||||
/// <param name="writer">The <see cref="TextWriter"/> for writing output.</param>
|
/// <param name="writer">The <see cref="TextWriter"/> for writing output.</param>
|
||||||
public ViewComponentContext(
|
public ViewComponentContext(
|
||||||
ViewComponentDescriptor viewComponentDescriptor,
|
ViewComponentDescriptor viewComponentDescriptor,
|
||||||
object[] arguments,
|
IDictionary<string, object> arguments,
|
||||||
HtmlEncoder htmlEncoder,
|
HtmlEncoder htmlEncoder,
|
||||||
ViewContext viewContext,
|
ViewContext viewContext,
|
||||||
TextWriter writer)
|
TextWriter writer)
|
||||||
|
|
@ -82,15 +82,15 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the View Component arguments.
|
/// Gets or sets the view component arguments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The property setter is provided for unit test purposes only.
|
/// The property setter is provided for unit test purposes only.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public object[] Arguments { get; set; }
|
public IDictionary<string, object> Arguments { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="HtmlEncoder"/>.
|
/// Gets or sets the <see cref="System.Text.Encodings.Web.HtmlEncoder"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The property setter is provided for unit test purposes only.
|
/// The property setter is provided for unit test purposes only.
|
||||||
|
|
@ -98,7 +98,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
public HtmlEncoder HtmlEncoder { get; set; }
|
public HtmlEncoder HtmlEncoder { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="ViewComponentDescriptor"/> for the View Component being invoked.
|
/// Gets or sets the <see cref="ViewComponents.ViewComponentDescriptor"/> for the view component being invoked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The property setter is provided for unit test purposes only.
|
/// The property setter is provided for unit test purposes only.
|
||||||
|
|
@ -106,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
public ViewComponentDescriptor ViewComponentDescriptor { get; set; }
|
public ViewComponentDescriptor ViewComponentDescriptor { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="ViewContext"/>.
|
/// Gets or sets the <see cref="Rendering.ViewContext"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The property setter is provided for unit test purposes only.
|
/// The property setter is provided for unit test purposes only.
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A descriptor for a View Component.
|
/// A descriptor for a view component.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DebuggerDisplay("{DisplayName}")]
|
[DebuggerDisplay("{DisplayName}")]
|
||||||
public class ViewComponentDescriptor
|
public class ViewComponentDescriptor
|
||||||
|
|
@ -23,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the display name of the View Component.
|
/// Gets or sets the display name of the view component.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string DisplayName
|
public string DisplayName
|
||||||
{
|
{
|
||||||
|
|
@ -53,8 +54,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// The full name is defaulted to the full namespace of the View Component class, prepended to
|
/// The full name is defaulted to the full namespace of the view component class, prepended to
|
||||||
/// the the class name with a '.' character as the separator. If the View Component class uses
|
/// the the class name with a '.' character as the separator. If the view component class uses
|
||||||
/// <code>ViewComponent</code> as a suffix, the suffix will be omitted from the <see cref="FullName"/>.
|
/// <code>ViewComponent</code> as a suffix, the suffix will be omitted from the <see cref="FullName"/>.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <example>
|
/// <example>
|
||||||
|
|
@ -89,7 +90,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// The short name is defaulted to the name of the View Component class. If the View Component class uses
|
/// The short name is defaulted to the name of the view component class. If the view component class uses
|
||||||
/// <code>ViewComponent</code> as a suffix, the suffix will be omitted from the <see cref="ShortName"/>.
|
/// <code>ViewComponent</code> as a suffix, the suffix will be omitted from the <see cref="ShortName"/>.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <example>
|
/// <example>
|
||||||
|
|
@ -115,8 +116,13 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
public string ShortName { get; set; }
|
public string ShortName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="Type"/>.
|
/// Gets or sets the <see cref="System.Type"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Type Type { get; set; }
|
public Type Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="System.Reflection.MethodInfo"/> to invoke.
|
||||||
|
/// </summary>
|
||||||
|
public MethodInfo MethodInfo { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
// 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.Linq.Expressions;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
|
||||||
{
|
|
||||||
public static class ViewComponentMethodSelector
|
|
||||||
{
|
|
||||||
public const string AsyncMethodName = "InvokeAsync";
|
|
||||||
public const string SyncMethodName = "Invoke";
|
|
||||||
|
|
||||||
public static MethodInfo FindAsyncMethod(TypeInfo componentType, object[] args)
|
|
||||||
{
|
|
||||||
if (componentType == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(componentType));
|
|
||||||
}
|
|
||||||
|
|
||||||
var method = GetMethod(componentType, args, AsyncMethodName);
|
|
||||||
if (method == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!method.ReturnType.GetTypeInfo().IsGenericType ||
|
|
||||||
method.ReturnType.GetGenericTypeDefinition() != typeof(Task<>))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
Resources.FormatViewComponent_AsyncMethod_ShouldReturnTask(AsyncMethodName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return method;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MethodInfo FindSyncMethod(TypeInfo componentType, object[] args)
|
|
||||||
{
|
|
||||||
if (componentType == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(componentType));
|
|
||||||
}
|
|
||||||
|
|
||||||
var method = GetMethod(componentType, args, SyncMethodName);
|
|
||||||
if (method == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (method.ReturnType == typeof(void))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
Resources.FormatViewComponent_SyncMethod_ShouldReturnValue(SyncMethodName));
|
|
||||||
}
|
|
||||||
else if (method.ReturnType.IsAssignableFrom(typeof(Task)))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
Resources.FormatViewComponent_SyncMethod_CannotReturnTask(SyncMethodName, nameof(Task)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return method;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MethodInfo GetMethod(TypeInfo componentType, object[] args, string methodName)
|
|
||||||
{
|
|
||||||
Type[] types;
|
|
||||||
if (args == null || args.Length == 0)
|
|
||||||
{
|
|
||||||
types = Type.EmptyTypes;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
types = new Type[args.Length];
|
|
||||||
for (var i = 0; i < args.Length; i++)
|
|
||||||
{
|
|
||||||
types[i] = args[i]?.GetType() ?? typeof(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NET451
|
|
||||||
return componentType.AsType().GetMethod(
|
|
||||||
methodName,
|
|
||||||
BindingFlags.Public | BindingFlags.Instance,
|
|
||||||
binder: null,
|
|
||||||
types: types,
|
|
||||||
modifiers: null);
|
|
||||||
#else
|
|
||||||
var method = componentType.AsType().GetMethod(methodName, types: types);
|
|
||||||
// At most one method (including static and instance methods) with the same parameter types can exist
|
|
||||||
// per type.
|
|
||||||
return method != null && method.IsStatic ? null : method;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -42,13 +43,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
FullName = "Full.Name.Text",
|
FullName = "Full.Name.Text",
|
||||||
ShortName = "Text",
|
ShortName = "Text",
|
||||||
Type = typeof(TextViewComponent),
|
Type = typeof(TextViewComponent),
|
||||||
|
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||||
};
|
};
|
||||||
|
|
||||||
var actionContext = CreateActionContext(descriptor);
|
var actionContext = CreateActionContext(descriptor);
|
||||||
|
|
||||||
var viewComponentResult = new ViewComponentResult
|
var viewComponentResult = new ViewComponentResult
|
||||||
{
|
{
|
||||||
Arguments = new object[] { "World!" },
|
Arguments = new { name = "World!" },
|
||||||
ViewData = null,
|
ViewData = null,
|
||||||
TempData = null,
|
TempData = null,
|
||||||
ViewComponentName = "Text"
|
ViewComponentName = "Text"
|
||||||
|
|
@ -132,13 +134,43 @@ namespace Microsoft.AspNet.Mvc
|
||||||
FullName = "Full.Name.Text",
|
FullName = "Full.Name.Text",
|
||||||
ShortName = "Text",
|
ShortName = "Text",
|
||||||
Type = typeof(TextViewComponent),
|
Type = typeof(TextViewComponent),
|
||||||
|
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||||
};
|
};
|
||||||
|
|
||||||
var actionContext = CreateActionContext(descriptor);
|
var actionContext = CreateActionContext(descriptor);
|
||||||
|
|
||||||
var viewComponentResult = new ViewComponentResult()
|
var viewComponentResult = new ViewComponentResult()
|
||||||
{
|
{
|
||||||
Arguments = new object[] { "World!" },
|
Arguments = new { name = "World!" },
|
||||||
|
ViewComponentName = "Text",
|
||||||
|
TempData = _tempDataDictionary,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await viewComponentResult.ExecuteResultAsync(actionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = ReadBody(actionContext.HttpContext.Response);
|
||||||
|
Assert.Equal("Hello, World!", body);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_UsesDictionaryArguments()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptor = new ViewComponentDescriptor()
|
||||||
|
{
|
||||||
|
FullName = "Full.Name.Text",
|
||||||
|
ShortName = "Text",
|
||||||
|
Type = typeof(TextViewComponent),
|
||||||
|
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||||
|
};
|
||||||
|
|
||||||
|
var actionContext = CreateActionContext(descriptor);
|
||||||
|
|
||||||
|
var viewComponentResult = new ViewComponentResult()
|
||||||
|
{
|
||||||
|
Arguments = new Dictionary<string, object> { ["name"] = "World!" },
|
||||||
ViewComponentName = "Text",
|
ViewComponentName = "Text",
|
||||||
TempData = _tempDataDictionary,
|
TempData = _tempDataDictionary,
|
||||||
};
|
};
|
||||||
|
|
@ -160,13 +192,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
FullName = "Full.Name.AsyncText",
|
FullName = "Full.Name.AsyncText",
|
||||||
ShortName = "AsyncText",
|
ShortName = "AsyncText",
|
||||||
Type = typeof(AsyncTextViewComponent),
|
Type = typeof(AsyncTextViewComponent),
|
||||||
|
MethodInfo = typeof(AsyncTextViewComponent).GetMethod(nameof(AsyncTextViewComponent.InvokeAsync)),
|
||||||
};
|
};
|
||||||
|
|
||||||
var actionContext = CreateActionContext(descriptor);
|
var actionContext = CreateActionContext(descriptor);
|
||||||
|
|
||||||
var viewComponentResult = new ViewComponentResult()
|
var viewComponentResult = new ViewComponentResult()
|
||||||
{
|
{
|
||||||
Arguments = new object[] { "World!" },
|
Arguments = new { name = "World!" },
|
||||||
ViewComponentName = "AsyncText",
|
ViewComponentName = "AsyncText",
|
||||||
TempData = _tempDataDictionary,
|
TempData = _tempDataDictionary,
|
||||||
};
|
};
|
||||||
|
|
@ -188,6 +221,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
FullName = "Full.Name.Text",
|
FullName = "Full.Name.Text",
|
||||||
ShortName = "Text",
|
ShortName = "Text",
|
||||||
Type = typeof(TextViewComponent),
|
Type = typeof(TextViewComponent),
|
||||||
|
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||||
};
|
};
|
||||||
|
|
||||||
var adapter = new TestDiagnosticListener();
|
var adapter = new TestDiagnosticListener();
|
||||||
|
|
@ -196,7 +230,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var viewComponentResult = new ViewComponentResult()
|
var viewComponentResult = new ViewComponentResult()
|
||||||
{
|
{
|
||||||
Arguments = new object[] { "World!" },
|
Arguments = new { name = "World!" },
|
||||||
ViewComponentName = "Text",
|
ViewComponentName = "Text",
|
||||||
TempData = _tempDataDictionary,
|
TempData = _tempDataDictionary,
|
||||||
};
|
};
|
||||||
|
|
@ -226,13 +260,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
FullName = "Full.Name.Text",
|
FullName = "Full.Name.Text",
|
||||||
ShortName = "Text",
|
ShortName = "Text",
|
||||||
Type = typeof(TextViewComponent),
|
Type = typeof(TextViewComponent),
|
||||||
|
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||||
};
|
};
|
||||||
|
|
||||||
var actionContext = CreateActionContext(descriptor);
|
var actionContext = CreateActionContext(descriptor);
|
||||||
|
|
||||||
var viewComponentResult = new ViewComponentResult()
|
var viewComponentResult = new ViewComponentResult()
|
||||||
{
|
{
|
||||||
Arguments = new object[] { "World!" },
|
Arguments = new { name = "World!" },
|
||||||
ViewComponentName = "Text",
|
ViewComponentName = "Text",
|
||||||
TempData = _tempDataDictionary,
|
TempData = _tempDataDictionary,
|
||||||
};
|
};
|
||||||
|
|
@ -254,13 +289,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
FullName = "Full.Name.Text",
|
FullName = "Full.Name.Text",
|
||||||
ShortName = "Text",
|
ShortName = "Text",
|
||||||
Type = typeof(TextViewComponent),
|
Type = typeof(TextViewComponent),
|
||||||
|
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||||
};
|
};
|
||||||
|
|
||||||
var actionContext = CreateActionContext(descriptor);
|
var actionContext = CreateActionContext(descriptor);
|
||||||
|
|
||||||
var viewComponentResult = new ViewComponentResult()
|
var viewComponentResult = new ViewComponentResult()
|
||||||
{
|
{
|
||||||
Arguments = new object[] { "World!" },
|
Arguments = new { name = "World!" },
|
||||||
ViewComponentName = "Full.Name.Text",
|
ViewComponentName = "Full.Name.Text",
|
||||||
TempData = _tempDataDictionary,
|
TempData = _tempDataDictionary,
|
||||||
};
|
};
|
||||||
|
|
@ -282,13 +318,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
FullName = "Full.Name.Text",
|
FullName = "Full.Name.Text",
|
||||||
ShortName = "Text",
|
ShortName = "Text",
|
||||||
Type = typeof(TextViewComponent),
|
Type = typeof(TextViewComponent),
|
||||||
|
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||||
};
|
};
|
||||||
|
|
||||||
var actionContext = CreateActionContext(descriptor);
|
var actionContext = CreateActionContext(descriptor);
|
||||||
|
|
||||||
var viewComponentResult = new ViewComponentResult()
|
var viewComponentResult = new ViewComponentResult()
|
||||||
{
|
{
|
||||||
Arguments = new object[] { "World!" },
|
Arguments = new { name = "World!" },
|
||||||
ViewComponentType = typeof(TextViewComponent),
|
ViewComponentType = typeof(TextViewComponent),
|
||||||
TempData = _tempDataDictionary,
|
TempData = _tempDataDictionary,
|
||||||
};
|
};
|
||||||
|
|
@ -310,13 +347,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
FullName = "Full.Name.Text",
|
FullName = "Full.Name.Text",
|
||||||
ShortName = "Text",
|
ShortName = "Text",
|
||||||
Type = typeof(TextViewComponent),
|
Type = typeof(TextViewComponent),
|
||||||
|
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke))
|
||||||
};
|
};
|
||||||
|
|
||||||
var actionContext = CreateActionContext(descriptor);
|
var actionContext = CreateActionContext(descriptor);
|
||||||
|
|
||||||
var viewComponentResult = new ViewComponentResult()
|
var viewComponentResult = new ViewComponentResult()
|
||||||
{
|
{
|
||||||
Arguments = new object[] { "World!" },
|
Arguments = new { name = "World!" },
|
||||||
ViewComponentType = typeof(TextViewComponent),
|
ViewComponentType = typeof(TextViewComponent),
|
||||||
StatusCode = 404,
|
StatusCode = 404,
|
||||||
TempData = _tempDataDictionary,
|
TempData = _tempDataDictionary,
|
||||||
|
|
@ -367,6 +405,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
FullName = "Full.Name.Text",
|
FullName = "Full.Name.Text",
|
||||||
ShortName = "Text",
|
ShortName = "Text",
|
||||||
Type = typeof(TextViewComponent),
|
Type = typeof(TextViewComponent),
|
||||||
|
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||||
};
|
};
|
||||||
|
|
||||||
var actionContext = CreateActionContext(descriptor);
|
var actionContext = CreateActionContext(descriptor);
|
||||||
|
|
@ -375,7 +414,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var viewComponentResult = new ViewComponentResult()
|
var viewComponentResult = new ViewComponentResult()
|
||||||
{
|
{
|
||||||
Arguments = new object[] { "World!" },
|
Arguments = new { name = "World!" },
|
||||||
ViewComponentName = "Text",
|
ViewComponentName = "Text",
|
||||||
ContentType = contentType,
|
ContentType = contentType,
|
||||||
TempData = _tempDataDictionary,
|
TempData = _tempDataDictionary,
|
||||||
|
|
@ -403,6 +442,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
FullName = "Full.Name.Text",
|
FullName = "Full.Name.Text",
|
||||||
ShortName = "Text",
|
ShortName = "Text",
|
||||||
Type = typeof(TextViewComponent),
|
Type = typeof(TextViewComponent),
|
||||||
|
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||||
};
|
};
|
||||||
|
|
||||||
var actionContext = CreateActionContext(descriptor);
|
var actionContext = CreateActionContext(descriptor);
|
||||||
|
|
@ -412,7 +452,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var viewComponentResult = new ViewComponentResult()
|
var viewComponentResult = new ViewComponentResult()
|
||||||
{
|
{
|
||||||
Arguments = new object[] { "World!" },
|
Arguments = new { name = "World!" },
|
||||||
ViewComponentName = "Text",
|
ViewComponentName = "Text",
|
||||||
ContentType = new MediaTypeHeaderValue("text/html") { Encoding = Encoding.UTF8 },
|
ContentType = new MediaTypeHeaderValue("text/html") { Encoding = Encoding.UTF8 },
|
||||||
TempData = _tempDataDictionary,
|
TempData = _tempDataDictionary,
|
||||||
|
|
@ -439,6 +479,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
FullName = "Full.Name.Text",
|
FullName = "Full.Name.Text",
|
||||||
ShortName = "Text",
|
ShortName = "Text",
|
||||||
Type = typeof(TextViewComponent),
|
Type = typeof(TextViewComponent),
|
||||||
|
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||||
};
|
};
|
||||||
|
|
||||||
var actionContext = CreateActionContext(descriptor);
|
var actionContext = CreateActionContext(descriptor);
|
||||||
|
|
@ -447,7 +488,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var viewComponentResult = new ViewComponentResult()
|
var viewComponentResult = new ViewComponentResult()
|
||||||
{
|
{
|
||||||
Arguments = new object[] { "World!" },
|
Arguments = new { name = "World!" },
|
||||||
ViewComponentName = "Text",
|
ViewComponentName = "Text",
|
||||||
TempData = _tempDataDictionary,
|
TempData = _tempDataDictionary,
|
||||||
};
|
};
|
||||||
|
|
@ -459,7 +500,10 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Assert.Equal(expectedContentType, actionContext.HttpContext.Response.ContentType);
|
Assert.Equal(expectedContentType, actionContext.HttpContext.Response.ContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IServiceCollection CreateServices(object diagnosticListener, HttpContext context, params ViewComponentDescriptor[] descriptors)
|
private IServiceCollection CreateServices(
|
||||||
|
object diagnosticListener,
|
||||||
|
HttpContext context,
|
||||||
|
params ViewComponentDescriptor[] descriptors)
|
||||||
{
|
{
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
var diagnosticSource = new DiagnosticListener("Microsoft.AspNet");
|
var diagnosticSource = new DiagnosticListener("Microsoft.AspNet");
|
||||||
|
|
@ -535,12 +579,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
private class AsyncTextViewComponent : ViewComponent
|
private class AsyncTextViewComponent : ViewComponent
|
||||||
{
|
{
|
||||||
public HtmlString Invoke()
|
|
||||||
{
|
|
||||||
// Should never run.
|
|
||||||
throw null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<HtmlString> InvokeAsync(string name)
|
public Task<HtmlString> InvokeAsync(string name)
|
||||||
{
|
{
|
||||||
return Task.FromResult(new HtmlString("Hello-Async, " + name));
|
return Task.FromResult(new HtmlString("Hello-Async, " + name));
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Abstractions;
|
using Microsoft.AspNet.Mvc.Abstractions;
|
||||||
|
|
@ -43,7 +44,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var viewContext = new ViewContext(
|
var viewContext = new ViewContext(
|
||||||
actionContext,
|
actionContext,
|
||||||
view,
|
view,
|
||||||
viewData,
|
viewData,
|
||||||
new TempDataDictionary(httpContext, new SessionStateTempDataProvider()),
|
new TempDataDictionary(httpContext, new SessionStateTempDataProvider()),
|
||||||
TextWriter.Null,
|
TextWriter.Null,
|
||||||
new HtmlHelperOptions());
|
new HtmlHelperOptions());
|
||||||
|
|
@ -57,7 +58,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var viewComponentContext = new ViewComponentContext(
|
var viewComponentContext = new ViewComponentContext(
|
||||||
viewComponentDescriptor,
|
viewComponentDescriptor,
|
||||||
new object[0],
|
new Dictionary<string, object>(),
|
||||||
new HtmlTestEncoder(),
|
new HtmlTestEncoder(),
|
||||||
viewContext,
|
viewContext,
|
||||||
writer);
|
writer);
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
|
||||||
using Microsoft.AspNet.Routing;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc.Infrastructure;
|
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -23,9 +24,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var descriptor = Assert.Single(descriptors);
|
var descriptor = Assert.Single(descriptors);
|
||||||
Assert.Equal(typeof(ConventionsViewComponent), descriptor.Type);
|
Assert.Same(typeof(ConventionsViewComponent), descriptor.Type);
|
||||||
Assert.Equal("Microsoft.AspNet.Mvc.ViewComponents.Conventions", descriptor.FullName);
|
Assert.Equal("Microsoft.AspNet.Mvc.ViewComponents.Conventions", descriptor.FullName);
|
||||||
Assert.Equal("Conventions", descriptor.ShortName);
|
Assert.Equal("Conventions", descriptor.ShortName);
|
||||||
|
Assert.Same(typeof(ConventionsViewComponent).GetMethod("Invoke"), descriptor.MethodInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -42,15 +44,156 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
Assert.Equal(typeof(AttributeViewComponent), descriptor.Type);
|
Assert.Equal(typeof(AttributeViewComponent), descriptor.Type);
|
||||||
Assert.Equal("AttributesAreGreat", descriptor.FullName);
|
Assert.Equal("AttributesAreGreat", descriptor.FullName);
|
||||||
Assert.Equal("AttributesAreGreat", descriptor.ShortName);
|
Assert.Equal("AttributesAreGreat", descriptor.ShortName);
|
||||||
|
Assert.Same(typeof(AttributeViewComponent).GetMethod("InvokeAsync"), descriptor.MethodInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(NoMethodsViewComponent))]
|
||||||
|
[InlineData(typeof(NonPublicInvokeAsyncViewComponent))]
|
||||||
|
[InlineData(typeof(NonPublicInvokeViewComponent))]
|
||||||
|
public void GetViewComponents_ThrowsIfTypeHasNoInvocationMethods(Type type)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = $"Could not find an 'Invoke' or 'InvokeAsync' method for the view component '{type}'.";
|
||||||
|
var provider = CreateProvider(type);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => provider.GetViewComponents().ToArray());
|
||||||
|
Assert.Equal(expected, ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(MultipleInvokeViewComponent))]
|
||||||
|
[InlineData(typeof(MultipleInvokeAsyncViewComponent))]
|
||||||
|
[InlineData(typeof(InvokeAndInvokeAsyncViewComponent))]
|
||||||
|
public void GetViewComponents_ThrowsIfTypeHasAmbiguousInvocationMethods(Type type)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = $"View component '{type}' must have exactly one public method named " +
|
||||||
|
"'InvokeAsync' or 'Invoke'.";
|
||||||
|
var provider = CreateProvider(type);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => provider.GetViewComponents().ToArray());
|
||||||
|
Assert.Equal(expected, ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(NonGenericTaskReturningInvokeAsyncViewComponent))]
|
||||||
|
[InlineData(typeof(VoidReturningInvokeAsyncViewComponent))]
|
||||||
|
[InlineData(typeof(NonTaskReturningInvokeAsyncViewComponent))]
|
||||||
|
public void GetViewComponents_ThrowsIfInvokeAsyncDoesNotHaveCorrectReturnType(Type type)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = $"Method 'InvokeAsync' of view component '{type}' should be declared to return Task<T>.";
|
||||||
|
var provider = CreateProvider(type);
|
||||||
|
|
||||||
|
// Act and Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => provider.GetViewComponents().ToArray());
|
||||||
|
Assert.Equal(expected, ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetViewComponents_ThrowsIfInvokeReturnsATask()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(TaskReturningInvokeViewComponent);
|
||||||
|
var expected = $"Method 'Invoke' of view component '{type}' cannot return a Task.";
|
||||||
|
var provider = CreateProvider(type);
|
||||||
|
|
||||||
|
// Act and Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => provider.GetViewComponents().ToArray());
|
||||||
|
Assert.Equal(expected, ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetViewComponents_ThrowsIfInvokeIsVoidReturning()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(VoidReturningInvokeViewComponent);
|
||||||
|
var expected = $"Method 'Invoke' of view component '{type}' should be declared to return a value.";
|
||||||
|
var provider = CreateProvider(type);
|
||||||
|
|
||||||
|
// Act and Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => provider.GetViewComponents().ToArray());
|
||||||
|
Assert.Equal(expected, ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ConventionsViewComponent
|
private class ConventionsViewComponent
|
||||||
{
|
{
|
||||||
|
public string Invoke() => "Hello world";
|
||||||
}
|
}
|
||||||
|
|
||||||
[ViewComponent(Name = "AttributesAreGreat")]
|
[ViewComponent(Name = "AttributesAreGreat")]
|
||||||
private class AttributeViewComponent
|
private class AttributeViewComponent
|
||||||
{
|
{
|
||||||
|
public Task<string> InvokeAsync() => Task.FromResult("Hello world");
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MultipleInvokeViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke() => null;
|
||||||
|
|
||||||
|
public IViewComponentResult Invoke(int a) => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NoMethodsViewComponent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NonPublicInvokeViewComponent
|
||||||
|
{
|
||||||
|
private IViewComponentResult Invoke() => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NonPublicInvokeAsyncViewComponent
|
||||||
|
{
|
||||||
|
protected Task<IViewComponentResult> InvokeAsync() => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MultipleInvokeAsyncViewComponent
|
||||||
|
{
|
||||||
|
public Task<IViewComponentResult> InvokeAsync(string a) => null;
|
||||||
|
|
||||||
|
public Task<IViewComponentResult> InvokeAsync(int a) => null;
|
||||||
|
|
||||||
|
public Task<IViewComponentResult> InvokeAsync(int a, int b) => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InvokeAndInvokeAsyncViewComponent
|
||||||
|
{
|
||||||
|
public Task<IViewComponentResult> InvokeAsync(string a) => null;
|
||||||
|
|
||||||
|
public string InvokeAsync(int a) => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NonGenericTaskReturningInvokeAsyncViewComponent
|
||||||
|
{
|
||||||
|
public Task InvokeAsync() => Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class VoidReturningInvokeAsyncViewComponent
|
||||||
|
{
|
||||||
|
public void InvokeAsync()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NonTaskReturningInvokeAsyncViewComponent
|
||||||
|
{
|
||||||
|
public long InvokeAsync() => 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TaskReturningInvokeViewComponent
|
||||||
|
{
|
||||||
|
public Task Invoke() => Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VoidReturningInvokeViewComponent
|
||||||
|
{
|
||||||
|
public void Invoke(int x)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefaultViewComponentDescriptorProvider CreateProvider(Type componentType)
|
private DefaultViewComponentDescriptorProvider CreateProvider(Type componentType)
|
||||||
|
|
@ -80,11 +223,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
return
|
return
|
||||||
GetAssemblyProvider()
|
GetAssemblyProvider()
|
||||||
.CandidateAssemblies
|
.CandidateAssemblies
|
||||||
.SelectMany(a => a.DefinedTypes)
|
.SelectMany(a => a.DefinedTypes);
|
||||||
#if DNX451
|
|
||||||
.Select(t => t.GetTypeInfo())
|
|
||||||
#endif
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IAssemblyProvider GetAssemblyProvider()
|
private static IAssemblyProvider GetAssemblyProvider()
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
{
|
{
|
||||||
public class DefaultViewComponentSelectorTest
|
public class DefaultViewComponentSelectorTest
|
||||||
{
|
{
|
||||||
|
private static readonly string Namespace = typeof(DefaultViewComponentSelectorTest).Namespace;
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SelectComponent_ByShortNameWithSuffix()
|
public void SelectComponent_ByShortNameWithSuffix()
|
||||||
{
|
{
|
||||||
|
|
@ -22,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
var result = selector.SelectComponent("Suffix");
|
var result = selector.SelectComponent("Suffix");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(typeof(SuffixViewComponent), result.Type);
|
Assert.Same(typeof(ViewComponentContainer.SuffixViewComponent), result.Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -32,10 +34,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
var selector = CreateSelector();
|
var selector = CreateSelector();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = selector.SelectComponent("Microsoft.AspNet.Mvc.ViewComponents.Suffix");
|
var result = selector.SelectComponent($"{Namespace}.Suffix");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(typeof(SuffixViewComponent), result.Type);
|
Assert.Same(typeof(ViewComponentContainer.SuffixViewComponent), result.Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -48,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
var result = selector.SelectComponent("WithoutSuffix");
|
var result = selector.SelectComponent("WithoutSuffix");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(typeof(WithoutSuffix), result.Type);
|
Assert.Same(typeof(ViewComponentContainer.WithoutSuffix), result.Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -58,10 +60,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
var selector = CreateSelector();
|
var selector = CreateSelector();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = selector.SelectComponent("Microsoft.AspNet.Mvc.ViewComponents.WithoutSuffix");
|
var result = selector.SelectComponent($"{Namespace}.WithoutSuffix");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(typeof(WithoutSuffix), result.Type);
|
Assert.Same(typeof(ViewComponentContainer.WithoutSuffix), result.Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -74,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
var result = selector.SelectComponent("ByAttribute");
|
var result = selector.SelectComponent("ByAttribute");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(typeof(ByAttribute), result.Type);
|
Assert.Same(typeof(ViewComponentContainer.ByAttribute), result.Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -87,7 +89,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
var result = selector.SelectComponent("ByNamingConvention");
|
var result = selector.SelectComponent("ByNamingConvention");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(typeof(ByNamingConventionViewComponent), result.Type);
|
Assert.Same(typeof(ViewComponentContainer.ByNamingConventionViewComponent), result.Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -98,9 +100,9 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
|
|
||||||
var expected =
|
var expected =
|
||||||
"The view component name 'Ambiguous' matched multiple types:" + Environment.NewLine +
|
"The view component name 'Ambiguous' matched multiple types:" + Environment.NewLine +
|
||||||
"Type: 'Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentSelectorTest+Ambiguous1' - " +
|
$"Type: '{typeof(ViewComponentContainer.Ambiguous1)}' - " +
|
||||||
"Name: 'Namespace1.Ambiguous'" + Environment.NewLine +
|
"Name: 'Namespace1.Ambiguous'" + Environment.NewLine +
|
||||||
"Type: 'Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentSelectorTest+Ambiguous2' - " +
|
$"Type: '{typeof(ViewComponentContainer.Ambiguous2)}' - " +
|
||||||
"Name: 'Namespace2.Ambiguous'";
|
"Name: 'Namespace2.Ambiguous'";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -120,7 +122,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
var result = selector.SelectComponent("Namespace1.Ambiguous");
|
var result = selector.SelectComponent("Namespace1.Ambiguous");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(typeof(Ambiguous1), result.Type);
|
Assert.Same(typeof(ViewComponentContainer.Ambiguous1), result.Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -135,44 +137,56 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
var result = selector.SelectComponent(name);
|
var result = selector.SelectComponent(name);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(typeof(FullNameInAttribute), result.Type);
|
Assert.Same(typeof(ViewComponentContainer.FullNameInAttribute), result.Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IViewComponentSelector CreateSelector()
|
private IViewComponentSelector CreateSelector()
|
||||||
{
|
{
|
||||||
return new FilteredViewComponentSelector();
|
var provider = new DefaultViewComponentDescriptorCollectionProvider(
|
||||||
|
new FilteredViewComponentDescriptorProvider());
|
||||||
|
return new DefaultViewComponentSelector(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SuffixViewComponent : ViewComponent
|
private class ViewComponentContainer
|
||||||
{
|
{
|
||||||
}
|
public class SuffixViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public string Invoke() => "Hello";
|
||||||
|
}
|
||||||
|
|
||||||
private class WithoutSuffix : ViewComponent
|
public class WithoutSuffix : ViewComponent
|
||||||
{
|
{
|
||||||
}
|
public string Invoke() => "Hello";
|
||||||
|
}
|
||||||
|
|
||||||
private class ByNamingConventionViewComponent
|
public class ByNamingConventionViewComponent
|
||||||
{
|
{
|
||||||
}
|
public string Invoke() => "Hello";
|
||||||
|
}
|
||||||
|
|
||||||
[ViewComponent]
|
[ViewComponent]
|
||||||
private class ByAttribute
|
public class ByAttribute
|
||||||
{
|
{
|
||||||
}
|
public string Invoke() => "Hello";
|
||||||
|
}
|
||||||
|
|
||||||
[ViewComponent(Name = "Namespace1.Ambiguous")]
|
[ViewComponent(Name = "Namespace1.Ambiguous")]
|
||||||
private class Ambiguous1
|
public class Ambiguous1
|
||||||
{
|
{
|
||||||
}
|
public string Invoke() => "Hello";
|
||||||
|
}
|
||||||
|
|
||||||
[ViewComponent(Name = "Namespace2.Ambiguous")]
|
[ViewComponent(Name = "Namespace2.Ambiguous")]
|
||||||
private class Ambiguous2
|
public class Ambiguous2
|
||||||
{
|
{
|
||||||
}
|
public string Invoke() => "Hello";
|
||||||
|
}
|
||||||
|
|
||||||
[ViewComponent(Name = "CoolNameSpace.FullNameInAttribute")]
|
[ViewComponent(Name = "CoolNameSpace.FullNameInAttribute")]
|
||||||
private class FullNameInAttribute
|
public class FullNameInAttribute
|
||||||
{
|
{
|
||||||
|
public string Invoke() => "Hello";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will only consider types nested inside this class as ViewComponent classes
|
// This will only consider types nested inside this class as ViewComponent classes
|
||||||
|
|
@ -181,10 +195,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
public FilteredViewComponentDescriptorProvider()
|
public FilteredViewComponentDescriptorProvider()
|
||||||
: base(GetAssemblyProvider())
|
: base(GetAssemblyProvider())
|
||||||
{
|
{
|
||||||
AllowedTypes = typeof(DefaultViewComponentSelectorTest).GetNestedTypes(BindingFlags.NonPublic);
|
AllowedTypes = typeof(ViewComponentContainer).GetNestedTypes(bindingAttr: BindingFlags.Public);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type[] AllowedTypes { get; private set; }
|
public Type[] AllowedTypes { get; }
|
||||||
|
|
||||||
protected override bool IsViewComponentType(TypeInfo typeInfo)
|
protected override bool IsViewComponentType(TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
|
|
@ -208,19 +222,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||||
{
|
{
|
||||||
var assemblyProvider = new StaticAssemblyProvider();
|
var assemblyProvider = new StaticAssemblyProvider();
|
||||||
assemblyProvider.CandidateAssemblies.Add(
|
assemblyProvider.CandidateAssemblies.Add(
|
||||||
typeof(FilteredViewComponentSelector).GetTypeInfo().Assembly);
|
typeof(ViewComponentContainer).GetTypeInfo().Assembly);
|
||||||
|
|
||||||
return assemblyProvider;
|
return assemblyProvider;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will only consider types nested inside this class as ViewComponent classes
|
|
||||||
private class FilteredViewComponentSelector : DefaultViewComponentSelector
|
|
||||||
{
|
|
||||||
public FilteredViewComponentSelector()
|
|
||||||
: base(new DefaultViewComponentDescriptorCollectionProvider(new FilteredViewComponentDescriptorProvider()))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
#if MOCK_SUPPORT
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Abstractions;
|
using Microsoft.AspNet.Mvc.Abstractions;
|
||||||
|
|
@ -57,7 +57,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var viewComponentContext = new ViewComponentContext(
|
var viewComponentContext = new ViewComponentContext(
|
||||||
viewComponentDescriptor,
|
viewComponentDescriptor,
|
||||||
new object[0],
|
new Dictionary<string, object>(),
|
||||||
new HtmlTestEncoder(),
|
new HtmlTestEncoder(),
|
||||||
viewContext,
|
viewContext,
|
||||||
writer);
|
writer);
|
||||||
|
|
@ -65,5 +65,4 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return viewComponentContext;
|
return viewComponentContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
|
@ -85,7 +86,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var viewComponentContext = new ViewComponentContext(
|
var viewComponentContext = new ViewComponentContext(
|
||||||
viewComponentDescriptor,
|
viewComponentDescriptor,
|
||||||
new object[0],
|
new Dictionary<string, object>(),
|
||||||
new HtmlTestEncoder(),
|
new HtmlTestEncoder(),
|
||||||
viewContext,
|
viewContext,
|
||||||
writer);
|
writer);
|
||||||
|
|
|
||||||
|
|
@ -1,282 +0,0 @@
|
||||||
// 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.Globalization;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
|
||||||
{
|
|
||||||
public class ViewComponentMethodSelectorTest
|
|
||||||
{
|
|
||||||
[Theory]
|
|
||||||
[InlineData(typeof(ViewComponentWithSyncInvoke), new object[] { "" })]
|
|
||||||
[InlineData(typeof(ViewComponentWithAsyncInvoke), new object[] { 42, false })]
|
|
||||||
[InlineData(typeof(ViewComponentWithNonPublicNonInstanceInvokes), new object[] { })]
|
|
||||||
[InlineData(typeof(ViewComponentWithNonPublicNonInstanceInvokes), new object[] { "" })]
|
|
||||||
public void FindAsyncMethod_ReturnsNull_IfMatchCannotBeFound(Type type, object[] args)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeInfo = type.GetTypeInfo();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var method = ViewComponentMethodSelector.FindAsyncMethod(typeInfo, args);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Null(method);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(typeof(ViewComponentWithAsyncInvoke), new object[0])]
|
|
||||||
[InlineData(typeof(ViewComponentWithSyncInvoke), new object[] { "" })]
|
|
||||||
[InlineData(typeof(ViewComponentWithAsyncInvoke), new object[] { "" })]
|
|
||||||
[InlineData(typeof(ViewComponentWithSyncInvoke), new object[] { 42 })]
|
|
||||||
[InlineData(typeof(ViewComponentWithAsyncInvoke), new object[] { "", 42 })]
|
|
||||||
[InlineData(typeof(ViewComponentWithNonPublicNonInstanceInvokes), new object[] { })]
|
|
||||||
[InlineData(typeof(ViewComponentWithNonPublicNonInstanceInvokes), new object[] { "" })]
|
|
||||||
[InlineData(typeof(BaseClass), new object[] { })]
|
|
||||||
public void FindSyncMethod_ReturnsNull_IfMatchCannotBeFound(Type type, object[] args)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeInfo = type.GetTypeInfo();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var method = ViewComponentMethodSelector.FindSyncMethod(typeInfo, args);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Null(method);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(new object[] { new object[] { "Hello" } })]
|
|
||||||
[InlineData(new object[] { new object[] { 4 } })]
|
|
||||||
[InlineData(new object[] { new object[] { "", 5 } })]
|
|
||||||
public void FindAsyncMethod_ThrowsIfInvokeAsyncDoesNotHaveCorrectReturnType(object[] args)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeInfo = typeof(TypeWithInvalidInvokeAsync).GetTypeInfo();
|
|
||||||
|
|
||||||
// Act and Assert
|
|
||||||
var ex = Assert.Throws<InvalidOperationException>(
|
|
||||||
() => ViewComponentMethodSelector.FindAsyncMethod(typeInfo, args));
|
|
||||||
Assert.Equal("The async view component method 'InvokeAsync' should be declared to return Task<T>.",
|
|
||||||
ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void FindSyncMethod_ThrowsIfInvokeSyncIsAVoidMethod()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expectedMessage = "The view component method 'Invoke' should be declared to return a value.";
|
|
||||||
var typeInfo = typeof(TypeWithInvalidInvokeSync).GetTypeInfo();
|
|
||||||
|
|
||||||
// Act and Assert
|
|
||||||
var ex = Assert.Throws<InvalidOperationException>(
|
|
||||||
() => ViewComponentMethodSelector.FindSyncMethod(typeInfo, new object[] { 4 }));
|
|
||||||
Assert.Equal(expectedMessage, ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void FindSyncMethod_ThrowsIfInvokeSyncReturnsTask()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expectedMessage = "The view component method 'Invoke' cannot return a Task.";
|
|
||||||
var typeInfo = typeof(TypeWithInvalidInvokeSync).GetTypeInfo();
|
|
||||||
|
|
||||||
// Act and Assert
|
|
||||||
var ex = Assert.Throws<InvalidOperationException>(
|
|
||||||
() => ViewComponentMethodSelector.FindSyncMethod(typeInfo, new object[] { "" }));
|
|
||||||
Assert.Equal(expectedMessage, ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TheoryData FindAsyncMethod_ReturnsMethodMatchingParametersData
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var derivedClass = new DerivedClass();
|
|
||||||
|
|
||||||
return new TheoryData<Type, object[], string>
|
|
||||||
{
|
|
||||||
{ typeof(ViewComponentWithAsyncInvoke), new object[] { "", }, "1" },
|
|
||||||
{ typeof(ViewComponentWithAsyncInvoke), new object[] { "", 2 }, "2" },
|
|
||||||
{ typeof(ViewComponentWithAsyncInvoke), new object[] { "", 0, 1 }, "3" },
|
|
||||||
{ typeof(ViewComponentWithAsyncInvoke), new object[] { 1, false, 1 }, "4" },
|
|
||||||
{ typeof(MethodsWithValueConversions), new object[] { 2, (byte)1, (byte)2 }, "2" },
|
|
||||||
{ typeof(MethodsWithValueConversions), new object[] { derivedClass, derivedClass }, "4" },
|
|
||||||
{ typeof(MethodsWithValueConversions), new object[] { CultureInfo.InvariantCulture }, "6" },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(FindAsyncMethod_ReturnsMethodMatchingParametersData))]
|
|
||||||
public void FindAsyncMethod_ReturnsMethodMatchingParameters(Type type, object[] args, string expectedId)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeInfo = type.GetTypeInfo();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var method = ViewComponentMethodSelector.FindAsyncMethod(typeInfo, args);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(method);
|
|
||||||
var data = method.GetCustomAttribute<MethodDataAttribute>();
|
|
||||||
Assert.Equal(expectedId, data.Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TheoryData FindSyncMethod_ReturnsMethodMatchingParametersData
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var derivedClass = new DerivedAgain();
|
|
||||||
|
|
||||||
return new TheoryData<Type, object[], string>
|
|
||||||
{
|
|
||||||
{ typeof(ViewComponentWithSyncInvoke), new object[] { }, "1" },
|
|
||||||
{ typeof(ViewComponentWithSyncInvoke), new object[] { 2, 3 }, "2" },
|
|
||||||
{ typeof(ViewComponentWithSyncInvoke), new object[] { "", 0, true }, "3" },
|
|
||||||
{ typeof(MethodsWithValueConversions), new object[] { 1, (byte)2, 3.0f }, "1" },
|
|
||||||
{ typeof(MethodsWithValueConversions), new object[] { derivedClass, derivedClass }, "3" },
|
|
||||||
{ typeof(MethodsWithValueConversions), new object[] { "Hello world" }, "5" },
|
|
||||||
{ typeof(DerivedClass), new object[] { }, "Derived1" },
|
|
||||||
#if !DNXCORE50
|
|
||||||
{ typeof(DerivedAgain), new object[] { "" }, "Derived2" },
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(FindSyncMethod_ReturnsMethodMatchingParametersData))]
|
|
||||||
public void FindSyncMethod_ReturnsMethodMatchingParameters(Type type, object[] args, string expectedId)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeInfo = type.GetTypeInfo();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var method = ViewComponentMethodSelector.FindSyncMethod(typeInfo, args);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotNull(method);
|
|
||||||
var data = method.GetCustomAttribute<MethodDataAttribute>();
|
|
||||||
Assert.Equal(expectedId, data.Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ViewComponentWithSyncInvoke
|
|
||||||
{
|
|
||||||
[MethodData("1")]
|
|
||||||
public int Invoke() => 3;
|
|
||||||
|
|
||||||
[MethodData("2")]
|
|
||||||
public int Invoke(int a, int? b) => a + b.Value;
|
|
||||||
|
|
||||||
[MethodData("3")]
|
|
||||||
public int Invoke(string a, int b, bool? c) => 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ViewComponentWithAsyncInvoke
|
|
||||||
{
|
|
||||||
[MethodData("1")]
|
|
||||||
public Task<string> InvokeAsync(string value) => Task.FromResult(value.ToUpperInvariant());
|
|
||||||
|
|
||||||
[MethodData("2")]
|
|
||||||
public Task<string> InvokeAsync(string a, int b) => Task.FromResult(a + b);
|
|
||||||
|
|
||||||
[MethodData("3")]
|
|
||||||
public Task<string> InvokeAsync(string a, int? b, int c) => Task.FromResult(a + b + c);
|
|
||||||
|
|
||||||
[MethodData("4")]
|
|
||||||
public Task<string> InvokeAsync(int? a, bool? b, int c) => Task.FromResult(a.ToString() + b + c);
|
|
||||||
|
|
||||||
[MethodData("5")]
|
|
||||||
public Task<string> InvokeAsync(object value) => Task.FromResult(value.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MethodsWithValueConversions
|
|
||||||
{
|
|
||||||
[MethodData("1")]
|
|
||||||
public int Invoke(long a, char b, double c) => 1;
|
|
||||||
|
|
||||||
[MethodData("2")]
|
|
||||||
public Task<int> InvokeAsync(float a, float b, byte c) => Task.FromResult(1);
|
|
||||||
|
|
||||||
[MethodData("3")]
|
|
||||||
public int Invoke(BaseClass a, DerivedClass b) => 1;
|
|
||||||
|
|
||||||
[MethodData("4")]
|
|
||||||
public Task<int> InvokeAsync(BaseClass a, DerivedClass b) => Task.FromResult(1);
|
|
||||||
|
|
||||||
[MethodData("5")]
|
|
||||||
public int Invoke(IEnumerable<char> value) => 1;
|
|
||||||
|
|
||||||
[MethodData("6")]
|
|
||||||
public Task<int> InvokeAsync(IFormatProvider formatProvider) => Task.FromResult(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ViewComponentWithNonPublicNonInstanceInvokes
|
|
||||||
{
|
|
||||||
public static int Invoke() => 1;
|
|
||||||
|
|
||||||
private int Invoke(string a) => 2;
|
|
||||||
|
|
||||||
public static Task<int> InvokeAsync() => Task.FromResult(3);
|
|
||||||
|
|
||||||
protected Task<string> InvokeAsync(string a) => Task.FromResult(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BaseClass
|
|
||||||
{
|
|
||||||
[MethodData("Base")]
|
|
||||||
public static int Invoke() => 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DerivedClass : BaseClass
|
|
||||||
{
|
|
||||||
[MethodData("Derived1")]
|
|
||||||
public new int Invoke() => 1;
|
|
||||||
|
|
||||||
[MethodData("Derived2")]
|
|
||||||
public int Invoke(string x) => 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DerivedAgain : DerivedClass
|
|
||||||
{
|
|
||||||
[MethodData("DerivedAgain")]
|
|
||||||
public new static int Invoke(string x) => 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TypeWithInvalidInvokeAsync
|
|
||||||
{
|
|
||||||
public Task InvokeAsync(string value) => Task.FromResult(value);
|
|
||||||
|
|
||||||
public void InvokeAsync(int value)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public long InvokeAsync(string a, int b) => b;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TypeWithInvalidInvokeSync
|
|
||||||
{
|
|
||||||
public Task Invoke(string value) => Task.FromResult(value);
|
|
||||||
|
|
||||||
public void Invoke(int value)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MethodDataAttribute : Attribute
|
|
||||||
{
|
|
||||||
public MethodDataAttribute(string data)
|
|
||||||
{
|
|
||||||
Data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Data { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,9 +2,11 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Abstractions;
|
using Microsoft.AspNet.Mvc.Abstractions;
|
||||||
|
|
@ -147,9 +149,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = string.Join(Environment.NewLine,
|
var expected = string.Join(Environment.NewLine,
|
||||||
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
|
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
|
||||||
"location1",
|
"location1",
|
||||||
"location2");
|
"location2");
|
||||||
|
|
||||||
var view = Mock.Of<IView>();
|
var view = Mock.Of<IView>();
|
||||||
|
|
||||||
|
|
@ -185,9 +187,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = string.Join(Environment.NewLine,
|
var expected = string.Join(Environment.NewLine,
|
||||||
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
|
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
|
||||||
"location1",
|
"location1",
|
||||||
"location2");
|
"location2");
|
||||||
|
|
||||||
var view = Mock.Of<IView>();
|
var view = Mock.Of<IView>();
|
||||||
|
|
||||||
|
|
@ -223,11 +225,11 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = string.Join(Environment.NewLine,
|
var expected = string.Join(Environment.NewLine,
|
||||||
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
|
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
|
||||||
"location1",
|
"location1",
|
||||||
"location2",
|
"location2",
|
||||||
"location3",
|
"location3",
|
||||||
"location4");
|
"location4");
|
||||||
|
|
||||||
var view = Mock.Of<IView>();
|
var view = Mock.Of<IView>();
|
||||||
|
|
||||||
|
|
@ -535,11 +537,12 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
ShortName = "Invoke",
|
ShortName = "Invoke",
|
||||||
Type = typeof(object),
|
Type = typeof(object),
|
||||||
|
MethodInfo = typeof(object).GetTypeInfo().DeclaredMethods.First()
|
||||||
};
|
};
|
||||||
|
|
||||||
var viewComponentContext = new ViewComponentContext(
|
var viewComponentContext = new ViewComponentContext(
|
||||||
viewComponentDescriptor,
|
viewComponentDescriptor,
|
||||||
new object[0],
|
new Dictionary<string, object>(),
|
||||||
new HtmlTestEncoder(),
|
new HtmlTestEncoder(),
|
||||||
viewContext,
|
viewContext,
|
||||||
TextWriter.Null);
|
TextWriter.Null);
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
@Component.Invoke("JsonTextInView")
|
@await Component.InvokeAsync("JsonTextInView")
|
||||||
|
|
@ -1 +1 @@
|
||||||
@Component.Invoke("RequestId")
|
@await Component.InvokeAsync("RequestId")
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
@model Person
|
@model Person
|
||||||
<h1>@Model.Name</h1>
|
<h1>@Model.Name</h1>
|
||||||
@Component.Invoke("InheritingViewComponent", Model.Address)
|
@await Component.InvokeAsync("InheritingViewComponent", new { address = Model.Address })
|
||||||
|
|
@ -12,4 +12,4 @@
|
||||||
}
|
}
|
||||||
ViewWithRelativePath-content
|
ViewWithRelativePath-content
|
||||||
<partial>@await Html.PartialAsync("../Shared/_PartialThatSetsTitle.cshtml")</partial>
|
<partial>@await Html.PartialAsync("../Shared/_PartialThatSetsTitle.cshtml")</partial>
|
||||||
@await Component.InvokeAsync("ComponentWithRelativePath", person)
|
@await Component.InvokeAsync("ComponentWithRelativePath", new { person })
|
||||||
|
|
@ -2,6 +2,6 @@
|
||||||
ViewData["Title"] = "Page title";
|
ViewData["Title"] = "Page title";
|
||||||
// The invoked partial sets a title, but this shouldn't override the current page's ViewData \ ViewBag.
|
// The invoked partial sets a title, but this shouldn't override the current page's ViewData \ ViewBag.
|
||||||
await Html.RenderPartialAsync("_PartialThatSetsTitle");
|
await Html.RenderPartialAsync("_PartialThatSetsTitle");
|
||||||
await Component.RenderInvokeAsync("ComponentThatSetsTitle");
|
@await Component.InvokeAsync("ComponentThatSetsTitle")
|
||||||
Layout = "/Views/Shared/_LayoutWithTitle.cshtml";
|
Layout = "/Views/Shared/_LayoutWithTitle.cshtml";
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<Index>
|
<Index>
|
||||||
@ViewContext.ExecutingFilePath
|
@ViewContext.ExecutingFilePath
|
||||||
@ViewContext.View.Path
|
@ViewContext.View.Path
|
||||||
@Component.Invoke("ComponentForViewWithPaths")
|
@await Component.InvokeAsync("ComponentForViewWithPaths")
|
||||||
@Html.Partial("_Partial")
|
@Html.Partial("_Partial")
|
||||||
</Index>
|
</Index>
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
|
|
@ -58,7 +59,7 @@ namespace MvcSample.Web.Components
|
||||||
|
|
||||||
await result.ExecuteAsync(new ViewComponentContext(
|
await result.ExecuteAsync(new ViewComponentContext(
|
||||||
viewComponentDescriptor,
|
viewComponentDescriptor,
|
||||||
new object[0],
|
new Dictionary<string, object>(),
|
||||||
_htmlEncoder,
|
_htmlEncoder,
|
||||||
ViewContext,
|
ViewContext,
|
||||||
writer));
|
writer));
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
<h3>Current Tag Cloud from Tag Helper</h3>
|
<h3>Current Tag Cloud from Tag Helper</h3>
|
||||||
<tag-cloud count="Model.TagsToShow" surround="div" />
|
<tag-cloud count="Model.TagsToShow" surround="div" />
|
||||||
<h3>Current Tag Cloud from ViewComponentHelper:</h3>
|
<h3>Current Tag Cloud from ViewComponentHelper:</h3>
|
||||||
<section bold>@await Component.InvokeAsync("Tags", 15)</section>
|
<section bold>@await Component.InvokeAsync("Tags", new { count = 15 })</section>
|
||||||
@{
|
@{
|
||||||
RenderTemplate(
|
RenderTemplate(
|
||||||
"Tag Cloud from Template: ",
|
"Tag Cloud from Template: ",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue