From 80d019d72625b630618291c900b1ac56d3da3395 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Thu, 26 Sep 2019 10:48:40 -0700 Subject: [PATCH 1/7] Introduce ComponentTagHelper Fixes https://github.com/aspnet/AspNetCore/issues/13726 --- .../BlazorServerApp/Pages/_Host.cshtml | 4 +- .../ComponentWithParametersTest.cs | 75 +++++++ .../ComponentWithParameters.razor | 48 +++++ .../ComponentsApp.Server/Pages/_Host.cshtml | 2 +- .../Pages/ComponentWithParameters.cshtml | 37 ++++ .../Pages/MultipleComponents.cshtml | 20 +- .../TestServer/Pages/PrerenderedHost.cshtml | 3 +- .../TestServer/Pages/_ServerHost.cshtml | 3 +- .../TestServer/Pages/_ViewImports.cshtml | 3 + ...ft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs | 16 ++ .../Mvc.TagHelpers/src/ComponentTagHelper.cs | 80 +++++++ .../test/ComponentTagHelperTest.cs | 75 +++++++ ...MvcViewFeaturesMvcCoreBuilderExtensions.cs | 4 +- .../src/HtmlHelperComponentExtensions.cs | 127 ----------- .../src/RazorComponents/ComponentRenderer.cs | 110 ++++++++++ .../src/RazorComponents/IComponentRenderer.cs | 19 ++ .../StaticComponentRenderer.cs | 3 +- .../UnsupportedJavaScriptRuntime.cs | 1 - .../HtmlHelperComponentExtensions.cs | 50 +++++ src/Mvc/Mvc.ViewFeatures/src/Resources.resx | 3 + .../ComponentRendererTest.cs} | 204 +++++++----------- .../test/RazorComponents/HtmlRendererTest.cs | 4 +- .../HtmlHelperComponentExtensionsTest.cs | 56 +++++ .../samples/MvcSandbox/Components/App.razor | 13 +- .../MvcSandbox/Components/NotFound.razor | 4 - .../MvcSandbox/Pages/Components.cshtml | 6 +- .../MvcSandbox/Pages/Components.cshtml.cs | 16 -- .../MvcSandbox/Views/Shared/_Layout.cshtml | 3 + .../BlazorServerWeb-CSharp/Pages/_Host.cshtml | 4 +- 29 files changed, 689 insertions(+), 304 deletions(-) create mode 100644 src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs create mode 100644 src/Components/test/testassets/BasicTestApp/ComponentWithParameters.razor create mode 100644 src/Components/test/testassets/TestServer/Pages/ComponentWithParameters.cshtml create mode 100644 src/Components/test/testassets/TestServer/Pages/_ViewImports.cshtml create mode 100644 src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs create mode 100644 src/Mvc/Mvc.TagHelpers/test/ComponentTagHelperTest.cs delete mode 100644 src/Mvc/Mvc.ViewFeatures/src/HtmlHelperComponentExtensions.cs create mode 100644 src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentRenderer.cs create mode 100644 src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IComponentRenderer.cs create mode 100644 src/Mvc/Mvc.ViewFeatures/src/Rendering/HtmlHelperComponentExtensions.cs rename src/Mvc/Mvc.ViewFeatures/test/{HtmlHelperComponentExtensionsTests.cs => RazorComponents/ComponentRendererTest.cs} (83%) create mode 100644 src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperComponentExtensionsTest.cs delete mode 100644 src/Mvc/samples/MvcSandbox/Components/NotFound.razor delete mode 100644 src/Mvc/samples/MvcSandbox/Pages/Components.cshtml.cs diff --git a/src/Components/Samples/BlazorServerApp/Pages/_Host.cshtml b/src/Components/Samples/BlazorServerApp/Pages/_Host.cshtml index 7818ea3afd..4fe357cf81 100644 --- a/src/Components/Samples/BlazorServerApp/Pages/_Host.cshtml +++ b/src/Components/Samples/BlazorServerApp/Pages/_Host.cshtml @@ -13,9 +13,7 @@ - - @(await Html.RenderComponentAsync(RenderMode.ServerPrerendered)) - + diff --git a/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs new file mode 100644 index 0000000000..858c68f069 --- /dev/null +++ b/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs @@ -0,0 +1,75 @@ +// 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; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using TestServer; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests +{ + public class ComponentWithParametersTest : ServerTestBase> + { + public ComponentWithParametersTest( + BrowserFixture browserFixture, + BasicTestAppServerSiteFixture serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + } + + public DateTime LastLogTimeStamp { get; set; } = DateTime.MinValue; + + public override async Task InitializeAsync() + { + await base.InitializeAsync(); + + // Capture the last log timestamp so that we can filter logs when we + // check for duplicate connections. + var lastLog = Browser.Manage().Logs.GetLog(LogType.Browser).LastOrDefault(); + if (lastLog != null) + { + LastLogTimeStamp = lastLog.Timestamp; + } + } + + [Fact] + public void PassingParametersToComponentsWorks() + { + Navigate("/prerendered/componentwithparameters?QueryValue=testQueryValue"); + + BeginInteractivity(); + + Browser.Exists(By.CssSelector(".interactive")); + + var parameter1 = Browser.FindElement(By.CssSelector(".Param1")); + Assert.Equal(100, parameter1.FindElements(By.CssSelector("li")).Count); + Assert.Equal("99 99", parameter1.FindElement(By.CssSelector("li:last-child")).Text); + + // The assigned value is of a more derived type than the declared model type. This check + // verifies we use the actual model type during round tripping. + var parameter2 = Browser.FindElement(By.CssSelector(".Param2")); + Assert.Equal("Value Derived-Value", parameter2.Text); + + // This check verifies CaptureUnmatchedValues works + var parameter3 = Browser.FindElements(By.CssSelector(".Param3 li")); + Assert.Collection( + parameter3, + p => Assert.Equal("key1 testQueryValue", p.Text), + p => Assert.Equal("key2 43", p.Text)); + } + + private void BeginInteractivity() + { + Browser.FindElement(By.Id("load-boot-script")).Click(); + } + } +} diff --git a/src/Components/test/testassets/BasicTestApp/ComponentWithParameters.razor b/src/Components/test/testassets/BasicTestApp/ComponentWithParameters.razor new file mode 100644 index 0000000000..8ec03cf1f0 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/ComponentWithParameters.razor @@ -0,0 +1,48 @@ +

Component With Parameters

+ +
    + @foreach (var value in Param1) + { +
  • @value.StringProperty @value.IntProperty
  • + } +
+ +@* Making sure polymorphism works *@ +
@DerivedParam2.StringProperty @DerivedParam2.DerivedProperty
+ +@* Making sure CaptureUnmatchedValues works *@ + +
    + @foreach (var value in Param3.OrderBy(kvp => kvp.Key)) + { +
  • @value.Key @value.Value
  • + } +
