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);
|
||||
}
|
||||
|
||||
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 class="sidebar">
|
||||
<cache expires-after="TimeSpan.FromMinutes(20)">
|
||||
@Component.Invoke("FeaturedMovies")
|
||||
@await Component.InvokeAsync("FeaturedMovies")
|
||||
</cache>
|
||||
</div>
|
||||
<div style="clear: left"></div>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
<em>Critics say:</em>
|
||||
<cache vary-by="@movie.Name">
|
||||
@Component.Invoke("FeaturedMovies", movie.Name)
|
||||
@await Component.InvokeAsync("Movies", new { movieName = movie.Name })
|
||||
</cache>
|
||||
</dd>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,22 +7,27 @@ using Microsoft.AspNet.Html;
|
|||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Supports the rendering of view components in a view.
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
void RenderInvoke(string name, params object[] args);
|
||||
|
||||
void RenderInvoke(Type componentType, params object[] args);
|
||||
|
||||
Task<IHtmlContent> InvokeAsync(string name, params object[] args);
|
||||
|
||||
Task<IHtmlContent> InvokeAsync(Type componentType, params object[] args);
|
||||
|
||||
Task RenderInvokeAsync(string name, params object[] args);
|
||||
|
||||
Task RenderInvokeAsync(Type componentType, params object[] args);
|
||||
/// <summary>
|
||||
/// Invokes a view component of type <paramref name="componentType" />.
|
||||
/// </summary>
|
||||
/// <param name="componentType">The view component <see cref="Type"/>.</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(Type componentType, object arguments);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,21 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Logging
|
|||
{
|
||||
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;
|
||||
|
||||
static DefaultViewComponentInvokerLoggerExtensions()
|
||||
{
|
||||
_viewComponentExecuting = LoggerMessage.Define<string>(
|
||||
_viewComponentExecuting = LoggerMessage.Define<string, string[]>(
|
||||
LogLevel.Debug,
|
||||
1,
|
||||
"Executing view component {ViewComponentName}");
|
||||
"Executing view component {ViewComponentName} with arguments ({Arguments}).");
|
||||
|
||||
_viewComponentExecuted = LoggerMessage.Define<string, double, string>(
|
||||
LogLevel.Debug,
|
||||
|
|
@ -32,9 +38,29 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Logging
|
|||
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(
|
||||
|
|
|
|||
|
|
@ -8,41 +8,29 @@ namespace Microsoft.AspNet.Mvc.Logging
|
|||
{
|
||||
public static class ViewComponentResultLoggerExtensions
|
||||
{
|
||||
private static readonly Action<ILogger, string, string[], Exception> _viewComponentResultExecuting;
|
||||
private static readonly Action<ILogger, string, Exception> _viewComponentResultExecuting;
|
||||
|
||||
static ViewComponentResultLoggerExtensions()
|
||||
{
|
||||
_viewComponentResultExecuting = LoggerMessage.Define<string, string[]>(
|
||||
_viewComponentResultExecuting = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
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))
|
||||
{
|
||||
var formattedArguments = new string[arguments.Length];
|
||||
for (var i = 0; i < arguments.Length; i++)
|
||||
{
|
||||
formattedArguments[i] = Convert.ToString(arguments[i]);
|
||||
}
|
||||
|
||||
_viewComponentResultExecuting(logger, viewComponentName, formattedArguments, null);
|
||||
_viewComponentResultExecuting(logger, viewComponentName, 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))
|
||||
{
|
||||
var formattedArguments = new string[arguments.Length];
|
||||
for (var i = 0; i < arguments.Length; i++)
|
||||
{
|
||||
formattedArguments[i] = Convert.ToString(arguments[i]);
|
||||
}
|
||||
|
||||
_viewComponentResultExecuting(logger, viewComponentType.Name, formattedArguments, null);
|
||||
_viewComponentResultExecuting(logger, viewComponentType.Name, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
<DisabledCustomTools>.resx</DisabledCustomTools>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
/// <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>
|
||||
internal static string ViewComponent_AsyncMethod_ShouldReturnTask
|
||||
{
|
||||
|
|
@ -35,11 +35,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
/// <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>
|
||||
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>
|
||||
|
|
@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
/// <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>
|
||||
internal static string ViewComponent_SyncMethod_ShouldReturnValue
|
||||
{
|
||||
|
|
@ -67,11 +67,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
/// <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>
|
||||
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>
|
||||
|
|
@ -107,7 +107,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Could not find an '{0}' method matching the parameters.
|
||||
/// Could not find an '{0}' or '{1}' method for the view component '{2}'.
|
||||
/// </summary>
|
||||
internal static string ViewComponent_CannotFindMethod
|
||||
{
|
||||
|
|
@ -115,27 +115,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Could not find an '{0}' method matching the parameters.
|
||||
/// Could not find an '{0}' or '{1}' method for the view component '{2}'.
|
||||
/// </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);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod"), p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -859,7 +843,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The view component method '{0}' cannot return a {1}.
|
||||
/// Method '{0}' of view component '{1}' cannot return a {2}.
|
||||
/// </summary>
|
||||
internal static string ViewComponent_SyncMethod_CannotReturnTask
|
||||
{
|
||||
|
|
@ -867,11 +851,27 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The view component method '{0}' cannot return a {1}.
|
||||
/// Method '{0}' of view component '{1}' cannot return a {2}.
|
||||
/// </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)
|
||||
|
|
|
|||
|
|
@ -7,48 +7,76 @@ using Microsoft.AspNet.Html;
|
|||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="IViewComponentHelper"/>.
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(helper));
|
||||
}
|
||||
|
||||
helper.RenderInvoke(typeof(TComponent), args);
|
||||
return helper.InvokeAsync(componentType, arguments: null);
|
||||
}
|
||||
|
||||
public static Task<IHtmlContent> InvokeAsync<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>
|
||||
/// <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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -122,13 +122,13 @@
|
|||
<comment>{1} is the newline character</comment>
|
||||
</data>
|
||||
<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 name="ViewComponent_MustReturnValue" xml:space="preserve">
|
||||
<value>A view component must return a non-null value.</value>
|
||||
</data>
|
||||
<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 name="ViewComponent_CannotFindComponent" xml:space="preserve">
|
||||
<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>
|
||||
</data>
|
||||
<data name="ViewComponent_CannotFindMethod" xml:space="preserve">
|
||||
<value>Could not find an '{0}' method matching the parameters.</value>
|
||||
</data>
|
||||
<data name="ViewComponent_CannotFindMethod_WithFallback" xml:space="preserve">
|
||||
<value>Could not find an '{0}' or '{1}' method matching the parameters.</value>
|
||||
<value>Could not find an '{0}' or '{1}' method for the view component '{2}'.</value>
|
||||
</data>
|
||||
<data name="ViewComponent_InvalidReturnValue" xml:space="preserve">
|
||||
<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>
|
||||
</data>
|
||||
<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>
|
||||
</root>
|
||||
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Html;
|
||||
using Microsoft.AspNet.Mvc.Internal;
|
||||
using Microsoft.AspNet.Mvc.Logging;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
|
@ -26,7 +28,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// <summary>
|
||||
/// Gets or sets the arguments provided to the view component.
|
||||
/// </summary>
|
||||
public object[] Arguments { get; set; }
|
||||
public object Arguments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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 logger = loggerFactory.CreateLogger<ViewComponentResult>();
|
||||
var htmlEncoder = services.GetRequiredService<HtmlEncoder>();
|
||||
|
||||
var viewData = ViewData;
|
||||
if (viewData == null)
|
||||
|
|
@ -119,23 +122,29 @@ namespace Microsoft.AspNet.Mvc
|
|||
htmlHelperOptions);
|
||||
|
||||
(viewComponentHelper as ICanHasViewContext)?.Contextualize(viewContext);
|
||||
var result = await GetViewComponentResult(viewComponentHelper, logger);
|
||||
|
||||
if (ViewComponentType == null && ViewComponentName == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatViewComponentResult_NameOrTypeMustBeSet(
|
||||
nameof(ViewComponentName),
|
||||
nameof(ViewComponentType)));
|
||||
}
|
||||
else if (ViewComponentType == null)
|
||||
{
|
||||
logger.ViewComponentResultExecuting(ViewComponentName, Arguments);
|
||||
await viewComponentHelper.RenderInvokeAsync(ViewComponentName, Arguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.ViewComponentResultExecuting(ViewComponentType, Arguments);
|
||||
await viewComponentHelper.RenderInvokeAsync(ViewComponentType, Arguments);
|
||||
}
|
||||
result.WriteTo(writer, htmlEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
private Task<IHtmlContent> GetViewComponentResult(IViewComponentHelper viewComponentHelper, ILogger logger)
|
||||
{
|
||||
if (ViewComponentType == null && ViewComponentName == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatViewComponentResult_NameOrTypeMustBeSet(
|
||||
nameof(ViewComponentName),
|
||||
nameof(ViewComponentType)));
|
||||
}
|
||||
else if (ViewComponentType == null)
|
||||
{
|
||||
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.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||
{
|
||||
|
|
@ -14,6 +16,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
/// </summary>
|
||||
public class DefaultViewComponentDescriptorProvider : IViewComponentDescriptorProvider
|
||||
{
|
||||
private const string AsyncMethodName = "InvokeAsync";
|
||||
private const string SyncMethodName = "Invoke";
|
||||
private readonly IAssemblyProvider _assemblyProvider;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -32,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
|
||||
return types
|
||||
.Where(IsViewComponentType)
|
||||
.Select(CreateCandidate);
|
||||
.Select(CreateDescriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -47,11 +51,11 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="typeInfo">The <see cref="TypeInfo"/>.</param>
|
||||
/// <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>
|
||||
protected virtual bool IsViewComponentType(TypeInfo typeInfo)
|
||||
{
|
||||
|
|
@ -63,16 +67,70 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
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),
|
||||
ShortName = ViewComponentConventions.GetComponentName(typeInfo),
|
||||
Type = typeInfo.AsType(),
|
||||
Type = type,
|
||||
MethodInfo = FindMethod(type)
|
||||
};
|
||||
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Html;
|
||||
|
|
@ -10,9 +9,13 @@ using Microsoft.AspNet.Mvc.Rendering;
|
|||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNet.Mvc.ViewFeatures.Buffer;
|
||||
using Microsoft.AspNet.Mvc.ViewFeatures.Internal;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation for <see cref="IViewComponentHelper"/>.
|
||||
/// </summary>
|
||||
public class DefaultViewComponentHelper : IViewComponentHelper, ICanHasViewContext
|
||||
{
|
||||
private readonly IViewComponentDescriptorCollectionProvider _descriptorProvider;
|
||||
|
|
@ -22,6 +25,16 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
private readonly IViewBufferScope _viewBufferScope;
|
||||
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(
|
||||
IViewComponentDescriptorCollectionProvider descriptorProvider,
|
||||
HtmlEncoder htmlEncoder,
|
||||
|
|
@ -61,6 +74,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
_viewBufferScope = viewBufferScope;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Contextualize(ViewContext viewContext)
|
||||
{
|
||||
if (viewContext == null)
|
||||
|
|
@ -71,133 +85,41 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
_viewContext = viewContext;
|
||||
}
|
||||
|
||||
public IHtmlContent Invoke(string name, params object[] arguments)
|
||||
/// <inheritdoc />
|
||||
public Task<IHtmlContent> InvokeAsync(string name, 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))
|
||||
{
|
||||
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);
|
||||
if (descriptor == null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return descriptor;
|
||||
|
|
@ -208,58 +130,30 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
componentType.FullName));
|
||||
}
|
||||
|
||||
private Task InvokeCoreAsync(
|
||||
TextWriter writer,
|
||||
private async Task<IHtmlContent> InvokeCoreAsync(
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation for <see cref="IViewComponentInvoker"/>.
|
||||
/// </summary>
|
||||
public class DefaultViewComponentInvoker : IViewComponentInvoker
|
||||
{
|
||||
private readonly ITypeActivatorCache _typeActivatorCache;
|
||||
|
|
@ -23,6 +26,13 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
private readonly DiagnosticSource _diagnosticSource;
|
||||
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(
|
||||
ITypeActivatorCache typeActivatorCache,
|
||||
IViewComponentActivator viewComponentActivator,
|
||||
|
|
@ -55,27 +65,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Invoke(ViewComponentContext context)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task InvokeAsync(ViewComponentContext context)
|
||||
{
|
||||
if (context == null)
|
||||
|
|
@ -83,32 +73,25 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
IViewComponentResult result;
|
||||
|
||||
var asyncMethod = ViewComponentMethodSelector.FindAsyncMethod(
|
||||
context.ViewComponentDescriptor.Type.GetTypeInfo(),
|
||||
context.Arguments);
|
||||
if (asyncMethod == null)
|
||||
var methodInfo = context.ViewComponentDescriptor?.MethodInfo;
|
||||
if (methodInfo == null)
|
||||
{
|
||||
// We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still
|
||||
// execute the IViewResult asynchronously.
|
||||
var syncMethod = ViewComponentMethodSelector.FindSyncMethod(
|
||||
context.ViewComponentDescriptor.Type.GetTypeInfo(),
|
||||
context.Arguments);
|
||||
if (syncMethod == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatViewComponent_CannotFindMethod_WithFallback(
|
||||
ViewComponentMethodSelector.SyncMethodName, ViewComponentMethodSelector.AsyncMethodName));
|
||||
}
|
||||
else
|
||||
{
|
||||
result = InvokeSyncCore(syncMethod, context);
|
||||
}
|
||||
throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(
|
||||
nameof(ViewComponentDescriptor.MethodInfo),
|
||||
nameof(ViewComponentDescriptor)));
|
||||
}
|
||||
|
||||
var isAsync = typeof(Task).GetTypeInfo().IsAssignableFrom(methodInfo.ReturnType.GetTypeInfo());
|
||||
IViewComponentResult result;
|
||||
if (isAsync)
|
||||
{
|
||||
result = await InvokeAsyncCore(context);
|
||||
}
|
||||
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);
|
||||
|
|
@ -129,29 +112,20 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
return component;
|
||||
}
|
||||
|
||||
private async Task<IViewComponentResult> InvokeAsyncCore(
|
||||
MethodInfo method,
|
||||
ViewComponentContext context)
|
||||
private async Task<IViewComponentResult> InvokeAsyncCore(ViewComponentContext context)
|
||||
{
|
||||
if (method == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(method));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var component = CreateComponent(context);
|
||||
|
||||
using (_logger.ViewComponentScope(context))
|
||||
{
|
||||
var method = context.ViewComponentDescriptor.MethodInfo;
|
||||
var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, method.GetParameters());
|
||||
|
||||
_diagnosticSource.BeforeViewComponent(context, component);
|
||||
_logger.ViewComponentExecuting(context);
|
||||
_logger.ViewComponentExecuting(context, arguments);
|
||||
|
||||
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);
|
||||
_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);
|
||||
|
||||
using (_logger.ViewComponentScope(context))
|
||||
{
|
||||
_diagnosticSource.BeforeViewComponent(context, component);
|
||||
_logger.ViewComponentExecuting(context);
|
||||
var method = context.ViewComponentDescriptor.MethodInfo;
|
||||
var arguments = ControllerActionExecutor.PrepareArguments(
|
||||
context.Arguments,
|
||||
method.GetParameters());
|
||||
|
||||
_diagnosticSource.BeforeViewComponent(context, component);
|
||||
_logger.ViewComponentExecuting(context, arguments);
|
||||
|
||||
var startTime = Environment.TickCount;
|
||||
object result;
|
||||
try
|
||||
{
|
||||
var startTime = Environment.TickCount;
|
||||
var result = method.Invoke(component, context.Arguments);
|
||||
|
||||
var viewComponentResult = CoerceToViewComponentResult(result);
|
||||
_logger.ViewComponentExecuted(context, startTime, viewComponentResult);
|
||||
_diagnosticSource.AfterViewComponent(context, viewComponentResult, component);
|
||||
|
||||
return viewComponentResult;
|
||||
result = method.Invoke(component, arguments);
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
|
|
@ -198,6 +162,12 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
exceptionInfo.Throw();
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Discovers the View Components in the application.
|
||||
/// Discovers the view components in the application.
|
||||
/// </summary>
|
||||
public interface IViewComponentDescriptorProvider
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,10 +5,17 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the contract for execution of a view component.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||
{
|
||||
/// <summary>
|
||||
/// Selects a View Component based on a View Component name.
|
||||
/// Selects a view component based on a view component name.
|
||||
/// </summary>
|
||||
public interface IViewComponentSelector
|
||||
{
|
||||
/// <summary>
|
||||
/// Selects a View Component based on <paramref name="componentName"/>.
|
||||
/// Selects a view component based on <paramref name="componentName"/>.
|
||||
/// </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>
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
|
|
@ -10,7 +11,7 @@ using Microsoft.AspNet.Mvc.ViewFeatures;
|
|||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||
{
|
||||
/// <summary>
|
||||
/// A context for View Components.
|
||||
/// A context for view components.
|
||||
/// </summary>
|
||||
public class ViewComponentContext
|
||||
{
|
||||
|
|
@ -23,7 +24,6 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
public ViewComponentContext()
|
||||
{
|
||||
ViewComponentDescriptor = new ViewComponentDescriptor();
|
||||
Arguments = new object[0];
|
||||
ViewContext = new ViewContext();
|
||||
}
|
||||
|
||||
|
|
@ -31,14 +31,14 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
/// Creates a new <see cref="ViewComponentContext"/>.
|
||||
/// </summary>
|
||||
/// <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 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="writer">The <see cref="TextWriter"/> for writing output.</param>
|
||||
public ViewComponentContext(
|
||||
ViewComponentDescriptor viewComponentDescriptor,
|
||||
object[] arguments,
|
||||
IDictionary<string, object> arguments,
|
||||
HtmlEncoder htmlEncoder,
|
||||
ViewContext viewContext,
|
||||
TextWriter writer)
|
||||
|
|
@ -82,15 +82,15 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the View Component arguments.
|
||||
/// Gets or sets the view component arguments.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The property setter is provided for unit test purposes only.
|
||||
/// </remarks>
|
||||
public object[] Arguments { get; set; }
|
||||
public IDictionary<string, object> Arguments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="HtmlEncoder"/>.
|
||||
/// Gets or sets the <see cref="System.Text.Encodings.Web.HtmlEncoder"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The property setter is provided for unit test purposes only.
|
||||
|
|
@ -98,7 +98,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
public HtmlEncoder HtmlEncoder { get; set; }
|
||||
|
||||
/// <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>
|
||||
/// <remarks>
|
||||
/// The property setter is provided for unit test purposes only.
|
||||
|
|
@ -106,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
public ViewComponentDescriptor ViewComponentDescriptor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ViewContext"/>.
|
||||
/// Gets or sets the <see cref="Rendering.ViewContext"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The property setter is provided for unit test purposes only.
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||
{
|
||||
/// <summary>
|
||||
/// A descriptor for a View Component.
|
||||
/// A descriptor for a view component.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{DisplayName}")]
|
||||
public class ViewComponentDescriptor
|
||||
|
|
@ -23,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display name of the View Component.
|
||||
/// Gets or sets the display name of the view component.
|
||||
/// </summary>
|
||||
public string DisplayName
|
||||
{
|
||||
|
|
@ -53,8 +54,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// 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 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
|
||||
/// <code>ViewComponent</code> as a suffix, the suffix will be omitted from the <see cref="FullName"/>.
|
||||
/// </para>
|
||||
/// <example>
|
||||
|
|
@ -89,7 +90,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <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"/>.
|
||||
/// </para>
|
||||
/// <example>
|
||||
|
|
@ -115,8 +116,13 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
public string ShortName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Type"/>.
|
||||
/// Gets or sets the <see cref="System.Type"/>.
|
||||
/// </summary>
|
||||
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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -42,13 +43,14 @@ namespace Microsoft.AspNet.Mvc
|
|||
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 object[] { "World!" },
|
||||
Arguments = new { name = "World!" },
|
||||
ViewData = null,
|
||||
TempData = null,
|
||||
ViewComponentName = "Text"
|
||||
|
|
@ -132,13 +134,43 @@ namespace Microsoft.AspNet.Mvc
|
|||
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 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",
|
||||
TempData = _tempDataDictionary,
|
||||
};
|
||||
|
|
@ -160,13 +192,14 @@ namespace Microsoft.AspNet.Mvc
|
|||
FullName = "Full.Name.AsyncText",
|
||||
ShortName = "AsyncText",
|
||||
Type = typeof(AsyncTextViewComponent),
|
||||
MethodInfo = typeof(AsyncTextViewComponent).GetMethod(nameof(AsyncTextViewComponent.InvokeAsync)),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
||||
var viewComponentResult = new ViewComponentResult()
|
||||
{
|
||||
Arguments = new object[] { "World!" },
|
||||
Arguments = new { name = "World!" },
|
||||
ViewComponentName = "AsyncText",
|
||||
TempData = _tempDataDictionary,
|
||||
};
|
||||
|
|
@ -188,6 +221,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
Type = typeof(TextViewComponent),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
};
|
||||
|
||||
var adapter = new TestDiagnosticListener();
|
||||
|
|
@ -196,7 +230,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
var viewComponentResult = new ViewComponentResult()
|
||||
{
|
||||
Arguments = new object[] { "World!" },
|
||||
Arguments = new { name = "World!" },
|
||||
ViewComponentName = "Text",
|
||||
TempData = _tempDataDictionary,
|
||||
};
|
||||
|
|
@ -226,13 +260,14 @@ namespace Microsoft.AspNet.Mvc
|
|||
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 object[] { "World!" },
|
||||
Arguments = new { name = "World!" },
|
||||
ViewComponentName = "Text",
|
||||
TempData = _tempDataDictionary,
|
||||
};
|
||||
|
|
@ -254,13 +289,14 @@ namespace Microsoft.AspNet.Mvc
|
|||
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 object[] { "World!" },
|
||||
Arguments = new { name = "World!" },
|
||||
ViewComponentName = "Full.Name.Text",
|
||||
TempData = _tempDataDictionary,
|
||||
};
|
||||
|
|
@ -282,13 +318,14 @@ namespace Microsoft.AspNet.Mvc
|
|||
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 object[] { "World!" },
|
||||
Arguments = new { name = "World!" },
|
||||
ViewComponentType = typeof(TextViewComponent),
|
||||
TempData = _tempDataDictionary,
|
||||
};
|
||||
|
|
@ -310,13 +347,14 @@ namespace Microsoft.AspNet.Mvc
|
|||
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 object[] { "World!" },
|
||||
Arguments = new { name = "World!" },
|
||||
ViewComponentType = typeof(TextViewComponent),
|
||||
StatusCode = 404,
|
||||
TempData = _tempDataDictionary,
|
||||
|
|
@ -367,6 +405,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
Type = typeof(TextViewComponent),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
@ -375,7 +414,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
var viewComponentResult = new ViewComponentResult()
|
||||
{
|
||||
Arguments = new object[] { "World!" },
|
||||
Arguments = new { name = "World!" },
|
||||
ViewComponentName = "Text",
|
||||
ContentType = contentType,
|
||||
TempData = _tempDataDictionary,
|
||||
|
|
@ -403,6 +442,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
Type = typeof(TextViewComponent),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
@ -412,7 +452,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
var viewComponentResult = new ViewComponentResult()
|
||||
{
|
||||
Arguments = new object[] { "World!" },
|
||||
Arguments = new { name = "World!" },
|
||||
ViewComponentName = "Text",
|
||||
ContentType = new MediaTypeHeaderValue("text/html") { Encoding = Encoding.UTF8 },
|
||||
TempData = _tempDataDictionary,
|
||||
|
|
@ -439,6 +479,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
FullName = "Full.Name.Text",
|
||||
ShortName = "Text",
|
||||
Type = typeof(TextViewComponent),
|
||||
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
|
||||
};
|
||||
|
||||
var actionContext = CreateActionContext(descriptor);
|
||||
|
|
@ -447,7 +488,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
var viewComponentResult = new ViewComponentResult()
|
||||
{
|
||||
Arguments = new object[] { "World!" },
|
||||
Arguments = new { name = "World!" },
|
||||
ViewComponentName = "Text",
|
||||
TempData = _tempDataDictionary,
|
||||
};
|
||||
|
|
@ -459,7 +500,10 @@ namespace Microsoft.AspNet.Mvc
|
|||
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 diagnosticSource = new DiagnosticListener("Microsoft.AspNet");
|
||||
|
|
@ -535,12 +579,6 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
private class AsyncTextViewComponent : ViewComponent
|
||||
{
|
||||
public HtmlString Invoke()
|
||||
{
|
||||
// Should never run.
|
||||
throw null;
|
||||
}
|
||||
|
||||
public Task<HtmlString> InvokeAsync(string name)
|
||||
{
|
||||
return Task.FromResult(new HtmlString("Hello-Async, " + name));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Mvc.Abstractions;
|
||||
|
|
@ -43,7 +44,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
var viewContext = new ViewContext(
|
||||
actionContext,
|
||||
view,
|
||||
viewData,
|
||||
viewData,
|
||||
new TempDataDictionary(httpContext, new SessionStateTempDataProvider()),
|
||||
TextWriter.Null,
|
||||
new HtmlHelperOptions());
|
||||
|
|
@ -57,7 +58,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
var viewComponentContext = new ViewComponentContext(
|
||||
viewComponentDescriptor,
|
||||
new object[0],
|
||||
new Dictionary<string, object>(),
|
||||
new HtmlTestEncoder(),
|
||||
viewContext,
|
||||
writer);
|
||||
|
|
|
|||
|
|
@ -2,13 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
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;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ViewComponents
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -23,9 +24,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
|
||||
// Assert
|
||||
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("Conventions", descriptor.ShortName);
|
||||
Assert.Same(typeof(ConventionsViewComponent).GetMethod("Invoke"), descriptor.MethodInfo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -42,15 +44,156 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
Assert.Equal(typeof(AttributeViewComponent), descriptor.Type);
|
||||
Assert.Equal("AttributesAreGreat", descriptor.FullName);
|
||||
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
|
||||
{
|
||||
public string Invoke() => "Hello world";
|
||||
}
|
||||
|
||||
[ViewComponent(Name = "AttributesAreGreat")]
|
||||
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)
|
||||
|
|
@ -80,11 +223,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
return
|
||||
GetAssemblyProvider()
|
||||
.CandidateAssemblies
|
||||
.SelectMany(a => a.DefinedTypes)
|
||||
#if DNX451
|
||||
.Select(t => t.GetTypeInfo())
|
||||
#endif
|
||||
;
|
||||
.SelectMany(a => a.DefinedTypes);
|
||||
}
|
||||
|
||||
private static IAssemblyProvider GetAssemblyProvider()
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
{
|
||||
public class DefaultViewComponentSelectorTest
|
||||
{
|
||||
private static readonly string Namespace = typeof(DefaultViewComponentSelectorTest).Namespace;
|
||||
|
||||
[Fact]
|
||||
public void SelectComponent_ByShortNameWithSuffix()
|
||||
{
|
||||
|
|
@ -22,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
var result = selector.SelectComponent("Suffix");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(SuffixViewComponent), result.Type);
|
||||
Assert.Same(typeof(ViewComponentContainer.SuffixViewComponent), result.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -32,10 +34,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
var selector = CreateSelector();
|
||||
|
||||
// Act
|
||||
var result = selector.SelectComponent("Microsoft.AspNet.Mvc.ViewComponents.Suffix");
|
||||
var result = selector.SelectComponent($"{Namespace}.Suffix");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(SuffixViewComponent), result.Type);
|
||||
Assert.Same(typeof(ViewComponentContainer.SuffixViewComponent), result.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -48,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
var result = selector.SelectComponent("WithoutSuffix");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(WithoutSuffix), result.Type);
|
||||
Assert.Same(typeof(ViewComponentContainer.WithoutSuffix), result.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -58,10 +60,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
var selector = CreateSelector();
|
||||
|
||||
// Act
|
||||
var result = selector.SelectComponent("Microsoft.AspNet.Mvc.ViewComponents.WithoutSuffix");
|
||||
var result = selector.SelectComponent($"{Namespace}.WithoutSuffix");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(WithoutSuffix), result.Type);
|
||||
Assert.Same(typeof(ViewComponentContainer.WithoutSuffix), result.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -74,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
var result = selector.SelectComponent("ByAttribute");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(ByAttribute), result.Type);
|
||||
Assert.Same(typeof(ViewComponentContainer.ByAttribute), result.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -87,7 +89,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
var result = selector.SelectComponent("ByNamingConvention");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(ByNamingConventionViewComponent), result.Type);
|
||||
Assert.Same(typeof(ViewComponentContainer.ByNamingConventionViewComponent), result.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -98,9 +100,9 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
|
||||
var expected =
|
||||
"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 +
|
||||
"Type: 'Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentSelectorTest+Ambiguous2' - " +
|
||||
$"Type: '{typeof(ViewComponentContainer.Ambiguous2)}' - " +
|
||||
"Name: 'Namespace2.Ambiguous'";
|
||||
|
||||
// Act
|
||||
|
|
@ -120,7 +122,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
var result = selector.SelectComponent("Namespace1.Ambiguous");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(Ambiguous1), result.Type);
|
||||
Assert.Same(typeof(ViewComponentContainer.Ambiguous1), result.Type);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -135,44 +137,56 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
var result = selector.SelectComponent(name);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(FullNameInAttribute), result.Type);
|
||||
Assert.Same(typeof(ViewComponentContainer.FullNameInAttribute), result.Type);
|
||||
}
|
||||
|
||||
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]
|
||||
private class ByAttribute
|
||||
{
|
||||
}
|
||||
[ViewComponent]
|
||||
public class ByAttribute
|
||||
{
|
||||
public string Invoke() => "Hello";
|
||||
}
|
||||
|
||||
[ViewComponent(Name = "Namespace1.Ambiguous")]
|
||||
private class Ambiguous1
|
||||
{
|
||||
}
|
||||
[ViewComponent(Name = "Namespace1.Ambiguous")]
|
||||
public class Ambiguous1
|
||||
{
|
||||
public string Invoke() => "Hello";
|
||||
}
|
||||
|
||||
[ViewComponent(Name = "Namespace2.Ambiguous")]
|
||||
private class Ambiguous2
|
||||
{
|
||||
}
|
||||
[ViewComponent(Name = "Namespace2.Ambiguous")]
|
||||
public class Ambiguous2
|
||||
{
|
||||
public string Invoke() => "Hello";
|
||||
}
|
||||
|
||||
[ViewComponent(Name = "CoolNameSpace.FullNameInAttribute")]
|
||||
private class FullNameInAttribute
|
||||
{
|
||||
[ViewComponent(Name = "CoolNameSpace.FullNameInAttribute")]
|
||||
public class FullNameInAttribute
|
||||
{
|
||||
public string Invoke() => "Hello";
|
||||
}
|
||||
}
|
||||
|
||||
// This will only consider types nested inside this class as ViewComponent classes
|
||||
|
|
@ -181,10 +195,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
public FilteredViewComponentDescriptorProvider()
|
||||
: 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)
|
||||
{
|
||||
|
|
@ -208,19 +222,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
|
|||
{
|
||||
var assemblyProvider = new StaticAssemblyProvider();
|
||||
assemblyProvider.CandidateAssemblies.Add(
|
||||
typeof(FilteredViewComponentSelector).GetTypeInfo().Assembly);
|
||||
typeof(ViewComponentContainer).GetTypeInfo().Assembly);
|
||||
|
||||
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.
|
||||
// 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 Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Mvc.Abstractions;
|
||||
|
|
@ -57,7 +57,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
var viewComponentContext = new ViewComponentContext(
|
||||
viewComponentDescriptor,
|
||||
new object[0],
|
||||
new Dictionary<string, object>(),
|
||||
new HtmlTestEncoder(),
|
||||
viewContext,
|
||||
writer);
|
||||
|
|
@ -65,5 +65,4 @@ namespace Microsoft.AspNet.Mvc
|
|||
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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
|
@ -85,7 +86,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
var viewComponentContext = new ViewComponentContext(
|
||||
viewComponentDescriptor,
|
||||
new object[0],
|
||||
new Dictionary<string, object>(),
|
||||
new HtmlTestEncoder(),
|
||||
viewContext,
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Mvc.Abstractions;
|
||||
|
|
@ -147,9 +149,9 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
// Arrange
|
||||
var expected = string.Join(Environment.NewLine,
|
||||
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
|
||||
"location1",
|
||||
"location2");
|
||||
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
|
||||
"location1",
|
||||
"location2");
|
||||
|
||||
var view = Mock.Of<IView>();
|
||||
|
||||
|
|
@ -185,9 +187,9 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
// Arrange
|
||||
var expected = string.Join(Environment.NewLine,
|
||||
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
|
||||
"location1",
|
||||
"location2");
|
||||
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
|
||||
"location1",
|
||||
"location2");
|
||||
|
||||
var view = Mock.Of<IView>();
|
||||
|
||||
|
|
@ -223,11 +225,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
// Arrange
|
||||
var expected = string.Join(Environment.NewLine,
|
||||
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
|
||||
"location1",
|
||||
"location2",
|
||||
"location3",
|
||||
"location4");
|
||||
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
|
||||
"location1",
|
||||
"location2",
|
||||
"location3",
|
||||
"location4");
|
||||
|
||||
var view = Mock.Of<IView>();
|
||||
|
||||
|
|
@ -535,11 +537,12 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
ShortName = "Invoke",
|
||||
Type = typeof(object),
|
||||
MethodInfo = typeof(object).GetTypeInfo().DeclaredMethods.First()
|
||||
};
|
||||
|
||||
var viewComponentContext = new ViewComponentContext(
|
||||
viewComponentDescriptor,
|
||||
new object[0],
|
||||
new Dictionary<string, object>(),
|
||||
new HtmlTestEncoder(),
|
||||
viewContext,
|
||||
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
|
||||
<h1>@Model.Name</h1>
|
||||
@Component.Invoke("InheritingViewComponent", Model.Address)
|
||||
@await Component.InvokeAsync("InheritingViewComponent", new { address = Model.Address })
|
||||
|
|
@ -12,4 +12,4 @@
|
|||
}
|
||||
ViewWithRelativePath-content
|
||||
<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";
|
||||
// The invoked partial sets a title, but this shouldn't override the current page's ViewData \ ViewBag.
|
||||
await Html.RenderPartialAsync("_PartialThatSetsTitle");
|
||||
await Component.RenderInvokeAsync("ComponentThatSetsTitle");
|
||||
@await Component.InvokeAsync("ComponentThatSetsTitle")
|
||||
Layout = "/Views/Shared/_LayoutWithTitle.cshtml";
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<Index>
|
||||
@ViewContext.ExecutingFilePath
|
||||
@ViewContext.View.Path
|
||||
@Component.Invoke("ComponentForViewWithPaths")
|
||||
@await Component.InvokeAsync("ComponentForViewWithPaths")
|
||||
@Html.Partial("_Partial")
|
||||
</Index>
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// 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.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
|
|
@ -58,7 +59,7 @@ namespace MvcSample.Web.Components
|
|||
|
||||
await result.ExecuteAsync(new ViewComponentContext(
|
||||
viewComponentDescriptor,
|
||||
new object[0],
|
||||
new Dictionary<string, object>(),
|
||||
_htmlEncoder,
|
||||
ViewContext,
|
||||
writer));
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
<h3>Current Tag Cloud from Tag Helper</h3>
|
||||
<tag-cloud count="Model.TagsToShow" surround="div" />
|
||||
<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(
|
||||
"Tag Cloud from Template: ",
|
||||
|
|
|
|||
Loading…
Reference in New Issue