Modify IViewComponentHelper to remove method selection ambiguity.

Fixes #612
This commit is contained in:
Pranav K 2015-12-15 14:24:19 -08:00
parent ce0e35ff75
commit 399e516065
38 changed files with 712 additions and 896 deletions

View File

@ -38,18 +38,5 @@ namespace TagHelperSample.Web.Components
return View(movies); return View(movies);
} }
public IViewComponentResult Invoke(string movieName)
{
string quote;
if (!_cache.TryGetValue(movieName, out quote))
{
IChangeToken expirationToken;
quote = _moviesService.GetCriticsQuote(out expirationToken);
_cache.Set(movieName, quote, new MemoryCacheEntryOptions().AddExpirationToken(expirationToken));
}
return Content(quote);
}
} }
} }

View File

@ -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);
}
}
}

View File

@ -35,7 +35,7 @@
</div> </div>
<div class="sidebar"> <div class="sidebar">
<cache expires-after="TimeSpan.FromMinutes(20)"> <cache expires-after="TimeSpan.FromMinutes(20)">
@Component.Invoke("FeaturedMovies") @await Component.InvokeAsync("FeaturedMovies")
</cache> </cache>
</div> </div>
<div style="clear: left"></div> <div style="clear: left"></div>

View File

@ -10,7 +10,7 @@
</div> </div>
<em>Critics say:</em> <em>Critics say:</em>
<cache vary-by="@movie.Name"> <cache vary-by="@movie.Name">
@Component.Invoke("FeaturedMovies", movie.Name) @await Component.InvokeAsync("Movies", new { movieName = movie.Name })
</cache> </cache>
</dd> </dd>
} }

View File

@ -7,22 +7,27 @@ using Microsoft.AspNet.Html;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
/// <summary>
/// Supports the rendering of view components in a view.
/// </summary>
public interface IViewComponentHelper public interface IViewComponentHelper
{ {
IHtmlContent Invoke(string name, params object[] args); /// <summary>
/// Invokes a view component with the specified <paramref name="name"/>.
/// </summary>
/// <param name="name">The name of the view component.</param>
/// <param name="arguments">Arguments to be passed to the invoked view component method.</param>
/// <returns>A <see cref="Task"/> that on completion returns the rendered <see cref="IHtmlContent" />.
/// </returns>
Task<IHtmlContent> InvokeAsync(string name, object arguments);
IHtmlContent Invoke(Type componentType, params object[] args); /// <summary>
/// Invokes a view component of type <paramref name="componentType" />.
void RenderInvoke(string name, params object[] args); /// </summary>
/// <param name="componentType">The view component <see cref="Type"/>.</param>
void RenderInvoke(Type componentType, params object[] args); /// <param name="arguments">Arguments to be passed to the invoked view component method.</param>
/// <returns>A <see cref="Task"/> that on completion returns the rendered <see cref="IHtmlContent" />.
Task<IHtmlContent> InvokeAsync(string name, params object[] args); /// </returns>
Task<IHtmlContent> InvokeAsync(Type componentType, object arguments);
Task<IHtmlContent> InvokeAsync(Type componentType, params object[] args);
Task RenderInvokeAsync(string name, params object[] args);
Task RenderInvokeAsync(Type componentType, params object[] args);
} }
} }

View File

@ -10,15 +10,21 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Logging
{ {
public static class DefaultViewComponentInvokerLoggerExtensions public static class DefaultViewComponentInvokerLoggerExtensions
{ {
private static readonly Action<ILogger, string, Exception> _viewComponentExecuting; private static readonly string[] EmptyArguments =
#if NET451
new string[0];
#else
Array.Empty<string>();
#endif
private static readonly Action<ILogger, string, string[], Exception> _viewComponentExecuting;
private static readonly Action<ILogger, string, double, string, Exception> _viewComponentExecuted; private static readonly Action<ILogger, string, double, string, Exception> _viewComponentExecuted;
static DefaultViewComponentInvokerLoggerExtensions() static DefaultViewComponentInvokerLoggerExtensions()
{ {
_viewComponentExecuting = LoggerMessage.Define<string>( _viewComponentExecuting = LoggerMessage.Define<string, string[]>(
LogLevel.Debug, LogLevel.Debug,
1, 1,
"Executing view component {ViewComponentName}"); "Executing view component {ViewComponentName} with arguments ({Arguments}).");
_viewComponentExecuted = LoggerMessage.Define<string, double, string>( _viewComponentExecuted = LoggerMessage.Define<string, double, string>(
LogLevel.Debug, LogLevel.Debug,
@ -32,9 +38,29 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Logging
return logger.BeginScopeImpl(new ViewComponentLogScope(context.ViewComponentDescriptor)); return logger.BeginScopeImpl(new ViewComponentLogScope(context.ViewComponentDescriptor));
} }
public static void ViewComponentExecuting(this ILogger logger, ViewComponentContext context) public static void ViewComponentExecuting(
this ILogger logger,
ViewComponentContext context,
object[] arguments)
{ {
_viewComponentExecuting(logger, context.ViewComponentDescriptor.DisplayName, null); var formattedArguments = GetFormattedArguments(arguments);
_viewComponentExecuting(logger, context.ViewComponentDescriptor.DisplayName, formattedArguments, null);
}
private static string[] GetFormattedArguments(object[] arguments)
{
if (arguments == null || arguments.Length == 0)
{
return EmptyArguments;
}
var formattedArguments = new string[arguments.Length];
for (var i = 0; i < formattedArguments.Length; i++)
{
formattedArguments[i] = Convert.ToString(arguments[i]);
}
return formattedArguments;
} }
public static void ViewComponentExecuted( public static void ViewComponentExecuted(

View File

@ -8,41 +8,29 @@ namespace Microsoft.AspNet.Mvc.Logging
{ {
public static class ViewComponentResultLoggerExtensions public static class ViewComponentResultLoggerExtensions
{ {
private static readonly Action<ILogger, string, string[], Exception> _viewComponentResultExecuting; private static readonly Action<ILogger, string, Exception> _viewComponentResultExecuting;
static ViewComponentResultLoggerExtensions() static ViewComponentResultLoggerExtensions()
{ {
_viewComponentResultExecuting = LoggerMessage.Define<string, string[]>( _viewComponentResultExecuting = LoggerMessage.Define<string>(
LogLevel.Information, LogLevel.Information,
1, 1,
"Executing ViewComponentResult, running {ViewComponentName} with arguments ({Arguments})."); "Executing ViewComponentResult, running {ViewComponentName}.");
} }
public static void ViewComponentResultExecuting(this ILogger logger, string viewComponentName, object[] arguments) public static void ViewComponentResultExecuting(this ILogger logger, string viewComponentName)
{ {
if (logger.IsEnabled(LogLevel.Information)) if (logger.IsEnabled(LogLevel.Information))
{ {
var formattedArguments = new string[arguments.Length]; _viewComponentResultExecuting(logger, viewComponentName, null);
for (var i = 0; i < arguments.Length; i++)
{
formattedArguments[i] = Convert.ToString(arguments[i]);
}
_viewComponentResultExecuting(logger, viewComponentName, formattedArguments, null);
} }
} }
public static void ViewComponentResultExecuting(this ILogger logger, Type viewComponentType, object[] arguments) public static void ViewComponentResultExecuting(this ILogger logger, Type viewComponentType)
{ {
if (logger.IsEnabled(LogLevel.Information)) if (logger.IsEnabled(LogLevel.Information))
{ {
var formattedArguments = new string[arguments.Length]; _viewComponentResultExecuting(logger, viewComponentType.Name, null);
for (var i = 0; i < arguments.Length; i++)
{
formattedArguments[i] = Convert.ToString(arguments[i]);
}
_viewComponentResultExecuting(logger, viewComponentType.Name, formattedArguments, null);
} }
} }
} }

View File

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<DisabledCustomTools>.resx</DisabledCustomTools>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
} }
/// <summary> /// <summary>
/// The async view component method '{0}' should be declared to return Task&lt;T&gt;. /// Method '{0}' of view component '{1}' should be declared to return {2}&lt;T&gt;.
/// </summary> /// </summary>
internal static string ViewComponent_AsyncMethod_ShouldReturnTask internal static string ViewComponent_AsyncMethod_ShouldReturnTask
{ {
@ -35,11 +35,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
} }
/// <summary> /// <summary>
/// The async view component method '{0}' should be declared to return Task&lt;T&gt;. /// Method '{0}' of view component '{1}' should be declared to return {2}&lt;T&gt;.
/// </summary> /// </summary>
internal static string FormatViewComponent_AsyncMethod_ShouldReturnTask(object p0) internal static string FormatViewComponent_AsyncMethod_ShouldReturnTask(object p0, object p1, object p2)
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AsyncMethod_ShouldReturnTask"), p0); return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AsyncMethod_ShouldReturnTask"), p0, p1, p2);
} }
/// <summary> /// <summary>
@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
} }
/// <summary> /// <summary>
/// The view component method '{0}' should be declared to return a value. /// Method '{0}' of view component '{1}' should be declared to return a value.
/// </summary> /// </summary>
internal static string ViewComponent_SyncMethod_ShouldReturnValue internal static string ViewComponent_SyncMethod_ShouldReturnValue
{ {
@ -67,11 +67,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
} }
/// <summary> /// <summary>
/// The view component method '{0}' should be declared to return a value. /// Method '{0}' of view component '{1}' should be declared to return a value.
/// </summary> /// </summary>
internal static string FormatViewComponent_SyncMethod_ShouldReturnValue(object p0) internal static string FormatViewComponent_SyncMethod_ShouldReturnValue(object p0, object p1)
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_ShouldReturnValue"), p0); return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_ShouldReturnValue"), p0, p1);
} }
/// <summary> /// <summary>
@ -107,7 +107,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
} }
/// <summary> /// <summary>
/// Could not find an '{0}' method matching the parameters. /// Could not find an '{0}' or '{1}' method for the view component '{2}'.
/// </summary> /// </summary>
internal static string ViewComponent_CannotFindMethod internal static string ViewComponent_CannotFindMethod
{ {
@ -115,27 +115,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
} }
/// <summary> /// <summary>
/// Could not find an '{0}' method matching the parameters. /// Could not find an '{0}' or '{1}' method for the view component '{2}'.
/// </summary> /// </summary>
internal static string FormatViewComponent_CannotFindMethod(object p0) internal static string FormatViewComponent_CannotFindMethod(object p0, object p1, object p2)
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod"), p0); return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod"), p0, p1, p2);
}
/// <summary>
/// Could not find an '{0}' or '{1}' method matching the parameters.
/// </summary>
internal static string ViewComponent_CannotFindMethod_WithFallback
{
get { return GetString("ViewComponent_CannotFindMethod_WithFallback"); }
}
/// <summary>
/// Could not find an '{0}' or '{1}' method matching the parameters.
/// </summary>
internal static string FormatViewComponent_CannotFindMethod_WithFallback(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod_WithFallback"), p0, p1);
} }
/// <summary> /// <summary>
@ -859,7 +843,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
} }
/// <summary> /// <summary>
/// The view component method '{0}' cannot return a {1}. /// Method '{0}' of view component '{1}' cannot return a {2}.
/// </summary> /// </summary>
internal static string ViewComponent_SyncMethod_CannotReturnTask internal static string ViewComponent_SyncMethod_CannotReturnTask
{ {
@ -867,11 +851,27 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
} }
/// <summary> /// <summary>
/// The view component method '{0}' cannot return a {1}. /// Method '{0}' of view component '{1}' cannot return a {2}.
/// </summary> /// </summary>
internal static string FormatViewComponent_SyncMethod_CannotReturnTask(object p0, object p1) internal static string FormatViewComponent_SyncMethod_CannotReturnTask(object p0, object p1, object p2)
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_CannotReturnTask"), p0, p1); return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_CannotReturnTask"), p0, p1, p2);
}
/// <summary>
/// View component '{0}' must have exactly one public method named '{1}' or '{2}'.
/// </summary>
internal static string ViewComponent_AmbiguousMethods
{
get { return GetString("ViewComponent_AmbiguousMethods"); }
}
/// <summary>
/// View component '{0}' must have exactly one public method named '{1}' or '{2}'.
/// </summary>
internal static string FormatViewComponent_AmbiguousMethods(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AmbiguousMethods"), p0, p1, p2);
} }
private static string GetString(string name, params string[] formatterNames) private static string GetString(string name, params string[] formatterNames)