+ +@code +{ + [Parameter] public List Param1 { get; set; } + + [Parameter] public TestModel Param2 { get; set; } + + [Parameter(CaptureUnmatchedValues = true)] public IDictionary Param3 { get; set; } + + private DerivedModel DerivedParam2 => (DerivedModel)Param2; + + public static List TestModelValues => Enumerable.Range(0, 100).Select(c => new TestModel { StringProperty = c.ToString(), IntProperty = c }).ToList(); + + public static DerivedModel DerivedModelValue = new DerivedModel { StringProperty = "Value", DerivedProperty = "Derived-Value" }; + + public class TestModel + { + + public string StringProperty { get; set; } + + public int IntProperty { get; set; } + } + + public class DerivedModel : TestModel + { + public string DerivedProperty { get; set; } + } +} diff --git a/src/Components/test/testassets/ComponentsApp.Server/Pages/_Host.cshtml b/src/Components/test/testassets/ComponentsApp.Server/Pages/_Host.cshtml index 77d00063ca..9aa4ff85ad 100644 --- a/src/Components/test/testassets/ComponentsApp.Server/Pages/_Host.cshtml +++ b/src/Components/test/testassets/ComponentsApp.Server/Pages/_Host.cshtml @@ -12,7 +12,7 @@ - @(await Html.RenderComponentAsync(RenderMode.Server)) + + + +@functions +{ + [BindProperty(SupportsGet = true)] + public string QueryValue { get; set; } +} diff --git a/src/Components/test/testassets/TestServer/Pages/MultipleComponents.cshtml b/src/Components/test/testassets/TestServer/Pages/MultipleComponents.cshtml index 61ce8d6efb..a42238a5c9 100644 --- a/src/Components/test/testassets/TestServer/Pages/MultipleComponents.cshtml +++ b/src/Components/test/testassets/TestServer/Pages/MultipleComponents.cshtml @@ -10,26 +10,26 @@
- @(await Html.RenderComponentAsync(RenderMode.ServerPrerendered)) - @(await Html.RenderComponentAsync(RenderMode.Server)) - @(await Html.RenderComponentAsync(RenderMode.Static, new { Name = "John" })) - @(await Html.RenderComponentAsync(RenderMode.Server)) + + + +

Some content before

- @(await Html.RenderComponentAsync(RenderMode.Server)) +

Some content between

- @(await Html.RenderComponentAsync(RenderMode.ServerPrerendered)) +

Some content after

Some content before

- @(await Html.RenderComponentAsync(RenderMode.Server)) - @(await Html.RenderComponentAsync(RenderMode.ServerPrerendered)) + +

Some content after

