Esempio n. 1
0
        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;
            }
        }
Esempio n. 2
0
        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);
        }
Esempio n. 3
0
        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;
                }
            }
        }