View File

@ -7,48 +7,76 @@ using Microsoft.AspNet.Html;
namespace Microsoft.AspNet.Mvc.Rendering namespace Microsoft.AspNet.Mvc.Rendering
{ {
/// <summary>
/// Extension methods for <see cref="IViewComponentHelper"/>.
/// </summary>
public static class ViewComponentHelperExtensions public static class ViewComponentHelperExtensions
{ {
public static IHtmlContent Invoke<TComponent>(this IViewComponentHelper helper, params object[] args) /// <summary>
/// Invokes a view component with the specified <paramref name="name"/>.
/// </summary>
/// <param name="name">The name of the view component.</param>
/// <returns>A <see cref="Task"/> that on completion returns the rendered <see cref="IHtmlContent" />.
/// </returns>
public static Task<IHtmlContent> InvokeAsync(this IViewComponentHelper helper, string name)
{ {
if (helper == null) if (helper == null)
{ {
throw new ArgumentNullException(nameof(helper)); throw new ArgumentNullException(nameof(helper));
} }
return helper.Invoke(typeof(TComponent), args); return helper.InvokeAsync(name, arguments: null);
} }
public static void RenderInvoke<TComponent>(this IViewComponentHelper helper, params object[] args) /// <summary>
/// Invokes a view component of type <paramref name="componentType" />.
/// </summary>
/// <param name="componentType">The view component <see cref="Type"/>.</param>
/// <returns>A <see cref="Task"/> that on completion returns the rendered <see cref="IHtmlContent" />.
/// </returns>
public static Task<IHtmlContent> InvokeAsync(this IViewComponentHelper helper, Type componentType)
{ {
if (helper == null) if (helper == null)
{ {
throw new ArgumentNullException(nameof(helper)); throw new ArgumentNullException(nameof(helper));
} }
helper.RenderInvoke(typeof(TComponent), args); return helper.InvokeAsync(componentType, arguments: null);
} }
public static Task<IHtmlContent> InvokeAsync<TComponent>( /// <summary>
this IViewComponentHelper helper, /// Invokes a view component of type <typeparam name="TComponent"/>.
params object[] args) /// </summary>
/// <param name="helper">The <see cref="IViewComponentHelper"/>.</param>
/// <param name="arguments">Arguments to be passed to the invoked view component method.</param>
/// <typeparam name="TComponent">The <see cref="Type"/> of the view component.</typeparam>
/// <returns>A <see cref="Task"/> that on completion returns the rendered <see cref="IHtmlContent" />.
/// </returns>
public static Task<IHtmlContent> InvokeAsync<TComponent>(this IViewComponentHelper helper, object arguments)
{ {
if (helper == null) if (helper == null)
{ {
throw new ArgumentNullException(nameof(helper)); throw new ArgumentNullException(nameof(helper));
} }
return helper.InvokeAsync(typeof(TComponent), args); return helper.InvokeAsync(typeof(TComponent), arguments);
} }
public static Task RenderInvokeAsync<TComponent>(this IViewComponentHelper helper, params object[] args) /// <summary>
/// Invokes a view component of type <typeparam name="TComponent"/>.
/// </summary>
/// <param name="helper">The <see cref="IViewComponentHelper"/>.</param>
/// <typeparam name="TComponent">The <see cref="Type"/> of the view component.</typeparam>
/// <returns>A <see cref="Task"/> that on completion returns the rendered <see cref="IHtmlContent" />.
/// </returns>
public static Task<IHtmlContent> InvokeAsync<TComponent>(this IViewComponentHelper helper)
{ {
if (helper == null) if (helper == null)
{ {
throw new ArgumentNullException(nameof(helper)); throw new ArgumentNullException(nameof(helper));
} }
return helper.RenderInvokeAsync(typeof(TComponent), args); return helper.InvokeAsync(typeof(TComponent), arguments: null);
} }
} }
} }

View File

@ -122,13 +122,13 @@
<comment>{1} is the newline character</comment> <comment>{1} is the newline character</comment>
</data> </data>
<data name="ViewComponent_AsyncMethod_ShouldReturnTask" xml:space="preserve"> <data name="ViewComponent_AsyncMethod_ShouldReturnTask" xml:space="preserve">
<value>The async view component method '{0}' should be declared to return Task&lt;T&gt;.</value> <value>Method '{0}' of view component '{1}' should be declared to return {2}&lt;T&gt;.</value>
</data> </data>
<data name="ViewComponent_MustReturnValue" xml:space="preserve"> <data name="ViewComponent_MustReturnValue" xml:space="preserve">
<value>A view component must return a non-null value.</value> <value>A view component must return a non-null value.</value>
</data> </data>
<data name="ViewComponent_SyncMethod_ShouldReturnValue" xml:space="preserve"> <data name="ViewComponent_SyncMethod_ShouldReturnValue" xml:space="preserve">
<value>The view component method '{0}' should be declared to return a value.</value> <value>Method '{0}' of view component '{1}' should be declared to return a value.</value>
</data> </data>
<data name="ViewComponent_CannotFindComponent" xml:space="preserve"> <data name="ViewComponent_CannotFindComponent" xml:space="preserve">
<value>A view component named '{0}' could not be found.</value> <value>A view component named '{0}' could not be found.</value>
@ -137,10 +137,7 @@
<value>An invoker could not be created for the view component '{0}'.</value> <value>An invoker could not be created for the view component '{0}'.</value>
</data> </data>
<data name="ViewComponent_CannotFindMethod" xml:space="preserve"> <data name="ViewComponent_CannotFindMethod" xml:space="preserve">
<value>Could not find an '{0}' method matching the parameters.</value> <value>Could not find an '{0}' or '{1}' method for the view component '{2}'.</value>
</data>
<data name="ViewComponent_CannotFindMethod_WithFallback" xml:space="preserve">
<value>Could not find an '{0}' or '{1}' method matching the parameters.</value>
</data> </data>
<data name="ViewComponent_InvalidReturnValue" xml:space="preserve"> <data name="ViewComponent_InvalidReturnValue" xml:space="preserve">
<value>View components only support returning {0}, {1} or {2}.</value> <value>View components only support returning {0}, {1} or {2}.</value>
@ -278,6 +275,9 @@
<value>The collection already contains an entry with key '{0}'.</value> <value>The collection already contains an entry with key '{0}'.</value>
</data> </data>
<data name="ViewComponent_SyncMethod_CannotReturnTask" xml:space="preserve"> <data name="ViewComponent_SyncMethod_CannotReturnTask" xml:space="preserve">
<value>The view component method '{0}' cannot return a {1}.</value> <value>Method '{0}' of view component '{1}' cannot return a {2}.</value>
</data>
<data name="ViewComponent_AmbiguousMethods" xml:space="preserve">
<value>View component '{0}' must have exactly one public method named '{1}' or '{2}'.</value>
</data> </data>
</root> </root>

View File