- @(await Html.RenderComponentAsync(RenderMode.Server, new { Name = "Albert" })) - @(await Html.RenderComponentAsync(RenderMode.ServerPrerendered, new { Name = "Abraham" })) + +
diff --git a/src/Components/test/testassets/TestServer/Pages/PrerenderedHost.cshtml b/src/Components/test/testassets/TestServer/Pages/PrerenderedHost.cshtml index 1ba6f2f1c9..2d7eacfb9b 100644 --- a/src/Components/test/testassets/TestServer/Pages/PrerenderedHost.cshtml +++ b/src/Components/test/testassets/TestServer/Pages/PrerenderedHost.cshtml @@ -1,5 +1,6 @@ @page @using BasicTestApp.RouterTest + @@ -7,7 +8,7 @@ - @(await Html.RenderComponentAsync(RenderMode.ServerPrerendered)) + @* So that E2E tests can make assertions about both the prerendered and diff --git a/src/Components/test/testassets/TestServer/Pages/_ServerHost.cshtml b/src/Components/test/testassets/TestServer/Pages/_ServerHost.cshtml index 668123d6f9..af2f28f658 100644 --- a/src/Components/test/testassets/TestServer/Pages/_ServerHost.cshtml +++ b/src/Components/test/testassets/TestServer/Pages/_ServerHost.cshtml @@ -1,4 +1,5 @@ @page "" +@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" @@ -11,7 +12,7 @@ - @(await Html.RenderComponentAsync(RenderMode.Server)) + diff --git a/src/Components/test/testassets/TestServer/Pages/_ViewImports.cshtml b/src/Components/test/testassets/TestServer/Pages/_ViewImports.cshtml new file mode 100644 index 0000000000..59d4a92b6d --- /dev/null +++ b/src/Components/test/testassets/TestServer/Pages/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@using BasicTestApp + diff --git a/src/Mvc/Mvc.TagHelpers/ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs b/src/Mvc/Mvc.TagHelpers/ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs index b940696196..ee158337cc 100644 --- a/src/Mvc/Mvc.TagHelpers/ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs +++ b/src/Mvc/Mvc.TagHelpers/ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs @@ -105,6 +105,22 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers public CacheTagHelperOptions() { } public long SizeLimit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } + [Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute("component", Attributes="type")] + public partial class ComponentTagHelper : Microsoft.AspNetCore.Razor.TagHelpers.TagHelper + { + public ComponentTagHelper() { } + [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("type")] + public System.Type ComponentType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("parameters", DictionaryAttributePrefix="parameter-")] + public System.Collections.Generic.IDictionary Parameters { get { throw null; } set { } } + [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("render-mode")] + public Microsoft.AspNetCore.Mvc.Rendering.RenderMode RenderMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + [Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute] + [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute] + public Microsoft.AspNetCore.Mvc.Rendering.ViewContext ViewContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + [System.Diagnostics.DebuggerStepThroughAttribute] + public override System.Threading.Tasks.Task ProcessAsync(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output) { throw null; } + } [Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute("distributed-cache", Attributes="name")] public partial class DistributedCacheTagHelper : Microsoft.AspNetCore.Mvc.TagHelpers.CacheTagHelperBase { diff --git a/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs b/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs new file mode 100644 index 0000000000..be05af7bad --- /dev/null +++ b/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs @@ -0,0 +1,80 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers +{ + /// + /// A that renders a Razor component. + /// + [HtmlTargetElement("component", Attributes = ComponentTypeName, TagStructure = TagStructure.WithoutEndTag)] + public class ComponentTagHelper : TagHelper + { + private const string ComponentParameterName = "parameters"; + private const string ComponentParameterPrefix = "parameter-"; + private const string ComponentTypeName = "type"; + private const string RenderModeName = "render-mode"; + private IDictionary _parameters; + + /// + /// Gets or sets the for the current request. + /// + [HtmlAttributeNotBound] + [ViewContext] + public ViewContext ViewContext { get; set; } + + /// + /// Gets or sets values for component parameters. + /// + [HtmlAttributeName(ComponentParameterName, DictionaryAttributePrefix = ComponentParameterPrefix)] + public IDictionary Parameters + { + get + { + _parameters ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + return _parameters; + } + set => _parameters = value; + } + + /// + /// Gets or sets the component type. This value is required. + /// + [HtmlAttributeName(ComponentTypeName)] + public Type ComponentType { get; set; } + + /// + /// Gets or sets the + /// + [HtmlAttributeName(RenderModeName)] + public RenderMode RenderMode { get; set; } + + /// + public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + var componentRenderer = ViewContext.HttpContext.RequestServices.GetRequiredService(); + var result = await componentRenderer.RenderComponentAsync(ViewContext, ComponentType, RenderMode, _parameters); + + // Reset the TagName. We don't want `component` to render. + output.TagName = null; + output.Content.SetHtmlContent(result); + } + } +} diff --git a/src/Mvc/Mvc.TagHelpers/test/ComponentTagHelperTest.cs b/src/Mvc/Mvc.TagHelpers/test/ComponentTagHelperTest.cs new file mode 100644 index 0000000000..5c1aff4a7e --- /dev/null +++ b/src/Mvc/Mvc.TagHelpers/test/ComponentTagHelperTest.cs @@ -0,0 +1,75 @@ +// 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.IO.Pipes; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers +{ + public class ComponentTagHelperTest + { + [Fact] + public async Task ProcessAsync_RendersComponent() + { + // Arrange + var tagHelper = new ComponentTagHelper + { + ViewContext = GetViewContext(), + }; + var context = GetTagHelperContext(); + var output = GetTagHelperOutput(); + + // Act + await tagHelper.ProcessAsync(context, output); + + // Assert + var content = HtmlContentUtilities.HtmlContentToString(output.Content); + Assert.Equal("Hello world", content); + Assert.Null(output.TagName); + } + + private static TagHelperContext GetTagHelperContext() + { + return new TagHelperContext( + "component", + new TagHelperAttributeList(), + new Dictionary(), + Guid.NewGuid().ToString("N")); + } + + private static TagHelperOutput GetTagHelperOutput() + { + return new TagHelperOutput( + "component", + new TagHelperAttributeList(), + (_, __) => Task.FromResult(new DefaultTagHelperContent())); + } + + private ViewContext GetViewContext() + { + var htmlContent = new HtmlContentBuilder().AppendHtml("Hello world"); + var renderer = Mock.Of(c => + c.RenderComponentAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) == Task.FromResult(htmlContent)); + + var httpContext = new DefaultHttpContext + { + RequestServices = new ServiceCollection().AddSingleton(renderer).BuildServiceProvider(), + }; + + return new ViewContext + { + HttpContext = httpContext, + }; + } + } +} diff --git a/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs index 7b17edbe29..84ffe50ce1 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs @@ -18,7 +18,6 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers; using Microsoft.AspNetCore.Mvc.ViewFeatures.Filters; using Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure; -using Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Microsoft.JSInterop; @@ -206,8 +205,9 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddSingleton(); // - // Component prerendering + // Component rendering // + services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); diff --git a/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperComponentExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperComponentExtensions.cs deleted file mode 100644 index 303c820cc9..0000000000 --- a/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperComponentExtensions.cs +++ /dev/null @@ -1,127 +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.Threading.Tasks; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Html; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.ViewFeatures; -using Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNetCore.Mvc.Rendering -{ - /// - /// Extensions for rendering components. - /// - public static class HtmlHelperComponentExtensions - { - private static readonly object ComponentSequenceKey = new object(); - - /// - /// Renders the . - /// - /// The . - /// The for the component. - /// The HTML produced by the rendered . - public static Task RenderComponentAsync(this IHtmlHelper htmlHelper, RenderMode renderMode) where TComponent : IComponent - { - if (htmlHelper == null) - { - throw new ArgumentNullException(nameof(htmlHelper)); - } - - return htmlHelper.RenderComponentAsync(renderMode, null); - } - - /// - /// Renders the . - /// - /// The . - /// An containing the parameters to pass - /// to the component. - /// The for the component. - /// The HTML produced by the rendered . - public static async Task RenderComponentAsync( - this IHtmlHelper htmlHelper, - RenderMode renderMode, - object parameters) where TComponent : IComponent - { - if (htmlHelper == null) - { - throw new ArgumentNullException(nameof(htmlHelper)); - } - - var context = htmlHelper.ViewContext.HttpContext; - return renderMode switch - { - RenderMode.Server => NonPrerenderedServerComponent(context, GetOrCreateInvocationId(htmlHelper.ViewContext), typeof(TComponent), GetParametersCollection(parameters)), - RenderMode.ServerPrerendered => await PrerenderedServerComponentAsync(context, GetOrCreateInvocationId(htmlHelper.ViewContext), typeof(TComponent), GetParametersCollection(parameters)), - RenderMode.Static => await StaticComponentAsync(context, typeof(TComponent), GetParametersCollection(parameters)), - _ => throw new ArgumentException("Invalid render mode", nameof(renderMode)), - }; - } - - private static ServerComponentInvocationSequence GetOrCreateInvocationId(ViewContext viewContext) - { - if (!viewContext.Items.TryGetValue(ComponentSequenceKey, out var result)) - { - result = new ServerComponentInvocationSequence(); - viewContext.Items[ComponentSequenceKey] = result; - } - - return (ServerComponentInvocationSequence)result; - } - - private static ParameterView GetParametersCollection(object parameters) => parameters == null ? - ParameterView.Empty : - ParameterView.FromDictionary(HtmlHelper.ObjectToDictionary(parameters)); - - private static async Task StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection) - { - var serviceProvider = context.RequestServices; - var prerenderer = serviceProvider.GetRequiredService(); - - - var result = await prerenderer.PrerenderComponentAsync( - parametersCollection, - context, - type); - - return new ComponentHtmlContent(result); - } - - private static async Task PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) - { - var serviceProvider = context.RequestServices; - var prerenderer = serviceProvider.GetRequiredService(); - var invocationSerializer = serviceProvider.GetRequiredService(); - - var currentInvocation = invocationSerializer.SerializeInvocation( - invocationId, - type, - parametersCollection, - prerendered: true); - - var result = await prerenderer.PrerenderComponentAsync( - parametersCollection, - context, - type); - - return new ComponentHtmlContent( - invocationSerializer.GetPreamble(currentInvocation), - result, - invocationSerializer.GetEpilogue(currentInvocation)); - } - - private static IHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) - { - var serviceProvider = context.RequestServices; - var invocationSerializer = serviceProvider.GetRequiredService(); - var currentInvocation = invocationSerializer.SerializeInvocation(invocationId, type, parametersCollection, prerendered: false); - - return new ComponentHtmlContent(invocationSerializer.GetPreamble(currentInvocation)); - } - } -} diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentRenderer.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentRenderer.cs new file mode 100644 index 0000000000..44a618fb04 --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/ComponentRenderer.cs @@ -0,0 +1,110 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures +{ + internal class ComponentRenderer : IComponentRenderer + { + private static readonly object ComponentSequenceKey = new object(); + private readonly StaticComponentRenderer _staticComponentRenderer; + private readonly ServerComponentSerializer _serverComponentSerializer; + + public ComponentRenderer( + StaticComponentRenderer staticComponentRenderer, + ServerComponentSerializer serverComponentSerializer) + { + _staticComponentRenderer = staticComponentRenderer; + _serverComponentSerializer = serverComponentSerializer; + } + + public async Task RenderComponentAsync( + ViewContext viewContext, + Type componentType, + RenderMode renderMode, + object parameters) + { + if (viewContext is null) + { + throw new ArgumentNullException(nameof(viewContext)); + } + + if (componentType is null) + { + throw new ArgumentNullException(nameof(componentType)); + } + + if (!typeof(IComponent).IsAssignableFrom(componentType)) + { + throw new ArgumentException(Resources.FormatTypeMustDeriveFromType(componentType, typeof(IComponent))); + } + + var context = viewContext.HttpContext; + var parameterView = parameters is null ? + ParameterView.Empty : + ParameterView.FromDictionary(HtmlHelper.ObjectToDictionary(parameters)); + + return renderMode switch + { + RenderMode.Server => NonPrerenderedServerComponent(context, GetOrCreateInvocationId(viewContext), componentType, parameterView), + RenderMode.ServerPrerendered => await PrerenderedServerComponentAsync(context, GetOrCreateInvocationId(viewContext), componentType, parameterView), + RenderMode.Static => await StaticComponentAsync(context, componentType, parameterView), + _ => throw new ArgumentException(Resources.FormatUnsupportedRenderMode(renderMode), nameof(renderMode)), + }; + } + + private static ServerComponentInvocationSequence GetOrCreateInvocationId(ViewContext viewContext) + { + if (!viewContext.Items.TryGetValue(ComponentSequenceKey, out var result)) + { + result = new ServerComponentInvocationSequence(); + viewContext.Items[ComponentSequenceKey] = result; + } + + return (ServerComponentInvocationSequence)result; + } + + private async Task StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection) + { + var result = await _staticComponentRenderer.PrerenderComponentAsync( + parametersCollection, + context, + type); + + return new ComponentHtmlContent(result); + } + + private async Task PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) + { + var currentInvocation = _serverComponentSerializer.SerializeInvocation( + invocationId, + type, + parametersCollection, + prerendered: true); + + var result = await _staticComponentRenderer.PrerenderComponentAsync( + parametersCollection, + context, + type); + + return new ComponentHtmlContent( + _serverComponentSerializer.GetPreamble(currentInvocation), + result, + _serverComponentSerializer.GetEpilogue(currentInvocation)); + } + + private IHtmlContent NonPrerenderedServerComponent(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) + { + var serviceProvider = context.RequestServices; + var currentInvocation = _serverComponentSerializer.SerializeInvocation(invocationId, type, parametersCollection, prerendered: false); + + return new ComponentHtmlContent(_serverComponentSerializer.GetPreamble(currentInvocation)); + } + } +} diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IComponentRenderer.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IComponentRenderer.cs new file mode 100644 index 0000000000..90df64f9f7 --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/IComponentRenderer.cs @@ -0,0 +1,19 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures +{ + internal interface IComponentRenderer + { + Task RenderComponentAsync( + ViewContext viewContext, + Type componentType, + RenderMode renderMode, + object parameters); + } +} diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/StaticComponentRenderer.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/StaticComponentRenderer.cs index 72e89faf5f..89304873c1 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/StaticComponentRenderer.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/StaticComponentRenderer.cs @@ -11,11 +11,10 @@ using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Routing; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents +namespace Microsoft.AspNetCore.Mvc.ViewFeatures { internal class StaticComponentRenderer { diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/UnsupportedJavaScriptRuntime.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/UnsupportedJavaScriptRuntime.cs index bcb36c37a0..da84020294 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/UnsupportedJavaScriptRuntime.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/UnsupportedJavaScriptRuntime.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.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.JSInterop; diff --git a/src/Mvc/Mvc.ViewFeatures/src/Rendering/HtmlHelperComponentExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/Rendering/HtmlHelperComponentExtensions.cs new file mode 100644 index 0000000000..178ef697dc --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/src/Rendering/HtmlHelperComponentExtensions.cs @@ -0,0 +1,50 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Rendering +{ + /// + /// Extensions for rendering components. + /// + public static class HtmlHelperComponentExtensions + { + /// + /// Renders the . + /// + /// The . + /// The for the component. + /// The HTML produced by the rendered . + public static Task RenderComponentAsync(this IHtmlHelper htmlHelper, RenderMode renderMode) where TComponent : IComponent + => RenderComponentAsync(htmlHelper, renderMode, parameters: null); + + /// + /// Renders the . + /// + /// The . + /// An containing the parameters to pass + /// to the component. + /// The for the component. + /// The HTML produced by the rendered . + public static Task RenderComponentAsync( + this IHtmlHelper htmlHelper, + RenderMode renderMode, + object parameters) where TComponent : IComponent + { + if (htmlHelper is null) + { + throw new ArgumentNullException(nameof(htmlHelper)); + } + + var viewContext = htmlHelper.ViewContext; + var componentRenderer = viewContext.HttpContext.RequestServices.GetRequiredService(); + return componentRenderer.RenderComponentAsync(viewContext, typeof(TComponent), renderMode, parameters); + } + } +} diff --git a/src/Mvc/Mvc.ViewFeatures/src/Resources.resx b/src/Mvc/Mvc.ViewFeatures/src/Resources.resx index e7362a9bf5..ae6a29aa81 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Resources.resx +++ b/src/Mvc/Mvc.ViewFeatures/src/Resources.resx @@ -295,4 +295,7 @@ Unsupported data type '{0}'. + + Unsupported RenderMode '{0}'. + \ No newline at end of file diff --git a/src/Mvc/Mvc.ViewFeatures/test/HtmlHelperComponentExtensionsTests.cs b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/ComponentRendererTest.cs similarity index 83% rename from src/Mvc/Mvc.ViewFeatures/test/HtmlHelperComponentExtensionsTests.cs rename to src/Mvc/Mvc.ViewFeatures/test/RazorComponents/ComponentRendererTest.cs index a7629497a7..1e89472473 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/HtmlHelperComponentExtensionsTests.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/ComponentRendererTest.cs @@ -13,7 +13,7 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -22,24 +22,26 @@ using Microsoft.Net.Http.Headers; using Moq; using Xunit; -namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test +namespace Microsoft.AspNetCore.Mvc.ViewFeatures { - public class HtmlHelperComponentExtensionsTests + public class ComponentRendererTest { private const string PrerenderedServerComponentPattern = "^(?.+?)$"; private const string ServerComponentPattern = "^$"; private static readonly IDataProtectionProvider _dataprotectorProvider = new EphemeralDataProtectionProvider(); + private readonly ComponentRenderer renderer = GetComponentRenderer(); + [Fact] public async Task CanRender_ParameterlessComponent() { // Arrange - var helper = CreateHelper(); + var viewContext = GetViewContext(); var writer = new StringWriter(); // Act - var result = await helper.RenderComponentAsync(RenderMode.Static); + var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Static, null); result.WriteTo(writer, HtmlEncoder.Default); var content = writer.ToString(); @@ -51,15 +53,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test public async Task CanRender_ParameterlessComponent_ServerMode() { // Arrange - var helper = CreateHelper(); - var writer = new StringWriter(); + var viewContext = GetViewContext(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act - var result = await helper.RenderComponentAsync(RenderMode.Server); - result.WriteTo(writer, HtmlEncoder.Default); - var content = writer.ToString(); + var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Server, null); + var content = HtmlContentUtilities.HtmlContentToString(result); var match = Regex.Match(content, ServerComponentPattern); // Assert @@ -82,15 +82,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test public async Task CanPrerender_ParameterlessComponent_ServerMode() { // Arrange - var helper = CreateHelper(); - var writer = new StringWriter(); + var viewContext = GetViewContext(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act - var result = await helper.RenderComponentAsync(RenderMode.ServerPrerendered); - result.WriteTo(writer, HtmlEncoder.Default); - var content = writer.ToString(); + var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.ServerPrerendered, null); + var content = HtmlContentUtilities.HtmlContentToString(result); var match = Regex.Match(content, PrerenderedServerComponentPattern, RegexOptions.Multiline); // Assert @@ -125,21 +123,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test public async Task CanRenderMultipleServerComponents() { // Arrange - var helper = CreateHelper(); - var firstWriter = new StringWriter(); - var secondWriter = new StringWriter(); + var viewContext = GetViewContext(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act - var firstResult = await helper.RenderComponentAsync(RenderMode.ServerPrerendered); - firstResult.WriteTo(firstWriter, HtmlEncoder.Default); - var firstComponent = firstWriter.ToString(); + var firstResult = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.ServerPrerendered, null); + var firstComponent = HtmlContentUtilities.HtmlContentToString(firstResult); var firstMatch = Regex.Match(firstComponent, PrerenderedServerComponentPattern, RegexOptions.Multiline); - var secondResult = await helper.RenderComponentAsync(RenderMode.Server); - secondResult.WriteTo(secondWriter, HtmlEncoder.Default); - var secondComponent = secondWriter.ToString(); + var secondResult = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Server, null); + var secondComponent = HtmlContentUtilities.HtmlContentToString(secondResult); var secondMatch = Regex.Match(secondComponent, ServerComponentPattern); // Assert @@ -171,20 +165,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test public async Task CanRender_ComponentWithParametersObject() { // Arrange - var helper = CreateHelper(); - var writer = new StringWriter(); + var viewContext = GetViewContext(); // Act - var result = await helper.RenderComponentAsync( - RenderMode.Static, - new - { - Name = "Steve" - }); - result.WriteTo(writer, HtmlEncoder.Default); - var content = writer.ToString(); + var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Static, new { Name = "Steve" }); // Assert + var content = HtmlContentUtilities.HtmlContentToString(result); Assert.Equal("

