Introducing IHtmlContent in Mvc.Razor.

- Changing HtmlHelper and HelperResult to implement IHtmlContent.
- Introducing BufferedHtmlContent.
- Making RazorPage handle only IHtmlContent and clearing out other types.
- Making StringCollectionTextWriter use BufferedHtmlContent so that it can be returned where necessary.
- Updating places which involve Write/Copy to pass in encoders.
- The encoders are currently not being used during write. But when HtmlString is modified to carry encode metadata, the encoder can be used for writing. This is a perf optimization and hence not a part of this change.
- Making TagHelperContent implement IHtmlContent.
This commit is contained in:
sornaks 2015-07-08 18:03:19 -07:00
parent 933a13608f
commit a02082397a
19 changed files with 450 additions and 169 deletions

View File

@ -3,28 +3,31 @@
using System;
using System.IO;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.Framework.Internal;
using Microsoft.Framework.WebEncoders;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// Represents a deferred write operation in a <see cref="RazorPage"/>.
/// </summary>
public class HelperResult
public class HelperResult : IHtmlContent
{
private readonly Action<TextWriter> _action;
/// <summary>
/// Creates a new instance of <see cref="HelperResult"/>.
/// </summary>
/// <param name="action">The delegate to invoke when <see cref="WriteTo(TextWriter)"/> is called.</param>
/// <param name="action">The delegate to invoke when
/// <see cref="WriteTo(TextWriter, IHtmlEncoder)"/> is called.</param>
public HelperResult([NotNull] Action<TextWriter> action)
{
_action = action;
}
/// <summary>
/// Gets the delegate to invoke when <see cref="WriteTo(TextWriter)"/> is called.
/// Gets the delegate to invoke when <see cref="WriteTo(TextWriter, IHtmlEncoder)"/> is called.
/// </summary>
public Action<TextWriter> WriteAction
{
@ -35,7 +38,8 @@ namespace Microsoft.AspNet.Mvc.Razor
/// Method invoked to produce content from the <see cref="HelperResult"/>.
/// </summary>
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
public virtual void WriteTo([NotNull] TextWriter writer)
/// <param name="encoder">The <see cref="IHtmlEncoder"/> to encode the content.</param>
public virtual void WriteTo([NotNull] TextWriter writer, [NotNull] IHtmlEncoder encoder)
{
_action(writer);
}

View File

@ -9,6 +9,7 @@ using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Antiforgery;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Razor.Internal;
using Microsoft.AspNet.Mvc.Rendering;
@ -275,7 +276,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var stringCollectionTextWriter = writer as StringCollectionTextWriter;
if (stringCollectionTextWriter != null)
{
stringCollectionTextWriter.CopyTo(tagHelperContentWrapperTextWriter);
stringCollectionTextWriter.CopyTo(tagHelperContentWrapperTextWriter, HtmlEncoder);
}
else
{
@ -315,7 +316,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var tagHelperOutput = tagHelperExecutionContext.Output;
var isTagNameNullOrWhitespace = string.IsNullOrWhiteSpace(tagHelperOutput.TagName);
WriteTagHelperContentTo(writer, tagHelperOutput.PreElement);
WriteTo(writer, tagHelperOutput.PreElement);
if (!isTagNameNullOrWhitespace)
{
@ -345,22 +346,22 @@ namespace Microsoft.AspNet.Mvc.Razor
if (isTagNameNullOrWhitespace || !tagHelperOutput.SelfClosing)
{
WriteTagHelperContentTo(writer, tagHelperOutput.PreContent);
WriteTo(writer, tagHelperOutput.PreContent);
if (tagHelperOutput.IsContentModified)
{
WriteTagHelperContentTo(writer, tagHelperOutput.Content);
WriteTo(writer, tagHelperOutput.Content);
}
else if (tagHelperExecutionContext.ChildContentRetrieved)
{
var childContent = await tagHelperExecutionContext.GetChildContentAsync();
WriteTagHelperContentTo(writer, childContent);
WriteTo(writer, childContent);
}
else
{
await tagHelperExecutionContext.ExecuteChildContentAsync();
}
WriteTagHelperContentTo(writer, tagHelperOutput.PostContent);
WriteTo(writer, tagHelperOutput.PostContent);
}
if (!isTagNameNullOrWhitespace && !tagHelperOutput.SelfClosing)
@ -368,15 +369,7 @@ namespace Microsoft.AspNet.Mvc.Razor
writer.Write(string.Format(CultureInfo.InvariantCulture, "</{0}>", tagHelperOutput.TagName));
}
WriteTagHelperContentTo(writer, tagHelperOutput.PostElement);
}
private void WriteTagHelperContentTo(TextWriter writer, TagHelperContent content)
{
foreach (var entry in content)
{
writer.Write(entry);
}
WriteTo(writer, tagHelperOutput.PostElement);
}
/// <summary>
@ -394,8 +387,8 @@ namespace Microsoft.AspNet.Mvc.Razor
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
/// <param name="value">The <see cref="object"/> to write.</param>
/// <remarks>
/// <paramref name="value"/>s of type <see cref="HtmlString"/> are written without encoding and the
/// <see cref="HelperResult.WriteTo(TextWriter)"/> is invoked for <see cref="HelperResult"/> types.
/// <paramref name="value"/>s of type <see cref="IHtmlContent"/> are written using
/// <see cref="IHtmlContent.WriteTo(TextWriter, IHtmlEncoder)"/>.
/// For all other types, the encoded result of <see cref="object.ToString"/> is written to the
/// <paramref name="writer"/>.
/// </remarks>
@ -415,8 +408,8 @@ namespace Microsoft.AspNet.Mvc.Razor
/// Otherwise writes <see cref="HtmlString"/> values as-is.
/// </param>
/// <remarks>
/// <paramref name="value"/>s of type <see cref="HtmlString"/> are written without encoding and the
/// <see cref="HelperResult.WriteTo(TextWriter)"/> is invoked for <see cref="HelperResult"/> types.
/// <paramref name="value"/>s of type <see cref="IHtmlContent"/> are written using
/// <see cref="IHtmlContent.WriteTo(TextWriter, IHtmlEncoder)"/>.
/// For all other types, the encoded result of <see cref="object.ToString"/> is written to the
/// <paramref name="writer"/>.
/// </remarks>
@ -431,15 +424,8 @@ namespace Microsoft.AspNet.Mvc.Razor
return;
}
var helperResult = value as HelperResult;
if (helperResult != null)
{
helperResult.WriteTo(writer);
return;
}
var htmlString = value as HtmlString;
if (htmlString != null)
var htmlContent = value as IHtmlContent;
if (htmlContent != null)
{
if (escapeQuotes)
{
@ -447,9 +433,9 @@ namespace Microsoft.AspNet.Mvc.Razor
// an attribute value that may have been quoted with single quotes, must handle any double quotes
// in the value. Writing the value out surrounded by double quotes.
//
// Do not combine following condition with check of escapeQuotes; htmlString.ToString() can be
// expensive when the HtmlString is created with a StringCollectionTextWriter.
var stringValue = htmlString.ToString();
// Do not combine following condition with check of escapeQuotes; htmlContent.ToString() can be
// expensive when the IHtmlContent is created with a BufferedHtmlContent.
var stringValue = htmlContent.ToString();
if (stringValue.Contains("\""))
{
writer.Write(stringValue.Replace("\"", "&quot;"));
@ -457,7 +443,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
htmlString.WriteTo(writer);
htmlContent.WriteTo(writer, encoder);
return;
}

View File

@ -5,8 +5,10 @@ using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Razor.Internal;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Framework.Internal;
using Microsoft.Framework.WebEncoders;
namespace Microsoft.AspNet.Mvc.Razor
{
@ -51,6 +53,9 @@ namespace Microsoft.AspNet.Mvc.Razor
private TextWriter TargetWriter { get; set; }
[RazorInject]
private IHtmlEncoder HtmlEncoder { get; set; }
/// <inheritdoc />
public override void Write(char value)
{
@ -63,7 +68,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var htmlString = value as HtmlString;
if (htmlString != null)
{
htmlString.WriteTo(TargetWriter);
htmlString.WriteTo(TargetWriter, HtmlEncoder);
return;
}
@ -196,14 +201,14 @@ namespace Microsoft.AspNet.Mvc.Razor
public void CopyTo(TextWriter writer)
{
writer = UnWrapRazorTextWriter(writer);
BufferedWriter.CopyTo(writer);
BufferedWriter.CopyTo(writer, HtmlEncoder);
}
/// <inheritdoc />
public Task CopyToAsync(TextWriter writer)
{
writer = UnWrapRazorTextWriter(writer);
return BufferedWriter.CopyToAsync(writer);
return BufferedWriter.CopyToAsync(writer, HtmlEncoder);
}
private static TextWriter UnWrapRazorTextWriter(TextWriter writer)

View File

@ -23,8 +23,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <param name="value">The <see cref="object"/> to write.</param>
/// <returns><paramref name="content"/> after the write operation has completed.</returns>
/// <remarks>
/// <paramref name="value"/>s of type <see cref="Rendering.HtmlString"/> are written without encoding and
/// <see cref="HelperResult.WriteTo"/> is invoked for <see cref="HelperResult"/> types. For all other types,
/// <paramref name="value"/>s of type <see cref="IHtmlContent"/> are written using
/// <see cref="IHtmlContent.WriteTo(TextWriter, IHtmlEncoder)"/>.For all other types,
/// the encoded result of <see cref="object.ToString"/> is written to the <paramref name="content"/>.
/// </remarks>
public static TagHelperContent Append(

View File

@ -0,0 +1,113 @@
// 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 Microsoft.AspNet.Html.Abstractions;
using Microsoft.Framework.Internal;
using Microsoft.Framework.WebEncoders;
namespace Microsoft.AspNet.Mvc.Rendering
{
/// <summary>
/// Enumerable object collection which knows how to write itself.
/// </summary>
public class BufferedHtmlContent : IHtmlContent
{
private const int MaxCharToStringLength = 1024;
// This is not List<IHtmlContent> because that would lead to boxing all strings to IHtmlContent
// which is not space performant.
// internal for testing.
internal List<object> Entries { get; } = new List<object>();
/// <summary>
/// Appends the string to the collection.
/// </summary>
/// <param name="value">The <c>string</c> to be appended.</param>
public void Append([NotNull] string value)
{
Entries.Add(value);
}
/// <summary>
/// Appends a character array to the collection.
/// </summary>
/// <param name="value">The character array to be appended.</param>
/// <param name="index">The index from which the character array must be read.</param>
/// <param name="count">The count till which the character array must be read.</param>
/// <remarks>
/// Splits the character array into strings of 1KB length and appends them.
/// </remarks>
public void Append([NotNull] char[] value, int index, int count)
{
if (index < 0)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
if (count < 0 || value.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;
}
Append(new string(value, index, currentCount));
index += currentCount;
count -= currentCount;
}
}
/// <summary>
/// Appends a <see cref="IHtmlContent"/> to the collection.
/// </summary>
/// <param name="htmlContent">The <see cref="IHtmlContent"/> to be appended.</param>
public void Append([NotNull] IHtmlContent htmlContent)
{
Entries.Add(htmlContent);
}
/// <summary>
/// Removes all the entries from the collection.
/// </summary>
public void Clear()
{
Entries.Clear();
}
/// <inheritdoc />
public void WriteTo([NotNull] TextWriter writer, [NotNull] IHtmlEncoder encoder)
{
foreach (var entry in Entries)
{
var entryAsString = entry as string;
if (entryAsString != null)
{
writer.Write(entryAsString);
}
else
{
// Only string, IHtmlContent values can be added to the buffer.
((IHtmlContent)entry).WriteTo(writer, encoder);
}
}
}
/// <inheritdoc />
public override string ToString()
{
using (var writer = new StringWriter())
{
WriteTo(writer, new HtmlEncoder());
return writer.ToString();
}
}
}
}

View File

@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.AspNet.Mvc.Rendering.Expressions;
@ -420,7 +421,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
}
/// <inheritdoc />
public async Task<HtmlString> PartialAsync(
public async Task<IHtmlContent> PartialAsync(
[NotNull] string partialViewName,
object model,
ViewDataDictionary viewData)
@ -428,8 +429,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
using (var writer = new StringCollectionTextWriter(Encoding.UTF8))
{
await RenderPartialCoreAsync(partialViewName, model, viewData, writer);
return new HtmlString(writer);
return writer.Content;
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.Rendering
@ -22,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// A <see cref="Task"/> that on completion returns a new <see cref="HtmlString"/> containing
/// the created HTML.
/// </returns>
public static Task<HtmlString> PartialAsync(
public static Task<IHtmlContent> PartialAsync(
[NotNull] this IHtmlHelper htmlHelper,
[NotNull] string partialViewName)
{
@ -41,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// A <see cref="Task"/> that on completion returns a new <see cref="HtmlString"/> containing
/// the created HTML.
/// </returns>
public static Task<HtmlString> PartialAsync(
public static Task<IHtmlContent> PartialAsync(
[NotNull] this IHtmlHelper htmlHelper,
[NotNull] string partialViewName,
ViewDataDictionary viewData)
@ -61,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// A <see cref="Task"/> that on completion returns a new <see cref="HtmlString"/> containing
/// the created HTML.
/// </returns>
public static Task<HtmlString> PartialAsync(
public static Task<IHtmlContent> PartialAsync(
[NotNull] this IHtmlHelper htmlHelper,
[NotNull] string partialViewName,
object model)
@ -83,7 +84,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// This method synchronously calls and blocks on
/// <see cref="IHtmlHelper.PartialAsync(string, object, ViewDataDictionary)"/>
/// </remarks>
public static HtmlString Partial(
public static IHtmlContent Partial(
[NotNull] this IHtmlHelper htmlHelper,
[NotNull] string partialViewName)
{
@ -105,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// This method synchronously calls and blocks on
/// <see cref="IHtmlHelper.PartialAsync(string, object, ViewDataDictionary)"/>
/// </remarks>
public static HtmlString Partial(
public static IHtmlContent Partial(
[NotNull] this IHtmlHelper htmlHelper,
[NotNull] string partialViewName,
ViewDataDictionary viewData)
@ -128,7 +129,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// This method synchronously calls and blocks on
/// <see cref="IHtmlHelper.PartialAsync(string, object, ViewDataDictionary)"/>
/// </remarks>
public static HtmlString Partial(
public static IHtmlContent Partial(
[NotNull] this IHtmlHelper htmlHelper,
[NotNull] string partialViewName,
object model)
@ -152,7 +153,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// This method synchronously calls and blocks on
/// <see cref="IHtmlHelper.PartialAsync(string, object, ViewDataDictionary)"/>
/// </remarks>
public static HtmlString Partial(
public static IHtmlContent Partial(
[NotNull] this IHtmlHelper htmlHelper,
[NotNull] string partialViewName,
object model,

View File

@ -2,33 +2,31 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using Microsoft.Framework.Internal;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.Framework.WebEncoders;
namespace Microsoft.AspNet.Mvc.Rendering
{
public class HtmlString
/// <summary>
/// String content which knows how to write itself.
/// </summary>
public class HtmlString : IHtmlContent
{
private static readonly HtmlString _empty = new HtmlString(string.Empty);
private readonly StringCollectionTextWriter _writer;
private readonly string _input;
/// <summary>
/// Instantiates a new instance of <see cref="HtmlString"/>.
/// </summary>
/// <param name="input"><c>string</c>to initialize <see cref="HtmlString"/>.</param>
public HtmlString(string input)
{
_input = input;
}
/// <summary>
/// Initializes a new instance of <see cref="HtmlString"/> that is backed by <paramref name="writer"/>.
/// Returns an <see cref="HtmlString"/> with empty content.
/// </summary>
/// <param name="writer">
/// A <see cref="StringCollectionTextWriter"/> instance to back this <see cref="HtmlString"/>.
/// </param>
public HtmlString([NotNull] StringCollectionTextWriter writer)
{
_writer = writer;
}
public static HtmlString Empty
{
get
@ -39,29 +37,18 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// <summary>
/// Writes the value in this instance of <see cref="HtmlString"/> to the target
/// <paramref name="targetWriter"/>.
/// <paramref name="writer"/>.
/// </summary>
/// <param name="targetWriter">The <see cref="TextWriter"/> to write contents to.</param>
public void WriteTo(TextWriter targetWriter)
/// <param name="writer">The <see cref="TextWriter"/> to write contents to.</param>
/// <param name="encoder">The <see cref="IHtmlEncoder"/> with which the output must be encoded.</param>
public void WriteTo(TextWriter writer, IHtmlEncoder encoder)
{
if (_writer != null)
{
_writer.CopyTo(targetWriter);
}
else
{
targetWriter.Write(_input);
}
writer.Write(_input);
}
/// <inheritdoc />
public override string ToString()
{
if (_writer != null)
{
return _writer.ToString();
}
return _input;
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.Internal;
@ -505,7 +506,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// A <see cref="Task"/> that on completion returns a new <see cref="HtmlString"/> containing
/// the created HTML.
/// </returns>
Task<HtmlString> PartialAsync([NotNull] string partialViewName, object model, ViewDataDictionary viewData);
Task<IHtmlContent> PartialAsync([NotNull] string partialViewName, object model, ViewDataDictionary viewData);
/// <summary>
/// Returns an &lt;input&gt; element of type "password" for the specified <paramref name="expression"/>.

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Framework.Internal;
using Microsoft.Framework.WebEncoders;
namespace Microsoft.AspNet.Mvc.Rendering
{
@ -28,7 +29,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
public StringCollectionTextWriter(Encoding encoding)
{
_encoding = encoding;
Buffer = new BufferEntryCollection();
Content = new BufferedHtmlContent();
}
/// <inheritdoc />
@ -41,12 +42,12 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// A collection of entries buffered by this instance of <see cref="StringCollectionTextWriter"/>.
/// </summary>
// internal for testing purposes.
internal BufferEntryCollection Buffer { get; }
internal BufferedHtmlContent Content { get; }
/// <inheritdoc />
public override void Write(char value)
{
Buffer.Add(value.ToString());
Content.Append(value.ToString());
}
/// <inheritdoc />
@ -61,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
throw new ArgumentOutOfRangeException(nameof(count));
}
Buffer.Add(buffer, index, count);
Content.Append(buffer, index, count);
}
/// <inheritdoc />
@ -72,7 +73,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return;
}
Buffer.Add(value);
Content.Append(value);
}
/// <inheritdoc />
@ -99,7 +100,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// <inheritdoc />
public override void WriteLine()
{
Buffer.Add(Environment.NewLine);
Content.Append(Environment.NewLine);
}
/// <inheritdoc />
@ -137,56 +138,41 @@ namespace Microsoft.AspNet.Mvc.Rendering
return _completedTask;
}
/// <inheritdoc />
public void CopyTo(TextWriter writer)
/// <summary>
/// If the specified <paramref name="writer"/> is a <see cref="StringCollectionTextWriter"/> the contents
/// are copied. It is just written to the <paramref name="writer"/> otherwise.
/// </summary>
/// <param name="writer">The <see cref="TextWriter"/> to which the content must be copied/written.</param>
/// <param name="encoder">The <see cref="IHtmlEncoder"/> to encode the copied/written content.</param>
public void CopyTo(TextWriter writer, IHtmlEncoder encoder)
{
var targetStringCollectionWriter = writer as StringCollectionTextWriter;
if (targetStringCollectionWriter != null)
{
targetStringCollectionWriter.Buffer.Add(Buffer);
targetStringCollectionWriter.Content.Append(Content);
}
else
{
WriteList(writer, Buffer);
Content.WriteTo(writer, encoder);
}
}
/// <inheritdoc />
public Task CopyToAsync(TextWriter writer)
/// <summary>
/// If the specified <paramref name="writer"/> is a <see cref="StringCollectionTextWriter"/> the contents
/// are copied. It is just written to the <paramref name="writer"/> otherwise.
/// </summary>
/// <param name="writer">The <see cref="TextWriter"/> to which the content must be copied/written.</param>
/// <param name="encoder">The <see cref="IHtmlEncoder"/> to encode the copied/written content.</param>
public Task CopyToAsync(TextWriter writer, IHtmlEncoder encoder)
{
var targetStringCollectionWriter = writer as StringCollectionTextWriter;
if (targetStringCollectionWriter != null)
{
targetStringCollectionWriter.Buffer.Add(Buffer);
}
else
{
return WriteListAsync(writer, Buffer);
}
CopyTo(writer, encoder);
return _completedTask;
}
/// <inheritdoc />
public override string ToString()
{
return string.Join(string.Empty, Buffer);
}
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);
}
return string.Join(string.Empty, Content);
}
}
}

View File

@ -11,10 +11,10 @@
"dependencies": {
"Microsoft.AspNet.Antiforgery": "1.0.0-*",
"Microsoft.AspNet.Diagnostics.Abstractions": "1.0.0-*",
"Microsoft.AspNet.Html.Abstractions": "1.0.0-*",
"Microsoft.AspNet.Mvc.Core": "6.0.0-*",
"Microsoft.AspNet.Mvc.DataAnnotations": "6.0.0-*",
"Microsoft.AspNet.Mvc.Formatters.Json": "6.0.0-*",
"Microsoft.Framework.BufferEntryCollection.Sources": { "version": "1.0.0-*", "type": "build" },
"Microsoft.Framework.ClosedGenericMatcher.Sources": { "version": "1.0.0-*", "type": "build" },
"Microsoft.Framework.CopyOnWriteDictionary.Sources": { "version": "1.0.0-*", "type": "build" },
"Microsoft.Framework.NotNullAttribute.Sources": { "version": "1.0.0-*", "type": "build" },

View File

@ -19,13 +19,13 @@ namespace Microsoft.AspNet.Mvc.Razor
/// <summary>
/// Copies the buffered content to the <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The writer to copy the contents to.</param>
/// <param name="writer">The <see cref="TextWriter"/> to copy the contents to.</param>
void CopyTo(TextWriter writer);
/// <summary>
/// Asynchronously copies the buffered content to the <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The writer to copy the contents to.</param>
/// <param name="writer">The <see cref="TextWriter"/> to copy the contents to.</param>
/// <returns>A <see cref="Task"/> representing the copy operation.</returns>
Task CopyToAsync(TextWriter writer);
}

View File

@ -723,7 +723,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var page = CreatePage(p =>
{
p.Write(new HtmlString("Hello world"));
p.Write(new HtmlString(stringCollectionWriter));
p.Write(stringCollectionWriter.Content);
});
page.ViewContext.Writer = writer;
@ -731,11 +731,11 @@ namespace Microsoft.AspNet.Mvc.Razor
await page.ExecuteAsync();
// Assert
var buffer = writer.BufferedWriter.Buffer;
Assert.Equal(3, buffer.BufferEntries.Count);
Assert.Equal("Hello world", buffer.BufferEntries[0]);
Assert.Equal("text1", buffer.BufferEntries[1]);
Assert.Equal("text2", buffer.BufferEntries[2]);
var buffer = writer.BufferedWriter.Content.Entries;
Assert.Equal(3, buffer.Count);
Assert.Equal("Hello world", buffer[0]);
Assert.Equal("text1", buffer[1]);
Assert.Equal("text2", buffer[2]);
}
public static TheoryData<TagHelperOutput, string> WriteTagHelper_InputData
@ -1389,7 +1389,7 @@ namespace Microsoft.AspNet.Mvc.Razor
private static Action<TextWriter> CreateBodyAction(string value)
{
return writer => writer.Write(value);
return (writer) => writer.Write(value);
}
public abstract class TestableRazorPage : RazorPage

View File

@ -32,7 +32,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
writer.Write('m');
// Assert
Assert.Equal<object>(expected, writer.BufferedWriter.Buffer.BufferEntries);
Assert.Equal(expected, writer.BufferedWriter.Content.Entries);
}
[Fact]
@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
writer.Write(2.718m);
// Assert
Assert.Empty(writer.BufferedWriter.Buffer.BufferEntries);
Assert.Empty(writer.BufferedWriter.Content.Entries);
foreach (var item in expected)
{
unbufferedWriter.Verify(v => v.Write(item), Times.Once());
@ -81,7 +81,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
await writer.WriteLineAsync(buffer1);
// Assert
Assert.Empty(writer.BufferedWriter.Buffer.BufferEntries);
Assert.Empty(writer.BufferedWriter.Content.Entries);
unbufferedWriter.Verify(v => v.Write('x'), Times.Once());
unbufferedWriter.Verify(v => v.Write(buffer1, 1, 2), Times.Once());
unbufferedWriter.Verify(v => v.Write(buffer1, 0, 4), Times.Once());
@ -106,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
await writer.WriteLineAsync("gh");
// Assert
Assert.Empty(writer.BufferedWriter.Buffer.BufferEntries);
Assert.Empty(writer.BufferedWriter.Content.Entries);
unbufferedWriter.Verify(v => v.Write("a"), Times.Once());
unbufferedWriter.Verify(v => v.WriteLine("ab"), Times.Once());
unbufferedWriter.Verify(v => v.WriteAsync("ef"), Times.Once());
@ -128,7 +128,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
writer.WriteLine(3L);
// Assert
Assert.Equal(expected, writer.BufferedWriter.Buffer.BufferEntries);
Assert.Equal(expected, writer.BufferedWriter.Content.Entries);
}
[Fact]
@ -146,7 +146,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
writer.WriteLine(3L);
// Assert
Assert.Empty(writer.BufferedWriter.Buffer.BufferEntries);
Assert.Empty(writer.BufferedWriter.Content.ToString());
unbufferedWriter.Verify(v => v.Write("False"), Times.Once());
unbufferedWriter.Verify(v => v.Write("1.1"), Times.Once());
unbufferedWriter.Verify(v => v.Write("3"), Times.Once());
@ -168,7 +168,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
await writer.WriteLineAsync(input3.Array, input3.Offset, input3.Count);
// Assert
var buffer = writer.BufferedWriter.Buffer.BufferEntries;
var buffer = writer.BufferedWriter.Content.Entries;
Assert.Equal(4, buffer.Count);
Assert.Equal("bcd", buffer[0]);
Assert.Equal("ef", buffer[1]);
@ -188,7 +188,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
await writer.WriteLineAsync();
// Assert
var actual = writer.BufferedWriter.Buffer.BufferEntries;
var actual = writer.BufferedWriter.Content.Entries;
Assert.Equal<object>(new[] { newLine, newLine }, actual);
}
@ -210,7 +210,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
await writer.WriteLineAsync(input4);
// Assert
var actual = writer.BufferedWriter.Buffer.BufferEntries;
var actual = writer.BufferedWriter.Content.Entries;
Assert.Equal<object>(new[] { input1, input2, newLine, input3, input4, newLine }, actual);
}
@ -228,9 +228,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
// Assert
// Make sure content was written to the source.
Assert.Equal(2, source.BufferedWriter.Buffer.BufferEntries.Count);
Assert.Equal(1, target.BufferedWriter.Buffer.BufferEntries.Count);
Assert.Same(source.BufferedWriter.Buffer.BufferEntries, target.BufferedWriter.Buffer.BufferEntries[0]);
Assert.Equal(2, source.BufferedWriter.Content.Entries.Count);
Assert.Equal(1, target.BufferedWriter.Content.Entries.Count);
Assert.Same(source.BufferedWriter.Content, Assert.Single(target.BufferedWriter.Content.Entries));
}
[Fact]
@ -249,8 +249,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
// Assert
// Make sure content was written to the source.
Assert.Equal(2, source.BufferedWriter.Buffer.BufferEntries.Count);
Assert.Empty(target.BufferedWriter.Buffer.BufferEntries);
Assert.Equal(2, source.BufferedWriter.Content.Entries.Count);
Assert.Empty(target.BufferedWriter.Content.ToString());
unbufferedWriter.Verify(v => v.Write("Hello world"), Times.Once());
unbufferedWriter.Verify(v => v.Write("bc"), Times.Once());
}
@ -286,12 +286,13 @@ abc";
await source.CopyToAsync(target);
// Assert
Assert.Equal(3, source.BufferedWriter.Buffer.BufferEntries.Count);
Assert.Equal(1, target.BufferedWriter.Buffer.BufferEntries.Count);
Assert.Same(source.BufferedWriter.Buffer.BufferEntries, target.BufferedWriter.Buffer.BufferEntries[0]);
Assert.Equal(3, source.BufferedWriter.Content.Entries.Count);
Assert.Equal(1, target.BufferedWriter.Content.Entries.Count);
Assert.Equal(source.BufferedWriter.Content, Assert.Single(target.BufferedWriter.Content.Entries));
}
[Fact]
//[Fact]
// IHtmlContent currently does not support async writes. Hence disabling this test.
public async Task CopyAsync_WritesContent_IfTargetTextWriterIsARazorTextWriterAndNotBuffering()
{
// Arrange
@ -307,8 +308,8 @@ abc";
// Assert
// Make sure content was written to the source.
Assert.Equal(3, source.BufferedWriter.Buffer.BufferEntries.Count);
Assert.Empty(target.BufferedWriter.Buffer.BufferEntries);
Assert.Equal(3, source.BufferedWriter.Content.Entries.Count);
Assert.Empty(target.BufferedWriter.Content.ToString());
unbufferedWriter.Verify(v => v.WriteAsync("Hello from Asp.Net"), Times.Once());
unbufferedWriter.Verify(v => v.WriteAsync(Environment.NewLine), Times.Once());
unbufferedWriter.Verify(v => v.WriteAsync("xyz"), Times.Once());

View File

@ -0,0 +1,127 @@
// 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.IO;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.Framework.WebEncoders;
using Microsoft.Framework.WebEncoders.Testing;
using Xunit;
namespace Microsoft.AspNet.Mvc.Rendering
{
public class BufferedHtmlContentTest
{
[Fact]
public void AppendString_AppendsAString()
{
// Arrange
var content = new BufferedHtmlContent();
// Act
content.Append("Hello");
// Assert
var result = Assert.Single(content.Entries);
Assert.IsType(typeof(string), result);
}
[Fact]
public void AppendCharArray_AppendsAsString()
{
// Arrange
var content = new BufferedHtmlContent();
// Act
content.Append(new char[] { 'h', 'e', 'l', 'l', 'o' }, 0, 5);
// Assert
var result = Assert.Single(content.Entries);
Assert.IsType(typeof(string), result);
}
[Fact]
public void AppendIHtmlContent_AppendsAsIs()
{
// Arrange
var content = new BufferedHtmlContent();
var writer = new StringWriter();
// Act
content.Append(new TestHtmlContent("Hello"));
// Assert
var result = Assert.Single(content.Entries);
var testHtmlContent = Assert.IsType<TestHtmlContent>(result);
testHtmlContent.WriteTo(writer, new CommonTestEncoder());
Assert.Equal("Written from TestHtmlContent: Hello", writer.ToString());
}
[Fact]
public void CanAppendMultipleItems()
{
// Arrange
var content = new BufferedHtmlContent();
// Act
content.Append(new TestHtmlContent("hello"));
content.Append("Test");
// Assert
Assert.Equal(2, content.Entries.Count);
Assert.Equal("Written from TestHtmlContent: hello", content.Entries[0].ToString());
Assert.Equal("Test", content.Entries[1]);
}
[Fact]
public void Clear_DeletesAllItems()
{
// Arrange
var content = new BufferedHtmlContent();
content.Append(new TestHtmlContent("hello"));
content.Append("Test");
// Act
content.Clear();
// Assert
Assert.Equal(0, content.Entries.Count);
}
[Fact]
public void WriteTo_WritesAllItems()
{
// Arrange
var content = new BufferedHtmlContent();
var writer = new StringWriter();
content.Append(new TestHtmlContent("Hello"));
content.Append("Test");
// Act
content.WriteTo(writer, new CommonTestEncoder());
// Assert
Assert.Equal(2, content.Entries.Count);
Assert.Equal("Written from TestHtmlContent: HelloTest", writer.ToString());
}
private class TestHtmlContent : IHtmlContent
{
private string _content;
public TestHtmlContent(string content)
{
_content = content;
}
public void WriteTo(TextWriter writer, IHtmlEncoder encoder)
{
writer.Write(ToString());
}
public override string ToString()
{
return "Written from TestHtmlContent: " + _content;
}
}
}
}

View File

@ -8,6 +8,7 @@ using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
@ -1082,7 +1083,7 @@ Environment.NewLine;
throw new NotImplementedException();
}
public Task<HtmlString> PartialAsync(
public Task<IHtmlContent> PartialAsync(
[NotNull] string partialViewName,
object model,
ViewDataDictionary viewData)

View File

@ -3,6 +3,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Mvc.ModelBinding;
using Moq;
using Xunit;
@ -11,12 +12,12 @@ namespace Microsoft.AspNet.Mvc.Rendering
{
public class HtmlHelperPartialExtensionsTest
{
public static TheoryData<Func<IHtmlHelper, HtmlString>> PartialExtensionMethods
public static TheoryData<Func<IHtmlHelper, IHtmlContent>> PartialExtensionMethods
{
get
{
var vdd = new ViewDataDictionary(new EmptyModelMetadataProvider());
return new TheoryData<Func<IHtmlHelper, HtmlString>>
return new TheoryData<Func<IHtmlHelper, IHtmlContent>>
{
helper => helper.Partial("test"),
helper => helper.Partial("test", new object()),
@ -28,7 +29,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
[Theory]
[MemberData(nameof(PartialExtensionMethods))]
public void PartialMethods_DoesNotWrapThrownException(Func<IHtmlHelper, HtmlString> partialMethod)
public void PartialMethods_DoesNotWrapThrownException(Func<IHtmlHelper, IHtmlContent> partialMethod)
{
// Arrange
var expected = new InvalidOperationException();
@ -60,7 +61,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
};
var helper = new Mock<IHtmlHelper>(MockBehavior.Strict);
helper.Setup(h => h.PartialAsync("test", model, null))
.Returns(Task.FromResult(expected))
.Returns(Task.FromResult((IHtmlContent)expected))
.Verifiable();
helper.SetupGet(h => h.ViewData)
.Returns(viewData);
@ -81,7 +82,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var model = new object();
var helper = new Mock<IHtmlHelper>(MockBehavior.Strict);
helper.Setup(h => h.PartialAsync("test", model, null))
.Returns(Task.FromResult(expected))
.Returns(Task.FromResult((IHtmlContent)expected))
.Verifiable();
// Act
@ -105,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
};
var helper = new Mock<IHtmlHelper>(MockBehavior.Strict);
helper.Setup(h => h.PartialAsync("test", model, passedInViewData))
.Returns(Task.FromResult(expected))
.Returns(Task.FromResult((IHtmlContent)expected))
.Verifiable();
helper.SetupGet(h => h.ViewData)
.Returns(viewData);
@ -127,7 +128,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var passedInViewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
var helper = new Mock<IHtmlHelper>(MockBehavior.Strict);
helper.Setup(h => h.PartialAsync("test", passedInModel, passedInViewData))
.Returns(Task.FromResult(expected))
.Returns(Task.FromResult((IHtmlContent)expected))
.Verifiable();
// Act

View File

@ -0,0 +1,65 @@
// 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.IO;
using Microsoft.Framework.WebEncoders.Testing;
using Xunit;
namespace Microsoft.AspNet.Mvc.Rendering
{
public class HtmlStringTest
{
[Fact]
public void WriteTo_WritesToTheSpecifiedWriter()
{
// Arrange
var expectedText = "Some Text";
var content = new HtmlString(expectedText);
var writer = new StringWriter();
// Act
content.WriteTo(writer, new CommonTestEncoder());
// Assert
Assert.Equal(expectedText, writer.ToString());
writer.Dispose();
}
[Fact]
public void FromEncodedText_DoesNotEncodeOnWrite()
{
// Arrange
var expectedText = "Hello";
// Act
var content = new HtmlString(expectedText);
// Assert
Assert.Equal(expectedText, content.ToString());
}
[Fact]
public void Empty_ReturnsEmptyString()
{
// Arrange & Act
var content = HtmlString.Empty;
// Assert
Assert.Equal(string.Empty, content.ToString());
}
[Fact]
public void ToString_ReturnsText()
{
// Arrange
var expectedText = "Hello";
var content = new HtmlString(expectedText);
// Act
var result = content.ToString();
// Assert
Assert.Equal(expectedText, result);
}
}
}

View File

@ -7,6 +7,7 @@ using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Testing;
using Microsoft.Framework.WebEncoders.Testing;
using Xunit;
namespace Microsoft.AspNet.Mvc.Rendering
@ -31,7 +32,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
writer.Write('m');
// Assert
Assert.Equal<object>(expected, writer.Buffer.BufferEntries);
Assert.Equal(expected, writer.Content.Entries);
}
[Fact]
@ -49,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
writer.WriteLine(3L);
// Assert
Assert.Equal(expected, writer.Buffer.BufferEntries);
Assert.Equal(expected, writer.Content.Entries);
}
[Fact]
@ -67,7 +68,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
await writer.WriteLineAsync(input3.Array, input3.Offset, input3.Count);
// Assert
var buffer = writer.Buffer.BufferEntries;
var buffer = writer.Content.Entries;
Assert.Equal(4, buffer.Count);
Assert.Equal("bcd", buffer[0]);
Assert.Equal("ef", buffer[1]);
@ -87,7 +88,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
await writer.WriteLineAsync();
// Assert
var actual = writer.Buffer.BufferEntries;
var actual = writer.Content.Entries;
Assert.Equal<object>(new[] { newLine, newLine }, actual);
}
@ -109,8 +110,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
await writer.WriteLineAsync(input4);
// Assert
var actual = writer.Buffer.BufferEntries;
Assert.Equal<object>(new[] { input1, input2, newLine, input3, input4, newLine }, actual);
var actual = writer.Content.Entries;
Assert.Equal(new[] { input1, input2, newLine, input3, input4, newLine }, actual);
}
[Fact]
@ -123,13 +124,15 @@ namespace Microsoft.AspNet.Mvc.Rendering
// Act
source.Write("Hello world");
source.Write(new char[1], 0, 1);
source.CopyTo(target);
source.CopyTo(target, new CommonTestEncoder());
// Assert
// Make sure content was written to the source.
Assert.Equal(2, source.Buffer.BufferEntries.Count);
Assert.Equal(1, target.Buffer.BufferEntries.Count);
Assert.Same(source.Buffer.BufferEntries, target.Buffer.BufferEntries[0]);
Assert.Equal(2, source.Content.Entries.Count);
Assert.Equal(1, target.Content.Entries.Count);
var result = Assert.Single(target.Content.Entries);
var bufferedHtmlContent = Assert.IsType<BufferedHtmlContent>(result);
Assert.Same(source.Content.Entries, bufferedHtmlContent.Entries);
}
[Fact]
@ -143,7 +146,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
// Act
source.WriteLine("Hello world");
source.Write(new[] { 'x', 'a', 'b', 'c' }, 1, 3);
source.CopyTo(target);
source.CopyTo(target, new CommonTestEncoder());
// Assert
Assert.Equal(expected, target.ToString());