// 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; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Html.Abstractions; using Microsoft.AspNet.Mvc.Razor.Internal; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.Framework.Internal; using Microsoft.Framework.WebEncoders; namespace Microsoft.AspNet.Mvc.Razor { /// /// A that is backed by a unbuffered writer (over the Response stream) and a buffered /// . When Flush or FlushAsync is invoked, the writer /// copies all content from the buffered writer to the unbuffered one and switches to writing to the unbuffered /// writer for all further write operations. /// /// /// This type is designed to avoid creating large in-memory strings when buffering and supporting the contract that /// expects. /// public class RazorTextWriter : TextWriter, IBufferedTextWriter { /// /// Creates a new instance of . /// /// The to write output to when this instance /// is no longer buffering. /// The character in which the output is written. /// The HTML encoder. public RazorTextWriter(TextWriter unbufferedWriter, Encoding encoding, IHtmlEncoder encoder) { UnbufferedWriter = unbufferedWriter; HtmlEncoder = encoder; BufferedWriter = new StringCollectionTextWriter(encoding); TargetWriter = BufferedWriter; } /// public override Encoding Encoding { get { return BufferedWriter.Encoding; } } /// public bool IsBuffering { get; private set; } = true; // Internal for unit testing internal StringCollectionTextWriter BufferedWriter { get; } private TextWriter UnbufferedWriter { get; } private TextWriter TargetWriter { get; set; } private IHtmlEncoder HtmlEncoder { get; } /// public override void Write(char value) { TargetWriter.Write(value); } /// public override void Write(object value) { var htmlContent = value as IHtmlContent; if (htmlContent != null) { htmlContent.WriteTo(TargetWriter, HtmlEncoder); return; } base.Write(value); } /// public override void Write([NotNull] char[] buffer, int index, int count) { if (index < 0) { throw new ArgumentOutOfRangeException(nameof(index)); } if (count < 0 || (buffer.Length - index < count)) { throw new ArgumentOutOfRangeException(nameof(count)); } TargetWriter.Write(buffer, index, count); } /// public override void Write(string value) { if (!string.IsNullOrEmpty(value)) { TargetWriter.Write(value); } } /// public override Task WriteAsync(char value) { return TargetWriter.WriteAsync(value); } /// public override Task WriteAsync([NotNull] char[] buffer, int index, int count) { if (index < 0) { throw new ArgumentOutOfRangeException(nameof(index)); } if (count < 0 || (buffer.Length - index < count)) { throw new ArgumentOutOfRangeException(nameof(count)); } return TargetWriter.WriteAsync(buffer, index, count); } /// public override Task WriteAsync(string value) { return TargetWriter.WriteAsync(value); } /// public override void WriteLine() { TargetWriter.WriteLine(); } /// public override void WriteLine(string value) { TargetWriter.WriteLine(value); } /// public override Task WriteLineAsync(char value) { return TargetWriter.WriteLineAsync(value); } /// public override Task WriteLineAsync(char[] value, int start, int offset) { return TargetWriter.WriteLineAsync(value, start, offset); } /// public override Task WriteLineAsync(string value) { return TargetWriter.WriteLineAsync(value); } /// public override Task WriteLineAsync() { return TargetWriter.WriteLineAsync(); } /// /// Copies the buffered content to the unbuffered writer and invokes flush on it. /// Additionally causes this instance to no longer buffer and direct all write operations /// to the unbuffered writer. /// public override void Flush() { if (IsBuffering) { IsBuffering = false; TargetWriter = UnbufferedWriter; CopyTo(UnbufferedWriter); } UnbufferedWriter.Flush(); } /// /// Copies the buffered content to the unbuffered writer and invokes flush on it. /// Additionally causes this instance to no longer buffer and direct all write operations /// to the unbuffered writer. /// /// A that represents the asynchronous copy and flush operations. public override async Task FlushAsync() { if (IsBuffering) { IsBuffering = false; TargetWriter = UnbufferedWriter; await CopyToAsync(UnbufferedWriter); } await UnbufferedWriter.FlushAsync(); } /// public void CopyTo(TextWriter writer) { writer = UnWrapRazorTextWriter(writer); BufferedWriter.CopyTo(writer, HtmlEncoder); } /// public Task CopyToAsync(TextWriter writer) { writer = UnWrapRazorTextWriter(writer); return BufferedWriter.CopyToAsync(writer, HtmlEncoder); } private static TextWriter UnWrapRazorTextWriter(TextWriter writer) { var targetRazorTextWriter = writer as RazorTextWriter; if (targetRazorTextWriter != null) { writer = targetRazorTextWriter.IsBuffering ? targetRazorTextWriter.BufferedWriter : targetRazorTextWriter.UnbufferedWriter; } return writer; } } }