Hello Steve!

", content); } @@ -192,20 +179,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test public async Task CanRender_ComponentWithParameters_ServerMode() { // Arrange - var helper = CreateHelper(); - var writer = new StringWriter(); + var viewContext = GetViewContext(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act - var result = await helper.RenderComponentAsync( - RenderMode.Server, - new - { - Name = "Daniel" - }); - result.WriteTo(writer, HtmlEncoder.Default); - var content = writer.ToString(); + var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Server, new { Name = "Daniel" }); + var content = HtmlContentUtilities.HtmlContentToString(result); var match = Regex.Match(content, ServerComponentPattern); // Assert @@ -237,20 +217,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test public async Task CanRender_ComponentWithNullParameters_ServerMode() { // Arrange - var helper = CreateHelper(); - var writer = new StringWriter(); + var viewContext = GetViewContext(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act - var result = await helper.RenderComponentAsync( - RenderMode.Server, - new - { - Name = (string)null - }); - result.WriteTo(writer, HtmlEncoder.Default); - var content = writer.ToString(); + + var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Server, new { Name = (string)null }); + var content = HtmlContentUtilities.HtmlContentToString(result); var match = Regex.Match(content, ServerComponentPattern); // Assert @@ -274,28 +248,22 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test Assert.Null(parameterDefinition.TypeName); Assert.Null(parameterDefinition.Assembly); - var value = Assert.Single(serverComponent.ParameterValues);; + var value = Assert.Single(serverComponent.ParameterValues); ; Assert.Null(value); } [Fact] - public async Task CanPrerender_ComponentWithParameters_ServerMode() + public async Task CanPrerender_ComponentWithParameters_ServerPrerenderedMode() { // Arrange - var helper = CreateHelper(); + var viewContext = GetViewContext(); var writer = new StringWriter(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act - var result = await helper.RenderComponentAsync( - RenderMode.ServerPrerendered, - new - { - Name = "Daniel" - }); - result.WriteTo(writer, HtmlEncoder.Default); - var content = writer.ToString(); + var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, new { Name = "Daniel" }); + var content = HtmlContentUtilities.HtmlContentToString(result); var match = Regex.Match(content, PrerenderedServerComponentPattern, RegexOptions.Multiline); // Assert @@ -336,23 +304,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test } [Fact] - public async Task CanPrerender_ComponentWithNullParameters_ServerMode() + public async Task CanPrerender_ComponentWithNullParameters_ServerPrerenderedMode() { // Arrange - var helper = CreateHelper(); + var viewContext = GetViewContext(); var writer = new StringWriter(); var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose) .ToTimeLimitedDataProtector(); // Act - var result = await helper.RenderComponentAsync( - RenderMode.ServerPrerendered, - new - { - Name = (string)null - }); - result.WriteTo(writer, HtmlEncoder.Default); - var content = writer.ToString(); + var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, new { Name = (string)null }); + var content = HtmlContentUtilities.HtmlContentToString(result); var match = Regex.Match(content, PrerenderedServerComponentPattern, RegexOptions.Multiline); // Assert @@ -396,39 +358,28 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test public async Task ComponentWithInvalidRenderMode_Throws() { // Arrange - var helper = CreateHelper(); - var writer = new StringWriter(); + var viewContext = GetViewContext(); // Act & Assert - var result = await Assert.ThrowsAsync(() => helper.RenderComponentAsync( - default, - new - { - Name = "Steve" - })); - Assert.Equal("renderMode", result.ParamName); + var ex = await ExceptionAssert.ThrowsArgumentAsync( + () => renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), default, new { Name = "Daniel" }), + "renderMode", + $"Unsupported RenderMode '{(RenderMode)default}'"); } [Fact] public async Task RenderComponent_DoesNotInvokeOnAfterRenderInComponent() { // Arrange - var helper = CreateHelper(); - var writer = new StringWriter(); + var viewContext = GetViewContext(); // Act var state = new OnAfterRenderState(); - var result = await helper.RenderComponentAsync( - RenderMode.Static, - new - { - State = state - }); - - result.WriteTo(writer, HtmlEncoder.Default); + var result = await renderer.RenderComponentAsync(viewContext, typeof(OnAfterRenderComponent), RenderMode.Static, new { state }); // Assert - Assert.Equal("