@ -3,7 +3,9 @@
using System; using System;
using System.Text; using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Html;
using Microsoft.AspNet.Mvc.Internal; using Microsoft.AspNet.Mvc.Internal;
using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Mvc.Logging;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
@ -26,7 +28,7 @@ namespace Microsoft.AspNet.Mvc
/// <summary> /// <summary>
/// Gets or sets the arguments provided to the view component. /// Gets or sets the arguments provided to the view component.
/// </summary> /// </summary>
public object[] Arguments { get; set; } public object Arguments { get; set; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response. /// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response.
@ -77,6 +79,7 @@ namespace Microsoft.AspNet.Mvc
var loggerFactory = services.GetRequiredService<ILoggerFactory>(); var loggerFactory = services.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<ViewComponentResult>(); var logger = loggerFactory.CreateLogger<ViewComponentResult>();
var htmlEncoder = services.GetRequiredService<HtmlEncoder>();
var viewData = ViewData; var viewData = ViewData;
if (viewData == null) if (viewData == null)
@ -119,23 +122,29 @@ namespace Microsoft.AspNet.Mvc
htmlHelperOptions); htmlHelperOptions);
(viewComponentHelper as ICanHasViewContext)?.Contextualize(viewContext); (viewComponentHelper as ICanHasViewContext)?.Contextualize(viewContext);
var result = await GetViewComponentResult(viewComponentHelper, logger);
if (ViewComponentType == null && ViewComponentName == null) result.WriteTo(writer, htmlEncoder);
{ }
throw new InvalidOperationException(Resources.FormatViewComponentResult_NameOrTypeMustBeSet( }
nameof(ViewComponentName),
nameof(ViewComponentType))); private Task<IHtmlContent> GetViewComponentResult(IViewComponentHelper viewComponentHelper, ILogger logger)
} {
else if (ViewComponentType == null) if (ViewComponentType == null && ViewComponentName == null)
{ {
logger.ViewComponentResultExecuting(ViewComponentName, Arguments); throw new InvalidOperationException(Resources.FormatViewComponentResult_NameOrTypeMustBeSet(
await viewComponentHelper.RenderInvokeAsync(ViewComponentName, Arguments); nameof(ViewComponentName),
} nameof(ViewComponentType)));
else }
{ else if (ViewComponentType == null)
logger.ViewComponentResultExecuting(ViewComponentType, Arguments); {
await viewComponentHelper.RenderInvokeAsync(ViewComponentType, Arguments); logger.ViewComponentResultExecuting(ViewComponentName);
} return viewComponentHelper.InvokeAsync(ViewComponentName, Arguments);
}
else
{
logger.ViewComponentResultExecuting(ViewComponentType);
return viewComponentHelper.InvokeAsync(ViewComponentType, Arguments);
} }
} }
} }

View File

@ -5,7 +5,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Infrastructure; using Microsoft.AspNet.Mvc.Infrastructure;
using Microsoft.AspNet.Mvc.ViewFeatures;
namespace Microsoft.AspNet.Mvc.ViewComponents namespace Microsoft.AspNet.Mvc.ViewComponents
{ {
@ -14,6 +16,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
/// </summary> /// </summary>
public class DefaultViewComponentDescriptorProvider : IViewComponentDescriptorProvider public class DefaultViewComponentDescriptorProvider : IViewComponentDescriptorProvider
{ {
private const string AsyncMethodName = "InvokeAsync";
private const string SyncMethodName = "Invoke";
private readonly IAssemblyProvider _assemblyProvider; private readonly IAssemblyProvider _assemblyProvider;
/// <summary> /// <summary>
@ -32,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
return types return types
.Where(IsViewComponentType) .Where(IsViewComponentType)
.Select(CreateCandidate); .Select(CreateDescriptor);
} }
/// <summary> /// <summary>
@ -47,11 +51,11 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
} }
/// <summary> /// <summary>
/// Determines whether or not the given <see cref="TypeInfo"/> is a View Component class. /// Determines whether or not the given <see cref="TypeInfo"/> is a view component class.
/// </summary> /// </summary>
/// <param name="typeInfo">The <see cref="TypeInfo"/>.</param> /// <param name="typeInfo">The <see cref="TypeInfo"/>.</param>
/// <returns> /// <returns>
/// <c>true</c> if <paramref name="typeInfo"/>represents a View Component class, otherwise <c>false</c>. /// <c>true</c> if <paramref name="typeInfo"/>represents a view component class, otherwise <c>false</c>.
/// </returns> /// </returns>
protected virtual bool IsViewComponentType(TypeInfo typeInfo) protected virtual bool IsViewComponentType(TypeInfo typeInfo)
{ {
@ -63,16 +67,70 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
return ViewComponentConventions.IsComponent(typeInfo); return ViewComponentConventions.IsComponent(typeInfo);
} }
private static ViewComponentDescriptor CreateCandidate(TypeInfo typeInfo) private static ViewComponentDescriptor CreateDescriptor(TypeInfo typeInfo)
{ {
var candidate = new ViewComponentDescriptor() var type = typeInfo.AsType();
var candidate = new ViewComponentDescriptor
{ {
FullName = ViewComponentConventions.GetComponentFullName(typeInfo), FullName = ViewComponentConventions.GetComponentFullName(typeInfo),
ShortName = ViewComponentConventions.GetComponentName(typeInfo), ShortName = ViewComponentConventions.GetComponentName(typeInfo),
Type = typeInfo.AsType(), Type = type,
MethodInfo = FindMethod(type)
}; };
return candidate; return candidate;
} }
private static MethodInfo FindMethod(Type componentType)
{
var componentName = componentType.FullName;
var methods = componentType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(method =>
string.Equals(method.Name, AsyncMethodName, StringComparison.Ordinal) ||
string.Equals(method.Name, SyncMethodName, StringComparison.Ordinal))
.ToArray();
if (methods.Length == 0)
{
throw new InvalidOperationException(
Resources.FormatViewComponent_CannotFindMethod(SyncMethodName, AsyncMethodName, componentName));
}
else if (methods.Length > 1)
{
throw new InvalidOperationException(
Resources.FormatViewComponent_AmbiguousMethods(componentName, AsyncMethodName, SyncMethodName));
}
var selectedMethod = methods[0];
if (string.Equals(selectedMethod.Name, AsyncMethodName, StringComparison.Ordinal))
{
if (!selectedMethod.ReturnType.GetTypeInfo().IsGenericType ||
selectedMethod.ReturnType.GetGenericTypeDefinition() != typeof(Task<>))
{
throw new InvalidOperationException(Resources.FormatViewComponent_AsyncMethod_ShouldReturnTask(
AsyncMethodName,
componentName,
nameof(Task)));
}
}
else
{
if (selectedMethod.ReturnType == typeof(void))
{
throw new InvalidOperationException(Resources.FormatViewComponent_SyncMethod_ShouldReturnValue(
SyncMethodName,
componentName));
}
else if (selectedMethod.ReturnType.IsAssignableFrom(typeof(Task)))
{
throw new InvalidOperationException(Resources.FormatViewComponent_SyncMethod_CannotReturnTask(
SyncMethodName,
componentName,
nameof(Task)));
}
}
return selectedMethod;
}
} }
} }

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.IO;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Html; using Microsoft.AspNet.Html;
@ -10,9 +9,13 @@ using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.ViewFeatures; using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Mvc.ViewFeatures.Buffer;
using Microsoft.AspNet.Mvc.ViewFeatures.Internal; using Microsoft.AspNet.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNet.Mvc.ViewComponents namespace Microsoft.AspNet.Mvc.ViewComponents
{ {
/// <summary>
/// Default implementation for <see cref="IViewComponentHelper"/>.
/// </summary>
public class DefaultViewComponentHelper : IViewComponentHelper, ICanHasViewContext public class DefaultViewComponentHelper : IViewComponentHelper, ICanHasViewContext
{ {
private readonly IViewComponentDescriptorCollectionProvider _descriptorProvider; private readonly IViewComponentDescriptorCollectionProvider _descriptorProvider;
@ -22,6 +25,16 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
private readonly IViewBufferScope _viewBufferScope; private readonly IViewBufferScope _viewBufferScope;
private ViewContext _viewContext; private ViewContext _viewContext;
/// <summary>
/// Initializes a new instance of <see cref="DefaultViewComponentHelper"/>.
/// </summary>
/// <param name="descriptorProvider">The <see cref="IViewComponentDescriptorCollectionProvider"/>
/// used to locate view components.</param>
/// <param name="htmlEncoder">The <see cref="HtmlEncoder"/>.</param>
/// <param name="selector">The <see cref="IViewComponentSelector"/>.</param>
/// <param name="invokerFactory">The <see cref="IViewComponentInvokerFactory"/>.</param>
/// <param name="viewBufferScope">The <see cref="IViewBufferScope"/> that manages the lifetime of
/// <see cref="ViewBuffer"/> instances.</param>
public DefaultViewComponentHelper( public DefaultViewComponentHelper(
IViewComponentDescriptorCollectionProvider descriptorProvider, IViewComponentDescriptorCollectionProvider descriptorProvider,
HtmlEncoder htmlEncoder, HtmlEncoder htmlEncoder,
@ -61,6 +74,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
_viewBufferScope = viewBufferScope; _viewBufferScope = viewBufferScope;
} }
/// <inheritdoc />
public void Contextualize(ViewContext viewContext) public void Contextualize(ViewContext viewContext)
{ {
if (viewContext == null) if (viewContext == null)
@ -71,133 +85,41 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
_viewContext = viewContext; _viewContext = viewContext;
} }
public IHtmlContent Invoke(string name, params object[] arguments) /// <inheritdoc />
public Task<IHtmlContent> InvokeAsync(string name, object arguments)
{ {
if (name == null) if (name == null)
{ {
throw new ArgumentNullException(nameof(name)); throw new ArgumentNullException(nameof(name));
} }
var descriptor = SelectComponent(name);
var viewBuffer = new ViewBuffer(_viewBufferScope, name);
using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding))
{
InvokeCore(writer, descriptor, arguments);
return writer.ContentBuilder;
}
}
public IHtmlContent Invoke(Type componentType, params object[] arguments)
{
if (componentType == null)
{
throw new ArgumentNullException(nameof(componentType));
}
var descriptor = SelectComponent(componentType);
var viewBuffer = new ViewBuffer(_viewBufferScope, componentType.Name);
using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding))
{
InvokeCore(writer, descriptor, arguments);
return writer.ContentBuilder;
}
}
public void RenderInvoke(string name, params object[] arguments)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var descriptor = SelectComponent(name);
InvokeCore(_viewContext.Writer, descriptor, arguments);
}
public void RenderInvoke(Type componentType, params object[] arguments)
{
if (componentType == null)
{
throw new ArgumentNullException(nameof(componentType));
}
var descriptor = SelectComponent(componentType);
InvokeCore(_viewContext.Writer, descriptor, arguments);
}
public async Task<IHtmlContent> InvokeAsync(string name, params object[] arguments)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var descriptor = SelectComponent(name);
var viewBuffer = new ViewBuffer(_viewBufferScope, name);
using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding))
{
await InvokeCoreAsync(writer, descriptor, arguments);
return writer.ContentBuilder;
}
}
public async Task<IHtmlContent> InvokeAsync(Type componentType, params object[] arguments)
{
if (componentType == null)
{
throw new ArgumentNullException(nameof(componentType));
}
var descriptor = SelectComponent(componentType);
var viewBuffer = new ViewBuffer(_viewBufferScope, componentType.Name);
using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding))
{
await InvokeCoreAsync(writer, descriptor, arguments);
return writer.ContentBuilder;
}
}
public Task RenderInvokeAsync(string name, params object[] arguments)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var descriptor = SelectComponent(name);
return InvokeCoreAsync(_viewContext.Writer, descriptor, arguments);
}
public Task RenderInvokeAsync(Type componentType, params object[] arguments)
{
if (componentType == null)
{
throw new ArgumentNullException(nameof(componentType));
}
var descriptor = SelectComponent(componentType);
return InvokeCoreAsync(_viewContext.Writer, descriptor, arguments);
}
private ViewComponentDescriptor SelectComponent(string name)
{
var descriptor = _selector.SelectComponent(name); var descriptor = _selector.SelectComponent(name);
if (descriptor == null) if (descriptor == null)
{ {
throw new InvalidOperationException(Resources.FormatViewComponent_CannotFindComponent(name)); throw new InvalidOperationException(Resources.FormatViewComponent_CannotFindComponent(name));
} }
return descriptor; return InvokeCoreAsync(descriptor, arguments);
}
/// <inheritdoc />
public Task<IHtmlContent> InvokeAsync(Type componentType, object arguments)
{
if (componentType == null)
{
throw new ArgumentNullException(nameof(componentType));
}
var descriptor = SelectComponent(componentType);
return InvokeCoreAsync(descriptor, arguments);
} }
private ViewComponentDescriptor SelectComponent(Type componentType) private ViewComponentDescriptor SelectComponent(Type componentType)
{ {
var descriptors = _descriptorProvider.ViewComponents; var descriptors = _descriptorProvider.ViewComponents;
foreach (var descriptor in descriptors.Items) for (var i = 0; i < descriptors.Items.Count; i++)
{ {
var descriptor = descriptors.Items[i];
if (descriptor.Type == componentType) if (descriptor.Type == componentType)
{ {
return descriptor; return descriptor;
@ -208,58 +130,30 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
componentType.FullName)); componentType.FullName));
} }
private Task InvokeCoreAsync( private async Task<IHtmlContent> InvokeCoreAsync(
TextWriter writer,
ViewComponentDescriptor descriptor, ViewComponentDescriptor descriptor,
object[] arguments) object arguments)
{ {
if (writer == null) var viewBuffer = new ViewBuffer(_viewBufferScope, descriptor.FullName);
using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding))
{ {
throw new ArgumentNullException(nameof(writer)); var context = new ViewComponentContext(
descriptor,
PropertyHelper.ObjectToDictionary(arguments),
_htmlEncoder,
_viewContext,
writer);
var invoker = _invokerFactory.CreateInstance(context);
if (invoker == null)
{
throw new InvalidOperationException(
Resources.FormatViewComponent_IViewComponentFactory_ReturnedNull(descriptor.FullName));
}
await invoker.InvokeAsync(context);
return writer.ContentBuilder;
} }
if (descriptor == null)
{
throw new ArgumentNullException(nameof(descriptor));
}
var context = new ViewComponentContext(descriptor, arguments, _htmlEncoder, _viewContext, writer);
var invoker = _invokerFactory.CreateInstance(context);
if (invoker == null)
{
throw new InvalidOperationException(
Resources.FormatViewComponent_IViewComponentFactory_ReturnedNull(descriptor.Type.FullName));
}
return invoker.InvokeAsync(context);
}
private void InvokeCore(
TextWriter writer,
ViewComponentDescriptor descriptor,
object[] arguments)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
if (descriptor == null)
{
throw new ArgumentNullException(nameof(descriptor));
}
var context = new ViewComponentContext(descriptor, arguments, _htmlEncoder, _viewContext, writer);
var invoker = _invokerFactory.CreateInstance(context);
if (invoker == null)
{
throw new InvalidOperationException(
Resources.FormatViewComponent_IViewComponentFactory_ReturnedNull(descriptor.Type.FullName));
}
invoker.Invoke(context);
} }
} }
} }

