private async Task <ViewBufferTextWriter> RenderPageAsync( TemplatePage page, PageContext context, bool invokeViewStarts) { var writer = context.Writer as ViewBufferTextWriter; if (writer == null) { Debug.Assert(_bufferScope != null); // If we get here, this is likely the top-level page (not a partial) - this means // that context.Writer is wrapping the output stream. We need to buffer, so create a buffered writer. ViewBuffer buffer = new ViewBuffer(_bufferScope, page.Path, ViewBuffer.ViewPageSize); writer = new ViewBufferTextWriter(buffer, context.Writer.Encoding, _htmlEncoder, context.Writer); } else { // This means we're writing something like a partial, where the output needs to be buffered. // Create a new buffer, but without the ability to flush. ViewBuffer buffer = new ViewBuffer(_bufferScope, page.Path, ViewBuffer.ViewPageSize); writer = new ViewBufferTextWriter(buffer, context.Writer.Encoding); } // The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers // and ViewComponents to reference it. var oldWriter = context.Writer; var oldFilePath = context.ExecutingFilePath; context.Writer = writer; context.ExecutingFilePath = page.Path; try { //Apply page specific callbacks first ExecutePageCallbacks(page, context.PrerenderCallbacks); //Apply engine-global callbacks ExecutePageCallbacks(page, PreRenderCallbacks.ToList()); if (invokeViewStarts) { // Execute view starts using the same context + writer as the page to render. await RenderViewStartsAsync(context); } await RenderPageCoreAsync(page, context); return(writer); } finally { context.Writer = oldWriter; context.ExecutingFilePath = oldFilePath; } }
public virtual async Task RenderAsync(PageContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } ViewBufferTextWriter bodyWriter = await RenderPageAsync(this.razorPage, context, invokeViewStarts : true); await RenderLayoutAsync(context, bodyWriter); }
private async Task RenderLayoutAsync( PageContext context, ViewBufferTextWriter bodyWriter) { // A layout page can specify another layout page. We'll need to continue // looking for layout pages until they're no longer specified. var previousPage = razorPage; var renderedLayouts = new List <TemplatePage>(); // This loop will execute Layout pages from the inside to the outside. With each // iteration, bodyWriter is replaced with the aggregate of all the "body" content // (including the layout page we just rendered). while (!string.IsNullOrEmpty(previousPage.Layout)) { if (!bodyWriter.IsBuffering) { // Once a call to RazorPage.FlushAsync is made, we can no longer render Layout pages - content has // already been written to the client and the layout content would be appended rather than surround // the body content. Throwing this exception wouldn't return a 500 (since content has already been // written), but a diagnostic component should be able to capture it. throw new InvalidOperationException("Layout cannot be rendered"); } TemplatePage layoutPage = GetLayoutPage(previousPage.Layout); if (renderedLayouts.Count > 0 && renderedLayouts.Any(l => string.Equals(l.Path, layoutPage.Path, StringComparison.Ordinal))) { // If the layout has been previously rendered as part of this view, we're potentially in a layout // rendering cycle. throw new InvalidOperationException("Layout has circular reference"); } // Notify the previous page that any writes that are performed on it are part of sections being written // in the layout. previousPage.IsLayoutBeingRendered = true; layoutPage.PreviousSectionWriters = previousPage.SectionWriters; layoutPage.BodyContent = bodyWriter.Buffer; bodyWriter = await RenderPageAsync(layoutPage, context, invokeViewStarts : false); renderedLayouts.Add(layoutPage); previousPage = layoutPage; } // Now we've reached and rendered the outer-most layout page. Nothing left to execute. // Ensure all defined sections were rendered or RenderBody was invoked for page without defined sections. foreach (var layoutPage in renderedLayouts) { layoutPage.EnsureRenderedBodyOrSections(); } if (bodyWriter.IsBuffering) { // If IsBuffering - then we've got a bunch of content in the view buffer. How to best deal with it // really depends on whether or not we're writing directly to the output or if we're writing to // another buffer. var viewBufferTextWriter = context.Writer as ViewBufferTextWriter; if (viewBufferTextWriter == null || !viewBufferTextWriter.IsBuffering) { // This means we're writing to a 'real' writer, probably to the actual output stream. // We're using PagedBufferedTextWriter here to 'smooth' synchronous writes of IHtmlContent values. using (var writer = _bufferScope.CreateWriter(context.Writer)) { await bodyWriter.Buffer.WriteToAsync(writer, _htmlEncoder); } } else { // This means we're writing to another buffer. Use MoveTo to combine them. bodyWriter.Buffer.MoveTo(viewBufferTextWriter.Buffer); return; } } }