Hello

", writer.ToString()); + var content = HtmlContentUtilities.HtmlContentToString(result); + Assert.Equal("

Hello

", content); Assert.False(state.OnAfterRenderRan); } @@ -436,10 +387,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test public async Task CanCatch_ComponentWithSynchronousException() { // Arrange - var helper = CreateHelper(); + var viewContext = GetViewContext(); // Act & Assert - var exception = await Assert.ThrowsAsync(() => helper.RenderComponentAsync( + var exception = await Assert.ThrowsAsync(() => renderer.RenderComponentAsync( + viewContext, + typeof(ExceptionComponent), RenderMode.Static, new { @@ -454,10 +407,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test public async Task CanCatch_ComponentWithAsynchronousException() { // Arrange - var helper = CreateHelper(); + var viewContext = GetViewContext(); // Act & Assert - var exception = await Assert.ThrowsAsync(() => helper.RenderComponentAsync( + var exception = await Assert.ThrowsAsync(() => renderer.RenderComponentAsync( + viewContext, + typeof(ExceptionComponent), RenderMode.Static, new { @@ -472,10 +427,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test public async Task Rendering_ComponentWithJsInteropThrows() { // Arrange - var helper = CreateHelper(); + var viewContext = GetViewContext(); // Act & Assert - var exception = await Assert.ThrowsAsync(() => helper.RenderComponentAsync( + var exception = await Assert.ThrowsAsync(() => renderer.RenderComponentAsync( + viewContext, + typeof(ExceptionComponent), RenderMode.Static, new { @@ -503,11 +460,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test var responseMock = new Mock(); responseMock.Setup(r => r.HasStarted).Returns(true); ctx.Features.Set(responseMock.Object); - var helper = CreateHelper(ctx); - var writer = new StringWriter(); + var viewContext = GetViewContext(ctx); // Act - var exception = await Assert.ThrowsAsync(() => helper.RenderComponentAsync( + var exception = await Assert.ThrowsAsync(() => renderer.RenderComponentAsync( + viewContext, + typeof(RedirectComponent), RenderMode.Static, new { @@ -515,8 +473,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test })); Assert.Equal("A navigation command was attempted during prerendering after the server already started sending the response. " + - "Navigation commands can not be issued during server-side prerendering after the response from the server has started. Applications must buffer the" + - "reponse and avoid using features like FlushAsync() before all components on the page have been rendered to prevent failed navigation commands.", + "Navigation commands can not be issued during server-side prerendering after the response from the server has started. Applications must buffer the" + + "reponse and avoid using features like FlushAsync() before all components on the page have been rendered to prevent failed navigation commands.", exception.Message); } @@ -530,10 +488,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test ctx.Request.PathBase = "/base"; ctx.Request.Path = "/path"; ctx.Request.QueryString = new QueryString("?query=value"); - var helper = CreateHelper(ctx); + var viewContext = GetViewContext(ctx); // Act - await helper.RenderComponentAsync( + await renderer.RenderComponentAsync( + viewContext, + typeof(RedirectComponent), RenderMode.Static, new { @@ -549,8 +509,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test public async Task CanRender_AsyncComponent() { // Arrange - var helper = CreateHelper(); - var writer = new StringWriter(); + var viewContext = GetViewContext(); var expectedContent = @" @@ -595,29 +554,29 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
"; // Act - var result = await helper.RenderComponentAsync(RenderMode.Static); - result.WriteTo(writer, HtmlEncoder.Default); - var content = writer.ToString(); + var result = await renderer.RenderComponentAsync(viewContext,typeof(AsyncComponent), RenderMode.Static, null); + var content = HtmlContentUtilities.HtmlContentToString(result); // Assert Assert.Equal(expectedContent.Replace("\r\n", "\n"), content); } - private static IHtmlHelper CreateHelper(HttpContext ctx = null, Action configureServices = null) + private static ComponentRenderer GetComponentRenderer() => + new ComponentRenderer( + new StaticComponentRenderer(HtmlEncoder.Default), + new ServerComponentSerializer(_dataprotectorProvider)); + + private static ViewContext GetViewContext(HttpContext context = null, Action configureServices = null) { var services = new ServiceCollection(); - services.AddSingleton(HtmlEncoder.Default); - services.AddSingleton(); services.AddSingleton(_dataprotectorProvider); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); configureServices?.Invoke(services); - var helper = new Mock(); - var context = ctx ?? new DefaultHttpContext(); + context ??= new DefaultHttpContext(); context.RequestServices = services.BuildServiceProvider(); context.Request.Scheme = "http"; context.Request.Host = new HostString("localhost"); @@ -625,12 +584,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test context.Request.Path = "/path"; context.Request.QueryString = QueryString.FromUriComponent("?query=value"); - helper.Setup(h => h.ViewContext) - .Returns(new ViewContext() - { - HttpContext = context - }); - return helper.Object; + return new ViewContext { HttpContext = context }; } private class TestComponent : IComponent diff --git a/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/HtmlRendererTest.cs b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/HtmlRendererTest.cs index 7a670696fb..67778e4163 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/HtmlRendererTest.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/RazorComponents/HtmlRendererTest.cs @@ -6,13 +6,11 @@ using System.Collections.Generic; using System.Runtime.ExceptionServices; using System.Text.Encodings.Web; using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Rendering; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; using Xunit; -namespace Microsoft.AspNetCore.Mvc.RazorComponents +namespace Microsoft.AspNetCore.Components.Rendering { public class HtmlRendererTest { diff --git a/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperComponentExtensionsTest.cs b/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperComponentExtensionsTest.cs new file mode 100644 index 0000000000..470b4b3ab0 --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperComponentExtensionsTest.cs @@ -0,0 +1,56 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Rendering +{ + public class HtmlHelperComponentExtensionsTest + { + [Fact] + public async Task RenderComponentAsync_Works() + { + // Arrange + var viewContext = GetViewContext(); + var htmlHelper = Mock.Of(h => h.ViewContext == viewContext); + + // Act + var result = await HtmlHelperComponentExtensions.RenderComponentAsync(htmlHelper, RenderMode.Static); + + // Assert + Assert.Equal("Hello world", HtmlContentUtilities.HtmlContentToString(result)); + } + + private static ViewContext GetViewContext() + { + var htmlContent = new HtmlContentBuilder().AppendHtml("Hello world"); + var renderer = Mock.Of(c => + c.RenderComponentAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) == Task.FromResult(htmlContent)); + + var httpContext = new DefaultHttpContext + { + RequestServices = new ServiceCollection().AddSingleton(renderer).BuildServiceProvider(), + }; + + var viewContext = new ViewContext { HttpContext = httpContext }; + return viewContext; + } + + private class TestComponent : IComponent + { + public void Attach(RenderHandle renderHandle) + { + } + + public Task SetParametersAsync(ParameterView parameters) => null; + } + } +} diff --git a/src/Mvc/samples/MvcSandbox/Components/App.razor b/src/Mvc/samples/MvcSandbox/Components/App.razor index 2bf3672b80..1c9b07ba20 100644 --- a/src/Mvc/samples/MvcSandbox/Components/App.razor +++ b/src/Mvc/samples/MvcSandbox/Components/App.razor @@ -1,2 +1,13 @@ @using Microsoft.AspNetCore.Components.Routing - \ No newline at end of file +@using MvcSandbox.Components.Shared + + + + + + + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/src/Mvc/samples/MvcSandbox/Components/NotFound.razor b/src/Mvc/samples/MvcSandbox/Components/NotFound.razor deleted file mode 100644 index 369bfb8dde..0000000000 --- a/src/Mvc/samples/MvcSandbox/Components/NotFound.razor +++ /dev/null @@ -1,4 +0,0 @@ -@using MvcSandbox.Components.Shared -@layout MainLayout -

