diff --git a/src/Microsoft.AspNet.Mvc.Razor/Buffer/IRazorBufferScope.cs b/src/Microsoft.AspNet.Mvc.Razor/Buffer/IRazorBufferScope.cs deleted file mode 100644 index 794ebfb00e..0000000000 --- a/src/Microsoft.AspNet.Mvc.Razor/Buffer/IRazorBufferScope.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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. - -namespace Microsoft.AspNet.Mvc.Razor.Buffer -{ - /// - /// Creates and manages the lifetime of instances. - /// - public interface IRazorBufferScope - { - /// - /// Gets a . - /// - /// The . - RazorBufferSegment GetSegment(); - } -} diff --git a/src/Microsoft.AspNet.Mvc.Razor/Buffer/MemoryPoolRazorBufferScope.cs b/src/Microsoft.AspNet.Mvc.Razor/Buffer/MemoryPoolRazorBufferScope.cs deleted file mode 100644 index 1c8cbe3239..0000000000 --- a/src/Microsoft.AspNet.Mvc.Razor/Buffer/MemoryPoolRazorBufferScope.cs +++ /dev/null @@ -1,82 +0,0 @@ -// 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 Microsoft.Extensions.MemoryPool; - -namespace Microsoft.AspNet.Mvc.Razor.Buffer -{ - /// - /// A that uses pooled memory. - /// - public class MemoryPoolRazorBufferScope : IRazorBufferScope, IDisposable - { - private const int SegmentSize = 1024; - private readonly IArraySegmentPool _pool; - private List> _leased; - private bool _disposed; - - /// - /// Initializes a new instance of . - /// - /// The for creating - /// instances. - public MemoryPoolRazorBufferScope(IArraySegmentPool pool) - { - _pool = pool; - } - - /// - public RazorBufferSegment GetSegment() - { - if (_disposed) - { - throw new ObjectDisposedException(typeof(MemoryPoolRazorBufferScope).FullName); - } - - if (_leased == null) - { - _leased = new List>(1); - } - - LeasedArraySegment segment = null; - - try - { - segment = _pool.Lease(SegmentSize); - _leased.Add(segment); - } - catch when (segment != null) - { - segment.Owner.Return(segment); - throw; - } - - return new RazorBufferSegment(segment.Data); - } - - /// - public void Dispose() - { - if (!_disposed) - { - _disposed = true; - - if (_leased == null) - { - return; - } - - for (var i = 0; i < _leased.Count; i++) - { - var segment = _leased[i]; - Array.Clear(segment.Data.Array, segment.Data.Offset, segment.Data.Count); - segment.Owner.Return(segment); - } - - _leased.Clear(); - } - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Razor/Buffer/RazorBufferSegment.cs b/src/Microsoft.AspNet.Mvc.Razor/Buffer/RazorBufferSegment.cs deleted file mode 100644 index 2a61592124..0000000000 --- a/src/Microsoft.AspNet.Mvc.Razor/Buffer/RazorBufferSegment.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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; - -namespace Microsoft.AspNet.Mvc.Razor.Buffer -{ - /// - /// Encapsulates a . - /// - public struct RazorBufferSegment - { - /// - /// Initializes a new instance of . - /// - /// The to encapsulate. - public RazorBufferSegment(ArraySegment data) - { - Data = data; - } - - /// - /// Gets the . - /// - public ArraySegment Data { get; } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index dc6e2d04a3..8a49814885 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Reflection; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Razor; -using Microsoft.AspNet.Mvc.Razor.Buffer; using Microsoft.AspNet.Mvc.Razor.Compilation; using Microsoft.AspNet.Mvc.Razor.Directives; using Microsoft.AspNet.Mvc.Razor.Internal; @@ -160,9 +159,6 @@ namespace Microsoft.Extensions.DependencyInjection // Consumed by the Cache tag helper to cache results across the lifetime of the application. services.TryAddSingleton(); - services.TryAddSingleton, DefaultArraySegmentPool>(); - services.TryAddScoped(); - if (PlatformServices.Default?.AssemblyLoadContextAccessor != null) { services.TryAddSingleton(PlatformServices.Default.AssemblyLoadContextAccessor); diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs index 0970302dd6..2dd18edca1 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs @@ -17,6 +17,7 @@ using Microsoft.AspNet.Mvc.Razor.Internal; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Mvc.ViewFeatures; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.Extensions.DependencyInjection; @@ -37,12 +38,12 @@ namespace Microsoft.AspNet.Mvc.Razor private bool _renderedBody; private AttributeInfo _attributeInfo; private TagHelperAttributeInfo _tagHelperAttributeInfo; - private StringCollectionTextWriter _valueBuffer; + private HtmlContentWrapperTextWriter _valueBuffer; + private IViewBufferScope _bufferScope; public RazorPage() { SectionWriters = new Dictionary(StringComparer.OrdinalIgnoreCase); - _writerScopes = new Stack(); } @@ -148,6 +149,20 @@ namespace Microsoft.AspNet.Mvc.Razor } } + private IViewBufferScope BufferScope + { + get + { + if (_bufferScope == null) + { + var services = ViewContext.HttpContext.RequestServices; + _bufferScope = services.GetRequiredService(); + } + + return _bufferScope; + } + } + /// /// Format an error message about using an indexer when the tag helper property is null. /// @@ -194,7 +209,8 @@ namespace Microsoft.AspNet.Mvc.Razor /// public void StartTagHelperWritingScope() { - StartTagHelperWritingScope(new StringCollectionTextWriter(Output.Encoding)); + var buffer = new ViewBuffer(BufferScope, Path); + StartTagHelperWritingScope(new HtmlContentWrapperTextWriter(buffer, Output.Encoding)); } /// @@ -221,7 +237,7 @@ namespace Microsoft.AspNet.Mvc.Razor // from HTML helpers) is redirected. ViewContext.Writer = writer; - _writerScopes.Push(ViewContext.Writer); + _writerScopes.Push(writer); } /// @@ -258,10 +274,10 @@ namespace Microsoft.AspNet.Mvc.Razor } else { - var stringCollectionTextWriter = writer as StringCollectionTextWriter; - if (stringCollectionTextWriter != null) + var htmlContentTextWriter = writer as HtmlContentWrapperTextWriter; + if (htmlContentTextWriter != null) { - tagHelperContent.Append(stringCollectionTextWriter.Content); + tagHelperContent.Append(htmlContentTextWriter.ContentBuilder); } else { @@ -586,7 +602,8 @@ namespace Microsoft.AspNet.Mvc.Razor { if (_valueBuffer == null) { - _valueBuffer = new StringCollectionTextWriter(Output.Encoding); + var buffer = new ViewBuffer(BufferScope, Path); + _valueBuffer = new HtmlContentWrapperTextWriter(buffer, Output.Encoding); } if (!string.IsNullOrEmpty(prefix)) @@ -604,7 +621,7 @@ namespace Microsoft.AspNet.Mvc.Razor { executionContext.AddHtmlAttribute( _tagHelperAttributeInfo.Name, - _valueBuffer?.Content ?? HtmlString.Empty); + (IHtmlContent)_valueBuffer?.ContentBuilder ?? HtmlString.Empty); _valueBuffer = null; } } diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorTextWriter.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorTextWriter.cs index bbb40f4011..3ca7aef74a 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorTextWriter.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorTextWriter.cs @@ -7,6 +7,7 @@ using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNet.Html; +using Microsoft.AspNet.Mvc.ViewFeatures; namespace Microsoft.AspNet.Mvc.Razor { diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs index 3b724dbc1f..d2394a2a0d 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs @@ -7,9 +7,9 @@ using System.Diagnostics; using System.Linq; using System.Text.Encodings.Web; using System.Threading.Tasks; -using Microsoft.AspNet.Mvc.Razor.Buffer; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewEngines; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNet.Mvc.Razor @@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.Razor private readonly IRazorViewEngine _viewEngine; private readonly IRazorPageActivator _pageActivator; private readonly HtmlEncoder _htmlEncoder; - private IRazorBufferScope _bufferScope; + private IViewBufferScope _bufferScope; /// /// Initializes a new instance of @@ -97,7 +97,7 @@ namespace Microsoft.AspNet.Mvc.Razor throw new ArgumentNullException(nameof(context)); } - _bufferScope = context.HttpContext.RequestServices.GetRequiredService(); + _bufferScope = context.HttpContext.RequestServices.GetRequiredService(); var bodyWriter = await RenderPageAsync(RazorPage, context, invokeViewStarts: true); await RenderLayoutAsync(context, bodyWriter); } @@ -108,7 +108,7 @@ namespace Microsoft.AspNet.Mvc.Razor bool invokeViewStarts) { Debug.Assert(_bufferScope != null); - var buffer = new RazorBuffer(_bufferScope, page.Path); + var buffer = new ViewBuffer(_bufferScope, page.Path); var razorTextWriter = new RazorTextWriter(context.Writer, buffer, _htmlEncoder); // The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Buffer/IViewBufferScope.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Buffer/IViewBufferScope.cs new file mode 100644 index 0000000000..d8ad81044d --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Buffer/IViewBufferScope.cs @@ -0,0 +1,17 @@ +// 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. + +namespace Microsoft.AspNet.Mvc.ViewFeatures.Buffer +{ + /// + /// Creates and manages the lifetime of instances. + /// + public interface IViewBufferScope + { + /// + /// Gets a . + /// + /// The . + ViewBufferValue[] GetSegment(); + } +} diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Buffer/MemoryPoolViewBufferScope.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Buffer/MemoryPoolViewBufferScope.cs new file mode 100644 index 0000000000..084a2bc1cf --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Buffer/MemoryPoolViewBufferScope.cs @@ -0,0 +1,80 @@ +// 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.Buffers; +using System.Collections.Generic; + +namespace Microsoft.AspNet.Mvc.ViewFeatures.Buffer +{ + /// + /// A that uses pooled memory. + /// + public class MemoryPoolViewBufferScope : IViewBufferScope, IDisposable + { + public static readonly int SegmentSize = 512; + private readonly ArrayPool _pool; + private List _leased; + private bool _disposed; + + /// + /// Initializes a new instance of . + /// + /// The for creating + /// instances. + public MemoryPoolViewBufferScope(ArrayPool pool) + { + _pool = pool; + } + + /// + public ViewBufferValue[] GetSegment() + { + if (_disposed) + { + throw new ObjectDisposedException(typeof(MemoryPoolViewBufferScope).FullName); + } + + if (_leased == null) + { + _leased = new List(1); + } + + ViewBufferValue[] segment = null; + + try + { + segment = _pool.Rent(SegmentSize); + _leased.Add(segment); + } + catch when (segment != null) + { + _pool.Return(segment); + throw; + } + + return segment; + } + + /// + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + + if (_leased == null) + { + return; + } + + for (var i = 0; i < _leased.Count; i++) + { + _pool.Return(_leased[i]); + } + + _leased.Clear(); + } + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Razor/Buffer/RazorBuffer.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Buffer/ViewBuffer.cs similarity index 76% rename from src/Microsoft.AspNet.Mvc.Razor/Buffer/RazorBuffer.cs rename to src/Microsoft.AspNet.Mvc.ViewFeatures/Buffer/ViewBuffer.cs index 906479aeef..9f94fafaa5 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Buffer/RazorBuffer.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Buffer/ViewBuffer.cs @@ -9,23 +9,23 @@ using System.Text.Encodings.Web; using Microsoft.AspNet.Html; using Microsoft.AspNet.Mvc.Rendering; -namespace Microsoft.AspNet.Mvc.Razor.Buffer +namespace Microsoft.AspNet.Mvc.ViewFeatures.Buffer { /// - /// An that is backed by a buffer provided by . + /// An that is backed by a buffer provided by . /// [DebuggerDisplay("{DebuggerToString()}")] - public class RazorBuffer : IHtmlContentBuilder + public class ViewBuffer : IHtmlContentBuilder { - private readonly IRazorBufferScope _bufferScope; + private readonly IViewBufferScope _bufferScope; private readonly string _name; /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// - /// The . + /// The . /// A name to identify this instance. - public RazorBuffer(IRazorBufferScope bufferScope, string name) + public ViewBuffer(IViewBufferScope bufferScope, string name) { if (bufferScope == null) { @@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer /// /// Gets the backing buffer. /// - public IList BufferSegments { get; private set; } + public IList BufferSegments { get; private set; } /// /// Gets the count of entries in the last element of . @@ -54,7 +54,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer return this; } - AppendValue(new RazorValue(unencoded)); + AppendValue(new ViewBufferValue(unencoded)); return this; } @@ -66,7 +66,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer return this; } - AppendValue(new RazorValue(content)); + AppendValue(new ViewBufferValue(content)); return this; } @@ -79,23 +79,23 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer } var value = new HtmlString(encoded); - AppendValue(new RazorValue(value)); + AppendValue(new ViewBufferValue(value)); return this; } - private void AppendValue(RazorValue value) + private void AppendValue(ViewBufferValue value) { - RazorBufferSegment segment; + ViewBufferValue[] segment; if (BufferSegments == null) { - BufferSegments = new List(1); + BufferSegments = new List(1); segment = _bufferScope.GetSegment(); BufferSegments.Add(segment); } else { segment = BufferSegments[BufferSegments.Count - 1]; - if (CurrentCount == segment.Data.Count) + if (CurrentCount == segment.Length) { segment = _bufferScope.GetSegment(); BufferSegments.Add(segment); @@ -103,7 +103,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer } } - segment.Data.Array[segment.Data.Offset + CurrentCount] = value; + segment[CurrentCount] = value; CurrentCount++; } @@ -137,11 +137,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer for (var i = 0; i < BufferSegments.Count; i++) { var segment = BufferSegments[i]; - var count = i == BufferSegments.Count - 1 ? CurrentCount : segment.Data.Count; + var count = i == BufferSegments.Count - 1 ? CurrentCount : segment.Length; for (var j = 0; j < count; j++) { - var value = segment.Data.Array[segment.Data.Offset + j]; + var value = segment[j]; value.WriteTo(writer, encoder); } } diff --git a/src/Microsoft.AspNet.Mvc.Razor/Buffer/RazorValue.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Buffer/ViewBufferValue.cs similarity index 81% rename from src/Microsoft.AspNet.Mvc.Razor/Buffer/RazorValue.cs rename to src/Microsoft.AspNet.Mvc.ViewFeatures/Buffer/ViewBufferValue.cs index 0807575985..7a29077b2c 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Buffer/RazorValue.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Buffer/ViewBufferValue.cs @@ -6,27 +6,27 @@ using System.IO; using System.Text.Encodings.Web; using Microsoft.AspNet.Html; -namespace Microsoft.AspNet.Mvc.Razor.Buffer +namespace Microsoft.AspNet.Mvc.ViewFeatures.Buffer { /// /// Encapsulates a string or value. /// - public struct RazorValue + public struct ViewBufferValue { /// - /// Initializes a new instance of with a string value. + /// Initializes a new instance of with a string value. /// /// The value. - public RazorValue(string value) + public ViewBufferValue(string value) { Value = value; } /// - /// Initializes a new instance of with a value. + /// Initializes a new instance of with a value. /// /// The . - public RazorValue(IHtmlContent content) + public ViewBufferValue(IHtmlContent content) { Value = content; } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs index db4ad3ed7b..a75acecf89 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Buffers; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Controllers; using Microsoft.AspNet.Mvc.Formatters; @@ -9,8 +10,10 @@ using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewComponents; using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Mvc.ViewFeatures; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Mvc.ViewFeatures.Internal; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.MemoryPool; using Microsoft.Extensions.OptionsModel; namespace Microsoft.Extensions.DependencyInjection @@ -136,6 +139,9 @@ namespace Microsoft.Extensions.DependencyInjection // These are stateless so their lifetime isn't really important. services.TryAddSingleton(); services.TryAddSingleton(); + + services.TryAddSingleton(ArrayPool.Shared); + services.TryAddScoped(); } } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs index 792429d7c9..4de8c683c1 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Html; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewFeatures; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Mvc.ViewFeatures.Internal; namespace Microsoft.AspNet.Mvc.ViewComponents @@ -18,13 +19,15 @@ namespace Microsoft.AspNet.Mvc.ViewComponents private readonly HtmlEncoder _htmlEncoder; private readonly IViewComponentInvokerFactory _invokerFactory; private readonly IViewComponentSelector _selector; + private readonly IViewBufferScope _viewBufferScope; private ViewContext _viewContext; public DefaultViewComponentHelper( IViewComponentDescriptorCollectionProvider descriptorProvider, HtmlEncoder htmlEncoder, IViewComponentSelector selector, - IViewComponentInvokerFactory invokerFactory) + IViewComponentInvokerFactory invokerFactory, + IViewBufferScope viewBufferScope) { if (descriptorProvider == null) { @@ -46,10 +49,16 @@ namespace Microsoft.AspNet.Mvc.ViewComponents throw new ArgumentNullException(nameof(invokerFactory)); } + if (viewBufferScope == null) + { + throw new ArgumentNullException(nameof(viewBufferScope)); + } + _descriptorProvider = descriptorProvider; _htmlEncoder = htmlEncoder; _selector = selector; _invokerFactory = invokerFactory; + _viewBufferScope = viewBufferScope; } public void Contextualize(ViewContext viewContext) @@ -71,10 +80,11 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var descriptor = SelectComponent(name); - using (var writer = new StringCollectionTextWriter(_viewContext.Writer.Encoding)) + var viewBuffer = new ViewBuffer(_viewBufferScope, name); + using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding)) { InvokeCore(writer, descriptor, arguments); - return writer.Content; + return writer.ContentBuilder; } } @@ -86,11 +96,11 @@ namespace Microsoft.AspNet.Mvc.ViewComponents } var descriptor = SelectComponent(componentType); - - using (var writer = new StringCollectionTextWriter(_viewContext.Writer.Encoding)) + var viewBuffer = new ViewBuffer(_viewBufferScope, componentType.Name); + using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding)) { InvokeCore(writer, descriptor, arguments); - return writer.Content; + return writer.ContentBuilder; } } @@ -125,10 +135,11 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var descriptor = SelectComponent(name); - using (var writer = new StringCollectionTextWriter(_viewContext.Writer.Encoding)) + var viewBuffer = new ViewBuffer(_viewBufferScope, name); + using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding)) { await InvokeCoreAsync(writer, descriptor, arguments); - return writer.Content; + return writer.ContentBuilder; } } @@ -141,10 +152,11 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var descriptor = SelectComponent(componentType); - using (var writer = new StringCollectionTextWriter(_viewContext.Writer.Encoding)) + var viewBuffer = new ViewBuffer(_viewBufferScope, componentType.Name); + using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding)) { await InvokeCoreAsync(writer, descriptor, arguments); - return writer.Content; + return writer.ContentBuilder; } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/DefaultDisplayTemplates.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/DefaultDisplayTemplates.cs index 049e132367..5081b41c07 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/DefaultDisplayTemplates.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/DefaultDisplayTemplates.cs @@ -10,9 +10,9 @@ using Microsoft.AspNet.Html; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewEngines; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Mvc.ViewFeatures.Internal; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Internal; namespace Microsoft.AspNet.Mvc.ViewFeatures { @@ -124,6 +124,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var fieldNameBase = oldPrefix; var result = new HtmlContentBuilder(); var viewEngine = serviceProvider.GetRequiredService(); + var viewBufferScope = serviceProvider.GetRequiredService(); var index = 0; foreach (var item in collection) @@ -143,6 +144,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var templateBuilder = new TemplateBuilder( viewEngine, + viewBufferScope, htmlHelper.ViewContext, htmlHelper.ViewData, modelExplorer, @@ -223,6 +225,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var serviceProvider = htmlHelper.ViewContext.HttpContext.RequestServices; var viewEngine = serviceProvider.GetRequiredService(); + var viewBufferScope = serviceProvider.GetRequiredService(); var content = new HtmlContentBuilder(); foreach (var propertyExplorer in modelExplorer.Properties) @@ -235,6 +238,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var templateBuilder = new TemplateBuilder( viewEngine, + viewBufferScope, htmlHelper.ViewContext, htmlHelper.ViewData, propertyExplorer, diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/DefaultEditorTemplates.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/DefaultEditorTemplates.cs index 87bf3bd671..c7ef0a4378 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/DefaultEditorTemplates.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/DefaultEditorTemplates.cs @@ -10,6 +10,7 @@ using Microsoft.AspNet.Html; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewEngines; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Mvc.ViewFeatures.Internal; using Microsoft.Extensions.DependencyInjection; @@ -87,6 +88,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var fieldNameBase = oldPrefix; var result = new HtmlContentBuilder(); var viewEngine = serviceProvider.GetRequiredService(); + var viewBufferScope = serviceProvider.GetRequiredService(); var index = 0; foreach (var item in collection) @@ -106,6 +108,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var templateBuilder = new TemplateBuilder( viewEngine, + viewBufferScope, htmlHelper.ViewContext, htmlHelper.ViewData, modelExplorer, @@ -245,6 +248,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var serviceProvider = htmlHelper.ViewContext.HttpContext.RequestServices; var viewEngine = serviceProvider.GetRequiredService(); + var viewBufferScope = serviceProvider.GetRequiredService(); var content = new HtmlContentBuilder(); foreach (var propertyExplorer in modelExplorer.Properties) @@ -257,6 +261,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var templateBuilder = new TemplateBuilder( viewEngine, + viewBufferScope, htmlHelper.ViewContext, htmlHelper.ViewData, propertyExplorer, diff --git a/src/Microsoft.AspNet.Mvc.Razor/HtmlContentWrapperTextWriter.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlContentWrapperTextWriter.cs similarity index 96% rename from src/Microsoft.AspNet.Mvc.Razor/HtmlContentWrapperTextWriter.cs rename to src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlContentWrapperTextWriter.cs index 6a53c013a1..ab1610ab5e 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/HtmlContentWrapperTextWriter.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlContentWrapperTextWriter.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Html; using Microsoft.AspNet.Mvc.Internal; -namespace Microsoft.AspNet.Mvc.Razor +namespace Microsoft.AspNet.Mvc.ViewFeatures { /// /// implementation which writes to an instance. @@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// Initializes a new instance of the class. /// /// The to write to. - /// The in which output is written. + /// The in which output is written. public HtmlContentWrapperTextWriter(IHtmlContentBuilder contentBuilder, Encoding encoding) { if (contentBuilder == null) diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs index b0caa20804..6810a4f476 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs @@ -13,6 +13,7 @@ using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewEngines; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Mvc.ViewFeatures.Internal; using Microsoft.Extensions.Internal; @@ -33,6 +34,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures private readonly IHtmlGenerator _htmlGenerator; private readonly ICompositeViewEngine _viewEngine; private readonly HtmlEncoder _htmlEncoder; + private readonly IViewBufferScope _bufferScope; private ViewContext _viewContext; @@ -43,6 +45,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures IHtmlGenerator htmlGenerator, ICompositeViewEngine viewEngine, IModelMetadataProvider metadataProvider, + IViewBufferScope bufferScope, HtmlEncoder htmlEncoder, UrlEncoder urlEncoder, JavaScriptEncoder javaScriptEncoder) @@ -62,6 +65,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures throw new ArgumentNullException(nameof(metadataProvider)); } + if (bufferScope == null) + { + throw new ArgumentNullException(nameof(bufferScope)); + } + if (htmlEncoder == null) { throw new ArgumentNullException(nameof(htmlEncoder)); @@ -80,6 +88,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures _viewEngine = viewEngine; _htmlGenerator = htmlGenerator; _htmlEncoder = htmlEncoder; + _bufferScope = bufferScope; MetadataProvider = metadataProvider; UrlEncoder = urlEncoder; JavaScriptEncoder = javaScriptEncoder; @@ -349,10 +358,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures { var metadata = ExpressionMetadataProvider.FromStringExpression(expression, ViewData, MetadataProvider); - return GenerateDisplay(metadata, - htmlFieldName ?? ExpressionHelper.GetExpressionText(expression), - templateName, - additionalViewData); + return GenerateDisplay( + metadata, + htmlFieldName ?? ExpressionHelper.GetExpressionText(expression), + templateName, + additionalViewData); } /// @@ -493,10 +503,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures throw new ArgumentNullException(nameof(partialViewName)); } - using (var writer = new StringCollectionTextWriter(Encoding.UTF8)) + var viewBuffer = new ViewBuffer(_bufferScope, partialViewName); + using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, Encoding.UTF8)) { await RenderPartialCoreAsync(partialViewName, model, viewData, writer); - return writer.Content; + return writer.ContentBuilder; } } @@ -519,6 +530,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures { var templateBuilder = new TemplateBuilder( _viewEngine, + _bufferScope, ViewContext, ViewData, modelExplorer, @@ -815,6 +827,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures { var templateBuilder = new TemplateBuilder( _viewEngine, + _bufferScope, ViewContext, ViewData, modelExplorer, diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs index 1443767a2f..35745e27e2 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs @@ -9,6 +9,7 @@ using Microsoft.AspNet.Html; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewEngines; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; namespace Microsoft.AspNet.Mvc.ViewFeatures { @@ -21,35 +22,19 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures IHtmlGenerator htmlGenerator, ICompositeViewEngine viewEngine, IModelMetadataProvider metadataProvider, + IViewBufferScope bufferScope, HtmlEncoder htmlEncoder, UrlEncoder urlEncoder, JavaScriptEncoder javaScriptEncoder) - : base(htmlGenerator, viewEngine, metadataProvider, htmlEncoder, urlEncoder, javaScriptEncoder) + : base( + htmlGenerator, + viewEngine, + metadataProvider, + bufferScope, + htmlEncoder, + urlEncoder, + javaScriptEncoder) { - if (htmlGenerator == null) - { - throw new ArgumentNullException(nameof(htmlGenerator)); - } - if (viewEngine == null) - { - throw new ArgumentNullException(nameof(viewEngine)); - } - if (metadataProvider == null) - { - throw new ArgumentNullException(nameof(metadataProvider)); - } - if (htmlEncoder == null) - { - throw new ArgumentNullException(nameof(htmlEncoder)); - } - if (urlEncoder == null) - { - throw new ArgumentNullException(nameof(urlEncoder)); - } - if (javaScriptEncoder == null) - { - throw new ArgumentNullException(nameof(javaScriptEncoder)); - } } /// diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/StringCollectionTextWriter.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/StringCollectionTextWriter.cs deleted file mode 100644 index bdac0103b7..0000000000 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/StringCollectionTextWriter.cs +++ /dev/null @@ -1,260 +0,0 @@ -// 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.Diagnostics; -using System.IO; -using System.Text; -using System.Text.Encodings.Web; -using System.Threading.Tasks; -using Microsoft.AspNet.Html; - -namespace Microsoft.AspNet.Mvc.ViewFeatures -{ - /// - /// A that stores individual write operations as a sequence of - /// and instances. - /// - /// - /// This is primarily designed to avoid creating large in-memory strings. - /// Refer to https://aspnetwebstack.codeplex.com/workitem/585 for more details. - /// - public class StringCollectionTextWriter : HtmlTextWriter - { - private const int MaxCharToStringLength = 1024; - private static readonly Task _completedTask = Task.FromResult(0); - - private readonly Encoding _encoding; - private readonly StringCollectionTextWriterContent _content; - - /// - /// Creates a new instance of . - /// - /// The character in which the output is written. - public StringCollectionTextWriter(Encoding encoding) - { - _encoding = encoding; - Entries = new List(); - _content = new StringCollectionTextWriterContent(Entries); - } - - /// - public override Encoding Encoding - { - get { return _encoding; } - } - - /// - /// Gets the content written to the writer as an . - /// - public IHtmlContent Content => _content; - - // internal for testing purposes. - internal List Entries { get; } - - /// - public override void Write(char value) - { - _content.Append(value.ToString()); - } - - /// - public override void Write(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - if (count < 0 || (buffer.Length - index < count)) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - while (count > 0) - { - // Split large char arrays into 1KB strings. - var currentCount = count; - if (MaxCharToStringLength < currentCount) - { - currentCount = MaxCharToStringLength; - } - - _content.Append(new string(buffer, index, currentCount)); - index += currentCount; - count -= currentCount; - } - } - - /// - public override void Write(string value) - { - if (string.IsNullOrEmpty(value)) - { - return; - } - - _content.Append(value); - } - - /// - public override void Write(IHtmlContent value) - { - _content.Append(value); - } - - /// - public override Task WriteAsync(char value) - { - Write(value); - return _completedTask; - } - - /// - public override Task WriteAsync(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - Write(buffer, index, count); - return _completedTask; - } - - /// - public override Task WriteAsync(string value) - { - Write(value); - return _completedTask; - } - - /// - public override void WriteLine() - { - _content.Append(Environment.NewLine); - } - - /// - public override void WriteLine(string value) - { - Write(value); - WriteLine(); - } - - /// - public override Task WriteLineAsync(char value) - { - WriteLine(value); - return _completedTask; - } - - /// - public override Task WriteLineAsync(char[] value, int start, int offset) - { - WriteLine(value, start, offset); - return _completedTask; - } - - /// - public override Task WriteLineAsync(string value) - { - WriteLine(value); - return _completedTask; - } - - /// - public override Task WriteLineAsync() - { - WriteLine(); - return _completedTask; - } - - /// - /// If the specified is a the contents - /// are copied. It is just written to the otherwise. - /// - /// The to which the content must be copied/written. - /// The to encode the copied/written content. - public void CopyTo(TextWriter writer, HtmlEncoder encoder) - { - var htmlTextWriter = writer as HtmlTextWriter; - if (htmlTextWriter != null) - { - htmlTextWriter.Write(Content); - } - else - { - Content.WriteTo(writer, encoder); - } - } - - /// - /// If the specified is a the contents - /// are copied. It is just written to the otherwise. - /// - /// The to which the content must be copied/written. - /// The to encode the copied/written content. - public Task CopyToAsync(TextWriter writer, HtmlEncoder encoder) - { - CopyTo(writer, encoder); - return _completedTask; - } - - [DebuggerDisplay("{DebuggerToString()}")] - private class StringCollectionTextWriterContent : IHtmlContent - { - private readonly List _entries; - - public StringCollectionTextWriterContent(List entries) - { - _entries = entries; - } - - public void Append(string value) - { - _entries.Add(value); - } - - public void Append(IHtmlContent content) - { - _entries.Add(content); - } - - public void WriteTo(TextWriter writer, HtmlEncoder encoder) - { - foreach (var item in _entries) - { - if (item == null) - { - continue; - } - - var itemAsString = item as string; - if (itemAsString != null) - { - writer.Write(itemAsString); - } - else - { - ((IHtmlContent)item).WriteTo(writer, encoder); - } - } - } - - private string DebuggerToString() - { - using (var writer = new StringWriter()) - { - WriteTo(writer, HtmlEncoder.Default); - return writer.ToString(); - } - } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/TemplateBuilder.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/TemplateBuilder.cs index 3410288dcd..f1bece92be 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/TemplateBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/TemplateBuilder.cs @@ -7,24 +7,27 @@ using Microsoft.AspNet.Html; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewEngines; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal { public class TemplateBuilder { - private IViewEngine _viewEngine; - private ViewContext _viewContext; - private ViewDataDictionary _viewData; - private ModelExplorer _modelExplorer; + private readonly IViewEngine _viewEngine; + private readonly IViewBufferScope _bufferScope; + private readonly ViewContext _viewContext; + private readonly ViewDataDictionary _viewData; + private readonly ModelExplorer _modelExplorer; private object _model; - private ModelMetadata _metadata; - private string _htmlFieldName; - private string _templateName; - private bool _readOnly; - private object _additionalViewData; + private readonly ModelMetadata _metadata; + private readonly string _htmlFieldName; + private readonly string _templateName; + private readonly bool _readOnly; + private readonly object _additionalViewData; public TemplateBuilder( IViewEngine viewEngine, + IViewBufferScope bufferScope, ViewContext viewContext, ViewDataDictionary viewData, ModelExplorer modelExplorer, @@ -38,6 +41,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal throw new ArgumentNullException(nameof(viewEngine)); } + if (bufferScope == null) + { + throw new ArgumentNullException(nameof(_bufferScope)); + } + if (viewContext == null) { throw new ArgumentNullException(nameof(viewContext)); @@ -54,6 +62,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal } _viewEngine = viewEngine; + _bufferScope = bufferScope; _viewContext = viewContext; _viewData = viewData; _modelExplorer = modelExplorer; @@ -117,7 +126,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal var visitedObjectsKey = _model ?? _modelExplorer.ModelType; viewData.TemplateInfo.AddVisited(visitedObjectsKey); - var templateRenderer = new TemplateRenderer(_viewEngine, _viewContext, viewData, _templateName, _readOnly); + var templateRenderer = new TemplateRenderer( + _viewEngine, + _bufferScope, + _viewContext, + viewData, + _templateName, + _readOnly); return templateRenderer.Render(); } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/TemplateRenderer.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/TemplateRenderer.cs index 51043d46ce..4d6ce9a3fb 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/TemplateRenderer.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/TemplateRenderer.cs @@ -11,6 +11,7 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewEngines; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal @@ -67,14 +68,16 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal { IEnumerableOfIFormFileName, DefaultEditorTemplates.FileCollectionInputTemplate }, }; - private ViewContext _viewContext; - private ViewDataDictionary _viewData; - private IViewEngine _viewEngine; - private string _templateName; - private bool _readOnly; + private readonly IViewEngine _viewEngine; + private readonly IViewBufferScope _bufferScope; + private readonly ViewContext _viewContext; + private readonly ViewDataDictionary _viewData; + private readonly string _templateName; + private readonly bool _readOnly; public TemplateRenderer( IViewEngine viewEngine, + IViewBufferScope bufferScope, ViewContext viewContext, ViewDataDictionary viewData, string templateName, @@ -85,6 +88,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal throw new ArgumentNullException(nameof(viewEngine)); } + if (bufferScope == null) + { + throw new ArgumentNullException(nameof(bufferScope)); + } + if (viewContext == null) { throw new ArgumentNullException(nameof(viewContext)); @@ -96,6 +104,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal } _viewEngine = viewEngine; + _bufferScope = bufferScope; _viewContext = viewContext; _viewData = viewData; _templateName = templateName; @@ -118,7 +127,8 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal if (viewEngineResult.Success) { - using (var writer = new StringCollectionTextWriter(_viewContext.Writer.Encoding)) + var viewBuffer = new ViewBuffer(_bufferScope, viewName); + using (var writer = new HtmlContentWrapperTextWriter(viewBuffer, _viewContext.Writer.Encoding)) { // Forcing synchronous behavior so users don't have to await templates. var view = viewEngineResult.View; @@ -127,7 +137,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal var viewContext = new ViewContext(_viewContext, viewEngineResult.View, _viewData, writer); var renderTask = viewEngineResult.View.RenderAsync(viewContext); renderTask.GetAwaiter().GetResult(); - return writer.Content; + return writer.ContentBuilder; } } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/project.json b/src/Microsoft.AspNet.Mvc.ViewFeatures/project.json index c50b17a84c..881defd4c8 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/project.json +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/project.json @@ -32,7 +32,8 @@ "version": "1.0.0-*", "type": "build" }, - "Microsoft.Extensions.WebEncoders": "1.0.0-*" + "Microsoft.Extensions.WebEncoders": "1.0.0-*", + "System.Buffers": "4.0.0-*" }, "frameworks": { "net451": {}, diff --git a/test/Microsoft.AspNet.Mvc.Localization.Test/Internal/MvcLocalizationServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.Localization.Test/Internal/MvcLocalizationServiceCollectionExtensionsTest.cs index 9693ce73f1..479faeef72 100644 --- a/test/Microsoft.AspNet.Mvc.Localization.Test/Internal/MvcLocalizationServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Localization.Test/Internal/MvcLocalizationServiceCollectionExtensionsTest.cs @@ -111,6 +111,7 @@ namespace Microsoft.AspNet.Mvc.Localization.Internal { // Arrange var collection = new ServiceCollection(); + var htmlEncoder = new HtmlTestEncoder(); collection.Configure(options => { @@ -123,9 +124,10 @@ namespace Microsoft.AspNet.Mvc.Localization.Internal LanguageViewLocationExpanderFormat.Suffix, setupAction: null); + collection.Add(ServiceDescriptor.Transient(typeof(IHtmlLocalizer<>), typeof(TestHtmlLocalizer<>))); collection.Add(ServiceDescriptor.Transient(typeof(IHtmlLocalizer), typeof(TestViewLocalizer))); - collection.Add(ServiceDescriptor.Singleton(typeof(HtmlEncoder), typeof(HtmlTestEncoder))); + collection.Add(ServiceDescriptor.Singleton(typeof(HtmlEncoder), htmlEncoder)); // Assert Assert.Collection(collection, @@ -172,7 +174,7 @@ namespace Microsoft.AspNet.Mvc.Localization.Internal service => { Assert.Equal(typeof(HtmlEncoder), service.ServiceType); - Assert.Equal(typeof(HtmlTestEncoder), service.ImplementationInstance); + Assert.Same(htmlEncoder, service.ImplementationInstance); Assert.Equal(ServiceLifetime.Singleton, service.Lifetime); }); } diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs index 1bde6c983e..f791f7051d 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs @@ -5,22 +5,22 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Html; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.ModelBinding; -using Microsoft.AspNet.Mvc.Razor.Buffer; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Mvc.ViewFeatures; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Testing; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.WebEncoders.Testing; using Moq; using Xunit; @@ -185,7 +185,7 @@ namespace Microsoft.AspNet.Mvc.Razor var page = CreatePage(v => { v.HtmlEncoder = new HtmlTestEncoder(); - var buffer = new RazorBuffer(new TestRazorBufferScope(), v.Path); + var buffer = new ViewBuffer(new TestViewBufferScope(), v.Path); v.StartTagHelperWritingScope(new RazorTextWriter(TextWriter.Null, buffer, v.HtmlEncoder)); v.Write("Hello "); v.Write("World!"); @@ -1127,7 +1127,7 @@ namespace Microsoft.AspNet.Mvc.Razor public async Task Write_WithHtmlString_WritesValueWithoutEncoding() { // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(), string.Empty); + var buffer = new ViewBuffer(new TestViewBufferScope(), string.Empty); var writer = new RazorTextWriter(TextWriter.Null, buffer, new HtmlTestEncoder()); var page = CreatePage(p => @@ -1177,8 +1177,13 @@ namespace Microsoft.AspNet.Mvc.Razor private static ViewContext CreateViewContext(TextWriter writer = null) { writer = writer ?? new StringWriter(); + var httpContext = new DefaultHttpContext(); + var serviceProvider = new ServiceCollection() + .AddSingleton() + .BuildServiceProvider(); + httpContext.RequestServices = serviceProvider; var actionContext = new ActionContext( - new DefaultHttpContext(), + httpContext, new RouteData(), new ActionDescriptor()); return new ViewContext( diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorTextWriterTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorTextWriterTest.cs index e23766384b..cc7f417a62 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorTextWriterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorTextWriterTest.cs @@ -7,8 +7,8 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.AspNet.Mvc.Razor.Buffer; using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Testing; using Microsoft.Extensions.WebEncoders.Testing; using Moq; @@ -24,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test { // Arrange var expected = new object[] { "True", "3", "18446744073709551615", "Hello world", "3.14", "2.718", "m" }; - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); var writer = new RazorTextWriter(TextWriter.Null, buffer, new HtmlTestEncoder()); // Act @@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var expected = new[] { "True", "3", "18446744073709551615", "Hello world", "3.14", "2.718" }; var unbufferedWriter = new Mock(); unbufferedWriter.SetupGet(w => w.Encoding).Returns(Encoding.UTF8); - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); var writer = new RazorTextWriter(unbufferedWriter.Object, buffer, new HtmlTestEncoder()); var testClass = new TestClass(); @@ -76,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test // Arrange var unbufferedWriter = new Mock { CallBase = true }; unbufferedWriter.SetupGet(w => w.Encoding).Returns(Encoding.UTF8); - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); var writer = new RazorTextWriter(unbufferedWriter.Object, buffer, new HtmlTestEncoder()); var buffer1 = new[] { 'a', 'b', 'c', 'd' }; var buffer2 = new[] { 'd', 'e', 'f' }; @@ -106,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test // Arrange var unbufferedWriter = new Mock(); unbufferedWriter.SetupGet(w => w.Encoding).Returns(Encoding.UTF8); - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); var writer = new RazorTextWriter(unbufferedWriter.Object, buffer, new HtmlTestEncoder()); // Act @@ -131,7 +131,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test // Arrange var newLine = Environment.NewLine; var expected = new List { "False", newLine, "1.1", newLine, "3", newLine }; - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); var writer = new RazorTextWriter(TextWriter.Null, buffer, new HtmlTestEncoder()); // Act @@ -150,7 +150,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test // Arrange var unbufferedWriter = new Mock(); unbufferedWriter.SetupGet(w => w.Encoding).Returns(Encoding.UTF8); - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); var writer = new RazorTextWriter(unbufferedWriter.Object, buffer, new HtmlTestEncoder()); // Act @@ -172,7 +172,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test { // Arrange var newLine = Environment.NewLine; - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); var writer = new RazorTextWriter(TextWriter.Null, buffer, new HtmlTestEncoder()); // Act @@ -193,7 +193,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var input2 = "from"; var input3 = "ASP"; var input4 = ".Net"; - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); var writer = new RazorTextWriter(TextWriter.Null, buffer, new HtmlTestEncoder()); // Act @@ -212,7 +212,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test { // Arrange var stringWriter = new StringWriter(); - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); var writer = new RazorTextWriter(stringWriter, buffer, new HtmlTestEncoder()); writer.Flush(); @@ -225,10 +225,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Test Assert.Equal("Hello, world!", stringWriter.ToString()); } - private static object[] GetValues(RazorBuffer buffer) + private static object[] GetValues(ViewBuffer buffer) { return buffer.BufferSegments - .SelectMany(c => c.Data) + .SelectMany(c => c) .Select(d => d.Value) .TakeWhile(d => d != null) .ToArray(); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs index b2bf80dfc9..94773750fb 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs @@ -9,9 +9,9 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.ModelBinding; -using Microsoft.AspNet.Mvc.Razor.Buffer; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewFeatures; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Primitives; @@ -1661,7 +1661,7 @@ namespace Microsoft.AspNet.Mvc.Razor { var httpContext = new DefaultHttpContext(); var serviceProvider = new ServiceCollection() - .AddScoped() + .AddScoped() .BuildServiceProvider(); httpContext.RequestServices = serviceProvider; var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/TestRazorBufferScope.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/TestRazorBufferScope.cs deleted file mode 100644 index ad78f0c0d0..0000000000 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/TestRazorBufferScope.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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; - -namespace Microsoft.AspNet.Mvc.Razor.Buffer -{ - public class TestRazorBufferScope : IRazorBufferScope - { - public const int BufferSize = 128; - private readonly int _offset; - private readonly int _count; - - public TestRazorBufferScope() - : this(0, BufferSize) - { - - } - - public TestRazorBufferScope(int offset, int count) - { - _offset = offset; - _count = count; - } - - public RazorBufferSegment GetSegment() - { - var razorValues = new RazorValue[BufferSize]; - return new RazorBufferSegment(new ArraySegment(razorValues, _offset, _count)); - } - } -} diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/project.json b/test/Microsoft.AspNet.Mvc.Razor.Test/project.json index 1ccadd6f5b..e69d5fb2e5 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/project.json +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/project.json @@ -6,7 +6,8 @@ "compile": [ "../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs", "../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs", - "../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileTrigger.cs" + "../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileTrigger.cs", + "../Microsoft.AspNet.Mvc.ViewFeatures.Test/TestViewBufferScope.cs" ], "dependencies": { "Microsoft.AspNet.Http": "1.0.0-*", diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Buffer/RazorBufferTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Buffer/ViewBufferTest.cs similarity index 64% rename from test/Microsoft.AspNet.Mvc.Razor.Test/Buffer/RazorBufferTest.cs rename to test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Buffer/ViewBufferTest.cs index bb5e0a13bf..32d208ca7a 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Buffer/RazorBufferTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Buffer/ViewBufferTest.cs @@ -5,19 +5,20 @@ using System.IO; using System.Linq; using Microsoft.AspNet.Html; using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.Extensions.WebEncoders.Testing; using Moq; using Xunit; namespace Microsoft.AspNet.Mvc.Razor.Buffer { - public class RazorBufferTest + public class ViewBufferTest { [Fact] public void Append_AddsStringRazorValue() { // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); // Act buffer.Append("Hello world"); @@ -25,14 +26,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer // Assert var segment = Assert.Single(buffer.BufferSegments); Assert.Equal(1, buffer.CurrentCount); - Assert.Equal("Hello world", segment.Data.Array[0].Value); + Assert.Equal("Hello world", segment[0].Value); } [Fact] public void Append_AddsHtmlContentRazorValue() { // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); var content = new HtmlString("hello-world"); // Act @@ -41,14 +42,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer // Assert var segment = Assert.Single(buffer.BufferSegments); Assert.Equal(1, buffer.CurrentCount); - Assert.Same(content, segment.Data.Array[0].Value); + Assert.Same(content, segment[0].Value); } [Fact] public void AppendHtml_AddsHtmlStringValues() { // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); var value = "Hello world"; // Act @@ -57,7 +58,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer // Assert var segment = Assert.Single(buffer.BufferSegments); Assert.Equal(1, buffer.CurrentCount); - var htmlString = Assert.IsType(segment.Data.Array[0].Value); + var htmlString = Assert.IsType(segment[0].Value); Assert.Equal("Hello world", htmlString.ToString()); } @@ -65,8 +66,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer public void Append_CreatesNewSegments_WhenCurrentSegmentIsFull() { // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); - var expected = Enumerable.Range(0, TestRazorBufferScope.BufferSize).Select(i => i.ToString()); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); + var expected = Enumerable.Range(0, TestViewBufferScope.DefaultBufferSize).Select(i => i.ToString()); // Act foreach (var item in expected) @@ -79,51 +80,22 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer // Assert Assert.Equal(2, buffer.CurrentCount); Assert.Collection(buffer.BufferSegments, - segment => Assert.Equal(expected, segment.Data.Array.Select(v => v.Value)), + segment => Assert.Equal(expected, segment.Select(v => v.Value)), segment => { - var array = segment.Data.Array; + var array = segment; Assert.Equal("Hello", array[0].Value); Assert.Equal("world", array[1].Value); }); } - [Fact] - public void Append_CreatesNewSegments_WhenCurrentSegmentIsFull_ForBuffersWithNonZeroOffsets() - { - // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(3, 2), "some-name"); - - // Act - buffer.Append("1"); - buffer.Append("2"); - buffer.Append("3"); - buffer.Append("4"); - - // Assert - Assert.Equal(2, buffer.CurrentCount); - Assert.Collection(buffer.BufferSegments, - segment => - { - var array = segment.Data.Array; - Assert.Equal("1", array[3].Value); - Assert.Equal("2", array[4].Value); - }, - segment => - { - var array = segment.Data.Array; - Assert.Equal("3", array[3].Value); - Assert.Equal("4", array[4].Value); - }); - } - [Theory] [InlineData(1)] - [InlineData(TestRazorBufferScope.BufferSize + 3)] + [InlineData(TestViewBufferScope.DefaultBufferSize + 3)] public void Clear_ResetsBackingBufferAndIndex(int valuesToWrite) { // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); // Act for (var i = 0; i < valuesToWrite; i++) @@ -136,14 +108,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer // Assert var segment = Assert.Single(buffer.BufferSegments); Assert.Equal(1, buffer.CurrentCount); - Assert.Equal("world", segment.Data.Array[0].Value); + Assert.Equal("world", segment[0].Value); } [Fact] public void WriteTo_WritesSelf_WhenWriterIsHtmlTextWriter() { // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); var htmlWriter = new Mock(); htmlWriter.Setup(w => w.Write(buffer)).Verifiable(); @@ -159,7 +131,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer public void WriteTo_WritesRazorValues_ToTextWriter() { // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(), "some-name"); var writer = new StringWriter(); // Act @@ -180,7 +152,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Buffer public void WriteTo_WritesRazorValuesFromAllBuffers(int valuesToWrite) { // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(1, 5), "some-name"); + var buffer = new ViewBuffer(new TestViewBufferScope(4), "some-name"); var writer = new StringWriter(); var expected = string.Join("", Enumerable.Range(0, valuesToWrite).Select(_ => "abc")); diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs index 9a30a9bae2..baf250d3aa 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs @@ -18,7 +18,9 @@ using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Mvc.ViewFeatures; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Routing; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.OptionsModel; using Microsoft.Extensions.WebEncoders.Testing; using Moq; @@ -240,21 +242,6 @@ namespace Microsoft.AspNet.Mvc.Rendering .Setup(f => f.GetUrlHelper(It.IsAny())) .Returns(urlHelper); - var serviceProvider = new Mock(); - serviceProvider - .Setup(s => s.GetService(typeof(ICompositeViewEngine))) - .Returns(viewEngine); - serviceProvider - .Setup(s => s.GetService(typeof(IUrlHelperFactory))) - .Returns(urlHelperFactory.Object); - serviceProvider - .Setup(s => s.GetService(typeof(IViewComponentHelper))) - .Returns(new Mock().Object); - serviceProvider - .Setup(s => s.GetService(typeof(IViewComponentHelper))) - .Returns(new Mock().Object); - - httpContext.RequestServices = serviceProvider.Object; if (htmlGenerator == null) { htmlGenerator = new DefaultHtmlGenerator( @@ -270,6 +257,7 @@ namespace Microsoft.AspNet.Mvc.Rendering htmlGenerator, viewEngine, provider, + new TestViewBufferScope(), new HtmlTestEncoder(), UrlEncoder.Default, JavaScriptEncoder.Default); @@ -278,14 +266,22 @@ namespace Microsoft.AspNet.Mvc.Rendering { innerHelper = innerHelperWrapper(innerHelper); } - serviceProvider - .Setup(s => s.GetService(typeof(IHtmlHelper))) - .Returns(() => innerHelper); + + var serviceProvider = new ServiceCollection() + .AddSingleton(viewEngine) + .AddSingleton(urlHelperFactory.Object) + .AddSingleton(Mock.Of()) + .AddSingleton(innerHelper) + .AddSingleton() + .BuildServiceProvider(); + + httpContext.RequestServices = serviceProvider; var htmlHelper = new HtmlHelper( htmlGenerator, viewEngine, provider, + new TestViewBufferScope(), new HtmlTestEncoder(), UrlEncoder.Default, JavaScriptEncoder.Default); diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs index 29f2f34ea4..70d1e72ef5 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs @@ -10,6 +10,7 @@ using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Mvc.ViewFeatures; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Testing; using Moq; using Xunit; @@ -1567,6 +1568,7 @@ namespace Microsoft.AspNet.Mvc.Rendering new Mock(MockBehavior.Strict).Object, new Mock(MockBehavior.Strict).Object, metadataProvider, + new TestViewBufferScope(), new Mock(MockBehavior.Strict).Object, new Mock(MockBehavior.Strict).Object, new Mock(MockBehavior.Strict).Object) diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/ViewContextTests.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/ViewContextTests.cs index 5a9c10e6d3..cf5f507fb2 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/ViewContextTests.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/ViewContextTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Text; +using Microsoft.AspNet.Html; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.ModelBinding; @@ -58,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.Rendering htmlHelperOptions: new HtmlHelperOptions()); var view = Mock.Of(); var viewData = new ViewDataDictionary(originalContext.ViewData); - var writer = new StringCollectionTextWriter(Encoding.UTF8); + var writer = new HtmlContentWrapperTextWriter(new HtmlContentBuilder(), Encoding.UTF8); // Act var context = new ViewContext(originalContext, view, viewData, writer); diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/TestViewBufferScope.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/TestViewBufferScope.cs new file mode 100644 index 0000000000..174c768933 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/TestViewBufferScope.cs @@ -0,0 +1,18 @@ +// 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. + +namespace Microsoft.AspNet.Mvc.ViewFeatures.Buffer +{ + public class TestViewBufferScope : IViewBufferScope + { + public const int DefaultBufferSize = 128; + private readonly int _bufferSize; + + public TestViewBufferScope(int bufferSize = DefaultBufferSize) + { + _bufferSize = bufferSize; + } + + public ViewBufferValue[] GetSegment() => new ViewBufferValue[_bufferSize]; + } +} diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs index 75ed4b33b3..6a67d670b5 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs @@ -16,6 +16,7 @@ using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.ViewComponents; using Microsoft.AspNet.Mvc.ViewFeatures; +using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -482,6 +483,7 @@ namespace Microsoft.AspNet.Mvc services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); return services; } diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/HtmlContentWrapperTextWriterTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/HtmlContentWrapperTextWriterTest.cs similarity index 66% rename from test/Microsoft.AspNet.Mvc.Razor.Test/HtmlContentWrapperTextWriterTest.cs rename to test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/HtmlContentWrapperTextWriterTest.cs index 78bfc7746e..0ed1a854a2 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/HtmlContentWrapperTextWriterTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/HtmlContentWrapperTextWriterTest.cs @@ -2,14 +2,17 @@ // 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.Linq; using System.Text; +using System.Text.Encodings.Web; using System.Threading.Tasks; -using Microsoft.AspNet.Mvc.Razor.Buffer; +using Microsoft.AspNet.Html; using Microsoft.AspNet.Mvc.Rendering; using Xunit; -namespace Microsoft.AspNet.Mvc.Razor +namespace Microsoft.AspNet.Mvc.ViewFeatures { public class HtmlContentWrapperTextWriterTest { @@ -20,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.Razor var input1 = new ArraySegment(new char[] { 'a', 'b', 'c', 'd' }, 1, 3); var input2 = new ArraySegment(new char[] { 'e', 'f' }, 0, 2); var input3 = new ArraySegment(new char[] { 'g', 'h', 'i', 'j' }, 3, 1); - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new TestHtmlContentBuilder(); var writer = new HtmlContentWrapperTextWriter(buffer, Encoding.UTF8); // Act @@ -29,12 +32,11 @@ namespace Microsoft.AspNet.Mvc.Razor await writer.WriteLineAsync(input3.Array, input3.Offset, input3.Count); // Assert - var bufferValues = GetValues(buffer); - Assert.Equal(4, bufferValues.Length); - Assert.Equal("bcd", bufferValues[0]); - Assert.Equal("ef", bufferValues[1]); - Assert.Equal("j", bufferValues[2]); - Assert.Equal(Environment.NewLine, bufferValues[3]); + Assert.Collection(buffer.Values, + value => Assert.Equal("bcd", value), + value => Assert.Equal("ef", value), + value => Assert.Equal("j", value), + value => Assert.Equal(Environment.NewLine, value)); } [Fact] @@ -42,14 +44,15 @@ namespace Microsoft.AspNet.Mvc.Razor { // Arrange var charArray = Enumerable.Range(0, 2050).Select(_ => 'a').ToArray(); - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new TestHtmlContentBuilder(); var writer = new HtmlContentWrapperTextWriter(buffer, Encoding.UTF8); // Act writer.Write(charArray); // Assert - Assert.Collection(GetValues(buffer), + Assert.Collection( + buffer.Values, value => Assert.Equal(new string('a', 1024), value), value => Assert.Equal(new string('a', 1024), value), value => Assert.Equal("aa", value)); @@ -59,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.Razor public void Write_HtmlContent_AddsToEntries() { // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new TestHtmlContentBuilder(); var writer = new HtmlContentWrapperTextWriter(buffer, Encoding.UTF8); var content = new HtmlString("Hello, world!"); @@ -68,7 +71,7 @@ namespace Microsoft.AspNet.Mvc.Razor // Assert Assert.Collection( - GetValues(buffer), + buffer.Values, item => Assert.Same(content, item)); } @@ -76,7 +79,7 @@ namespace Microsoft.AspNet.Mvc.Razor public void Write_Object_HtmlContent_AddsToEntries() { // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new TestHtmlContentBuilder(); var writer = new HtmlContentWrapperTextWriter(buffer, Encoding.UTF8); var content = new HtmlString("Hello, world!"); @@ -85,7 +88,7 @@ namespace Microsoft.AspNet.Mvc.Razor // Assert Assert.Collection( - GetValues(buffer), + buffer.Values, item => Assert.Same(content, item)); } @@ -93,7 +96,7 @@ namespace Microsoft.AspNet.Mvc.Razor public void WriteLine_Object_HtmlContent_AddsToEntries() { // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new TestHtmlContentBuilder(); var writer = new HtmlContentWrapperTextWriter(buffer, Encoding.UTF8); var content = new HtmlString("Hello, world!"); @@ -102,7 +105,7 @@ namespace Microsoft.AspNet.Mvc.Razor // Assert Assert.Collection( - GetValues(buffer), + buffer.Values, item => Assert.Same(content, item), item => Assert.Equal(Environment.NewLine, item)); } @@ -116,7 +119,7 @@ namespace Microsoft.AspNet.Mvc.Razor var input2 = "from"; var input3 = "ASP"; var input4 = ".Net"; - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new TestHtmlContentBuilder(); var writer = new HtmlContentWrapperTextWriter(buffer, Encoding.UTF8); // Act @@ -126,15 +129,14 @@ namespace Microsoft.AspNet.Mvc.Razor await writer.WriteLineAsync(input4); // Assert - var actual = GetValues(buffer); - Assert.Equal(new object[] { input1, input2, newLine, input3, input4, newLine }, actual); + Assert.Equal(new[] { input1, input2, newLine, input3, input4, newLine }, buffer.Values); } [Fact] public void Write_HtmlContent_WritesToBuffer() { // Arrange - var buffer = new RazorBuffer(new TestRazorBufferScope(), "some-name"); + var buffer = new TestHtmlContentBuilder(); var writer = new HtmlContentWrapperTextWriter(buffer, Encoding.UTF8); var content = new HtmlString("Hello, world!"); @@ -143,17 +145,42 @@ namespace Microsoft.AspNet.Mvc.Razor // Assert Assert.Collection( - GetValues(buffer), + buffer.Values, item => Assert.Same(content, item)); } - private static object[] GetValues(RazorBuffer buffer) + private class TestHtmlContentBuilder : IHtmlContentBuilder { - return buffer.BufferSegments - .SelectMany(c => c.Data) - .Select(d => d.Value) - .TakeWhile(d => d != null) - .ToArray(); + public List Values { get; } = new List(); + + public IHtmlContentBuilder Append(string unencoded) + { + Values.Add(unencoded); + return this; + } + + public IHtmlContentBuilder Append(IHtmlContent content) + { + Values.Add(content); + return this; + } + + public IHtmlContentBuilder AppendHtml(string encoded) + { + Values.Add(new HtmlString(encoded)); + return this; + } + + public IHtmlContentBuilder Clear() + { + Values.Clear(); + return this; + } + + public void WriteTo(TextWriter writer, HtmlEncoder encoder) + { + throw new NotSupportedException(); + } } } } diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/StringCollectionTextWriterTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/StringCollectionTextWriterTest.cs deleted file mode 100644 index 8249a4e3bb..0000000000 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/StringCollectionTextWriterTest.cs +++ /dev/null @@ -1,214 +0,0 @@ -// 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.Mvc.Rendering; -using Microsoft.AspNet.Testing; -using Microsoft.Extensions.Internal; -using Microsoft.Extensions.WebEncoders.Testing; -using Xunit; - -namespace Microsoft.AspNet.Mvc.ViewFeatures -{ - public class StringCollectionTextWriterTest - { - [Fact] - [ReplaceCulture] - public void Write_WritesDataTypes_ToBuffer() - { - // Arrange - var expected = new[] { "True", "3", "18446744073709551615", "Hello world", "3.14", "2.718", "m" }; - var writer = new StringCollectionTextWriter(Encoding.UTF8); - - // Act - writer.Write(true); - writer.Write(3); - writer.Write(ulong.MaxValue); - writer.Write(new TestClass()); - writer.Write(3.14); - writer.Write(2.718m); - writer.Write('m'); - - // Assert - Assert.Equal(expected, writer.Entries); - } - - [Fact] - [ReplaceCulture] - public void WriteLine_WritesDataTypes_ToBuffer() - { - // Arrange - var newLine = Environment.NewLine; - var expected = new List { "False", newLine, "1.1", newLine, "3", newLine }; - var writer = new StringCollectionTextWriter(Encoding.UTF8); - - // Act - writer.WriteLine(false); - writer.WriteLine(1.1f); - writer.WriteLine(3L); - - // Assert - Assert.Equal(expected, writer.Entries); - } - - [Fact] - public async Task Write_WritesCharBuffer() - { - // Arrange - var input1 = new ArraySegment(new char[] { 'a', 'b', 'c', 'd' }, 1, 3); - var input2 = new ArraySegment(new char[] { 'e', 'f' }, 0, 2); - var input3 = new ArraySegment(new char[] { 'g', 'h', 'i', 'j' }, 3, 1); - var writer = new StringCollectionTextWriter(Encoding.UTF8); - - // Act - writer.Write(input1.Array, input1.Offset, input1.Count); - await writer.WriteAsync(input2.Array, input2.Offset, input2.Count); - await writer.WriteLineAsync(input3.Array, input3.Offset, input3.Count); - - // Assert - var buffer = writer.Entries; - Assert.Equal(4, buffer.Count); - Assert.Equal("bcd", buffer[0]); - Assert.Equal("ef", buffer[1]); - Assert.Equal("j", buffer[2]); - Assert.Equal(Environment.NewLine, buffer[3]); - } - - [Fact] - public async Task WriteLines_WritesCharBuffer() - { - // Arrange - var newLine = Environment.NewLine; - var writer = new StringCollectionTextWriter(Encoding.UTF8); - - // Act - writer.WriteLine(); - await writer.WriteLineAsync(); - - // Assert - var actual = writer.Entries; - Assert.Equal(new[] { newLine, newLine }, actual); - } - - [Fact] - public async Task Write_WritesStringBuffer() - { - // Arrange - var newLine = Environment.NewLine; - var input1 = "Hello"; - var input2 = "from"; - var input3 = "ASP"; - var input4 = ".Net"; - var writer = new StringCollectionTextWriter(Encoding.UTF8); - - // Act - writer.Write(input1); - writer.WriteLine(input2); - await writer.WriteAsync(input3); - await writer.WriteLineAsync(input4); - - // Assert - var actual = writer.Entries; - Assert.Equal(new[] { input1, input2, newLine, input3, input4, newLine }, actual); - } - - [Fact] - public void Write_HtmlContent_AddsToEntries() - { - // Arrange - var writer = new StringCollectionTextWriter(Encoding.UTF8); - var content = new HtmlString("Hello, world!"); - - // Act - writer.Write(content); - - // Assert - Assert.Collection( - writer.Entries, - item => Assert.Same(content, item)); - } - - [Fact] - public void Write_Object_HtmlContent_AddsToEntries() - { - // Arrange - var writer = new StringCollectionTextWriter(Encoding.UTF8); - var content = new HtmlString("Hello, world!"); - - // Act - writer.Write((object)content); - - // Assert - Assert.Collection( - writer.Entries, - item => Assert.Same(content, item)); - } - - [Fact] - public void WriteLine_Object_HtmlContent_AddsToEntries() - { - // Arrange - var writer = new StringCollectionTextWriter(Encoding.UTF8); - var content = new HtmlString("Hello, world!"); - - // Act - writer.WriteLine(content); - - // Assert - Assert.Collection( - writer.Entries, - item => Assert.Same(content, item), - item => Assert.Equal(Environment.NewLine, item)); - } - - [Fact] - public void Copy_CopiesContent_IfTargetTextWriterIsAStringCollectionTextWriter() - { - // Arrange - var source = new StringCollectionTextWriter(Encoding.UTF8); - var target = new StringCollectionTextWriter(Encoding.UTF8); - - // Act - source.Write("Hello world"); - source.Write(new char[1], 0, 1); - source.CopyTo(target, new HtmlTestEncoder()); - - // Assert - // Make sure content was written to the source. - Assert.Equal(2, source.Entries.Count); - Assert.Equal(1, target.Entries.Count); - - var entry = Assert.Single(target.Entries); - Assert.Same(source.Content, entry); - } - - [Fact] - public void Copy_WritesContent_IfTargetTextWriterIsNotAStringCollectionTextWriter() - { - // Arrange - var source = new StringCollectionTextWriter(Encoding.UTF8); - var target = new StringWriter(); - var expected = @"Hello world" + Environment.NewLine + "abc"; - - // Act - source.WriteLine("Hello world"); - source.Write(new[] { 'x', 'a', 'b', 'c' }, 1, 3); - source.CopyTo(target, new HtmlTestEncoder()); - - // Assert - Assert.Equal(expected, target.ToString()); - } - - private class TestClass - { - public override string ToString() - { - return "Hello world"; - } - } - } -} \ No newline at end of file