View File

@ -16,6 +16,9 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNet.Mvc.ViewComponents namespace Microsoft.AspNet.Mvc.ViewComponents
{ {
/// <summary>
/// Default implementation for <see cref="IViewComponentInvoker"/>.
/// </summary>
public class DefaultViewComponentInvoker : IViewComponentInvoker public class DefaultViewComponentInvoker : IViewComponentInvoker
{ {
private readonly ITypeActivatorCache _typeActivatorCache; private readonly ITypeActivatorCache _typeActivatorCache;
@ -23,6 +26,13 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
private readonly DiagnosticSource _diagnosticSource; private readonly DiagnosticSource _diagnosticSource;
private readonly ILogger _logger; private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of <see cref="DefaultViewComponentInvoker"/>.
/// </summary>
/// <param name="typeActivatorCache">Caches factories for instantiating view component instances.</param>
/// <param name="viewComponentActivator">The <see cref="IViewComponentActivator"/>.</param>
/// <param name="diagnosticSource">The <see cref="DiagnosticSource"/>.</param>
/// <param name="logger">The <see cref="ILogger"/>.</param>
public DefaultViewComponentInvoker( public DefaultViewComponentInvoker(
ITypeActivatorCache typeActivatorCache, ITypeActivatorCache typeActivatorCache,
IViewComponentActivator viewComponentActivator, IViewComponentActivator viewComponentActivator,
@ -55,27 +65,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
_logger = logger; _logger = logger;
} }
public void Invoke(ViewComponentContext context) /// <inheritdoc />
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var method = ViewComponentMethodSelector.FindSyncMethod(
context.ViewComponentDescriptor.Type.GetTypeInfo(),
context.Arguments);
if (method == null)
{
throw new InvalidOperationException(
Resources.FormatViewComponent_CannotFindMethod(ViewComponentMethodSelector.SyncMethodName));
}
var result = InvokeSyncCore(method, context);
result.Execute(context);
}
public async Task InvokeAsync(ViewComponentContext context) public async Task InvokeAsync(ViewComponentContext context)
{ {
if (context == null) if (context == null)
@ -83,32 +73,25 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
IViewComponentResult result; var methodInfo = context.ViewComponentDescriptor?.MethodInfo;
if (methodInfo == null)
var asyncMethod = ViewComponentMethodSelector.FindAsyncMethod(
context.ViewComponentDescriptor.Type.GetTypeInfo(),
context.Arguments);
if (asyncMethod == null)
{ {
// We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(
// execute the IViewResult asynchronously. nameof(ViewComponentDescriptor.MethodInfo),
var syncMethod = ViewComponentMethodSelector.FindSyncMethod( nameof(ViewComponentDescriptor)));
context.ViewComponentDescriptor.Type.GetTypeInfo(), }
context.Arguments);
if (syncMethod == null) var isAsync = typeof(Task).GetTypeInfo().IsAssignableFrom(methodInfo.ReturnType.GetTypeInfo());
{ IViewComponentResult result;
throw new InvalidOperationException( if (isAsync)
Resources.FormatViewComponent_CannotFindMethod_WithFallback( {
ViewComponentMethodSelector.SyncMethodName, ViewComponentMethodSelector.AsyncMethodName)); result = await InvokeAsyncCore(context);
}
else
{
result = InvokeSyncCore(syncMethod, context);
}
} }
else else
{ {
result = await InvokeAsyncCore(asyncMethod, context); // We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still
// execute the IViewResult asynchronously.
result = InvokeSyncCore(context);
} }
await result.ExecuteAsync(context); await result.ExecuteAsync(context);
@ -129,29 +112,20 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
return component; return component;
} }
private async Task<IViewComponentResult> InvokeAsyncCore( private async Task<IViewComponentResult> InvokeAsyncCore(ViewComponentContext context)
MethodInfo method,
ViewComponentContext context)
{ {
if (method == null)
{
throw new ArgumentNullException(nameof(method));
}
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var component = CreateComponent(context); var component = CreateComponent(context);
using (_logger.ViewComponentScope(context)) using (_logger.ViewComponentScope(context))
{ {
var method = context.ViewComponentDescriptor.MethodInfo;
var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, method.GetParameters());
_diagnosticSource.BeforeViewComponent(context, component); _diagnosticSource.BeforeViewComponent(context, component);
_logger.ViewComponentExecuting(context); _logger.ViewComponentExecuting(context, arguments);
var startTime = Environment.TickCount; var startTime = Environment.TickCount;
var result = await ControllerActionExecutor.ExecuteAsync(method, component, context.Arguments); var result = await ControllerActionExecutor.ExecuteAsync(method, component, arguments);
var viewComponentResult = CoerceToViewComponentResult(result); var viewComponentResult = CoerceToViewComponentResult(result);
_logger.ViewComponentExecuted(context, startTime, viewComponentResult); _logger.ViewComponentExecuted(context, startTime, viewComponentResult);
@ -161,35 +135,25 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
} }
} }
public IViewComponentResult InvokeSyncCore(MethodInfo method, ViewComponentContext context) private IViewComponentResult InvokeSyncCore(ViewComponentContext context)
{ {
if (method == null)
{
throw new ArgumentNullException(nameof(method));
}
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var component = CreateComponent(context); var component = CreateComponent(context);
using (_logger.ViewComponentScope(context)) using (_logger.ViewComponentScope(context))
{ {
_diagnosticSource.BeforeViewComponent(context, component); var method = context.ViewComponentDescriptor.MethodInfo;
_logger.ViewComponentExecuting(context); var arguments = ControllerActionExecutor.PrepareArguments(
context.Arguments,
method.GetParameters());
_diagnosticSource.BeforeViewComponent(context, component);
_logger.ViewComponentExecuting(context, arguments);
var startTime = Environment.TickCount;
object result;
try try
{ {
var startTime = Environment.TickCount; result = method.Invoke(component, arguments);
var result = method.Invoke(component, context.Arguments);
var viewComponentResult = CoerceToViewComponentResult(result);
_logger.ViewComponentExecuted(context, startTime, viewComponentResult);
_diagnosticSource.AfterViewComponent(context, viewComponentResult, component);
return viewComponentResult;
} }
catch (TargetInvocationException ex) catch (TargetInvocationException ex)
{ {
@ -198,6 +162,12 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
exceptionInfo.Throw(); exceptionInfo.Throw();
return null; // Unreachable return null; // Unreachable
} }
var viewComponentResult = CoerceToViewComponentResult(result);
_logger.ViewComponentExecuted(context, startTime, viewComponentResult);
_diagnosticSource.AfterViewComponent(context, viewComponentResult, component);
return viewComponentResult;
} }
} }