Not Found

-

Sorry, nothing was found.

\ No newline at end of file diff --git a/src/Mvc/samples/MvcSandbox/Pages/Components.cshtml b/src/Mvc/samples/MvcSandbox/Pages/Components.cshtml index cdafd22f68..89401bf85b 100644 --- a/src/Mvc/samples/MvcSandbox/Pages/Components.cshtml +++ b/src/Mvc/samples/MvcSandbox/Pages/Components.cshtml @@ -1,5 +1,4 @@ @page -@model MvcSandbox.Pages.ComponentsModel @{ Layout = null; } @@ -15,8 +14,7 @@ - @(await Html.RenderComponentAsync(RenderMode.Static)) - - + + diff --git a/src/Mvc/samples/MvcSandbox/Pages/Components.cshtml.cs b/src/Mvc/samples/MvcSandbox/Pages/Components.cshtml.cs deleted file mode 100644 index fce8214cb5..0000000000 --- a/src/Mvc/samples/MvcSandbox/Pages/Components.cshtml.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace MvcSandbox.Pages -{ - public class ComponentsModel : PageModel - { - public void OnGet() - { - } - } -} \ No newline at end of file diff --git a/src/Mvc/samples/MvcSandbox/Views/Shared/_Layout.cshtml b/src/Mvc/samples/MvcSandbox/Views/Shared/_Layout.cshtml index a3da90da30..1b670c2b65 100644 --- a/src/Mvc/samples/MvcSandbox/Views/Shared/_Layout.cshtml +++ b/src/Mvc/samples/MvcSandbox/Views/Shared/_Layout.cshtml @@ -19,6 +19,9 @@ + diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/_Host.cshtml b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/_Host.cshtml index 7a104af51d..70eaf5f755 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/_Host.cshtml +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/_Host.cshtml @@ -13,9 +13,7 @@ - - @(await Html.RenderComponentAsync(RenderMode.ServerPrerendered)) - +
From d1faff412631e15269d9c8d62ebf823bf82807bd Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 1 Oct 2019 13:42:32 -0700 Subject: [PATCH 2/7] Fixup --- .../ComponentWithParametersTest.cs | 15 --------------- .../Pages/ComponentWithParameters.cshtml | 8 ++++---- .../TestServer/Pages/MultipleComponents.cshtml | 6 +++--- src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs | 4 ++-- 4 files changed, 9 insertions(+), 24 deletions(-) diff --git a/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs index 858c68f069..87ed133449 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs @@ -26,21 +26,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests { } - public DateTime LastLogTimeStamp { get; set; } = DateTime.MinValue; - - public override async Task InitializeAsync() - { - await base.InitializeAsync(); - - // Capture the last log timestamp so that we can filter logs when we - // check for duplicate connections. - var lastLog = Browser.Manage().Logs.GetLog(LogType.Browser).LastOrDefault(); - if (lastLog != null) - { - LastLogTimeStamp = lastLog.Timestamp; - } - } - [Fact] public void PassingParametersToComponentsWorks() { diff --git a/src/Components/test/testassets/TestServer/Pages/ComponentWithParameters.cshtml b/src/Components/test/testassets/TestServer/Pages/ComponentWithParameters.cshtml index b182ce21e9..3d630a05fd 100644 --- a/src/Components/test/testassets/TestServer/Pages/ComponentWithParameters.cshtml +++ b/src/Components/test/testassets/TestServer/Pages/ComponentWithParameters.cshtml @@ -2,10 +2,10 @@ + param-Param1="ComponentWithParameters.TestModelValues" + param-Param2="ComponentWithParameters.DerivedModelValue" + param-key1="QueryValue" + param-key2="43" /> @* So that E2E tests can make assertions about both the prerendered and diff --git a/src/Components/test/testassets/TestServer/Pages/MultipleComponents.cshtml b/src/Components/test/testassets/TestServer/Pages/MultipleComponents.cshtml index a42238a5c9..47c7677829 100644 --- a/src/Components/test/testassets/TestServer/Pages/MultipleComponents.cshtml +++ b/src/Components/test/testassets/TestServer/Pages/MultipleComponents.cshtml @@ -12,7 +12,7 @@
- +

Some content before

@@ -28,8 +28,8 @@
- - + +
diff --git a/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs b/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs index be05af7bad..7015c687e4 100644 --- a/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs +++ b/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs @@ -17,8 +17,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlTargetElement("component", Attributes = ComponentTypeName, TagStructure = TagStructure.WithoutEndTag)] public class ComponentTagHelper : TagHelper { - private const string ComponentParameterName = "parameters"; - private const string ComponentParameterPrefix = "parameter-"; + private const string ComponentParameterName = "params"; + private const string ComponentParameterPrefix = "param-"; private const string ComponentTypeName = "type"; private const string RenderModeName = "render-mode"; private IDictionary _parameters; From a7d40aeb0383868a89b2d4925ade8ed2da691e62 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 1 Oct 2019 13:47:39 -0700 Subject: [PATCH 3/7] Update ref --- .../ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mvc/Mvc.TagHelpers/ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs b/src/Mvc/Mvc.TagHelpers/ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs index ee158337cc..d453996255 100644 --- a/src/Mvc/Mvc.TagHelpers/ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs +++ b/src/Mvc/Mvc.TagHelpers/ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs @@ -105,13 +105,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers public CacheTagHelperOptions() { } public long SizeLimit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } - [Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute("component", Attributes="type")] + [Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute("component", Attributes="type", TagStructure=Microsoft.AspNetCore.Razor.TagHelpers.TagStructure.WithoutEndTag)] public partial class ComponentTagHelper : Microsoft.AspNetCore.Razor.TagHelpers.TagHelper { public ComponentTagHelper() { } [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("type")] public System.Type ComponentType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("parameters", DictionaryAttributePrefix="parameter-")] + [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("params", DictionaryAttributePrefix="param-")] public System.Collections.Generic.IDictionary Parameters { get { throw null; } set { } } [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("render-mode")] public Microsoft.AspNetCore.Mvc.Rendering.RenderMode RenderMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } From 2e7ff1e9b8c9da8af9cc23d11c8c6ba5f4f3c80a Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 2 Oct 2019 13:03:32 -0700 Subject: [PATCH 4/7] Restore the app element --- .../content/BlazorServerWeb-CSharp/Pages/_Host.cshtml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/_Host.cshtml b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/_Host.cshtml index 70eaf5f755..14d8ae4c3e 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/_Host.cshtml +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/_Host.cshtml @@ -13,7 +13,9 @@ - + + +
From 0557585b0aee860bad50bac30db135519e31d877 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 2 Oct 2019 15:20:17 -0700 Subject: [PATCH 5/7] More fixups --- .../ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs b/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs index e9618a4842..9c94c22443 100644 --- a/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs +++ b/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs @@ -325,7 +325,6 @@ namespace Microsoft.AspNetCore.Mvc.Rendering public static partial class HtmlHelperComponentExtensions { public static System.Threading.Tasks.Task RenderComponentAsync(this Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper htmlHelper, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; } - [System.Diagnostics.DebuggerStepThroughAttribute] public static System.Threading.Tasks.Task RenderComponentAsync(this Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper htmlHelper, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, object parameters) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; } } public static partial class HtmlHelperDisplayExtensions From 3fe0e303c61c4e4fe3dc9c6cfc0f8773731ca036 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 9 Oct 2019 12:33:54 -0700 Subject: [PATCH 6/7] Make sealed --- .../ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs | 2 +- src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mvc/Mvc.TagHelpers/ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs b/src/Mvc/Mvc.TagHelpers/ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs index d453996255..ef7a542790 100644 --- a/src/Mvc/Mvc.TagHelpers/ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs +++ b/src/Mvc/Mvc.TagHelpers/ref/Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs @@ -106,7 +106,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers public long SizeLimit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } [Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute("component", Attributes="type", TagStructure=Microsoft.AspNetCore.Razor.TagHelpers.TagStructure.WithoutEndTag)] - public partial class ComponentTagHelper : Microsoft.AspNetCore.Razor.TagHelpers.TagHelper + public sealed partial class ComponentTagHelper : Microsoft.AspNetCore.Razor.TagHelpers.TagHelper { public ComponentTagHelper() { } [Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute("type")] diff --git a/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs b/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs index 7015c687e4..6dc9bfe722 100644 --- a/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs +++ b/src/Mvc/Mvc.TagHelpers/src/ComponentTagHelper.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// A that renders a Razor component. /// [HtmlTargetElement("component", Attributes = ComponentTypeName, TagStructure = TagStructure.WithoutEndTag)] - public class ComponentTagHelper : TagHelper + public sealed class ComponentTagHelper : TagHelper { private const string ComponentParameterName = "params"; private const string ComponentParameterPrefix = "param-"; From 79a1769e47ac951f9471541c537d2c5098b2d3ce Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 11 Oct 2019 13:46:45 -0700 Subject: [PATCH 7/7] fixup --- .../ComponentWithParametersTest.cs | 2 +- .../Pages/MultipleComponents.cshtml | 4 +-- ....AspNetCore.Mvc.ViewFeatures.netcoreapp.cs | 1 + .../HtmlHelperComponentExtensions.cs | 26 ++++++++++++++++--- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs index 87ed133449..1aefbe2b8e 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/ComponentWithParametersTest.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests } [Fact] - public void PassingParametersToComponentsWorks() + public void PassingParametersToComponentsFromThePageWorks() { Navigate("/prerendered/componentwithparameters?QueryValue=testQueryValue"); diff --git a/src/Components/test/testassets/TestServer/Pages/MultipleComponents.cshtml b/src/Components/test/testassets/TestServer/Pages/MultipleComponents.cshtml index 47c7677829..c387700129 100644 --- a/src/Components/test/testassets/TestServer/Pages/MultipleComponents.cshtml +++ b/src/Components/test/testassets/TestServer/Pages/MultipleComponents.cshtml @@ -10,8 +10,8 @@
- - + @(await Html.RenderComponentAsync(RenderMode.ServerPrerendered)) + @(await Html.RenderComponentAsync(RenderMode.Server))
diff --git a/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs b/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs index 9c94c22443..dfbd0dec1b 100644 --- a/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs +++ b/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs @@ -324,6 +324,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering } public static partial class HtmlHelperComponentExtensions { + public static System.Threading.Tasks.Task RenderComponentAsync(this Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper htmlHelper, System.Type componentType, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, object parameters) { throw null; } public static System.Threading.Tasks.Task RenderComponentAsync(this Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper htmlHelper, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; } public static System.Threading.Tasks.Task RenderComponentAsync(this Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper htmlHelper, Microsoft.AspNetCore.Mvc.Rendering.RenderMode renderMode, object parameters) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; } } diff --git a/src/Mvc/Mvc.ViewFeatures/src/Rendering/HtmlHelperComponentExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/Rendering/HtmlHelperComponentExtensions.cs index 178ef697dc..16f021574a 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Rendering/HtmlHelperComponentExtensions.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/Rendering/HtmlHelperComponentExtensions.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering public static class HtmlHelperComponentExtensions { /// - /// Renders the . + /// Renders the . /// /// The . /// The for the component. @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering => RenderComponentAsync(htmlHelper, renderMode, parameters: null); /// - /// Renders the . + /// Renders the . /// /// The . /// An containing the parameters to pass @@ -36,15 +36,35 @@ namespace Microsoft.AspNetCore.Mvc.Rendering this IHtmlHelper htmlHelper, RenderMode renderMode, object parameters) where TComponent : IComponent + => RenderComponentAsync(htmlHelper, typeof(TComponent), renderMode, parameters); + + /// + /// Renders the specified . + /// + /// The . + /// The component type. + /// An containing the parameters to pass + /// to the component. + /// The for the component. + public static Task RenderComponentAsync( + this IHtmlHelper htmlHelper, + Type componentType, + RenderMode renderMode, + object parameters) { if (htmlHelper is null) { throw new ArgumentNullException(nameof(htmlHelper)); } + if (componentType is null) + { + throw new ArgumentNullException(nameof(componentType)); + } + var viewContext = htmlHelper.ViewContext; var componentRenderer = viewContext.HttpContext.RequestServices.GetRequiredService(); - return componentRenderer.RenderComponentAsync(viewContext, typeof(TComponent), renderMode, parameters); + return componentRenderer.RenderComponentAsync(viewContext, componentType, renderMode, parameters); } } }