@Body
diff --git a/src/Components/test/testassets/ComponentsApp.Server/ComponentsApp.Server.csproj b/src/Components/test/testassets/ComponentsApp.Server/ComponentsApp.Server.csproj
index be50b6a49919..6fa0b66dc386 100644
--- a/src/Components/test/testassets/ComponentsApp.Server/ComponentsApp.Server.csproj
+++ b/src/Components/test/testassets/ComponentsApp.Server/ComponentsApp.Server.csproj
@@ -10,6 +10,7 @@
+
diff --git a/src/Components/test/testassets/ComponentsApp.Server/wwwroot/index.html b/src/Components/test/testassets/ComponentsApp.Server/Pages/Index.cshtml
similarity index 78%
rename from src/Components/test/testassets/ComponentsApp.Server/wwwroot/index.html
rename to src/Components/test/testassets/ComponentsApp.Server/Pages/Index.cshtml
index b432fd88f8c3..9988e733aedd 100644
--- a/src/Components/test/testassets/ComponentsApp.Server/wwwroot/index.html
+++ b/src/Components/test/testassets/ComponentsApp.Server/Pages/Index.cshtml
@@ -1,3 +1,6 @@
+@page "{*clientroutes}"
+@using ComponentsApp.App
+
@@ -9,7 +12,7 @@
-
Loading...
+
@(await Html.RenderComponentAsync())
diff --git a/src/Components/test/testassets/ComponentsApp.Server/Startup.cs b/src/Components/test/testassets/ComponentsApp.Server/Startup.cs
index f269649446b0..62479bc3990c 100644
--- a/src/Components/test/testassets/ComponentsApp.Server/Startup.cs
+++ b/src/Components/test/testassets/ComponentsApp.Server/Startup.cs
@@ -14,8 +14,10 @@ public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
+ services.AddMvc();
services.AddSingleton();
- services.AddRazorComponents();
+ services.AddRazorComponents();
+
services.AddSingleton();
}
@@ -27,7 +29,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
}
app.UseStaticFiles();
- app.UseRazorComponents();
+ app.UseRouting(builder =>
+ {
+ builder.MapRazorPages();
+ builder.MapComponentHub("app");
+ });
}
}
}
diff --git a/src/Components/test/testassets/TestServer/Startup.cs b/src/Components/test/testassets/TestServer/Startup.cs
index 7b740845be64..87a1c1c2572e 100644
--- a/src/Components/test/testassets/TestServer/Startup.cs
+++ b/src/Components/test/testassets/TestServer/Startup.cs
@@ -1,6 +1,8 @@
-using Microsoft.AspNetCore.Components.Server;
+using BasicTestApp;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -24,7 +26,7 @@ public void ConfigureServices(IServiceCollection services)
{
options.AddPolicy("AllowAll", _ => { /* Controlled below */ });
});
- services.AddRazorComponents();
+ services.AddRazorComponents();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -47,8 +49,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
// we're not relying on any extra magic inside UseServerSideBlazor, since it's
// important that people can set up these bits of middleware manually (e.g., to
// swap in UseAzureSignalR instead of UseSignalR).
- subdirApp.UseSignalR(route => route.MapHub(ComponentsHub.DefaultPath));
- subdirApp.UseBlazor();
+ subdirApp.UseRouting(routes =>
+ routes.MapHub(ComponentHub.DefaultPath).AddComponent(selector: "root"));
+
+ subdirApp.MapWhen(
+ ctx => ctx.Features.Get()?.Endpoint == null,
+ blazorBuilder => blazorBuilder.UseBlazor());
});
}
diff --git a/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs
index 9ef038271dc3..285ba5aec58d 100644
--- a/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs
+++ b/src/Mvc/Mvc.ViewFeatures/src/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs
@@ -4,6 +4,8 @@
using System;
using System.Buffers;
using System.Linq;
+using Microsoft.AspNetCore.Components.Server;
+using Microsoft.AspNetCore.Components.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
@@ -17,8 +19,10 @@
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;
namespace Microsoft.Extensions.DependencyInjection
{
@@ -199,6 +203,12 @@ internal static void AddViewServices(IServiceCollection services)
ServiceDescriptor.Transient());
services.TryAddSingleton();
+ //
+ // Component prerendering
+ //
+ services.TryAddSingleton();
+ services.TryAddScoped();
+ services.TryAddScoped();
services.TryAddTransient();
diff --git a/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperComponentExtensions.cs b/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperRazorComponentExtensions.cs
similarity index 53%
rename from src/Mvc/Mvc.ViewFeatures/src/HtmlHelperComponentExtensions.cs
rename to src/Mvc/Mvc.ViewFeatures/src/HtmlHelperRazorComponentExtensions.cs
index e6af6078c992..50f3fc58aced 100644
--- a/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperComponentExtensions.cs
+++ b/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperRazorComponentExtensions.cs
@@ -1,14 +1,13 @@
// 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 System.Text.Encodings.Web;
+using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
-using Microsoft.AspNetCore.Components.Rendering;
+using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
@@ -16,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
///
/// Extensions for rendering components.
///
- public static class HtmlHelperComponentExtensions
+ public static class HtmlHelperRazorComponentExtensions
{
///
/// Renders the .
@@ -27,7 +26,7 @@ public static Task RenderComponentAsync(this IHtmlHelp
{
if (htmlHelper == null)
{
- throw new System.ArgumentNullException(nameof(htmlHelper));
+ throw new ArgumentNullException(nameof(htmlHelper));
}
return htmlHelper.RenderComponentAsync(null);
@@ -46,39 +45,21 @@ public static async Task RenderComponentAsync(
{
if (htmlHelper == null)
{
- throw new System.ArgumentNullException(nameof(htmlHelper));
+ throw new ArgumentNullException(nameof(htmlHelper));
}
- var serviceProvider = htmlHelper.ViewContext.HttpContext.RequestServices;
- var encoder = serviceProvider.GetRequiredService();
- var dispatcher = Renderer.CreateDefaultDispatcher();
- using (var htmlRenderer = new HtmlRenderer(serviceProvider, encoder.Encode, dispatcher))
- {
- var result = await dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(
- parameters == null ?
- ParameterCollection.Empty :
- ParameterCollection.FromDictionary(HtmlHelper.ObjectToDictionary(parameters))));
+ var httpContext = htmlHelper.ViewContext.HttpContext;
+ var serviceProvider = httpContext.RequestServices;
+ var prerenderer = serviceProvider.GetRequiredService();
- return new ComponentHtmlContent(result);
- }
- }
-
- private class ComponentHtmlContent : IHtmlContent
- {
- private readonly IEnumerable _componentResult;
-
- public ComponentHtmlContent(IEnumerable componentResult)
+ var result = await prerenderer.PrerenderComponentAsync(new ComponentPrerenderingContext
{
- _componentResult = componentResult;
- }
+ Context = httpContext,
+ ComponentType = typeof(TComponent),
+ Parameters = parameters == null ? ParameterCollection.Empty : ParameterCollection.FromDictionary(HtmlHelper.ObjectToDictionary(parameters))
+ });
- public void WriteTo(TextWriter writer, HtmlEncoder encoder)
- {
- foreach (var element in _componentResult)
- {
- writer.Write(element);
- }
- }
+ return new ComponentHtmlContent(result);
}
}
}
diff --git a/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/ComponentHtmlContent.cs b/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/ComponentHtmlContent.cs
new file mode 100644
index 000000000000..d91e80d1239d
--- /dev/null
+++ b/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/ComponentHtmlContent.cs
@@ -0,0 +1,29 @@
+// 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 System.Text.Encodings.Web;
+using Microsoft.AspNetCore.Html;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Mvc.ViewFeatures
+{
+ internal class ComponentHtmlContent : IHtmlContent
+ {
+ private readonly IEnumerable _componentResult;
+
+ public ComponentHtmlContent(IEnumerable componentResult)
+ {
+ _componentResult = componentResult;
+ }
+
+ public void WriteTo(TextWriter writer, HtmlEncoder encoder)
+ {
+ foreach (var element in _componentResult)
+ {
+ writer.Write(element);
+ }
+ }
+ }
+}
diff --git a/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/HttpUriHelper.cs b/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/HttpUriHelper.cs
new file mode 100644
index 000000000000..15cef58c04e1
--- /dev/null
+++ b/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/HttpUriHelper.cs
@@ -0,0 +1,59 @@
+// 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 Microsoft.AspNetCore.Components.Services;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+
+namespace Microsoft.AspNetCore.Mvc.ViewFeatures
+{
+ internal class HttpUriHelper : UriHelperBase
+ {
+ private HttpContext _context;
+
+ public HttpUriHelper()
+ {
+ }
+
+ public void InitializeState(HttpContext context)
+ {
+ _context = context;
+ InitializeState();
+ }
+
+ protected override void InitializeState()
+ {
+ if (_context == null)
+ {
+ throw new InvalidOperationException($"'{typeof(HttpUriHelper)}' not initialized.");
+ }
+ SetAbsoluteBaseUri(GetContextBaseUri());
+ SetAbsoluteUri(GetFullUri());
+ }
+
+ private string GetFullUri()
+ {
+ var request = _context.Request;
+ return UriHelper.BuildAbsolute(
+ request.Scheme,
+ request.Host,
+ request.PathBase,
+ request.Path,
+ request.QueryString);
+ }
+
+ private string GetContextBaseUri()
+ {
+ var request = _context.Request;
+ return UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase);
+ }
+
+ protected override void NavigateToCore(string uri, bool forceLoad)
+ {
+ // For now throw as we don't have a good way of aborting the request from here.
+ throw new InvalidOperationException(
+ "Redirects are not supported on a prerendering environment.");
+ }
+ }
+}
diff --git a/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/UnsupportedJavaScriptRuntime.cs b/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/UnsupportedJavaScriptRuntime.cs
new file mode 100644
index 000000000000..540a2dcb9ea7
--- /dev/null
+++ b/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/UnsupportedJavaScriptRuntime.cs
@@ -0,0 +1,22 @@
+// 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.JSInterop;
+
+namespace Microsoft.AspNetCore.Mvc.ViewFeatures
+{
+ internal class UnsupportedJavaScriptRuntime : IJSRuntime
+ {
+ public Task InvokeAsync(string identifier, params object[] args)
+ {
+ throw new InvalidOperationException("JavaScript interop calls cannot be issued during server-side prerendering, because the page has not yet loaded in the browser. Prerendered components must wrap any JavaScript interop calls in conditional logic to ensure those interop calls are not attempted during prerendering.");
+ }
+
+ public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef)
+ {
+ throw new InvalidOperationException("JavaScript interop calls cannot be issued during server-side prerendering, because the page has not yet loaded in the browser. Prerendered components must wrap any JavaScript interop calls in conditional logic to ensure those interop calls are not attempted during prerendering.");
+ }
+ }
+}
diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/MvcRazorComponentPrerenderer.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/MvcRazorComponentPrerenderer.cs
new file mode 100644
index 000000000000..8f39ffa2ab62
--- /dev/null
+++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/MvcRazorComponentPrerenderer.cs
@@ -0,0 +1,39 @@
+// 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.Text.Encodings.Web;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Components.Rendering;
+using Microsoft.AspNetCore.Components.Server;
+using Microsoft.AspNetCore.Components.Services;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents
+{
+ internal class MvcRazorComponentPrerenderer : IComponentPrerenderer
+ {
+ private readonly HtmlEncoder _encoder;
+
+ public MvcRazorComponentPrerenderer(HtmlEncoder encoder)
+ {
+ _encoder = encoder;
+ }
+
+ public async Task> PrerenderComponentAsync(ComponentPrerenderingContext context)
+ {
+ var dispatcher = Renderer.CreateDefaultDispatcher();
+ var parameters = context.Parameters;
+
+ // This shouldn't be moved to the constructor as we want a request scoped service.
+ var helper = (HttpUriHelper)context.Context.RequestServices.GetRequiredService();
+ helper.InitializeState(context.Context);
+ using (var htmlRenderer = new HtmlRenderer(context.Context.RequestServices, _encoder.Encode, dispatcher))
+ {
+ return await dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(
+ context.ComponentType,
+ parameters));
+ }
+ }
+ }
+}
diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/Prerendering/ComponentPrerrenderingContext.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/Prerendering/ComponentPrerrenderingContext.cs
new file mode 100644
index 000000000000..26bb6326bd55
--- /dev/null
+++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/Prerendering/ComponentPrerrenderingContext.cs
@@ -0,0 +1,29 @@
+// 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 Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Components.Server
+{
+ ///
+ /// The context for prerendering a component.
+ ///
+ public class ComponentPrerenderingContext
+ {
+ ///
+ /// Gets or sets the component type.
+ ///
+ public Type ComponentType { get; set; }
+
+ ///
+ /// Gets or sets the parameters for the component.
+ ///
+ public ParameterCollection Parameters { get; set; }
+
+ ///
+ /// Gets or sets the in which the prerendering has been initiated.
+ ///
+ public HttpContext Context { get; set; }
+ }
+}
diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/Prerendering/IComponentPrerenderer.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/Prerendering/IComponentPrerenderer.cs
new file mode 100644
index 000000000000..24480d0aa030
--- /dev/null
+++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/Prerendering/IComponentPrerenderer.cs
@@ -0,0 +1,21 @@
+// 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.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Components.Server
+{
+ ///
+ /// Prerrenders instances.
+ ///
+ public interface IComponentPrerenderer
+ {
+ ///
+ /// Prerrenders the component .
+ ///
+ /// The context in which the prerrendering is happening.
+ /// that will complete when the prerendering is done.
+ Task> PrerenderComponentAsync(ComponentPrerenderingContext context);
+ }
+}
diff --git a/src/Mvc/Mvc.ViewFeatures/test/HtmlHelperComponentExtensionsTests.cs b/src/Mvc/Mvc.ViewFeatures/test/HtmlHelperComponentExtensionsTests.cs
index b9439dbae1c2..014c5c200157 100644
--- a/src/Mvc/Mvc.ViewFeatures/test/HtmlHelperComponentExtensionsTests.cs
+++ b/src/Mvc/Mvc.ViewFeatures/test/HtmlHelperComponentExtensionsTests.cs
@@ -2,15 +2,18 @@
// 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 System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
+using Microsoft.AspNetCore.Components.Server;
+using Microsoft.AspNetCore.Components.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.JSInterop;
using Moq;
using Xunit;
@@ -53,6 +56,82 @@ public async Task CanRender_ComponentWithParametersObject()
Assert.Equal("Hello Steve!
", content);
}
+ [Fact]
+ public async Task CanCatch_ComponentWithSynchronousException()
+ {
+ // Arrange
+ var helper = CreateHelper();
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(() => helper.RenderComponentAsync(new
+ {
+ IsAsync = false
+ }));
+
+ // Assert
+ Assert.Equal("Threw an exception synchronously", exception.Message);
+ }
+
+ [Fact]
+ public async Task CanCatch_ComponentWithAsynchronousException()
+ {
+ // Arrange
+ var helper = CreateHelper();
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(() => helper.RenderComponentAsync(new
+ {
+ IsAsync = true
+ }));
+
+ // Assert
+ Assert.Equal("Threw an exception asynchronously", exception.Message);
+ }
+
+ [Fact]
+ public async Task Rendering_ComponentWithJsInteropThrows()
+ {
+ // Arrange
+ var helper = CreateHelper();
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(() => helper.RenderComponentAsync(new
+ {
+ JsInterop = true
+ }));
+
+ // Assert
+ Assert.Equal("JavaScript interop calls cannot be issued during server-side prerendering, " +
+ "because the page has not yet loaded in the browser. Prerendered components must wrap any JavaScript " +
+ "interop calls in conditional logic to ensure those interop calls are not attempted during prerendering.",
+ exception.Message);
+ }
+
+ [Fact]
+ public async Task UriHelperRedirect_ThrowsInvalidOperationException()
+ {
+ // Arrange
+ var ctx = new DefaultHttpContext();
+ ctx.Request.Scheme = "http";
+ ctx.Request.Host = new HostString("localhost");
+ ctx.Request.PathBase = "/base";
+ ctx.Request.Path = "/path";
+ ctx.Request.QueryString = new QueryString("?query=value");
+
+ var helper = CreateHelper(ctx);
+ var writer = new StringWriter();
+
+ // Act
+ var exception = await Assert.ThrowsAsync(() => helper.RenderComponentAsync(new
+ {
+ RedirectUri = "http://localhost/redirect"
+ }));
+
+ Assert.Equal("Redirects are not supported on a prerendering environment.", exception.Message);
+ }
+
+
+
[Fact]
public async Task CanRender_AsyncComponent()
{
@@ -108,23 +187,32 @@ public async Task CanRender_AsyncComponent()
var content = writer.ToString();
// Assert
- Assert.Equal(expectedContent.Replace("\r\n","\n"), content);
+ Assert.Equal(expectedContent.Replace("\r\n", "\n"), content);
}
- private static IHtmlHelper CreateHelper(Action configureServices = null)
+ private static IHtmlHelper CreateHelper(HttpContext ctx = null, Action configureServices = null)
{
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddSingleton(HtmlEncoder.Default);
- configureServices?.Invoke(serviceCollection);
+ var services = new ServiceCollection();
+ services.AddSingleton(HtmlEncoder.Default);
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ configureServices?.Invoke(services);
var helper = new Mock();
+ var context = ctx ?? new DefaultHttpContext();
+ context.RequestServices = services.BuildServiceProvider();
+ context.Request.Scheme = "http";
+ context.Request.Host = new HostString("localhost");
+ context.Request.PathBase = "/base";
+ context.Request.Path = "/path";
+ context.Request.QueryString = QueryString.FromUriComponent("?query=value");
+
helper.Setup(h => h.ViewContext)
.Returns(new ViewContext()
{
- HttpContext = new DefaultHttpContext()
- {
- RequestServices = serviceCollection.BuildServiceProvider()
- }
+ HttpContext = context
});
return helper.Object;
}
@@ -151,6 +239,47 @@ public Task SetParametersAsync(ParameterCollection parameters)
}
}
+ private class RedirectComponent : ComponentBase
+ {
+ [Inject] IUriHelper UriHelper { get; set; }
+
+ [Parameter] public string RedirectUri { get; set; }
+
+ [Parameter] public bool Force { get; set; }
+
+ protected override void OnInit()
+ {
+ UriHelper.NavigateTo(RedirectUri, Force);
+ }
+ }
+
+ private class ExceptionComponent : ComponentBase
+ {
+ [Parameter] bool IsAsync { get; set; }
+
+ [Parameter] bool JsInterop { get; set; }
+
+ [Inject] IJSRuntime JsRuntime { get; set; }
+
+ protected override async Task OnParametersSetAsync()
+ {
+ if (JsInterop)
+ {
+ await JsRuntime.InvokeAsync("window.alert", "Interop!");
+ }
+
+ if (!IsAsync)
+ {
+ throw new InvalidOperationException("Threw an exception synchronously");
+ }
+ else
+ {
+ await Task.Yield();
+ throw new InvalidOperationException("Threw an exception asynchronously");
+ }
+ }
+ }
+
private class GreetingComponent : ComponentBase
{
[Parameter] public string Name { get; set; }
diff --git a/src/Mvc/test/Mvc.FunctionalTests/BasicTests.cs b/src/Mvc/test/Mvc.FunctionalTests/BasicTests.cs
index 33b3355ce6ce..ff4ac8348a9e 100644
--- a/src/Mvc/test/Mvc.FunctionalTests/BasicTests.cs
+++ b/src/Mvc/test/Mvc.FunctionalTests/BasicTests.cs
@@ -481,6 +481,9 @@ public async Task ApplicationAssemblyPartIsListedAsFirstAssembly()
var expected = new[]
{
"BasicWebSite",
+ "Microsoft.AspNetCore.Components.Server",
+ "Microsoft.AspNetCore.SpaServices",
+ "Microsoft.AspNetCore.SpaServices.Extensions",
"Microsoft.AspNetCore.Mvc.TagHelpers",
"Microsoft.AspNetCore.Mvc.Razor",
};
diff --git a/src/Mvc/test/Mvc.FunctionalTests/ComponentRenderingFunctionalTests.cs b/src/Mvc/test/Mvc.FunctionalTests/ComponentRenderingFunctionalTests.cs
index 6e3ccce52856..dbbdd61985ee 100644
--- a/src/Mvc/test/Mvc.FunctionalTests/ComponentRenderingFunctionalTests.cs
+++ b/src/Mvc/test/Mvc.FunctionalTests/ComponentRenderingFunctionalTests.cs
@@ -5,6 +5,7 @@
using System.Net.Http;
using System.Threading.Tasks;
using AngleSharp.Parser.Html;
+using BasicWebSite;
using BasicWebSite.Services;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
@@ -15,11 +16,14 @@ public class ComponentRenderingFunctionalTests : IClassFixture fixture)
{
+ Factory = fixture;
Client = Client ?? CreateClient(fixture);
}
public HttpClient Client { get; }
+ public MvcTestFixture Factory { get; }
+
[Fact]
public async Task Renders_BasicComponent()
{
@@ -33,6 +37,53 @@ public async Task Renders_BasicComponent()
AssertComponent("\n Hello world!
\n", "Greetings", content);
}
+ [Fact]
+ public async Task Renders_BasicComponent_UsingRazorComponents_Prerrenderer()
+ {
+ // Arrange & Act
+ var client = Factory
+ .WithWebHostBuilder(builder => builder.ConfigureServices(services => services.AddRazorComponents()))
+ .CreateClient();
+
+ var response = await client.GetAsync("http://localhost/components");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var content = await response.Content.ReadAsStringAsync();
+
+ AssertComponent("\n Hello world!
\n", "Greetings", content);
+ }
+
+ [Fact]
+ public async Task Renders_RoutingComponent()
+ {
+ // Arrange & Act
+ var response = await Client.GetAsync("http://localhost/components/routable");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var content = await response.Content.ReadAsStringAsync();
+
+ AssertComponent("\n Router component\nRouted successfully
\n", "Routing", content);
+ }
+
+ [Fact]
+ public async Task Renders_RoutingComponent_UsingRazorComponents_Prerrenderer()
+ {
+ // Arrange & Act
+ var client = Factory
+ .WithWebHostBuilder(builder => builder.ConfigureServices(services => services.AddRazorComponents()))
+ .CreateClient();
+
+ var response = await client.GetAsync("http://localhost/components/routable");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var content = await response.Content.ReadAsStringAsync();
+
+ AssertComponent("\n Router component\nRouted successfully
\n", "Routing", content);
+ }
+
[Fact]
public async Task Renders_AsyncComponent()
{
diff --git a/src/Mvc/test/WebSites/BasicWebSite/BasicWebSite.csproj b/src/Mvc/test/WebSites/BasicWebSite/BasicWebSite.csproj
index 97ef847b07ff..89b48fe417b1 100644
--- a/src/Mvc/test/WebSites/BasicWebSite/BasicWebSite.csproj
+++ b/src/Mvc/test/WebSites/BasicWebSite/BasicWebSite.csproj
@@ -15,6 +15,7 @@
+
diff --git a/src/Mvc/test/WebSites/BasicWebSite/Controllers/ComponentsController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/RazorComponentsController.cs
similarity index 95%
rename from src/Mvc/test/WebSites/BasicWebSite/Controllers/ComponentsController.cs
rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/RazorComponentsController.cs
index 99ba2690ee17..c499c5f345c5 100644
--- a/src/Mvc/test/WebSites/BasicWebSite/Controllers/ComponentsController.cs
+++ b/src/Mvc/test/WebSites/BasicWebSite/Controllers/RazorComponentsController.cs
@@ -9,7 +9,7 @@
namespace BasicWebSite.Controllers
{
- public class ComponentsController : Controller
+ public class RazorComponentsController : Controller
{
private static WeatherRow[] _weatherData = new[]
{
@@ -51,6 +51,7 @@ public class ComponentsController : Controller
};
[HttpGet("/components")]
+ [HttpGet("/components/routable")]
public IActionResult Index()
{
return View();
diff --git a/src/Mvc/test/WebSites/BasicWebSite/RazorComponents/Fallback.razor b/src/Mvc/test/WebSites/BasicWebSite/RazorComponents/Fallback.razor
new file mode 100644
index 000000000000..ff7cde246bd2
--- /dev/null
+++ b/src/Mvc/test/WebSites/BasicWebSite/RazorComponents/Fallback.razor
@@ -0,0 +1 @@
+Route not found
\ No newline at end of file
diff --git a/src/Mvc/test/WebSites/BasicWebSite/RazorComponents/RoutedPage.razor b/src/Mvc/test/WebSites/BasicWebSite/RazorComponents/RoutedPage.razor
new file mode 100644
index 000000000000..7e2dbffeac5c
--- /dev/null
+++ b/src/Mvc/test/WebSites/BasicWebSite/RazorComponents/RoutedPage.razor
@@ -0,0 +1,2 @@
+@page "/components/routable"
+Routed successfully
diff --git a/src/Mvc/test/WebSites/BasicWebSite/RazorComponents/RouterContainer.razor b/src/Mvc/test/WebSites/BasicWebSite/RazorComponents/RouterContainer.razor
new file mode 100644
index 000000000000..ab5e66e9c229
--- /dev/null
+++ b/src/Mvc/test/WebSites/BasicWebSite/RazorComponents/RouterContainer.razor
@@ -0,0 +1,5 @@
+Router component
+
+
\ No newline at end of file
diff --git a/src/Mvc/test/WebSites/BasicWebSite/Views/Components/Index.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/RazorComponents/Index.cshtml
similarity index 76%
rename from src/Mvc/test/WebSites/BasicWebSite/Views/Components/Index.cshtml
rename to src/Mvc/test/WebSites/BasicWebSite/Views/RazorComponents/Index.cshtml
index 6bff9af76fe0..8a0211c49816 100644
--- a/src/Mvc/test/WebSites/BasicWebSite/Views/Components/Index.cshtml
+++ b/src/Mvc/test/WebSites/BasicWebSite/Views/RazorComponents/Index.cshtml
@@ -6,4 +6,8 @@
@(await Html.RenderComponentAsync(new { StartDate = new DateTime(2019, 01, 15) }))
+
+
+
+ @(await Html.RenderComponentAsync())
\ No newline at end of file
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/RazorComponentsWeb-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/RazorComponentsWeb-CSharp.csproj.in
index a592f9149553..90f398d369bf 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/RazorComponentsWeb-CSharp.csproj.in
+++ b/src/ProjectTemplates/Web.ProjectTemplates/RazorComponentsWeb-CSharp.csproj.in
@@ -10,6 +10,7 @@
+
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/Components/Startup.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/Components/Startup.cs
deleted file mode 100644
index 760f618a75c7..000000000000
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/Components/Startup.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Microsoft.AspNetCore.Components.Builder;
-
-namespace RazorComponentsWeb_CSharp.Components
-{
- public class Startup
- {
- public void Configure(IComponentsApplicationBuilder app)
- {
- app.AddComponent("app");
- }
- }
-}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/wwwroot/index.html b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/Pages/Index.cshtml
similarity index 83%
rename from src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/wwwroot/index.html
rename to src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/Pages/Index.cshtml
index dbec2f1a8fad..d3cc00f32c3e 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/wwwroot/index.html
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/Pages/Index.cshtml
@@ -1,3 +1,4 @@
+@page "{*clientPath}"
@@ -9,7 +10,7 @@
- Loading...
+ @(await Html.RenderComponentAsync())
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/Pages/_ViewImports.cshtml b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/Pages/_ViewImports.cshtml
new file mode 100644
index 000000000000..a7aad3ea3627
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/Pages/_ViewImports.cshtml
@@ -0,0 +1,3 @@
+@using RazorComponentsWeb_CSharp.Components
+@namespace RazorComponentsWeb_CSharp.Pages
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/Startup.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/Startup.cs
index 7854291ea984..f35edd4c14fb 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/Startup.cs
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorComponentsWeb-CSharp/Startup.cs
@@ -10,6 +10,7 @@
#endif
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using RazorComponentsWeb_CSharp.Components;
using RazorComponentsWeb_CSharp.Services;
namespace RazorComponentsWeb_CSharp
@@ -20,8 +21,12 @@ public class Startup
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
+ services.AddMvc()
+ .AddNewtonsoftJson();
+
+ services.AddRazorComponents();
+
services.AddSingleton();
- services.AddRazorComponents();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -43,7 +48,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseHttpsRedirection();
#endif
app.UseStaticFiles();
- app.UseRazorComponents();
+
+ app.UseRouting(routes =>
+ {
+ routes.MapRazorPages();
+ routes.MapComponentHub("app");
+ });
}
}
}