View File

@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ViewComponents namespace Microsoft.AspNet.Mvc.ViewComponents
{ {
/// <summary> /// <summary>
/// Discovers the View Components in the application. /// Discovers the view components in the application.
/// </summary> /// </summary>
public interface IViewComponentDescriptorProvider public interface IViewComponentDescriptorProvider
{ {

View File

@ -5,10 +5,17 @@ using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.ViewComponents namespace Microsoft.AspNet.Mvc.ViewComponents
{ {
/// <summary>
/// Specifies the contract for execution of a view component.
/// </summary>
public interface IViewComponentInvoker public interface IViewComponentInvoker
{ {
void Invoke(ViewComponentContext context); /// <summary>
/// Executes the view component specified by <see cref="ViewComponentContext.ViewComponentDescriptor"/>
/// of <paramref name="context"/> and writes the result to <see cref="ViewComponentContext.Writer"/>.
/// </summary>
/// <param name="context">The <see cref="ViewComponentContext"/>.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous operation of execution.</returns>
Task InvokeAsync(ViewComponentContext context); Task InvokeAsync(ViewComponentContext context);
} }
} }

View File

@ -4,14 +4,14 @@
namespace Microsoft.AspNet.Mvc.ViewComponents namespace Microsoft.AspNet.Mvc.ViewComponents
{ {
/// <summary> /// <summary>
/// Selects a View Component based on a View Component name. /// Selects a view component based on a view component name.
/// </summary> /// </summary>
public interface IViewComponentSelector public interface IViewComponentSelector
{ {
/// <summary> /// <summary>
/// Selects a View Component based on <paramref name="componentName"/>. /// Selects a view component based on <paramref name="componentName"/>.
/// </summary> /// </summary>
/// <param name="componentName">The View Component name.</param> /// <param name="componentName">The view component name.</param>
/// <returns>A <see cref="ViewComponentDescriptor"/>, or <c>null</c> if no match is found.</returns> /// <returns>A <see cref="ViewComponentDescriptor"/>, or <c>null</c> if no match is found.</returns>
ViewComponentDescriptor SelectComponent(string componentName); ViewComponentDescriptor SelectComponent(string componentName);
} }

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
@ -10,7 +11,7 @@ using Microsoft.AspNet.Mvc.ViewFeatures;
namespace Microsoft.AspNet.Mvc.ViewComponents namespace Microsoft.AspNet.Mvc.ViewComponents
{ {
/// <summary> /// <summary>
/// A context for View Components. /// A context for view components.
/// </summary> /// </summary>
public class ViewComponentContext public class ViewComponentContext
{ {
@ -23,7 +24,6 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
public ViewComponentContext() public ViewComponentContext()
{ {
ViewComponentDescriptor = new ViewComponentDescriptor(); ViewComponentDescriptor = new ViewComponentDescriptor();
Arguments = new object[0];
ViewContext = new ViewContext(); ViewContext = new ViewContext();
} }
@ -31,14 +31,14 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
/// Creates a new <see cref="ViewComponentContext"/>. /// Creates a new <see cref="ViewComponentContext"/>.
/// </summary> /// </summary>
/// <param name="viewComponentDescriptor"> /// <param name="viewComponentDescriptor">
/// The <see cref="ViewComponentContext"/> for the View Component being invoked. /// The <see cref="ViewComponentContext"/> for the view component being invoked.
/// </param> /// </param>
/// <param name="arguments">The View Component arguments.</param> /// <param name="arguments">The view component arguments.</param>
/// <param name="viewContext">The <see cref="ViewContext"/>.</param> /// <param name="viewContext">The <see cref="ViewContext"/>.</param>
/// <param name="writer">The <see cref="TextWriter"/> for writing output.</param> /// <param name="writer">The <see cref="TextWriter"/> for writing output.</param>
public ViewComponentContext( public ViewComponentContext(
ViewComponentDescriptor viewComponentDescriptor, ViewComponentDescriptor viewComponentDescriptor,
object[] arguments, IDictionary<string, object> arguments,
HtmlEncoder htmlEncoder, HtmlEncoder htmlEncoder,
ViewContext viewContext, ViewContext viewContext,
TextWriter writer) TextWriter writer)
@ -82,15 +82,15 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
} }
/// <summary> /// <summary>
/// Gets or sets the View Component arguments. /// Gets or sets the view component arguments.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The property setter is provided for unit test purposes only. /// The property setter is provided for unit test purposes only.
/// </remarks> /// </remarks>
public object[] Arguments { get; set; } public IDictionary<string, object> Arguments { get; set; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="HtmlEncoder"/>. /// Gets or sets the <see cref="System.Text.Encodings.Web.HtmlEncoder"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The property setter is provided for unit test purposes only. /// The property setter is provided for unit test purposes only.
@ -98,7 +98,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
public HtmlEncoder HtmlEncoder { get; set; } public HtmlEncoder HtmlEncoder { get; set; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="ViewComponentDescriptor"/> for the View Component being invoked. /// Gets or sets the <see cref="ViewComponents.ViewComponentDescriptor"/> for the view component being invoked.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The property setter is provided for unit test purposes only. /// The property setter is provided for unit test purposes only.
@ -106,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
public ViewComponentDescriptor ViewComponentDescriptor { get; set; } public ViewComponentDescriptor ViewComponentDescriptor { get; set; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="ViewContext"/>. /// Gets or sets the <see cref="Rendering.ViewContext"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The property setter is provided for unit test purposes only. /// The property setter is provided for unit test purposes only.

View File

@ -3,11 +3,12 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ViewComponents namespace Microsoft.AspNet.Mvc.ViewComponents
{ {
/// <summary> /// <summary>
/// A descriptor for a View Component. /// A descriptor for a view component.
/// </summary> /// </summary>
[DebuggerDisplay("{DisplayName}")] [DebuggerDisplay("{DisplayName}")]
public class ViewComponentDescriptor public class ViewComponentDescriptor
@ -23,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
} }
/// <summary> /// <summary>
/// Gets or sets the display name of the View Component. /// Gets or sets the display name of the view component.
/// </summary> /// </summary>
public string DisplayName public string DisplayName
{ {
@ -53,8 +54,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// The full name is defaulted to the full namespace of the View Component class, prepended to /// The full name is defaulted to the full namespace of the view component class, prepended to
/// the the class name with a '.' character as the separator. If the View Component class uses /// the the class name with a '.' character as the separator. If the view component class uses
/// <code>ViewComponent</code> as a suffix, the suffix will be omitted from the <see cref="FullName"/>. /// <code>ViewComponent</code> as a suffix, the suffix will be omitted from the <see cref="FullName"/>.
/// </para> /// </para>
/// <example> /// <example>
@ -89,7 +90,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// The short name is defaulted to the name of the View Component class. If the View Component class uses /// The short name is defaulted to the name of the view component class. If the view component class uses
/// <code>ViewComponent</code> as a suffix, the suffix will be omitted from the <see cref="ShortName"/>. /// <code>ViewComponent</code> as a suffix, the suffix will be omitted from the <see cref="ShortName"/>.
/// </para> /// </para>
/// <example> /// <example>
@ -115,8 +116,13 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
public string ShortName { get; set; } public string ShortName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="Type"/>. /// Gets or sets the <see cref="System.Type"/>.
/// </summary> /// </summary>
public Type Type { get; set; } public Type Type { get; set; }
/// <summary>
/// Gets or sets the <see cref="System.Reflection.MethodInfo"/> to invoke.
/// </summary>
public MethodInfo MethodInfo { get; set; }
} }
} }

View File

@ -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
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection;
using System.Text; using System.Text;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -42,13 +43,14 @@ namespace Microsoft.AspNet.Mvc
FullName = "Full.Name.Text", FullName = "Full.Name.Text",
ShortName = "Text", ShortName = "Text",
Type = typeof(TextViewComponent), Type = typeof(TextViewComponent),
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
}; };
var actionContext = CreateActionContext(descriptor); var actionContext = CreateActionContext(descriptor);
var viewComponentResult = new ViewComponentResult var viewComponentResult = new ViewComponentResult
{ {
Arguments = new object[] { "World!" }, Arguments = new { name = "World!" },
ViewData = null, ViewData = null,
TempData = null, TempData = null,
ViewComponentName = "Text" ViewComponentName = "Text"
@ -132,13 +134,43 @@ namespace Microsoft.AspNet.Mvc
FullName = "Full.Name.Text", FullName = "Full.Name.Text",
ShortName = "Text", ShortName = "Text",
Type = typeof(TextViewComponent), Type = typeof(TextViewComponent),
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
}; };
var actionContext = CreateActionContext(descriptor); var actionContext = CreateActionContext(descriptor);
var viewComponentResult = new ViewComponentResult() var viewComponentResult = new ViewComponentResult()
{ {
Arguments = new object[] { "World!" }, Arguments = new { name = "World!" },
ViewComponentName = "Text",
TempData = _tempDataDictionary,
};
// Act
await viewComponentResult.ExecuteResultAsync(actionContext);
// Assert
var body = ReadBody(actionContext.HttpContext.Response);
Assert.Equal("Hello, World!", body);
}
[Fact]
public async Task ExecuteResultAsync_UsesDictionaryArguments()
{
// Arrange
var descriptor = new ViewComponentDescriptor()
{
FullName = "Full.Name.Text",
ShortName = "Text",
Type = typeof(TextViewComponent),
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
};
var actionContext = CreateActionContext(descriptor);
var viewComponentResult = new ViewComponentResult()
{
Arguments = new Dictionary<string, object> { ["name"] = "World!" },
ViewComponentName = "Text", ViewComponentName = "Text",
TempData = _tempDataDictionary, TempData = _tempDataDictionary,
}; };
@ -160,13 +192,14 @@ namespace Microsoft.AspNet.Mvc
FullName = "Full.Name.AsyncText", FullName = "Full.Name.AsyncText",
ShortName = "AsyncText", ShortName = "AsyncText",
Type = typeof(AsyncTextViewComponent), Type = typeof(AsyncTextViewComponent),
MethodInfo = typeof(AsyncTextViewComponent).GetMethod(nameof(AsyncTextViewComponent.InvokeAsync)),
}; };
var actionContext = CreateActionContext(descriptor); var actionContext = CreateActionContext(descriptor);
var viewComponentResult = new ViewComponentResult() var viewComponentResult = new ViewComponentResult()
{ {
Arguments = new object[] { "World!" }, Arguments = new { name = "World!" },
ViewComponentName = "AsyncText", ViewComponentName = "AsyncText",
TempData = _tempDataDictionary, TempData = _tempDataDictionary,
}; };
@ -188,6 +221,7 @@ namespace Microsoft.AspNet.Mvc
FullName = "Full.Name.Text", FullName = "Full.Name.Text",
ShortName = "Text", ShortName = "Text",
Type = typeof(TextViewComponent), Type = typeof(TextViewComponent),
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
}; };
var adapter = new TestDiagnosticListener(); var adapter = new TestDiagnosticListener();
@ -196,7 +230,7 @@ namespace Microsoft.AspNet.Mvc
var viewComponentResult = new ViewComponentResult() var viewComponentResult = new ViewComponentResult()
{ {
Arguments = new object[] { "World!" }, Arguments = new { name = "World!" },
ViewComponentName = "Text", ViewComponentName = "Text",
TempData = _tempDataDictionary, TempData = _tempDataDictionary,
}; };
@ -226,13 +260,14 @@ namespace Microsoft.AspNet.Mvc
FullName = "Full.Name.Text", FullName = "Full.Name.Text",
ShortName = "Text", ShortName = "Text",
Type = typeof(TextViewComponent), Type = typeof(TextViewComponent),
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
}; };
var actionContext = CreateActionContext(descriptor); var actionContext = CreateActionContext(descriptor);
var viewComponentResult = new ViewComponentResult() var viewComponentResult = new ViewComponentResult()
{ {
Arguments = new object[] { "World!" }, Arguments = new { name = "World!" },
ViewComponentName = "Text", ViewComponentName = "Text",
TempData = _tempDataDictionary, TempData = _tempDataDictionary,
}; };
@ -254,13 +289,14 @@ namespace Microsoft.AspNet.Mvc
FullName = "Full.Name.Text", FullName = "Full.Name.Text",
ShortName = "Text", ShortName = "Text",
Type = typeof(TextViewComponent), Type = typeof(TextViewComponent),
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
}; };
var actionContext = CreateActionContext(descriptor); var actionContext = CreateActionContext(descriptor);
var viewComponentResult = new ViewComponentResult() var viewComponentResult = new ViewComponentResult()
{ {
Arguments = new object[] { "World!" }, Arguments = new { name = "World!" },
ViewComponentName = "Full.Name.Text", ViewComponentName = "Full.Name.Text",
TempData = _tempDataDictionary, TempData = _tempDataDictionary,
}; };
@ -282,13 +318,14 @@ namespace Microsoft.AspNet.Mvc
FullName = "Full.Name.Text", FullName = "Full.Name.Text",
ShortName = "Text", ShortName = "Text",
Type = typeof(TextViewComponent), Type = typeof(TextViewComponent),
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
}; };
var actionContext = CreateActionContext(descriptor); var actionContext = CreateActionContext(descriptor);
var viewComponentResult = new ViewComponentResult() var viewComponentResult = new ViewComponentResult()
{ {
Arguments = new object[] { "World!" }, Arguments = new { name = "World!" },
ViewComponentType = typeof(TextViewComponent), ViewComponentType = typeof(TextViewComponent),
TempData = _tempDataDictionary, TempData = _tempDataDictionary,
}; };
@ -310,13 +347,14 @@ namespace Microsoft.AspNet.Mvc
FullName = "Full.Name.Text", FullName = "Full.Name.Text",
ShortName = "Text", ShortName = "Text",
Type = typeof(TextViewComponent), Type = typeof(TextViewComponent),
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke))
}; };
var actionContext = CreateActionContext(descriptor); var actionContext = CreateActionContext(descriptor);
var viewComponentResult = new ViewComponentResult() var viewComponentResult = new ViewComponentResult()
{ {
Arguments = new object[] { "World!" }, Arguments = new { name = "World!" },
ViewComponentType = typeof(TextViewComponent), ViewComponentType = typeof(TextViewComponent),
StatusCode = 404, StatusCode = 404,
TempData = _tempDataDictionary, TempData = _tempDataDictionary,
@ -367,6 +405,7 @@ namespace Microsoft.AspNet.Mvc
FullName = "Full.Name.Text", FullName = "Full.Name.Text",
ShortName = "Text", ShortName = "Text",
Type = typeof(TextViewComponent), Type = typeof(TextViewComponent),
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
}; };
var actionContext = CreateActionContext(descriptor); var actionContext = CreateActionContext(descriptor);
@ -375,7 +414,7 @@ namespace Microsoft.AspNet.Mvc
var viewComponentResult = new ViewComponentResult() var viewComponentResult = new ViewComponentResult()
{ {
Arguments = new object[] { "World!" }, Arguments = new { name = "World!" },
ViewComponentName = "Text", ViewComponentName = "Text",
ContentType = contentType, ContentType = contentType,
TempData = _tempDataDictionary, TempData = _tempDataDictionary,
@ -403,6 +442,7 @@ namespace Microsoft.AspNet.Mvc
FullName = "Full.Name.Text", FullName = "Full.Name.Text",
ShortName = "Text", ShortName = "Text",
Type = typeof(TextViewComponent), Type = typeof(TextViewComponent),
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
}; };
var actionContext = CreateActionContext(descriptor); var actionContext = CreateActionContext(descriptor);
@ -412,7 +452,7 @@ namespace Microsoft.AspNet.Mvc
var viewComponentResult = new ViewComponentResult() var viewComponentResult = new ViewComponentResult()
{ {
Arguments = new object[] { "World!" }, Arguments = new { name = "World!" },
ViewComponentName = "Text", ViewComponentName = "Text",
ContentType = new MediaTypeHeaderValue("text/html") { Encoding = Encoding.UTF8 }, ContentType = new MediaTypeHeaderValue("text/html") { Encoding = Encoding.UTF8 },
TempData = _tempDataDictionary, TempData = _tempDataDictionary,
@ -439,6 +479,7 @@ namespace Microsoft.AspNet.Mvc
FullName = "Full.Name.Text", FullName = "Full.Name.Text",
ShortName = "Text", ShortName = "Text",
Type = typeof(TextViewComponent), Type = typeof(TextViewComponent),
MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)),
}; };
var actionContext = CreateActionContext(descriptor); var actionContext = CreateActionContext(descriptor);
@ -447,7 +488,7 @@ namespace Microsoft.AspNet.Mvc
var viewComponentResult = new ViewComponentResult() var viewComponentResult = new ViewComponentResult()
{ {
Arguments = new object[] { "World!" }, Arguments = new { name = "World!" },
ViewComponentName = "Text", ViewComponentName = "Text",
TempData = _tempDataDictionary, TempData = _tempDataDictionary,
}; };
@ -459,7 +500,10 @@ namespace Microsoft.AspNet.Mvc
Assert.Equal(expectedContentType, actionContext.HttpContext.Response.ContentType); Assert.Equal(expectedContentType, actionContext.HttpContext.Response.ContentType);
} }
private IServiceCollection CreateServices(object diagnosticListener, HttpContext context, params ViewComponentDescriptor[] descriptors) private IServiceCollection CreateServices(
object diagnosticListener,
HttpContext context,
params ViewComponentDescriptor[] descriptors)
{ {
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
var diagnosticSource = new DiagnosticListener("Microsoft.AspNet"); var diagnosticSource = new DiagnosticListener("Microsoft.AspNet");
@ -535,12 +579,6 @@ namespace Microsoft.AspNet.Mvc
private class AsyncTextViewComponent : ViewComponent private class AsyncTextViewComponent : ViewComponent
{ {
public HtmlString Invoke()
{
// Should never run.
throw null;
}
public Task<HtmlString> InvokeAsync(string name) public Task<HtmlString> InvokeAsync(string name)
{ {
return Task.FromResult(new HtmlString("Hello-Async, " + name)); return Task.FromResult(new HtmlString("Hello-Async, " + name));

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.Abstractions;
@ -57,7 +58,7 @@ namespace Microsoft.AspNet.Mvc
var viewComponentContext = new ViewComponentContext( var viewComponentContext = new ViewComponentContext(
viewComponentDescriptor, viewComponentDescriptor,
new object[0], new Dictionary<string, object>(),
new HtmlTestEncoder(), new HtmlTestEncoder(),
viewContext, viewContext,
writer); writer);

View File

@ -2,13 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Routing;
using Moq;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc.ViewComponents namespace Microsoft.AspNet.Mvc.ViewComponents

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Infrastructure; using Microsoft.AspNet.Mvc.Infrastructure;
using Xunit; using Xunit;
@ -23,9 +24,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
// Assert // Assert
var descriptor = Assert.Single(descriptors); var descriptor = Assert.Single(descriptors);
Assert.Equal(typeof(ConventionsViewComponent), descriptor.Type); Assert.Same(typeof(ConventionsViewComponent), descriptor.Type);
Assert.Equal("Microsoft.AspNet.Mvc.ViewComponents.Conventions", descriptor.FullName); Assert.Equal("Microsoft.AspNet.Mvc.ViewComponents.Conventions", descriptor.FullName);
Assert.Equal("Conventions", descriptor.ShortName); Assert.Equal("Conventions", descriptor.ShortName);
Assert.Same(typeof(ConventionsViewComponent).GetMethod("Invoke"), descriptor.MethodInfo);
} }
[Fact] [Fact]
@ -42,15 +44,156 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
Assert.Equal(typeof(AttributeViewComponent), descriptor.Type); Assert.Equal(typeof(AttributeViewComponent), descriptor.Type);
Assert.Equal("AttributesAreGreat", descriptor.FullName); Assert.Equal("AttributesAreGreat", descriptor.FullName);
Assert.Equal("AttributesAreGreat", descriptor.ShortName); Assert.Equal("AttributesAreGreat", descriptor.ShortName);
Assert.Same(typeof(AttributeViewComponent).GetMethod("InvokeAsync"), descriptor.MethodInfo);
}
[Theory]
[InlineData(typeof(NoMethodsViewComponent))]
[InlineData(typeof(NonPublicInvokeAsyncViewComponent))]
[InlineData(typeof(NonPublicInvokeViewComponent))]
public void GetViewComponents_ThrowsIfTypeHasNoInvocationMethods(Type type)
{
// Arrange
var expected = $"Could not find an 'Invoke' or 'InvokeAsync' method for the view component '{type}'.";
var provider = CreateProvider(type);
// Act
var ex = Assert.Throws<InvalidOperationException>(() => provider.GetViewComponents().ToArray());
Assert.Equal(expected, ex.Message);
}
[Theory]
[InlineData(typeof(MultipleInvokeViewComponent))]
[InlineData(typeof(MultipleInvokeAsyncViewComponent))]
[InlineData(typeof(InvokeAndInvokeAsyncViewComponent))]
public void GetViewComponents_ThrowsIfTypeHasAmbiguousInvocationMethods(Type type)
{
// Arrange
var expected = $"View component '{type}' must have exactly one public method named " +
"'InvokeAsync' or 'Invoke'.";
var provider = CreateProvider(type);
// Act
var ex = Assert.Throws<InvalidOperationException>(() => provider.GetViewComponents().ToArray());
Assert.Equal(expected, ex.Message);
}
[Theory]
[InlineData(typeof(NonGenericTaskReturningInvokeAsyncViewComponent))]
[InlineData(typeof(VoidReturningInvokeAsyncViewComponent))]
[InlineData(typeof(NonTaskReturningInvokeAsyncViewComponent))]
public void GetViewComponents_ThrowsIfInvokeAsyncDoesNotHaveCorrectReturnType(Type type)
{
// Arrange
var expected = $"Method 'InvokeAsync' of view component '{type}' should be declared to return Task<T>.";
var provider = CreateProvider(type);
// Act and Assert
var ex = Assert.Throws<InvalidOperationException>(() => provider.GetViewComponents().ToArray());
Assert.Equal(expected, ex.Message);
}
[Fact]
public void GetViewComponents_ThrowsIfInvokeReturnsATask()
{
// Arrange
var type = typeof(TaskReturningInvokeViewComponent);
var expected = $"Method 'Invoke' of view component '{type}' cannot return a Task.";
var provider = CreateProvider(type);
// Act and Assert
var ex = Assert.Throws<InvalidOperationException>(() => provider.GetViewComponents().ToArray());
Assert.Equal(expected, ex.Message);
}
[Fact]
public void GetViewComponents_ThrowsIfInvokeIsVoidReturning()
{
// Arrange
var type = typeof(VoidReturningInvokeViewComponent);
var expected = $"Method 'Invoke' of view component '{type}' should be declared to return a value.";
var provider = CreateProvider(type);
// Act and Assert
var ex = Assert.Throws<InvalidOperationException>(() => provider.GetViewComponents().ToArray());
Assert.Equal(expected, ex.Message);
} }
private class ConventionsViewComponent private class ConventionsViewComponent
{ {
public string Invoke() => "Hello world";
} }
[ViewComponent(Name = "AttributesAreGreat")] [ViewComponent(Name = "AttributesAreGreat")]
private class AttributeViewComponent private class AttributeViewComponent
{ {
public Task<string> InvokeAsync() => Task.FromResult("Hello world");
}
private class MultipleInvokeViewComponent
{
public IViewComponentResult Invoke() => null;
public IViewComponentResult Invoke(int a) => null;
}
private class NoMethodsViewComponent
{
}
private class NonPublicInvokeViewComponent
{
private IViewComponentResult Invoke() => null;
}
private class NonPublicInvokeAsyncViewComponent
{
protected Task<IViewComponentResult> InvokeAsync() => null;
}
private class MultipleInvokeAsyncViewComponent
{
public Task<IViewComponentResult> InvokeAsync(string a) => null;
public Task<IViewComponentResult> InvokeAsync(int a) => null;
public Task<IViewComponentResult> InvokeAsync(int a, int b) => null;
}
private class InvokeAndInvokeAsyncViewComponent
{
public Task<IViewComponentResult> InvokeAsync(string a) => null;
public string InvokeAsync(int a) => null;
}
private class NonGenericTaskReturningInvokeAsyncViewComponent
{
public Task InvokeAsync() => Task.FromResult(0);
}
private class VoidReturningInvokeAsyncViewComponent
{
public void InvokeAsync()
{
}
}
public class NonTaskReturningInvokeAsyncViewComponent
{
public long InvokeAsync() => 0L;
}
public class TaskReturningInvokeViewComponent
{
public Task Invoke() => Task.FromResult(0);
}
public class VoidReturningInvokeViewComponent
{
public void Invoke(int x)
{
}
} }
private DefaultViewComponentDescriptorProvider CreateProvider(Type componentType) private DefaultViewComponentDescriptorProvider CreateProvider(Type componentType)
@ -80,11 +223,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
return return
GetAssemblyProvider() GetAssemblyProvider()
.CandidateAssemblies .CandidateAssemblies
.SelectMany(a => a.DefinedTypes) .SelectMany(a => a.DefinedTypes);
#if DNX451
.Select(t => t.GetTypeInfo())
#endif
;
} }
private static IAssemblyProvider GetAssemblyProvider() private static IAssemblyProvider GetAssemblyProvider()

