From 399e516065368888b3340a84a4f496e7bab9b7ad Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 15 Dec 2015 14:24:19 -0800 Subject: [PATCH] Modify IViewComponentHelper to remove method selection ambiguity. Fixes #612 --- .../Components/FeaturedMoviesComponent.cs | 13 - .../Components/MoviesComponent.cs | 36 +++ .../Views/Movies/Index.cshtml | 2 +- .../Components/FeaturedMovies/Default.cshtml | 2 +- .../IViewComponentHelper.cs | 33 +- ...ultViewComponentInvokerLoggerExtensions.cs | 36 ++- .../ViewComponentResultLoggerExtensions.cs | 26 +- .../Microsoft.AspNet.Mvc.ViewFeatures.xproj | 1 + .../Properties/Resources.Designer.cs | 64 ++-- .../ViewComponentHelperExtensions.cs | 48 ++- .../Resources.resx | 68 ++--- .../ViewComponentResult.cs | 43 +-- .../DefaultViewComponentDescriptorProvider.cs | 70 ++++- .../DefaultViewComponentHelper.cs | 210 ++++--------- .../DefaultViewComponentInvoker.cs | 128 +++----- .../IViewComponentDescriptorProvider.cs | 2 +- .../ViewComponents/IViewComponentInvoker.cs | 11 +- .../ViewComponents/IViewComponentSelector.cs | 6 +- .../ViewComponents/ViewComponentContext.cs | 20 +- .../ViewComponents/ViewComponentDescriptor.cs | 18 +- .../ViewComponentMethodSelector.cs | 98 ------ .../ViewComponentResultTest.cs | 74 +++-- .../ContentViewComponentResultTest.cs | 5 +- .../DefaultViewComponentActivatorTests.cs | 6 - ...aultViewComponentDescriptorProviderTest.cs | 151 +++++++++- .../DefaultViewComponentSelectorTest.cs | 101 ++++--- .../HtmlContentViewComponentResultTest.cs | 7 +- .../JsonViewComponentResultTest.cs | 3 +- .../ViewComponentMethodSelectorTest.cs | 282 ------------------ .../ViewViewComponentResultTest.cs | 27 +- .../Views/Home/JsonTextInView.cshtml | 2 +- .../RequestScopedService/ViewComponent.cshtml | 2 +- .../Views/InheritingInherits/Index.cshtml | 2 +- .../ViewEngine/ViewWithRelativePath.cshtml | 2 +- .../Views/ViewEngine/ViewWithTitle.cshtml | 2 +- .../Views/ViewWithPaths/Index.cshtml | 2 +- .../TagCloudViewComponentTagHelper.cs | 3 +- .../TagHelpersWebSite/Views/Home/Index.cshtml | 2 +- 38 files changed, 712 insertions(+), 896 deletions(-) create mode 100644 samples/TagHelperSample.Web/Components/MoviesComponent.cs delete mode 100644 src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewComponentMethodSelector.cs delete mode 100644 test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentMethodSelectorTest.cs diff --git a/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs b/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs index b038feb711..def141a4a5 100644 --- a/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs +++ b/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs @@ -38,18 +38,5 @@ namespace TagHelperSample.Web.Components return View(movies); } - - public IViewComponentResult Invoke(string movieName) - { - string quote; - if (!_cache.TryGetValue(movieName, out quote)) - { - IChangeToken expirationToken; - quote = _moviesService.GetCriticsQuote(out expirationToken); - _cache.Set(movieName, quote, new MemoryCacheEntryOptions().AddExpirationToken(expirationToken)); - } - - return Content(quote); - } } } \ No newline at end of file diff --git a/samples/TagHelperSample.Web/Components/MoviesComponent.cs b/samples/TagHelperSample.Web/Components/MoviesComponent.cs new file mode 100644 index 0000000000..064707d3e9 --- /dev/null +++ b/samples/TagHelperSample.Web/Components/MoviesComponent.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/samples/TagHelperSample.Web/Views/Movies/Index.cshtml b/samples/TagHelperSample.Web/Views/Movies/Index.cshtml index 5eda5ea485..80fe8db349 100644 --- a/samples/TagHelperSample.Web/Views/Movies/Index.cshtml +++ b/samples/TagHelperSample.Web/Views/Movies/Index.cshtml @@ -35,7 +35,7 @@
diff --git a/samples/TagHelperSample.Web/Views/Shared/Components/FeaturedMovies/Default.cshtml b/samples/TagHelperSample.Web/Views/Shared/Components/FeaturedMovies/Default.cshtml index 8348bffa01..1444339630 100644 --- a/samples/TagHelperSample.Web/Views/Shared/Components/FeaturedMovies/Default.cshtml +++ b/samples/TagHelperSample.Web/Views/Shared/Components/FeaturedMovies/Default.cshtml @@ -10,7 +10,7 @@ Critics say: - @Component.Invoke("FeaturedMovies", movie.Name) + @await Component.InvokeAsync("Movies", new { movieName = movie.Name }) } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/IViewComponentHelper.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/IViewComponentHelper.cs index 4d1af76dd9..6a0bc31010 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/IViewComponentHelper.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/IViewComponentHelper.cs @@ -7,22 +7,27 @@ using Microsoft.AspNet.Html; namespace Microsoft.AspNet.Mvc { + /// + /// Supports the rendering of view components in a view. + /// public interface IViewComponentHelper { - IHtmlContent Invoke(string name, params object[] args); + /// + /// Invokes a view component with the specified . + /// + /// The name of the view component. + /// Arguments to be passed to the invoked view component method. + /// A that on completion returns the rendered . + /// + Task InvokeAsync(string name, object arguments); - IHtmlContent Invoke(Type componentType, params object[] args); - - void RenderInvoke(string name, params object[] args); - - void RenderInvoke(Type componentType, params object[] args); - - Task InvokeAsync(string name, params object[] args); - - Task InvokeAsync(Type componentType, params object[] args); - - Task RenderInvokeAsync(string name, params object[] args); - - Task RenderInvokeAsync(Type componentType, params object[] args); + /// + /// Invokes a view component of type . + /// + /// The view component . + /// Arguments to be passed to the invoked view component method. + /// A that on completion returns the rendered . + /// + Task InvokeAsync(Type componentType, object arguments); } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Logging/DefaultViewComponentInvokerLoggerExtensions.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Logging/DefaultViewComponentInvokerLoggerExtensions.cs index 9941109716..e2280c19db 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Logging/DefaultViewComponentInvokerLoggerExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Logging/DefaultViewComponentInvokerLoggerExtensions.cs @@ -10,15 +10,21 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Logging { public static class DefaultViewComponentInvokerLoggerExtensions { - private static readonly Action _viewComponentExecuting; + private static readonly string[] EmptyArguments = +#if NET451 + new string[0]; +#else + Array.Empty(); +#endif + private static readonly Action _viewComponentExecuting; private static readonly Action _viewComponentExecuted; static DefaultViewComponentInvokerLoggerExtensions() { - _viewComponentExecuting = LoggerMessage.Define( + _viewComponentExecuting = LoggerMessage.Define( LogLevel.Debug, 1, - "Executing view component {ViewComponentName}"); + "Executing view component {ViewComponentName} with arguments ({Arguments})."); _viewComponentExecuted = LoggerMessage.Define( LogLevel.Debug, @@ -32,9 +38,29 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Logging return logger.BeginScopeImpl(new ViewComponentLogScope(context.ViewComponentDescriptor)); } - public static void ViewComponentExecuting(this ILogger logger, ViewComponentContext context) + public static void ViewComponentExecuting( + this ILogger logger, + ViewComponentContext context, + object[] arguments) { - _viewComponentExecuting(logger, context.ViewComponentDescriptor.DisplayName, null); + var formattedArguments = GetFormattedArguments(arguments); + _viewComponentExecuting(logger, context.ViewComponentDescriptor.DisplayName, formattedArguments, null); + } + + private static string[] GetFormattedArguments(object[] arguments) + { + if (arguments == null || arguments.Length == 0) + { + return EmptyArguments; + } + + var formattedArguments = new string[arguments.Length]; + for (var i = 0; i < formattedArguments.Length; i++) + { + formattedArguments[i] = Convert.ToString(arguments[i]); + } + + return formattedArguments; } public static void ViewComponentExecuted( diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Logging/ViewComponentResultLoggerExtensions.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Logging/ViewComponentResultLoggerExtensions.cs index 2ed984060c..52a829a154 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Logging/ViewComponentResultLoggerExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Logging/ViewComponentResultLoggerExtensions.cs @@ -8,41 +8,29 @@ namespace Microsoft.AspNet.Mvc.Logging { public static class ViewComponentResultLoggerExtensions { - private static readonly Action _viewComponentResultExecuting; + private static readonly Action _viewComponentResultExecuting; static ViewComponentResultLoggerExtensions() { - _viewComponentResultExecuting = LoggerMessage.Define( + _viewComponentResultExecuting = LoggerMessage.Define( LogLevel.Information, 1, - "Executing ViewComponentResult, running {ViewComponentName} with arguments ({Arguments})."); + "Executing ViewComponentResult, running {ViewComponentName}."); } - public static void ViewComponentResultExecuting(this ILogger logger, string viewComponentName, object[] arguments) + public static void ViewComponentResultExecuting(this ILogger logger, string viewComponentName) { if (logger.IsEnabled(LogLevel.Information)) { - var formattedArguments = new string[arguments.Length]; - for (var i = 0; i < arguments.Length; i++) - { - formattedArguments[i] = Convert.ToString(arguments[i]); - } - - _viewComponentResultExecuting(logger, viewComponentName, formattedArguments, null); + _viewComponentResultExecuting(logger, viewComponentName, null); } } - public static void ViewComponentResultExecuting(this ILogger logger, Type viewComponentType, object[] arguments) + public static void ViewComponentResultExecuting(this ILogger logger, Type viewComponentType) { if (logger.IsEnabled(LogLevel.Information)) { - var formattedArguments = new string[arguments.Length]; - for (var i = 0; i < arguments.Length; i++) - { - formattedArguments[i] = Convert.ToString(arguments[i]); - } - - _viewComponentResultExecuting(logger, viewComponentType.Name, formattedArguments, null); + _viewComponentResultExecuting(logger, viewComponentType.Name, null); } } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Microsoft.AspNet.Mvc.ViewFeatures.xproj b/src/Microsoft.AspNet.Mvc.ViewFeatures/Microsoft.AspNet.Mvc.ViewFeatures.xproj index 4651550218..5bcc602f16 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Microsoft.AspNet.Mvc.ViewFeatures.xproj +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Microsoft.AspNet.Mvc.ViewFeatures.xproj @@ -3,6 +3,7 @@ 14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + .resx diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs index 93e8b9ac97..ffd083d356 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures } /// - /// The async view component method '{0}' should be declared to return Task<T>. + /// Method '{0}' of view component '{1}' should be declared to return {2}<T>. /// internal static string ViewComponent_AsyncMethod_ShouldReturnTask { @@ -35,11 +35,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures } /// - /// The async view component method '{0}' should be declared to return Task<T>. + /// Method '{0}' of view component '{1}' should be declared to return {2}<T>. /// - 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); } /// @@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures } /// - /// 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. /// internal static string ViewComponent_SyncMethod_ShouldReturnValue { @@ -67,11 +67,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures } /// - /// 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. /// - 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); } /// @@ -107,7 +107,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures } /// - /// Could not find an '{0}' method matching the parameters. + /// Could not find an '{0}' or '{1}' method for the view component '{2}'. /// internal static string ViewComponent_CannotFindMethod { @@ -115,27 +115,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures } /// - /// Could not find an '{0}' method matching the parameters. + /// Could not find an '{0}' or '{1}' method for the view component '{2}'. /// - 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); - } - - /// - /// Could not find an '{0}' or '{1}' method matching the parameters. - /// - internal static string ViewComponent_CannotFindMethod_WithFallback - { - get { return GetString("ViewComponent_CannotFindMethod_WithFallback"); } - } - - /// - /// Could not find an '{0}' or '{1}' method matching the parameters. - /// - internal static string FormatViewComponent_CannotFindMethod_WithFallback(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod_WithFallback"), p0, p1); + return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod"), p0, p1, p2); } /// @@ -859,7 +843,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures } /// - /// The view component method '{0}' cannot return a {1}. + /// Method '{0}' of view component '{1}' cannot return a {2}. /// internal static string ViewComponent_SyncMethod_CannotReturnTask { @@ -867,11 +851,27 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures } /// - /// The view component method '{0}' cannot return a {1}. + /// Method '{0}' of view component '{1}' cannot return a {2}. /// - 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); + } + + /// + /// View component '{0}' must have exactly one public method named '{1}' or '{2}'. + /// + internal static string ViewComponent_AmbiguousMethods + { + get { return GetString("ViewComponent_AmbiguousMethods"); } + } + + /// + /// View component '{0}' must have exactly one public method named '{1}' or '{2}'. + /// + 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) diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/ViewComponentHelperExtensions.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/ViewComponentHelperExtensions.cs index 59d73fd24a..f5513cae9e 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/ViewComponentHelperExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/ViewComponentHelperExtensions.cs @@ -7,48 +7,76 @@ using Microsoft.AspNet.Html; namespace Microsoft.AspNet.Mvc.Rendering { + /// + /// Extension methods for . + /// public static class ViewComponentHelperExtensions { - public static IHtmlContent Invoke(this IViewComponentHelper helper, params object[] args) + /// + /// Invokes a view component with the specified . + /// + /// The name of the view component. + /// A that on completion returns the rendered . + /// + public static Task InvokeAsync(this IViewComponentHelper helper, string name) { if (helper == null) { throw new ArgumentNullException(nameof(helper)); } - return helper.Invoke(typeof(TComponent), args); + return helper.InvokeAsync(name, arguments: null); } - public static void RenderInvoke(this IViewComponentHelper helper, params object[] args) + /// + /// Invokes a view component of type . + /// + /// The view component . + /// A that on completion returns the rendered . + /// + public static Task InvokeAsync(this IViewComponentHelper helper, Type componentType) { if (helper == null) { throw new ArgumentNullException(nameof(helper)); } - helper.RenderInvoke(typeof(TComponent), args); + return helper.InvokeAsync(componentType, arguments: null); } - public static Task InvokeAsync( - this IViewComponentHelper helper, - params object[] args) + /// + /// Invokes a view component of type . + /// + /// The . + /// Arguments to be passed to the invoked view component method. + /// The of the view component. + /// A that on completion returns the rendered . + /// + public static Task InvokeAsync(this IViewComponentHelper helper, object arguments) { if (helper == null) { throw new ArgumentNullException(nameof(helper)); } - return helper.InvokeAsync(typeof(TComponent), args); + return helper.InvokeAsync(typeof(TComponent), arguments); } - public static Task RenderInvokeAsync(this IViewComponentHelper helper, params object[] args) + /// + /// Invokes a view component of type . + /// + /// The . + /// The of the view component. + /// A that on completion returns the rendered . + /// + public static Task InvokeAsync(this IViewComponentHelper helper) { if (helper == null) { throw new ArgumentNullException(nameof(helper)); } - return helper.RenderInvokeAsync(typeof(TComponent), args); + return helper.InvokeAsync(typeof(TComponent), arguments: null); } } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx b/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx index 81c987746f..e389b62f3a 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx @@ -1,17 +1,17 @@  - @@ -122,13 +122,13 @@ {1} is the newline character - The async view component method '{0}' should be declared to return Task<T>. + Method '{0}' of view component '{1}' should be declared to return {2}<T>. A view component must return a non-null value. - 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. A view component named '{0}' could not be found. @@ -137,10 +137,7 @@ An invoker could not be created for the view component '{0}'. - Could not find an '{0}' method matching the parameters. - - - Could not find an '{0}' or '{1}' method matching the parameters. + Could not find an '{0}' or '{1}' method for the view component '{2}'. View components only support returning {0}, {1} or {2}. @@ -278,6 +275,9 @@ The collection already contains an entry with key '{0}'. - The view component method '{0}' cannot return a {1}. + Method '{0}' of view component '{1}' cannot return a {2}. + + + View component '{0}' must have exactly one public method named '{1}' or '{2}'. \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponentResult.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponentResult.cs index 0a78f946b7..de9cba9191 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponentResult.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponentResult.cs @@ -3,7 +3,9 @@ using System; using System.Text; +using System.Text.Encodings.Web; using System.Threading.Tasks; +using Microsoft.AspNet.Html; using Microsoft.AspNet.Mvc.Internal; using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Mvc.ModelBinding; @@ -26,7 +28,7 @@ namespace Microsoft.AspNet.Mvc /// /// Gets or sets the arguments provided to the view component. /// - public object[] Arguments { get; set; } + public object Arguments { get; set; } /// /// Gets or sets the representing the Content-Type header of the response. @@ -77,6 +79,7 @@ namespace Microsoft.AspNet.Mvc var loggerFactory = services.GetRequiredService(); var logger = loggerFactory.CreateLogger(); + var htmlEncoder = services.GetRequiredService(); var viewData = ViewData; if (viewData == null) @@ -119,23 +122,29 @@ namespace Microsoft.AspNet.Mvc htmlHelperOptions); (viewComponentHelper as ICanHasViewContext)?.Contextualize(viewContext); + var result = await GetViewComponentResult(viewComponentHelper, logger); - if (ViewComponentType == null && ViewComponentName == null) - { - throw new InvalidOperationException(Resources.FormatViewComponentResult_NameOrTypeMustBeSet( - nameof(ViewComponentName), - nameof(ViewComponentType))); - } - else if (ViewComponentType == null) - { - logger.ViewComponentResultExecuting(ViewComponentName, Arguments); - await viewComponentHelper.RenderInvokeAsync(ViewComponentName, Arguments); - } - else - { - logger.ViewComponentResultExecuting(ViewComponentType, Arguments); - await viewComponentHelper.RenderInvokeAsync(ViewComponentType, Arguments); - } + result.WriteTo(writer, htmlEncoder); + } + } + + private Task GetViewComponentResult(IViewComponentHelper viewComponentHelper, ILogger logger) + { + if (ViewComponentType == null && ViewComponentName == null) + { + throw new InvalidOperationException(Resources.FormatViewComponentResult_NameOrTypeMustBeSet( + nameof(ViewComponentName), + nameof(ViewComponentType))); + } + else if (ViewComponentType == null) + { + logger.ViewComponentResultExecuting(ViewComponentName); + return viewComponentHelper.InvokeAsync(ViewComponentName, Arguments); + } + else + { + logger.ViewComponentResultExecuting(ViewComponentType); + return viewComponentHelper.InvokeAsync(ViewComponentType, Arguments); } } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorProvider.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorProvider.cs index 0cd2f12fc5..739d43f01b 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorProvider.cs @@ -5,7 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Infrastructure; +using Microsoft.AspNet.Mvc.ViewFeatures; namespace Microsoft.AspNet.Mvc.ViewComponents { @@ -14,6 +16,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents /// public class DefaultViewComponentDescriptorProvider : IViewComponentDescriptorProvider { + private const string AsyncMethodName = "InvokeAsync"; + private const string SyncMethodName = "Invoke"; private readonly IAssemblyProvider _assemblyProvider; /// @@ -32,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents return types .Where(IsViewComponentType) - .Select(CreateCandidate); + .Select(CreateDescriptor); } /// @@ -47,11 +51,11 @@ namespace Microsoft.AspNet.Mvc.ViewComponents } /// - /// Determines whether or not the given is a View Component class. + /// Determines whether or not the given is a view component class. /// /// The . /// - /// true if represents a View Component class, otherwise false. + /// true if represents a view component class, otherwise false. /// protected virtual bool IsViewComponentType(TypeInfo typeInfo) { @@ -63,16 +67,70 @@ namespace Microsoft.AspNet.Mvc.ViewComponents return ViewComponentConventions.IsComponent(typeInfo); } - private static ViewComponentDescriptor CreateCandidate(TypeInfo typeInfo) + private static ViewComponentDescriptor CreateDescriptor(TypeInfo typeInfo) { - var candidate = new ViewComponentDescriptor() + var type = typeInfo.AsType(); + var candidate = new ViewComponentDescriptor { FullName = ViewComponentConventions.GetComponentFullName(typeInfo), ShortName = ViewComponentConventions.GetComponentName(typeInfo), - Type = typeInfo.AsType(), + Type = type, + MethodInfo = FindMethod(type) }; return candidate; } + + private static MethodInfo FindMethod(Type componentType) + { + var componentName = componentType.FullName; + var methods = componentType.GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Where(method => + string.Equals(method.Name, AsyncMethodName, StringComparison.Ordinal) || + string.Equals(method.Name, SyncMethodName, StringComparison.Ordinal)) + .ToArray(); + + if (methods.Length == 0) + { + throw new InvalidOperationException( + Resources.FormatViewComponent_CannotFindMethod(SyncMethodName, AsyncMethodName, componentName)); + } + else if (methods.Length > 1) + { + throw new InvalidOperationException( + Resources.FormatViewComponent_AmbiguousMethods(componentName, AsyncMethodName, SyncMethodName)); + } + + var selectedMethod = methods[0]; + if (string.Equals(selectedMethod.Name, AsyncMethodName, StringComparison.Ordinal)) + { + if (!selectedMethod.ReturnType.GetTypeInfo().IsGenericType || + selectedMethod.ReturnType.GetGenericTypeDefinition() != typeof(Task<>)) + { + throw new InvalidOperationException(Resources.FormatViewComponent_AsyncMethod_ShouldReturnTask( + AsyncMethodName, + componentName, + nameof(Task))); + } + } + else + { + if (selectedMethod.ReturnType == typeof(void)) + { + throw new InvalidOperationException(Resources.FormatViewComponent_SyncMethod_ShouldReturnValue( + SyncMethodName, + componentName)); + } + else if (selectedMethod.ReturnType.IsAssignableFrom(typeof(Task))) + { + throw new InvalidOperationException(Resources.FormatViewComponent_SyncMethod_CannotReturnTask( + SyncMethodName, + componentName, + nameof(Task))); + } + } + + return selectedMethod; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs index 4de8c683c1..9a1e1efcef 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNet.Html; @@ -10,9 +9,13 @@ using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewFeatures; using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Mvc.ViewFeatures.Internal; +using Microsoft.Extensions.Internal; namespace Microsoft.AspNet.Mvc.ViewComponents { + /// + /// Default implementation for . + /// public class DefaultViewComponentHelper : IViewComponentHelper, ICanHasViewContext { private readonly IViewComponentDescriptorCollectionProvider _descriptorProvider; @@ -22,6 +25,16 @@ namespace Microsoft.AspNet.Mvc.ViewComponents private readonly IViewBufferScope _viewBufferScope; private ViewContext _viewContext; + /// + /// Initializes a new instance of . + /// + /// The + /// used to locate view components. + /// The . + /// The . + /// The . + /// The that manages the lifetime of + /// instances. public DefaultViewComponentHelper( IViewComponentDescriptorCollectionProvider descriptorProvider, HtmlEncoder htmlEncoder, @@ -61,6 +74,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents _viewBufferScope = viewBufferScope; } + /// public void Contextualize(ViewContext viewContext) { if (viewContext == null) @@ -71,133 +85,41 @@ namespace Microsoft.AspNet.Mvc.ViewComponents _viewContext = viewContext; } - public IHtmlContent Invoke(string name, params object[] arguments) + /// + public Task InvokeAsync(string name, object arguments) { if (name == null) { throw new ArgumentNullException(nameof(name)); } - var descriptor = SelectComponent(name); - - var viewBuffer = new ViewBuffer(_viewBufferScope, name); - using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding)) - { - InvokeCore(writer, descriptor, arguments); - return writer.ContentBuilder; - } - } - - public IHtmlContent Invoke(Type componentType, params object[] arguments) - { - if (componentType == null) - { - throw new ArgumentNullException(nameof(componentType)); - } - - var descriptor = SelectComponent(componentType); - var viewBuffer = new ViewBuffer(_viewBufferScope, componentType.Name); - using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding)) - { - InvokeCore(writer, descriptor, arguments); - return writer.ContentBuilder; - } - } - - public void RenderInvoke(string name, params object[] arguments) - { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - var descriptor = SelectComponent(name); - InvokeCore(_viewContext.Writer, descriptor, arguments); - } - - public void RenderInvoke(Type componentType, params object[] arguments) - { - if (componentType == null) - { - throw new ArgumentNullException(nameof(componentType)); - } - - var descriptor = SelectComponent(componentType); - InvokeCore(_viewContext.Writer, descriptor, arguments); - } - - public async Task 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 InvokeAsync(Type componentType, params object[] arguments) - { - if (componentType == null) - { - throw new ArgumentNullException(nameof(componentType)); - } - - var descriptor = SelectComponent(componentType); - - var viewBuffer = new ViewBuffer(_viewBufferScope, componentType.Name); - using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding)) - { - await InvokeCoreAsync(writer, descriptor, arguments); - return writer.ContentBuilder; - } - } - - public Task RenderInvokeAsync(string name, params object[] arguments) - { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - var descriptor = SelectComponent(name); - return InvokeCoreAsync(_viewContext.Writer, descriptor, arguments); - } - - public Task RenderInvokeAsync(Type componentType, params object[] arguments) - { - if (componentType == null) - { - throw new ArgumentNullException(nameof(componentType)); - } - - var descriptor = SelectComponent(componentType); - return InvokeCoreAsync(_viewContext.Writer, descriptor, arguments); - } - - private ViewComponentDescriptor SelectComponent(string name) - { var descriptor = _selector.SelectComponent(name); if (descriptor == null) { throw new InvalidOperationException(Resources.FormatViewComponent_CannotFindComponent(name)); } - return descriptor; + return InvokeCoreAsync(descriptor, arguments); + } + + /// + public Task InvokeAsync(Type componentType, object arguments) + { + if (componentType == null) + { + throw new ArgumentNullException(nameof(componentType)); + } + + var descriptor = SelectComponent(componentType); + return InvokeCoreAsync(descriptor, arguments); } private ViewComponentDescriptor SelectComponent(Type componentType) { var descriptors = _descriptorProvider.ViewComponents; - foreach (var descriptor in descriptors.Items) + for (var i = 0; i < descriptors.Items.Count; i++) { + var descriptor = descriptors.Items[i]; if (descriptor.Type == componentType) { return descriptor; @@ -208,58 +130,30 @@ namespace Microsoft.AspNet.Mvc.ViewComponents componentType.FullName)); } - private Task InvokeCoreAsync( - TextWriter writer, + private async Task InvokeCoreAsync( ViewComponentDescriptor descriptor, - object[] arguments) + object arguments) { - if (writer == null) + var viewBuffer = new ViewBuffer(_viewBufferScope, descriptor.FullName); + using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding)) { - throw new ArgumentNullException(nameof(writer)); + var context = new ViewComponentContext( + descriptor, + PropertyHelper.ObjectToDictionary(arguments), + _htmlEncoder, + _viewContext, + writer); + + var invoker = _invokerFactory.CreateInstance(context); + if (invoker == null) + { + throw new InvalidOperationException( + Resources.FormatViewComponent_IViewComponentFactory_ReturnedNull(descriptor.FullName)); + } + + await invoker.InvokeAsync(context); + return writer.ContentBuilder; } - - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - var context = new ViewComponentContext(descriptor, arguments, _htmlEncoder, _viewContext, writer); - - var invoker = _invokerFactory.CreateInstance(context); - if (invoker == null) - { - throw new InvalidOperationException( - Resources.FormatViewComponent_IViewComponentFactory_ReturnedNull(descriptor.Type.FullName)); - } - - return invoker.InvokeAsync(context); - } - - private void InvokeCore( - TextWriter writer, - ViewComponentDescriptor descriptor, - object[] arguments) - { - if (writer == null) - { - throw new ArgumentNullException(nameof(writer)); - } - - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - var context = new ViewComponentContext(descriptor, arguments, _htmlEncoder, _viewContext, writer); - - var invoker = _invokerFactory.CreateInstance(context); - if (invoker == null) - { - throw new InvalidOperationException( - Resources.FormatViewComponent_IViewComponentFactory_ReturnedNull(descriptor.Type.FullName)); - } - - invoker.Invoke(context); } } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs index 21750a1989..f0aad1a660 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs @@ -16,6 +16,9 @@ using Microsoft.Extensions.Logging; namespace Microsoft.AspNet.Mvc.ViewComponents { + /// + /// Default implementation for . + /// public class DefaultViewComponentInvoker : IViewComponentInvoker { private readonly ITypeActivatorCache _typeActivatorCache; @@ -23,6 +26,13 @@ namespace Microsoft.AspNet.Mvc.ViewComponents private readonly DiagnosticSource _diagnosticSource; private readonly ILogger _logger; + /// + /// Initializes a new instance of . + /// + /// Caches factories for instantiating view component instances. + /// The . + /// The . + /// The . public DefaultViewComponentInvoker( ITypeActivatorCache typeActivatorCache, IViewComponentActivator viewComponentActivator, @@ -55,27 +65,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents _logger = logger; } - public void Invoke(ViewComponentContext context) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - var method = ViewComponentMethodSelector.FindSyncMethod( - context.ViewComponentDescriptor.Type.GetTypeInfo(), - context.Arguments); - if (method == null) - { - throw new InvalidOperationException( - Resources.FormatViewComponent_CannotFindMethod(ViewComponentMethodSelector.SyncMethodName)); - } - - var result = InvokeSyncCore(method, context); - - result.Execute(context); - } - + /// public async Task InvokeAsync(ViewComponentContext context) { if (context == null) @@ -83,32 +73,25 @@ namespace Microsoft.AspNet.Mvc.ViewComponents throw new ArgumentNullException(nameof(context)); } - IViewComponentResult result; - - var asyncMethod = ViewComponentMethodSelector.FindAsyncMethod( - context.ViewComponentDescriptor.Type.GetTypeInfo(), - context.Arguments); - if (asyncMethod == null) + var methodInfo = context.ViewComponentDescriptor?.MethodInfo; + if (methodInfo == null) { - // We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still - // execute the IViewResult asynchronously. - var syncMethod = ViewComponentMethodSelector.FindSyncMethod( - context.ViewComponentDescriptor.Type.GetTypeInfo(), - context.Arguments); - if (syncMethod == null) - { - throw new InvalidOperationException( - Resources.FormatViewComponent_CannotFindMethod_WithFallback( - ViewComponentMethodSelector.SyncMethodName, ViewComponentMethodSelector.AsyncMethodName)); - } - else - { - result = InvokeSyncCore(syncMethod, context); - } + throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(ViewComponentDescriptor.MethodInfo), + nameof(ViewComponentDescriptor))); + } + + var isAsync = typeof(Task).GetTypeInfo().IsAssignableFrom(methodInfo.ReturnType.GetTypeInfo()); + IViewComponentResult result; + if (isAsync) + { + result = await InvokeAsyncCore(context); } else { - result = await InvokeAsyncCore(asyncMethod, context); + // We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still + // execute the IViewResult asynchronously. + result = InvokeSyncCore(context); } await result.ExecuteAsync(context); @@ -129,29 +112,20 @@ namespace Microsoft.AspNet.Mvc.ViewComponents return component; } - private async Task InvokeAsyncCore( - MethodInfo method, - ViewComponentContext context) + private async Task InvokeAsyncCore(ViewComponentContext context) { - if (method == null) - { - throw new ArgumentNullException(nameof(method)); - } - - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - var component = CreateComponent(context); using (_logger.ViewComponentScope(context)) { + var method = context.ViewComponentDescriptor.MethodInfo; + var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, method.GetParameters()); + _diagnosticSource.BeforeViewComponent(context, component); - _logger.ViewComponentExecuting(context); + _logger.ViewComponentExecuting(context, arguments); var startTime = Environment.TickCount; - var result = await ControllerActionExecutor.ExecuteAsync(method, component, context.Arguments); + var result = await ControllerActionExecutor.ExecuteAsync(method, component, arguments); var viewComponentResult = CoerceToViewComponentResult(result); _logger.ViewComponentExecuted(context, startTime, viewComponentResult); @@ -161,35 +135,25 @@ namespace Microsoft.AspNet.Mvc.ViewComponents } } - public IViewComponentResult InvokeSyncCore(MethodInfo method, ViewComponentContext context) + private IViewComponentResult InvokeSyncCore(ViewComponentContext context) { - if (method == null) - { - throw new ArgumentNullException(nameof(method)); - } - - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - var component = CreateComponent(context); using (_logger.ViewComponentScope(context)) { - _diagnosticSource.BeforeViewComponent(context, component); - _logger.ViewComponentExecuting(context); + var method = context.ViewComponentDescriptor.MethodInfo; + var arguments = ControllerActionExecutor.PrepareArguments( + context.Arguments, + method.GetParameters()); + _diagnosticSource.BeforeViewComponent(context, component); + _logger.ViewComponentExecuting(context, arguments); + + var startTime = Environment.TickCount; + object result; try { - var startTime = Environment.TickCount; - var result = method.Invoke(component, context.Arguments); - - var viewComponentResult = CoerceToViewComponentResult(result); - _logger.ViewComponentExecuted(context, startTime, viewComponentResult); - _diagnosticSource.AfterViewComponent(context, viewComponentResult, component); - - return viewComponentResult; + result = method.Invoke(component, arguments); } catch (TargetInvocationException ex) { @@ -198,6 +162,12 @@ namespace Microsoft.AspNet.Mvc.ViewComponents exceptionInfo.Throw(); return null; // Unreachable } + + var viewComponentResult = CoerceToViewComponentResult(result); + _logger.ViewComponentExecuted(context, startTime, viewComponentResult); + _diagnosticSource.AfterViewComponent(context, viewComponentResult, component); + + return viewComponentResult; } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/IViewComponentDescriptorProvider.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/IViewComponentDescriptorProvider.cs index e47e5b0a51..e8d41025d4 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/IViewComponentDescriptorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/IViewComponentDescriptorProvider.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; namespace Microsoft.AspNet.Mvc.ViewComponents { /// - /// Discovers the View Components in the application. + /// Discovers the view components in the application. /// public interface IViewComponentDescriptorProvider { diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/IViewComponentInvoker.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/IViewComponentInvoker.cs index 7e9e5d7f8f..02c0e9a4bb 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/IViewComponentInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/IViewComponentInvoker.cs @@ -5,10 +5,17 @@ using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc.ViewComponents { + /// + /// Specifies the contract for execution of a view component. + /// public interface IViewComponentInvoker { - void Invoke(ViewComponentContext context); - + /// + /// Executes the view component specified by + /// of and writes the result to . + /// + /// The . + /// A that represents the asynchronous operation of execution. Task InvokeAsync(ViewComponentContext context); } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/IViewComponentSelector.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/IViewComponentSelector.cs index d4813b4d57..6ff2ba0de2 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/IViewComponentSelector.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/IViewComponentSelector.cs @@ -4,14 +4,14 @@ namespace Microsoft.AspNet.Mvc.ViewComponents { /// - /// Selects a View Component based on a View Component name. + /// Selects a view component based on a view component name. /// public interface IViewComponentSelector { /// - /// Selects a View Component based on . + /// Selects a view component based on . /// - /// The View Component name. + /// The view component name. /// A , or null if no match is found. ViewComponentDescriptor SelectComponent(string componentName); } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewComponentContext.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewComponentContext.cs index 699fecaf89..edb9eab8cd 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewComponentContext.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewComponentContext.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IO; using System.Text.Encodings.Web; using Microsoft.AspNet.Mvc.Rendering; @@ -10,7 +11,7 @@ using Microsoft.AspNet.Mvc.ViewFeatures; namespace Microsoft.AspNet.Mvc.ViewComponents { /// - /// A context for View Components. + /// A context for view components. /// public class ViewComponentContext { @@ -23,7 +24,6 @@ namespace Microsoft.AspNet.Mvc.ViewComponents public ViewComponentContext() { ViewComponentDescriptor = new ViewComponentDescriptor(); - Arguments = new object[0]; ViewContext = new ViewContext(); } @@ -31,14 +31,14 @@ namespace Microsoft.AspNet.Mvc.ViewComponents /// Creates a new . /// /// - /// The for the View Component being invoked. + /// The for the view component being invoked. /// - /// The View Component arguments. + /// The view component arguments. /// The . /// The for writing output. public ViewComponentContext( ViewComponentDescriptor viewComponentDescriptor, - object[] arguments, + IDictionary arguments, HtmlEncoder htmlEncoder, ViewContext viewContext, TextWriter writer) @@ -82,15 +82,15 @@ namespace Microsoft.AspNet.Mvc.ViewComponents } /// - /// Gets or sets the View Component arguments. + /// Gets or sets the view component arguments. /// /// /// The property setter is provided for unit test purposes only. /// - public object[] Arguments { get; set; } + public IDictionary Arguments { get; set; } /// - /// Gets or sets the . + /// Gets or sets the . /// /// /// The property setter is provided for unit test purposes only. @@ -98,7 +98,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents public HtmlEncoder HtmlEncoder { get; set; } /// - /// Gets or sets the for the View Component being invoked. + /// Gets or sets the for the view component being invoked. /// /// /// The property setter is provided for unit test purposes only. @@ -106,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents public ViewComponentDescriptor ViewComponentDescriptor { get; set; } /// - /// Gets or sets the . + /// Gets or sets the . /// /// /// The property setter is provided for unit test purposes only. diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewComponentDescriptor.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewComponentDescriptor.cs index 22abe0f75d..c554e0974f 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewComponentDescriptor.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewComponentDescriptor.cs @@ -3,11 +3,12 @@ using System; using System.Diagnostics; +using System.Reflection; namespace Microsoft.AspNet.Mvc.ViewComponents { /// - /// A descriptor for a View Component. + /// A descriptor for a view component. /// [DebuggerDisplay("{DisplayName}")] public class ViewComponentDescriptor @@ -23,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents } /// - /// Gets or sets the display name of the View Component. + /// Gets or sets the display name of the view component. /// public string DisplayName { @@ -53,8 +54,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents /// /// /// - /// The full name is defaulted to the full namespace of the View Component class, prepended to - /// the the class name with a '.' character as the separator. If the View Component class uses + /// The full name is defaulted to the full namespace of the view component class, prepended to + /// the the class name with a '.' character as the separator. If the view component class uses /// ViewComponent as a suffix, the suffix will be omitted from the . /// /// @@ -89,7 +90,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents /// /// /// - /// 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 /// ViewComponent as a suffix, the suffix will be omitted from the . /// /// @@ -115,8 +116,13 @@ namespace Microsoft.AspNet.Mvc.ViewComponents public string ShortName { get; set; } /// - /// Gets or sets the . + /// Gets or sets the . /// public Type Type { get; set; } + + /// + /// Gets or sets the to invoke. + /// + public MethodInfo MethodInfo { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewComponentMethodSelector.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewComponentMethodSelector.cs deleted file mode 100644 index c6ef5c730b..0000000000 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewComponentMethodSelector.cs +++ /dev/null @@ -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 - } - } -} diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs index 8fdc3eff6b..93abe8738f 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Reflection; using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; @@ -42,13 +43,14 @@ namespace Microsoft.AspNet.Mvc FullName = "Full.Name.Text", ShortName = "Text", Type = typeof(TextViewComponent), + MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)), }; var actionContext = CreateActionContext(descriptor); var viewComponentResult = new ViewComponentResult { - Arguments = new object[] { "World!" }, + Arguments = new { name = "World!" }, ViewData = null, TempData = null, ViewComponentName = "Text" @@ -132,13 +134,43 @@ namespace Microsoft.AspNet.Mvc FullName = "Full.Name.Text", ShortName = "Text", Type = typeof(TextViewComponent), + MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)), }; var actionContext = CreateActionContext(descriptor); var viewComponentResult = new ViewComponentResult() { - Arguments = new object[] { "World!" }, + Arguments = new { name = "World!" }, + ViewComponentName = "Text", + TempData = _tempDataDictionary, + }; + + // Act + await viewComponentResult.ExecuteResultAsync(actionContext); + + // Assert + var body = ReadBody(actionContext.HttpContext.Response); + Assert.Equal("Hello, World!", body); + } + + [Fact] + public async Task ExecuteResultAsync_UsesDictionaryArguments() + { + // Arrange + var descriptor = new ViewComponentDescriptor() + { + FullName = "Full.Name.Text", + ShortName = "Text", + Type = typeof(TextViewComponent), + MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)), + }; + + var actionContext = CreateActionContext(descriptor); + + var viewComponentResult = new ViewComponentResult() + { + Arguments = new Dictionary { ["name"] = "World!" }, ViewComponentName = "Text", TempData = _tempDataDictionary, }; @@ -160,13 +192,14 @@ namespace Microsoft.AspNet.Mvc FullName = "Full.Name.AsyncText", ShortName = "AsyncText", Type = typeof(AsyncTextViewComponent), + MethodInfo = typeof(AsyncTextViewComponent).GetMethod(nameof(AsyncTextViewComponent.InvokeAsync)), }; var actionContext = CreateActionContext(descriptor); var viewComponentResult = new ViewComponentResult() { - Arguments = new object[] { "World!" }, + Arguments = new { name = "World!" }, ViewComponentName = "AsyncText", TempData = _tempDataDictionary, }; @@ -188,6 +221,7 @@ namespace Microsoft.AspNet.Mvc FullName = "Full.Name.Text", ShortName = "Text", Type = typeof(TextViewComponent), + MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)), }; var adapter = new TestDiagnosticListener(); @@ -196,7 +230,7 @@ namespace Microsoft.AspNet.Mvc var viewComponentResult = new ViewComponentResult() { - Arguments = new object[] { "World!" }, + Arguments = new { name = "World!" }, ViewComponentName = "Text", TempData = _tempDataDictionary, }; @@ -226,13 +260,14 @@ namespace Microsoft.AspNet.Mvc FullName = "Full.Name.Text", ShortName = "Text", Type = typeof(TextViewComponent), + MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)), }; var actionContext = CreateActionContext(descriptor); var viewComponentResult = new ViewComponentResult() { - Arguments = new object[] { "World!" }, + Arguments = new { name = "World!" }, ViewComponentName = "Text", TempData = _tempDataDictionary, }; @@ -254,13 +289,14 @@ namespace Microsoft.AspNet.Mvc FullName = "Full.Name.Text", ShortName = "Text", Type = typeof(TextViewComponent), + MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)), }; var actionContext = CreateActionContext(descriptor); var viewComponentResult = new ViewComponentResult() { - Arguments = new object[] { "World!" }, + Arguments = new { name = "World!" }, ViewComponentName = "Full.Name.Text", TempData = _tempDataDictionary, }; @@ -282,13 +318,14 @@ namespace Microsoft.AspNet.Mvc FullName = "Full.Name.Text", ShortName = "Text", Type = typeof(TextViewComponent), + MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)), }; var actionContext = CreateActionContext(descriptor); var viewComponentResult = new ViewComponentResult() { - Arguments = new object[] { "World!" }, + Arguments = new { name = "World!" }, ViewComponentType = typeof(TextViewComponent), TempData = _tempDataDictionary, }; @@ -310,13 +347,14 @@ namespace Microsoft.AspNet.Mvc FullName = "Full.Name.Text", ShortName = "Text", Type = typeof(TextViewComponent), + MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)) }; var actionContext = CreateActionContext(descriptor); var viewComponentResult = new ViewComponentResult() { - Arguments = new object[] { "World!" }, + Arguments = new { name = "World!" }, ViewComponentType = typeof(TextViewComponent), StatusCode = 404, TempData = _tempDataDictionary, @@ -367,6 +405,7 @@ namespace Microsoft.AspNet.Mvc FullName = "Full.Name.Text", ShortName = "Text", Type = typeof(TextViewComponent), + MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)), }; var actionContext = CreateActionContext(descriptor); @@ -375,7 +414,7 @@ namespace Microsoft.AspNet.Mvc var viewComponentResult = new ViewComponentResult() { - Arguments = new object[] { "World!" }, + Arguments = new { name = "World!" }, ViewComponentName = "Text", ContentType = contentType, TempData = _tempDataDictionary, @@ -403,6 +442,7 @@ namespace Microsoft.AspNet.Mvc FullName = "Full.Name.Text", ShortName = "Text", Type = typeof(TextViewComponent), + MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)), }; var actionContext = CreateActionContext(descriptor); @@ -412,7 +452,7 @@ namespace Microsoft.AspNet.Mvc var viewComponentResult = new ViewComponentResult() { - Arguments = new object[] { "World!" }, + Arguments = new { name = "World!" }, ViewComponentName = "Text", ContentType = new MediaTypeHeaderValue("text/html") { Encoding = Encoding.UTF8 }, TempData = _tempDataDictionary, @@ -439,6 +479,7 @@ namespace Microsoft.AspNet.Mvc FullName = "Full.Name.Text", ShortName = "Text", Type = typeof(TextViewComponent), + MethodInfo = typeof(TextViewComponent).GetMethod(nameof(TextViewComponent.Invoke)), }; var actionContext = CreateActionContext(descriptor); @@ -447,7 +488,7 @@ namespace Microsoft.AspNet.Mvc var viewComponentResult = new ViewComponentResult() { - Arguments = new object[] { "World!" }, + Arguments = new { name = "World!" }, ViewComponentName = "Text", TempData = _tempDataDictionary, }; @@ -459,7 +500,10 @@ namespace Microsoft.AspNet.Mvc Assert.Equal(expectedContentType, actionContext.HttpContext.Response.ContentType); } - private IServiceCollection CreateServices(object diagnosticListener, HttpContext context, params ViewComponentDescriptor[] descriptors) + private IServiceCollection CreateServices( + object diagnosticListener, + HttpContext context, + params ViewComponentDescriptor[] descriptors) { var httpContext = new DefaultHttpContext(); var diagnosticSource = new DiagnosticListener("Microsoft.AspNet"); @@ -535,12 +579,6 @@ namespace Microsoft.AspNet.Mvc private class AsyncTextViewComponent : ViewComponent { - public HtmlString Invoke() - { - // Should never run. - throw null; - } - public Task InvokeAsync(string name) { return Task.FromResult(new HtmlString("Hello-Async, " + name)); diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ContentViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ContentViewComponentResultTest.cs index bcad673cf9..e950168fbc 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ContentViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ContentViewComponentResultTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.IO; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; @@ -43,7 +44,7 @@ namespace Microsoft.AspNet.Mvc var viewContext = new ViewContext( actionContext, view, - viewData, + viewData, new TempDataDictionary(httpContext, new SessionStateTempDataProvider()), TextWriter.Null, new HtmlHelperOptions()); @@ -57,7 +58,7 @@ namespace Microsoft.AspNet.Mvc var viewComponentContext = new ViewComponentContext( viewComponentDescriptor, - new object[0], + new Dictionary(), new HtmlTestEncoder(), viewContext, writer); diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentActivatorTests.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentActivatorTests.cs index 0762717b61..67957a052a 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentActivatorTests.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentActivatorTests.cs @@ -2,13 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO; using System.Threading.Tasks; -using Microsoft.AspNet.Http.Internal; -using Microsoft.AspNet.Mvc.ModelBinding; -using Microsoft.AspNet.Mvc.Rendering; -using Microsoft.AspNet.Routing; -using Moq; using Xunit; namespace Microsoft.AspNet.Mvc.ViewComponents diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentDescriptorProviderTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentDescriptorProviderTest.cs index 8487a967f1..80d9df3c76 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentDescriptorProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentDescriptorProviderTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Infrastructure; using Xunit; @@ -23,9 +24,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents // Assert var descriptor = Assert.Single(descriptors); - Assert.Equal(typeof(ConventionsViewComponent), descriptor.Type); + Assert.Same(typeof(ConventionsViewComponent), descriptor.Type); Assert.Equal("Microsoft.AspNet.Mvc.ViewComponents.Conventions", descriptor.FullName); Assert.Equal("Conventions", descriptor.ShortName); + Assert.Same(typeof(ConventionsViewComponent).GetMethod("Invoke"), descriptor.MethodInfo); } [Fact] @@ -42,15 +44,156 @@ namespace Microsoft.AspNet.Mvc.ViewComponents Assert.Equal(typeof(AttributeViewComponent), descriptor.Type); Assert.Equal("AttributesAreGreat", descriptor.FullName); Assert.Equal("AttributesAreGreat", descriptor.ShortName); + Assert.Same(typeof(AttributeViewComponent).GetMethod("InvokeAsync"), descriptor.MethodInfo); + } + + [Theory] + [InlineData(typeof(NoMethodsViewComponent))] + [InlineData(typeof(NonPublicInvokeAsyncViewComponent))] + [InlineData(typeof(NonPublicInvokeViewComponent))] + public void GetViewComponents_ThrowsIfTypeHasNoInvocationMethods(Type type) + { + // Arrange + var expected = $"Could not find an 'Invoke' or 'InvokeAsync' method for the view component '{type}'."; + var provider = CreateProvider(type); + + // Act + var ex = Assert.Throws(() => 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(() => 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."; + var provider = CreateProvider(type); + + // Act and Assert + var ex = Assert.Throws(() => 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(() => 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(() => provider.GetViewComponents().ToArray()); + Assert.Equal(expected, ex.Message); } private class ConventionsViewComponent { + public string Invoke() => "Hello world"; } [ViewComponent(Name = "AttributesAreGreat")] private class AttributeViewComponent { + public Task 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 InvokeAsync() => null; + } + + private class MultipleInvokeAsyncViewComponent + { + public Task InvokeAsync(string a) => null; + + public Task InvokeAsync(int a) => null; + + public Task InvokeAsync(int a, int b) => null; + } + + private class InvokeAndInvokeAsyncViewComponent + { + public Task InvokeAsync(string a) => null; + + public string InvokeAsync(int a) => null; + } + + private class NonGenericTaskReturningInvokeAsyncViewComponent + { + public Task InvokeAsync() => Task.FromResult(0); + } + + private class VoidReturningInvokeAsyncViewComponent + { + public void InvokeAsync() + { + } + } + + public class NonTaskReturningInvokeAsyncViewComponent + { + public long InvokeAsync() => 0L; + } + + public class TaskReturningInvokeViewComponent + { + public Task Invoke() => Task.FromResult(0); + } + + public class VoidReturningInvokeViewComponent + { + public void Invoke(int x) + { + } } private DefaultViewComponentDescriptorProvider CreateProvider(Type componentType) @@ -80,11 +223,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents return GetAssemblyProvider() .CandidateAssemblies - .SelectMany(a => a.DefinedTypes) -#if DNX451 - .Select(t => t.GetTypeInfo()) -#endif - ; + .SelectMany(a => a.DefinedTypes); } private static IAssemblyProvider GetAssemblyProvider() diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentSelectorTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentSelectorTest.cs index 4154a4207e..dc51c3338e 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentSelectorTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentSelectorTest.cs @@ -12,6 +12,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents { public class DefaultViewComponentSelectorTest { + private static readonly string Namespace = typeof(DefaultViewComponentSelectorTest).Namespace; + [Fact] public void SelectComponent_ByShortNameWithSuffix() { @@ -22,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var result = selector.SelectComponent("Suffix"); // Assert - Assert.Equal(typeof(SuffixViewComponent), result.Type); + Assert.Same(typeof(ViewComponentContainer.SuffixViewComponent), result.Type); } [Fact] @@ -32,10 +34,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var selector = CreateSelector(); // Act - var result = selector.SelectComponent("Microsoft.AspNet.Mvc.ViewComponents.Suffix"); + var result = selector.SelectComponent($"{Namespace}.Suffix"); // Assert - Assert.Equal(typeof(SuffixViewComponent), result.Type); + Assert.Same(typeof(ViewComponentContainer.SuffixViewComponent), result.Type); } [Fact] @@ -48,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var result = selector.SelectComponent("WithoutSuffix"); // Assert - Assert.Equal(typeof(WithoutSuffix), result.Type); + Assert.Same(typeof(ViewComponentContainer.WithoutSuffix), result.Type); } [Fact] @@ -58,10 +60,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var selector = CreateSelector(); // Act - var result = selector.SelectComponent("Microsoft.AspNet.Mvc.ViewComponents.WithoutSuffix"); + var result = selector.SelectComponent($"{Namespace}.WithoutSuffix"); // Assert - Assert.Equal(typeof(WithoutSuffix), result.Type); + Assert.Same(typeof(ViewComponentContainer.WithoutSuffix), result.Type); } [Fact] @@ -74,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var result = selector.SelectComponent("ByAttribute"); // Assert - Assert.Equal(typeof(ByAttribute), result.Type); + Assert.Same(typeof(ViewComponentContainer.ByAttribute), result.Type); } [Fact] @@ -87,7 +89,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var result = selector.SelectComponent("ByNamingConvention"); // Assert - Assert.Equal(typeof(ByNamingConventionViewComponent), result.Type); + Assert.Same(typeof(ViewComponentContainer.ByNamingConventionViewComponent), result.Type); } [Fact] @@ -98,9 +100,9 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var expected = "The view component name 'Ambiguous' matched multiple types:" + Environment.NewLine + - "Type: 'Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentSelectorTest+Ambiguous1' - " + + $"Type: '{typeof(ViewComponentContainer.Ambiguous1)}' - " + "Name: 'Namespace1.Ambiguous'" + Environment.NewLine + - "Type: 'Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentSelectorTest+Ambiguous2' - " + + $"Type: '{typeof(ViewComponentContainer.Ambiguous2)}' - " + "Name: 'Namespace2.Ambiguous'"; // Act @@ -120,7 +122,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var result = selector.SelectComponent("Namespace1.Ambiguous"); // Assert - Assert.Equal(typeof(Ambiguous1), result.Type); + Assert.Same(typeof(ViewComponentContainer.Ambiguous1), result.Type); } [Theory] @@ -135,44 +137,56 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var result = selector.SelectComponent(name); // Assert - Assert.Equal(typeof(FullNameInAttribute), result.Type); + Assert.Same(typeof(ViewComponentContainer.FullNameInAttribute), result.Type); } private IViewComponentSelector CreateSelector() { - return new FilteredViewComponentSelector(); + var provider = new DefaultViewComponentDescriptorCollectionProvider( + new FilteredViewComponentDescriptorProvider()); + return new DefaultViewComponentSelector(provider); } - private class SuffixViewComponent : ViewComponent + private class ViewComponentContainer { - } + public class SuffixViewComponent : ViewComponent + { + public string Invoke() => "Hello"; + } - private class WithoutSuffix : ViewComponent - { - } + public class WithoutSuffix : ViewComponent + { + public string Invoke() => "Hello"; + } - private class ByNamingConventionViewComponent - { - } + public class ByNamingConventionViewComponent + { + public string Invoke() => "Hello"; + } - [ViewComponent] - private class ByAttribute - { - } + [ViewComponent] + public class ByAttribute + { + public string Invoke() => "Hello"; + } - [ViewComponent(Name = "Namespace1.Ambiguous")] - private class Ambiguous1 - { - } + [ViewComponent(Name = "Namespace1.Ambiguous")] + public class Ambiguous1 + { + public string Invoke() => "Hello"; + } - [ViewComponent(Name = "Namespace2.Ambiguous")] - private class Ambiguous2 - { - } + [ViewComponent(Name = "Namespace2.Ambiguous")] + public class Ambiguous2 + { + public string Invoke() => "Hello"; + } - [ViewComponent(Name = "CoolNameSpace.FullNameInAttribute")] - private class FullNameInAttribute - { + [ViewComponent(Name = "CoolNameSpace.FullNameInAttribute")] + public class FullNameInAttribute + { + public string Invoke() => "Hello"; + } } // This will only consider types nested inside this class as ViewComponent classes @@ -181,10 +195,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents public FilteredViewComponentDescriptorProvider() : base(GetAssemblyProvider()) { - AllowedTypes = typeof(DefaultViewComponentSelectorTest).GetNestedTypes(BindingFlags.NonPublic); + AllowedTypes = typeof(ViewComponentContainer).GetNestedTypes(bindingAttr: BindingFlags.Public); } - public Type[] AllowedTypes { get; private set; } + public Type[] AllowedTypes { get; } protected override bool IsViewComponentType(TypeInfo typeInfo) { @@ -208,19 +222,10 @@ namespace Microsoft.AspNet.Mvc.ViewComponents { var assemblyProvider = new StaticAssemblyProvider(); assemblyProvider.CandidateAssemblies.Add( - typeof(FilteredViewComponentSelector).GetTypeInfo().Assembly); + typeof(ViewComponentContainer).GetTypeInfo().Assembly); return assemblyProvider; } } - - // This will only consider types nested inside this class as ViewComponent classes - private class FilteredViewComponentSelector : DefaultViewComponentSelector - { - public FilteredViewComponentSelector() - : base(new DefaultViewComponentDescriptorCollectionProvider(new FilteredViewComponentDescriptorProvider())) - { - } - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/HtmlContentViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/HtmlContentViewComponentResultTest.cs index 866bf7a075..8772d5993a 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/HtmlContentViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/HtmlContentViewComponentResultTest.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -#if MOCK_SUPPORT +using System.Collections.Generic; using System.IO; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; @@ -57,7 +57,7 @@ namespace Microsoft.AspNet.Mvc var viewComponentContext = new ViewComponentContext( viewComponentDescriptor, - new object[0], + new Dictionary(), new HtmlTestEncoder(), viewContext, writer); @@ -65,5 +65,4 @@ namespace Microsoft.AspNet.Mvc return viewComponentContext; } } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/JsonViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/JsonViewComponentResultTest.cs index 7b8b90f918..6dc39da368 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/JsonViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/JsonViewComponentResultTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IO; using System.Text; using Microsoft.AspNet.Http; @@ -85,7 +86,7 @@ namespace Microsoft.AspNet.Mvc var viewComponentContext = new ViewComponentContext( viewComponentDescriptor, - new object[0], + new Dictionary(), new HtmlTestEncoder(), viewContext, writer); diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentMethodSelectorTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentMethodSelectorTest.cs deleted file mode 100644 index c3344f5826..0000000000 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentMethodSelectorTest.cs +++ /dev/null @@ -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( - () => ViewComponentMethodSelector.FindAsyncMethod(typeInfo, args)); - Assert.Equal("The async view component method 'InvokeAsync' should be declared to return Task.", - 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( - () => 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( - () => ViewComponentMethodSelector.FindSyncMethod(typeInfo, new object[] { "" })); - Assert.Equal(expectedMessage, ex.Message); - } - - public static TheoryData FindAsyncMethod_ReturnsMethodMatchingParametersData - { - get - { - var derivedClass = new DerivedClass(); - - return new TheoryData - { - { 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(); - Assert.Equal(expectedId, data.Data); - } - - public static TheoryData FindSyncMethod_ReturnsMethodMatchingParametersData - { - get - { - var derivedClass = new DerivedAgain(); - - return new TheoryData - { - { 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(); - 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 InvokeAsync(string value) => Task.FromResult(value.ToUpperInvariant()); - - [MethodData("2")] - public Task InvokeAsync(string a, int b) => Task.FromResult(a + b); - - [MethodData("3")] - public Task InvokeAsync(string a, int? b, int c) => Task.FromResult(a + b + c); - - [MethodData("4")] - public Task InvokeAsync(int? a, bool? b, int c) => Task.FromResult(a.ToString() + b + c); - - [MethodData("5")] - public Task 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 InvokeAsync(float a, float b, byte c) => Task.FromResult(1); - - [MethodData("3")] - public int Invoke(BaseClass a, DerivedClass b) => 1; - - [MethodData("4")] - public Task InvokeAsync(BaseClass a, DerivedClass b) => Task.FromResult(1); - - [MethodData("5")] - public int Invoke(IEnumerable value) => 1; - - [MethodData("6")] - public Task InvokeAsync(IFormatProvider formatProvider) => Task.FromResult(1); - } - - private class ViewComponentWithNonPublicNonInstanceInvokes - { - public static int Invoke() => 1; - - private int Invoke(string a) => 2; - - public static Task InvokeAsync() => Task.FromResult(3); - - protected Task 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; } - } - } -} diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs index 67b67e17d1..2af1d3c1a2 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs @@ -2,9 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; @@ -147,9 +149,9 @@ namespace Microsoft.AspNet.Mvc { // Arrange var expected = string.Join(Environment.NewLine, - "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", - "location1", - "location2"); + "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", + "location1", + "location2"); var view = Mock.Of(); @@ -185,9 +187,9 @@ namespace Microsoft.AspNet.Mvc { // Arrange var expected = string.Join(Environment.NewLine, - "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", - "location1", - "location2"); + "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", + "location1", + "location2"); var view = Mock.Of(); @@ -223,11 +225,11 @@ namespace Microsoft.AspNet.Mvc { // Arrange var expected = string.Join(Environment.NewLine, - "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", - "location1", - "location2", - "location3", - "location4"); + "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", + "location1", + "location2", + "location3", + "location4"); var view = Mock.Of(); @@ -535,11 +537,12 @@ namespace Microsoft.AspNet.Mvc { ShortName = "Invoke", Type = typeof(object), + MethodInfo = typeof(object).GetTypeInfo().DeclaredMethods.First() }; var viewComponentContext = new ViewComponentContext( viewComponentDescriptor, - new object[0], + new Dictionary(), new HtmlTestEncoder(), viewContext, TextWriter.Null); diff --git a/test/WebSites/BasicWebSite/Views/Home/JsonTextInView.cshtml b/test/WebSites/BasicWebSite/Views/Home/JsonTextInView.cshtml index cc60ee4e84..1424208b13 100644 --- a/test/WebSites/BasicWebSite/Views/Home/JsonTextInView.cshtml +++ b/test/WebSites/BasicWebSite/Views/Home/JsonTextInView.cshtml @@ -1 +1 @@ -@Component.Invoke("JsonTextInView") \ No newline at end of file +@await Component.InvokeAsync("JsonTextInView") \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Views/RequestScopedService/ViewComponent.cshtml b/test/WebSites/BasicWebSite/Views/RequestScopedService/ViewComponent.cshtml index 1fed10c20f..4ee213cd41 100644 --- a/test/WebSites/BasicWebSite/Views/RequestScopedService/ViewComponent.cshtml +++ b/test/WebSites/BasicWebSite/Views/RequestScopedService/ViewComponent.cshtml @@ -1 +1 @@ -@Component.Invoke("RequestId") \ No newline at end of file +@await Component.InvokeAsync("RequestId") \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/InheritingInherits/Index.cshtml b/test/WebSites/RazorWebSite/Views/InheritingInherits/Index.cshtml index d60de5c219..333e22352a 100644 --- a/test/WebSites/RazorWebSite/Views/InheritingInherits/Index.cshtml +++ b/test/WebSites/RazorWebSite/Views/InheritingInherits/Index.cshtml @@ -1,3 +1,3 @@ @model Person

@Model.Name

-@Component.Invoke("InheritingViewComponent", Model.Address) \ No newline at end of file +@await Component.InvokeAsync("InheritingViewComponent", new { address = Model.Address }) \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithRelativePath.cshtml b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithRelativePath.cshtml index f138e7d744..6390921e2e 100644 --- a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithRelativePath.cshtml +++ b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithRelativePath.cshtml @@ -12,4 +12,4 @@ } ViewWithRelativePath-content @await Html.PartialAsync("../Shared/_PartialThatSetsTitle.cshtml") -@await Component.InvokeAsync("ComponentWithRelativePath", person) \ No newline at end of file +@await Component.InvokeAsync("ComponentWithRelativePath", new { person }) \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithTitle.cshtml b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithTitle.cshtml index d6e997ffd9..d90e24a409 100644 --- a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithTitle.cshtml +++ b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithTitle.cshtml @@ -2,6 +2,6 @@ ViewData["Title"] = "Page title"; // The invoked partial sets a title, but this shouldn't override the current page's ViewData \ ViewBag. await Html.RenderPartialAsync("_PartialThatSetsTitle"); - await Component.RenderInvokeAsync("ComponentThatSetsTitle"); + @await Component.InvokeAsync("ComponentThatSetsTitle") Layout = "/Views/Shared/_LayoutWithTitle.cshtml"; } \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/ViewWithPaths/Index.cshtml b/test/WebSites/RazorWebSite/Views/ViewWithPaths/Index.cshtml index 7262ab1db0..46f7e96b35 100644 --- a/test/WebSites/RazorWebSite/Views/ViewWithPaths/Index.cshtml +++ b/test/WebSites/RazorWebSite/Views/ViewWithPaths/Index.cshtml @@ -1,6 +1,6 @@  @ViewContext.ExecutingFilePath @ViewContext.View.Path - @Component.Invoke("ComponentForViewWithPaths") + @await Component.InvokeAsync("ComponentForViewWithPaths") @Html.Partial("_Partial") \ No newline at end of file diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs index 792b4efe26..1d43dd4910 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Encodings.Web; @@ -58,7 +59,7 @@ namespace MvcSample.Web.Components await result.ExecuteAsync(new ViewComponentContext( viewComponentDescriptor, - new object[0], + new Dictionary(), _htmlEncoder, ViewContext, writer)); diff --git a/test/WebSites/TagHelpersWebSite/Views/Home/Index.cshtml b/test/WebSites/TagHelpersWebSite/Views/Home/Index.cshtml index fa73aed1ec..f8edfad6d8 100644 --- a/test/WebSites/TagHelpersWebSite/Views/Home/Index.cshtml +++ b/test/WebSites/TagHelpersWebSite/Views/Home/Index.cshtml @@ -35,7 +35,7 @@

Current Tag Cloud from Tag Helper

Current Tag Cloud from ViewComponentHelper:

-
@await Component.InvokeAsync("Tags", 15)
+
@await Component.InvokeAsync("Tags", new { count = 15 })
@{ RenderTemplate( "Tag Cloud from Template: ",