public async Task ReplacesDashesWithDots_WhenTheyAppearInPairs() { // Arrange var circuitFactory = new TestCircuitFactory(() => "--1234--"); var circuitRegistry = new CircuitRegistry( Options.Create(new CircuitOptions()), Mock.Of <ILogger <CircuitRegistry> >(), TestCircuitIdFactory.CreateTestFactory()); var circuitPrerenderer = new CircuitPrerenderer(circuitFactory, circuitRegistry); var httpContext = new DefaultHttpContext(); var httpRequest = httpContext.Request; httpRequest.Scheme = "https"; httpRequest.Host = new HostString("example.com", 1234); httpRequest.Path = "/some/path"; var prerenderingContext = new ComponentPrerenderingContext { ComponentType = typeof(UriDisplayComponent), Parameters = ParameterView.Empty, Context = httpContext }; // Act var result = await circuitPrerenderer.PrerenderComponentAsync(prerenderingContext); // Assert Assert.Equal("..1234..", GetUnwrappedCircuitInfo(result).RootElement.GetProperty("circuitId").GetString()); }
public async Task <IEnumerable <string> > PrerenderComponentAsync(ComponentPrerenderingContext prerenderingContext) { var context = prerenderingContext.Context; var circuitHost = _circuitFactory.CreateCircuitHost( context, client: CircuitClientProxy.OfflineClient, GetFullUri(context.Request), GetFullBaseUri(context.Request)); // We don't need to unsubscribe because the circuit host object is scoped to this call. circuitHost.UnhandledException += CircuitHost_UnhandledException; // For right now we just do prerendering and dispose the circuit. In the future we will keep the circuit around and // reconnect to it from the ComponentsHub. If we keep the circuit/renderer we also need to unsubscribe this error // handler. try { return(await circuitHost.PrerenderComponentAsync( prerenderingContext.ComponentType, prerenderingContext.Parameters)); } finally { await circuitHost.DisposeAsync(); } }
public async Task ExtractsUriFromHttpContext_NonemptyPathBase() { // Arrange var circuitFactory = new TestCircuitFactory(); var circuitRegistry = new CircuitRegistry(Options.Create(new CircuitOptions()), Mock.Of <ILogger <CircuitRegistry> >()); var circuitPrerenderer = new CircuitPrerenderer(circuitFactory, circuitRegistry); var httpContext = new DefaultHttpContext(); var httpRequest = httpContext.Request; httpRequest.Scheme = "https"; httpRequest.Host = new HostString("example.com", 1234); httpRequest.PathBase = "/my/dir"; httpRequest.Path = "/some/path"; var prerenderingContext = new ComponentPrerenderingContext { ComponentType = typeof(UriDisplayComponent), Parameters = ParameterCollection.Empty, Context = httpContext }; // Act var result = await circuitPrerenderer.PrerenderComponentAsync(prerenderingContext); // Assert Assert.Equal(string.Join("", new[] { "The current URI is ", "https://example.com:1234/my/dir/some/path", " within base URI ", "https://example.com:1234/my/dir/" }), GetUnwrappedContent(result)); }
public async Task ExtractsUriFromHttpContext_NonemptyPathBase() { // Arrange var circuitFactory = new TestCircuitFactory(); var circuitPrerenderer = new CircuitPrerenderer(circuitFactory); var httpContext = new Mock <HttpContext>(); var httpRequest = new Mock <HttpRequest>().SetupAllProperties(); httpContext.Setup(h => h.Request).Returns(httpRequest.Object); httpRequest.Object.Scheme = "https"; httpRequest.Object.Host = new HostString("example.com", 1234); httpRequest.Object.PathBase = "/my/dir"; httpRequest.Object.Path = "/some/path"; var prerenderingContext = new ComponentPrerenderingContext { ComponentType = typeof(UriDisplayComponent), Parameters = ParameterCollection.Empty, Context = httpContext.Object }; // Act var result = await circuitPrerenderer.PrerenderComponentAsync(prerenderingContext); // Assert Assert.Equal(new[] { "The current URI is ", "https://example.com:1234/my/dir/some/path", " within base URI ", "https://example.com:1234/my/dir/" }, result); }
public async Task <ComponentPrerenderResult> PrerenderComponentAsync(ComponentPrerenderingContext prerenderingContext) { var context = prerenderingContext.Context; var navigationStatus = GetOrCreateNavigationStatus(context); if (navigationStatus.Navigated) { // Avoid creating a circuit host if other component earlier in the pipeline already triggered // a navigation request. Instead rendre nothing return(new ComponentPrerenderResult(Array.Empty <string>())); } var circuitHost = GetOrCreateCircuitHost(context, navigationStatus); ComponentRenderedText renderResult = default; try { renderResult = await circuitHost.PrerenderComponentAsync( prerenderingContext.ComponentType, prerenderingContext.Parameters); } catch (NavigationException navigationException) { // Cleanup the state as we won't need it any longer. // Signal callbacks that we don't have to register the circuit. await CleanupCircuitState(context, navigationStatus, circuitHost); // Navigation was attempted during prerendering. if (prerenderingContext.Context.Response.HasStarted) { // We can't perform a redirect as the server already started sending the response. // This is considered an application error as the developer should buffer the response until // all components have rendered. throw new InvalidOperationException("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.", navigationException); } context.Response.Redirect(navigationException.Location); return(new ComponentPrerenderResult(Array.Empty <string>())); } circuitHost.Descriptors.Add(new ComponentDescriptor { ComponentType = prerenderingContext.ComponentType, Prerendered = true }); var result = (new[] { $"<!-- M.A.C.Component:{{\"circuitId\":\"{circuitHost.CircuitId}\",\"rendererId\":\"{circuitHost.Renderer.Id}\",\"componentId\":\"{renderResult.ComponentId}\"}} -->", }).Concat(renderResult.Tokens).Concat( new[] { $"<!-- M.A.C.Component: {renderResult.ComponentId} -->" }); return(new ComponentPrerenderResult(result)); }
public async Task <IEnumerable <string> > 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 <IUriHelper>(); helper.InitializeState(context.Context); using (var htmlRenderer = new HtmlRenderer(context.Context.RequestServices, _encoder.Encode, dispatcher)) { return(await dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync( context.ComponentType, parameters))); } }
public async Task <IEnumerable <string> > PrerenderComponentAsync(ComponentPrerenderingContext prerenderingContext) { var context = prerenderingContext.Context; var circuitHost = _circuitFactory.CreateCircuitHost( context, client: null, GetFullUri(context.Request), GetFullBaseUri(context.Request)); // For right now we just do prerendering and dispose the circuit. In the future we will keep the circuit around and // reconnect to it from the ComponentsHub. try { return(await circuitHost.PrerenderComponentAsync( prerenderingContext.ComponentType, prerenderingContext.Parameters)); } finally { await circuitHost.DisposeAsync(); } }
public async Task DisposesCircuitScopeEvenIfPrerenderingThrows() { // Arrange var circuitFactory = new MockServiceScopeCircuitFactory(); var circuitRegistry = new CircuitRegistry( Options.Create(new CircuitOptions()), Mock.Of <ILogger <CircuitRegistry> >(), TestCircuitIdFactory.CreateTestFactory()); var httpContext = new DefaultHttpContext(); var prerenderer = new CircuitPrerenderer(circuitFactory, circuitRegistry); var prerenderingContext = new ComponentPrerenderingContext { ComponentType = typeof(ThrowExceptionComponent), Parameters = ParameterCollection.Empty, Context = httpContext }; // Act await Assert.ThrowsAsync <InvalidTimeZoneException>(async() => await prerenderer.PrerenderComponentAsync(prerenderingContext)); // Assert circuitFactory.MockServiceScope.Verify(scope => scope.Dispose(), Times.Once()); }
public async Task <ComponentPrerenderResult> PrerenderComponentAsync(ComponentPrerenderingContext prerenderingContext) { var context = prerenderingContext.Context; var circuitHost = GetOrCreateCircuitHost(context); var renderResult = await circuitHost.PrerenderComponentAsync( prerenderingContext.ComponentType, prerenderingContext.Parameters); circuitHost.Descriptors.Add(new ComponentDescriptor { ComponentType = prerenderingContext.ComponentType, Prerendered = true }); var result = new[] { $"<!-- M.A.C.Component:{{\"circuitId\":\"{circuitHost.CircuitId}\",\"rendererId\":\"{circuitHost.Renderer.Id}\",\"componentId\":\"{renderResult.ComponentId}\"}} -->", }.Concat(renderResult.Tokens).Concat( new[] { $"<!-- M.A.C.Component: {renderResult.ComponentId} -->" }); return(new ComponentPrerenderResult(result)); }
public async Task <ComponentPrerenderResult> PrerenderComponentAsync(ComponentPrerenderingContext prerenderingContext) { var context = prerenderingContext.Context; var cancellationStatus = GetOrCreateCancellationStatus(context); if (cancellationStatus.Canceled) { // Avoid creating a circuit host if other component earlier in the pipeline already triggered // cancellation (e.g., by navigating or throwing). Instead render nothing. return(new ComponentPrerenderResult(Array.Empty <string>())); } var circuitHost = GetOrCreateCircuitHost(context, cancellationStatus); ComponentRenderedText renderResult; try { renderResult = await circuitHost.PrerenderComponentAsync( prerenderingContext.ComponentType, prerenderingContext.Parameters); } catch (NavigationException navigationException) { // Cleanup the state as we won't need it any longer. // Signal callbacks that we don't have to register the circuit. await CleanupCircuitState(context, cancellationStatus, circuitHost); // Navigation was attempted during prerendering. if (prerenderingContext.Context.Response.HasStarted) { // We can't perform a redirect as the server already started sending the response. // This is considered an application error as the developer should buffer the response until // all components have rendered. throw new InvalidOperationException("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.", navigationException); } context.Response.Redirect(navigationException.Location); return(new ComponentPrerenderResult(Array.Empty <string>())); } catch { // If prerendering any component fails, cancel prerendering entirely and dispose the DI scope await CleanupCircuitState(context, cancellationStatus, circuitHost); throw; } circuitHost.Descriptors.Add(new ComponentDescriptor { ComponentType = prerenderingContext.ComponentType, Prerendered = true }); var record = JsonSerializer.Serialize(new PrerenderedComponentRecord( // We need to do this due to the fact that -- is not allowed within HTML comments and HTML doesn't encode '-'. // We will never have '..' sequences because we Base64UrlEncode the circuit id circuitHost.CircuitId.Replace("--", ".."), circuitHost.Renderer.Id, renderResult.ComponentId), _jsonSerializationOptions); var result = (new[] { $"<!-- M.A.C.Component: {record} -->", }).Concat(renderResult.Tokens).Concat( new[] { $"<!-- M.A.C.Component: {renderResult.ComponentId} -->" }); return(new ComponentPrerenderResult(result)); }