View File

@ -12,6 +12,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
{ {
public class DefaultViewComponentSelectorTest public class DefaultViewComponentSelectorTest
{ {
private static readonly string Namespace = typeof(DefaultViewComponentSelectorTest).Namespace;
[Fact] [Fact]
public void SelectComponent_ByShortNameWithSuffix() public void SelectComponent_ByShortNameWithSuffix()
{ {
@ -22,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
var result = selector.SelectComponent("Suffix"); var result = selector.SelectComponent("Suffix");
// Assert // Assert
Assert.Equal(typeof(SuffixViewComponent), result.Type); Assert.Same(typeof(ViewComponentContainer.SuffixViewComponent), result.Type);
} }
[Fact] [Fact]
@ -32,10 +34,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
var selector = CreateSelector(); var selector = CreateSelector();
// Act // Act
var result = selector.SelectComponent("Microsoft.AspNet.Mvc.ViewComponents.Suffix"); var result = selector.SelectComponent($"{Namespace}.Suffix");
// Assert // Assert
Assert.Equal(typeof(SuffixViewComponent), result.Type); Assert.Same(typeof(ViewComponentContainer.SuffixViewComponent), result.Type);
} }
[Fact] [Fact]
@ -48,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
var result = selector.SelectComponent("WithoutSuffix"); var result = selector.SelectComponent("WithoutSuffix");
// Assert // Assert
Assert.Equal(typeof(WithoutSuffix), result.Type); Assert.Same(typeof(ViewComponentContainer.WithoutSuffix), result.Type);
} }
[Fact] [Fact]
@ -58,10 +60,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
var selector = CreateSelector(); var selector = CreateSelector();
// Act // Act
var result = selector.SelectComponent("Microsoft.AspNet.Mvc.ViewComponents.WithoutSuffix"); var result = selector.SelectComponent($"{Namespace}.WithoutSuffix");
// Assert // Assert
Assert.Equal(typeof(WithoutSuffix), result.Type); Assert.Same(typeof(ViewComponentContainer.WithoutSuffix), result.Type);
} }
[Fact] [Fact]
@ -74,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
var result = selector.SelectComponent("ByAttribute"); var result = selector.SelectComponent("ByAttribute");
// Assert // Assert
Assert.Equal(typeof(ByAttribute), result.Type); Assert.Same(typeof(ViewComponentContainer.ByAttribute), result.Type);
} }
[Fact] [Fact]
@ -87,7 +89,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
var result = selector.SelectComponent("ByNamingConvention"); var result = selector.SelectComponent("ByNamingConvention");
// Assert // Assert
Assert.Equal(typeof(ByNamingConventionViewComponent), result.Type); Assert.Same(typeof(ViewComponentContainer.ByNamingConventionViewComponent), result.Type);
} }
[Fact] [Fact]
@ -98,9 +100,9 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
var expected = var expected =
"The view component name 'Ambiguous' matched multiple types:" + Environment.NewLine + "The view component name 'Ambiguous' matched multiple types:" + Environment.NewLine +
"Type: 'Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentSelectorTest+Ambiguous1' - " + $"Type: '{typeof(ViewComponentContainer.Ambiguous1)}' - " +
"Name: 'Namespace1.Ambiguous'" + Environment.NewLine + "Name: 'Namespace1.Ambiguous'" + Environment.NewLine +
"Type: 'Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentSelectorTest+Ambiguous2' - " + $"Type: '{typeof(ViewComponentContainer.Ambiguous2)}' - " +
"Name: 'Namespace2.Ambiguous'"; "Name: 'Namespace2.Ambiguous'";
// Act // Act
@ -120,7 +122,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
var result = selector.SelectComponent("Namespace1.Ambiguous"); var result = selector.SelectComponent("Namespace1.Ambiguous");
// Assert // Assert
Assert.Equal(typeof(Ambiguous1), result.Type); Assert.Same(typeof(ViewComponentContainer.Ambiguous1), result.Type);
} }
[Theory] [Theory]
@ -135,44 +137,56 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
var result = selector.SelectComponent(name); var result = selector.SelectComponent(name);
// Assert // Assert
Assert.Equal(typeof(FullNameInAttribute), result.Type); Assert.Same(typeof(ViewComponentContainer.FullNameInAttribute), result.Type);
} }
private IViewComponentSelector CreateSelector() private IViewComponentSelector CreateSelector()
{ {
return new FilteredViewComponentSelector(); var provider = new DefaultViewComponentDescriptorCollectionProvider(
new FilteredViewComponentDescriptorProvider());
return new DefaultViewComponentSelector(provider);
} }
private class SuffixViewComponent : ViewComponent private class ViewComponentContainer
{ {
} public class SuffixViewComponent : ViewComponent
{
public string Invoke() => "Hello";
}
private class WithoutSuffix : ViewComponent public class WithoutSuffix : ViewComponent
{ {
} public string Invoke() => "Hello";
}
private class ByNamingConventionViewComponent public class ByNamingConventionViewComponent
{ {
} public string Invoke() => "Hello";
}
[ViewComponent] [ViewComponent]
private class ByAttribute public class ByAttribute
{ {
} public string Invoke() => "Hello";
}
[ViewComponent(Name = "Namespace1.Ambiguous")] [ViewComponent(Name = "Namespace1.Ambiguous")]
private class Ambiguous1 public class Ambiguous1
{ {
} public string Invoke() => "Hello";
}
[ViewComponent(Name = "Namespace2.Ambiguous")] [ViewComponent(Name = "Namespace2.Ambiguous")]
private class Ambiguous2 public class Ambiguous2
{ {
} public string Invoke() => "Hello";
}
[ViewComponent(Name = "CoolNameSpace.FullNameInAttribute")] [ViewComponent(Name = "CoolNameSpace.FullNameInAttribute")]
private class FullNameInAttribute public class FullNameInAttribute
{ {
public string Invoke() => "Hello";
}
} }
// This will only consider types nested inside this class as ViewComponent classes // This will only consider types nested inside this class as ViewComponent classes
@ -181,10 +195,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
public FilteredViewComponentDescriptorProvider() public FilteredViewComponentDescriptorProvider()
: base(GetAssemblyProvider()) : base(GetAssemblyProvider())
{ {
AllowedTypes = typeof(DefaultViewComponentSelectorTest).GetNestedTypes(BindingFlags.NonPublic); AllowedTypes = typeof(ViewComponentContainer).GetNestedTypes(bindingAttr: BindingFlags.Public);
} }
public Type[] AllowedTypes { get; private set; } public Type[] AllowedTypes { get; }
protected override bool IsViewComponentType(TypeInfo typeInfo) protected override bool IsViewComponentType(TypeInfo typeInfo)
{ {
@ -208,19 +222,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
{ {
var assemblyProvider = new StaticAssemblyProvider(); var assemblyProvider = new StaticAssemblyProvider();
assemblyProvider.CandidateAssemblies.Add( assemblyProvider.CandidateAssemblies.Add(
typeof(FilteredViewComponentSelector).GetTypeInfo().Assembly); typeof(ViewComponentContainer).GetTypeInfo().Assembly);
return assemblyProvider; return assemblyProvider;
} }
} }
// This will only consider types nested inside this class as ViewComponent classes
private class FilteredViewComponentSelector : DefaultViewComponentSelector
{
public FilteredViewComponentSelector()
: base(new DefaultViewComponentDescriptorCollectionProvider(new FilteredViewComponentDescriptorProvider()))
{
}
}
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if MOCK_SUPPORT using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.Abstractions;
@ -57,7 +57,7 @@ namespace Microsoft.AspNet.Mvc
var viewComponentContext = new ViewComponentContext( var viewComponentContext = new ViewComponentContext(
viewComponentDescriptor, viewComponentDescriptor,
new object[0], new Dictionary<string, object>(),
new HtmlTestEncoder(), new HtmlTestEncoder(),
viewContext, viewContext,
writer); writer);
@ -66,4 +66,3 @@ namespace Microsoft.AspNet.Mvc
} }
} }
} }
#endif

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
@ -85,7 +86,7 @@ namespace Microsoft.AspNet.Mvc
var viewComponentContext = new ViewComponentContext( var viewComponentContext = new ViewComponentContext(
viewComponentDescriptor, viewComponentDescriptor,
new object[0], new Dictionary<string, object>(),
new HtmlTestEncoder(), new HtmlTestEncoder(),
viewContext, viewContext,
writer); writer);

