// Copyright (c) Microsoft Open Technologies, Inc. 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.IO; using System.Text; using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc.Razor { /// /// A that represents individual write operations as a sequence of strings when buffering. /// The writer is backed by an unbuffered writer. When Flush or FlushAsync is invoked, the writer /// copies all content to the unbuffered writier and switches to writing to the unbuffered writer for all further /// write operations. /// /// /// This is primarily designed to avoid creating large in-memory strings. /// Refer to https://aspnetwebstack.codeplex.com/workitem/585 for more details. /// public class RazorTextWriter : TextWriter { private static readonly Task _completedTask = Task.FromResult(0); private readonly TextWriter _unbufferedWriter; private readonly Encoding _encoding; /// /// 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. public RazorTextWriter(TextWriter unbufferedWriter, Encoding encoding) { _unbufferedWriter = unbufferedWriter; _encoding = encoding; Buffer = new BufferEntryCollection(); } /// public override Encoding Encoding { get { return _encoding; } } /// /// Gets a flag that determines if this instance of is buffering content. /// public bool IsBuffering { get; private set; } = true; /// /// A collection of entries buffered by this instance of . /// public BufferEntryCollection Buffer { get; private set; } /// public override void Write(char value) { if (IsBuffering) { Buffer.Add(value.ToString()); } else { _unbufferedWriter.Write(value); } } /// public override void Write([NotNull] char[] buffer, int index, int count) { if (index < 0) { throw new ArgumentOutOfRangeException("index"); } if (count < 0) { throw new ArgumentOutOfRangeException("count"); } if (buffer.Length - index < count) { throw new ArgumentOutOfRangeException("count"); } if (IsBuffering) { Buffer.Add(buffer, index, count); } else { _unbufferedWriter.Write(buffer, index, count); } } /// public override void Write(string value) { if (!string.IsNullOrEmpty(value)) { if (IsBuffering) { Buffer.Add(value); } else { _unbufferedWriter.Write(value); } } } /// public override Task WriteAsync(char value) { if (IsBuffering) { Write(value); return _completedTask; } else { return _unbufferedWriter.WriteAsync(value); } } /// public override Task WriteAsync([NotNull] char[] buffer, int index, int count) { if (IsBuffering) { Write(buffer, index, count); return _completedTask; } else { return _unbufferedWriter.WriteAsync(buffer, index, count); } } /// public override Task WriteAsync(string value) { if (IsBuffering) { Write(value); return _completedTask; } else { return _unbufferedWriter.WriteAsync(value); } } /// public override void WriteLine() { if (IsBuffering) { Buffer.Add(Environment.NewLine); } else { _unbufferedWriter.WriteLine(); } } /// public override void WriteLine(string value) { if (IsBuffering) { Write(value); WriteLine(); } else { _unbufferedWriter.WriteLine(value); } } /// public override Task WriteLineAsync(char value) { if (IsBuffering) { WriteLine(value); return _completedTask; } else { return _unbufferedWriter.WriteLineAsync(value); } } /// public override Task WriteLineAsync(char[] value, int start, int offset) { if (IsBuffering) { WriteLine(value, start, offset); return _completedTask; } else { return _unbufferedWriter.WriteLineAsync(value, start, offset); } } /// public override Task WriteLineAsync(string value) { if (IsBuffering) { WriteLine(value); return _completedTask; } else { return _unbufferedWriter.WriteLineAsync(value); } } /// public override Task WriteLineAsync() { if (IsBuffering) { WriteLine(); return _completedTask; } else { return _unbufferedWriter.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; 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; await CopyToAsync(_unbufferedWriter); } await _unbufferedWriter.FlushAsync(); } /// /// Copies the content of the to the instance. /// /// The writer to copy contents to. public void CopyTo(TextWriter writer) { var targetRazorTextWriter = writer as RazorTextWriter; if (targetRazorTextWriter != null && targetRazorTextWriter.IsBuffering) { targetRazorTextWriter.Buffer.Add(Buffer); } else { // If the target writer is not buffering, we can directly copy to it's unbuffered writer var targetWriter = targetRazorTextWriter != null ? targetRazorTextWriter._unbufferedWriter : writer; WriteList(targetWriter, Buffer); } } /// /// Copies the content of the to the specified instance. /// /// The writer to copy contents to. /// A task that represents the asynchronous copy operation. public Task CopyToAsync(TextWriter writer) { var targetRazorTextWriter = writer as RazorTextWriter; if (targetRazorTextWriter != null && targetRazorTextWriter.IsBuffering) { targetRazorTextWriter.Buffer.Add(Buffer); } else { // If the target writer is not buffering, we can directly copy to it's unbuffered writer var targetWriter = targetRazorTextWriter != null ? targetRazorTextWriter._unbufferedWriter : writer; return WriteListAsync(targetWriter, Buffer); } return _completedTask; } private static void WriteList(TextWriter writer, BufferEntryCollection values) { foreach (var value in values) { writer.Write(value); } } private static async Task WriteListAsync(TextWriter writer, BufferEntryCollection values) { foreach (var value in values) { await writer.WriteAsync(value); } } } }