View File

@ -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; }
}
}
}

View File

@ -2,9 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.Abstractions;
@ -147,9 +149,9 @@ namespace Microsoft.AspNet.Mvc
{ {
// Arrange // Arrange
var expected = string.Join(Environment.NewLine, var expected = string.Join(Environment.NewLine,
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:", "The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
"location1", "location1",
"location2"); "location2");
var view = Mock.Of<IView>(); var view = Mock.Of<IView>();
@ -185,9 +187,9 @@ namespace Microsoft.AspNet.Mvc
{ {
// Arrange // Arrange
var expected = string.Join(Environment.NewLine, var expected = string.Join(Environment.NewLine,
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:", "The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
"location1", "location1",
"location2"); "location2");
var view = Mock.Of<IView>(); var view = Mock.Of<IView>();
@ -223,11 +225,11 @@ namespace Microsoft.AspNet.Mvc
{ {
// Arrange // Arrange
var expected = string.Join(Environment.NewLine, var expected = string.Join(Environment.NewLine,
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:", "The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
"location1", "location1",
"location2", "location2",
"location3", "location3",
"location4"); "location4");
var view = Mock.Of<IView>(); var view = Mock.Of<IView>();
@ -535,11 +537,12 @@ namespace Microsoft.AspNet.Mvc
{ {
ShortName = "Invoke", ShortName = "Invoke",
Type = typeof(object), Type = typeof(object),
MethodInfo = typeof(object).GetTypeInfo().DeclaredMethods.First()
}; };
var viewComponentContext = new ViewComponentContext( var viewComponentContext = new ViewComponentContext(
viewComponentDescriptor, viewComponentDescriptor,
new object[0], new Dictionary<string, object>(),
new HtmlTestEncoder(), new HtmlTestEncoder(),
viewContext, viewContext,
TextWriter.Null); TextWriter.Null);

View File

@ -1 +1 @@
@Component.Invoke("JsonTextInView") @await Component.InvokeAsync("JsonTextInView")

View File

@ -1 +1 @@
@Component.Invoke("RequestId") @await Component.InvokeAsync("RequestId")

View File

@ -1,3 +1,3 @@
@model Person @model Person
<h1>@Model.Name</h1> <h1>@Model.Name</h1>
@Component.Invoke("InheritingViewComponent", Model.Address) @await Component.InvokeAsync("InheritingViewComponent", new { address = Model.Address })

View File

@ -12,4 +12,4 @@
} }
ViewWithRelativePath-content ViewWithRelativePath-content
<partial>@await Html.PartialAsync("../Shared/_PartialThatSetsTitle.cshtml")</partial> <partial>@await Html.PartialAsync("../Shared/_PartialThatSetsTitle.cshtml")</partial>
@await Component.InvokeAsync("ComponentWithRelativePath", person) @await Component.InvokeAsync("ComponentWithRelativePath", new { person })

View File

@ -2,6 +2,6 @@
ViewData["Title"] = "Page title"; ViewData["Title"] = "Page title";
// The invoked partial sets a title, but this shouldn't override the current page's ViewData \ ViewBag. // The invoked partial sets a title, but this shouldn't override the current page's ViewData \ ViewBag.
await Html.RenderPartialAsync("_PartialThatSetsTitle"); await Html.RenderPartialAsync("_PartialThatSetsTitle");
await Component.RenderInvokeAsync("ComponentThatSetsTitle"); @await Component.InvokeAsync("ComponentThatSetsTitle")
Layout = "/Views/Shared/_LayoutWithTitle.cshtml"; Layout = "/Views/Shared/_LayoutWithTitle.cshtml";
} }

View File

@ -1,6 +1,6 @@
<Index> <Index>
@ViewContext.ExecutingFilePath @ViewContext.ExecutingFilePath
@ViewContext.View.Path @ViewContext.View.Path
@Component.Invoke("ComponentForViewWithPaths") @await Component.InvokeAsync("ComponentForViewWithPaths")
@Html.Partial("_Partial") @Html.Partial("_Partial")
</Index> </Index>

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
@ -58,7 +59,7 @@ namespace MvcSample.Web.Components
await result.ExecuteAsync(new ViewComponentContext( await result.ExecuteAsync(new ViewComponentContext(
viewComponentDescriptor, viewComponentDescriptor,
new object[0], new Dictionary<string, object>(),
_htmlEncoder, _htmlEncoder,
ViewContext, ViewContext,
writer)); writer));

View File

@ -35,7 +35,7 @@
<h3>Current Tag Cloud from Tag Helper</h3> <h3>Current Tag Cloud from Tag Helper</h3>
<tag-cloud count="Model.TagsToShow" surround="div" /> <tag-cloud count="Model.TagsToShow" surround="div" />
<h3>Current Tag Cloud from ViewComponentHelper:</h3> <h3>Current Tag Cloud from ViewComponentHelper:</h3>
<section bold>@await Component.InvokeAsync("Tags", 15)</section> <section bold>@await Component.InvokeAsync("Tags", new { count = 15 })</section>
@{ @{
RenderTemplate( RenderTemplate(
"Tag Cloud from Template: ", "Tag Cloud from Template: ",