Merge branch 'release' into dev

This commit is contained in:
Ryan Nowak 2016-04-06 16:43:20 -07:00
commit 1366621f97
3 changed files with 403 additions and 144 deletions

View File

@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Html
throw new ArgumentNullException(nameof(args));
}
builder.AppendHtml(new HtmlFormatString(format, args));
builder.AppendHtml(new HtmlFormattableString(format, args));
return builder;
}
@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Html
throw new ArgumentNullException(nameof(args));
}
builder.AppendHtml(new HtmlFormatString(formatProvider, format, args));
builder.AppendHtml(new HtmlFormattableString(formatProvider, format, args));
return builder;
}
@ -219,147 +219,5 @@ namespace Microsoft.AspNetCore.Html
builder.AppendHtml(encoded);
return builder;
}
[DebuggerDisplay("{DebuggerToString()}")]
private class HtmlFormatString : IHtmlContent
{
private readonly IFormatProvider _formatProvider;
private readonly string _format;
private readonly object[] _args;
public HtmlFormatString(string format, object[] args)
: this(null, format, args)
{
}
public HtmlFormatString(IFormatProvider formatProvider, string format, object[] args)
{
Debug.Assert(format != null);
Debug.Assert(args != null);
_formatProvider = formatProvider ?? CultureInfo.CurrentCulture;
_format = format;
_args = args;
}
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
if (encoder == null)
{
throw new ArgumentNullException(nameof(encoder));
}
var formatProvider = new EncodingFormatProvider(_formatProvider, encoder);
writer.Write(string.Format(formatProvider, _format, _args));
}
private string DebuggerToString()
{
using (var writer = new StringWriter())
{
WriteTo(writer, HtmlEncoder.Default);
return writer.ToString();
}
}
}
// This class implements Html encoding via an ICustomFormatter. Passing an instance of this
// class into a string.Format method or anything similar will evaluate arguments implementing
// IHtmlContent without HTML encoding them, and will give other arguments the standard
// composite format string treatment, and then HTML encode the result.
//
// Plenty of examples of ICustomFormatter and the interactions with string.Format here:
// https://msdn.microsoft.com/en-us/library/system.string.format(v=vs.110).aspx#Format6_Example
private class EncodingFormatProvider : IFormatProvider, ICustomFormatter
{
private readonly HtmlEncoder _encoder;
private readonly IFormatProvider _formatProvider;
public EncodingFormatProvider(IFormatProvider formatProvider, HtmlEncoder encoder)
{
Debug.Assert(formatProvider != null);
Debug.Assert(encoder != null);
_formatProvider = formatProvider;
_encoder = encoder;
}
public string Format(string format, object arg, IFormatProvider formatProvider)
{
// These are the cases we need to special case. We trust the HtmlEncodedString or IHtmlContent instance
// to do the right thing with encoding.
var htmlString = arg as HtmlEncodedString;
if (htmlString != null)
{
return htmlString.ToString();
}
var htmlContent = arg as IHtmlContent;
if (htmlContent != null)
{
using (var writer = new StringWriter())
{
htmlContent.WriteTo(writer, _encoder);
return writer.ToString();
}
}
// If we get here then 'arg' is not an IHtmlContent, and we want to handle it the way a normal
// string.Format would work, but then HTML encode the result.
//
// First check for an ICustomFormatter - if the IFormatProvider is a CultureInfo, then it's likely
// that ICustomFormatter will be null.
var customFormatter = (ICustomFormatter)_formatProvider.GetFormat(typeof(ICustomFormatter));
if (customFormatter != null)
{
var result = customFormatter.Format(format, arg, _formatProvider);
if (result != null)
{
return _encoder.Encode(result);
}
}
// Next check if 'arg' is an IFormattable (DateTime is an example).
//
// An IFormattable will likely call back into the IFormatterProvider and ask for more information
// about how to format itself. This is the typical case when IFormatterProvider is a CultureInfo.
var formattable = arg as IFormattable;
if (formattable != null)
{
var result = formattable.ToString(format, _formatProvider);
if (result != null)
{
return _encoder.Encode(result);
}
}
// If we get here then there's nothing really smart left to try.
if (arg != null)
{
var result = arg.ToString();
if (result != null)
{
return _encoder.Encode(result);
}
}
return string.Empty;
}
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
{
return this;
}
return null;
}
}
}
}

View File

@ -0,0 +1,184 @@
// 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.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text.Encodings.Web;
namespace Microsoft.AspNetCore.Html
{
/// <summary>
/// An <see cref="IHtmlContent"/> implementation of composite string formatting
/// (see https://msdn.microsoft.com/en-us/library/txafckwd(v=vs.110).aspx) which HTML encodes
/// formatted arguments.
/// </summary>
[DebuggerDisplay("{DebuggerToString()}")]
public class HtmlFormattableString : IHtmlContent
{
private readonly IFormatProvider _formatProvider;
private readonly string _format;
private readonly object[] _args;
/// <summary>
/// Creates a new <see cref="HtmlFormattableString"/> with the given <paramref name="format"/> and
/// <paramref name="args"/>.
/// </summary>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array that contains objects to format.</param>
public HtmlFormattableString(string format, params object[] args)
: this(formatProvider: null, format: format, args: args)
{
}
/// <summary>
/// Creates a new <see cref="HtmlFormattableString"/> with the given <paramref name="formatProvider"/>,
/// <paramref name="format"/> and <paramref name="args"/>.
/// </summary>
/// <param name="formatProvider">An object that provides culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array that contains objects to format.</param>
public HtmlFormattableString(IFormatProvider formatProvider, string format, params object[] args)
{
if (format == null)
{
throw new ArgumentNullException(nameof(format));
}
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}
_formatProvider = formatProvider ?? CultureInfo.CurrentCulture;
_format = format;
_args = args;
}
/// <inheritdoc />
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
if (encoder == null)
{
throw new ArgumentNullException(nameof(encoder));
}
var formatProvider = new EncodingFormatProvider(_formatProvider, encoder);
writer.Write(string.Format(formatProvider, _format, _args));
}
private string DebuggerToString()
{
using (var writer = new StringWriter())
{
WriteTo(writer, HtmlEncoder.Default);
return writer.ToString();
}
}
// This class implements Html encoding via an ICustomFormatter. Passing an instance of this
// class into a string.Format method or anything similar will evaluate arguments implementing
// IHtmlContent without HTML encoding them, and will give other arguments the standard
// composite format string treatment, and then HTML encode the result.
//
// Plenty of examples of ICustomFormatter and the interactions with string.Format here:
// https://msdn.microsoft.com/en-us/library/system.string.format(v=vs.110).aspx#Format6_Example
private class EncodingFormatProvider : IFormatProvider, ICustomFormatter
{
private readonly HtmlEncoder _encoder;
private readonly IFormatProvider _formatProvider;
private StringWriter _writer;
public EncodingFormatProvider(IFormatProvider formatProvider, HtmlEncoder encoder)
{
Debug.Assert(formatProvider != null);
Debug.Assert(encoder != null);
_formatProvider = formatProvider;
_encoder = encoder;
}
public string Format(string format, object arg, IFormatProvider formatProvider)
{
// These are the cases we need to special case. We trust the HtmlEncodedString or IHtmlContent instance
// to do the right thing with encoding.
var htmlString = arg as HtmlEncodedString;
if (htmlString != null)
{
return htmlString.ToString();
}
var htmlContent = arg as IHtmlContent;
if (htmlContent != null)
{
_writer = _writer ?? new StringWriter();
htmlContent.WriteTo(_writer, _encoder);
var result = _writer.ToString();
_writer.GetStringBuilder().Clear();
return result;
}
// If we get here then 'arg' is not an IHtmlContent, and we want to handle it the way a normal
// string.Format would work, but then HTML encode the result.
//
// First check for an ICustomFormatter - if the IFormatProvider is a CultureInfo, then it's likely
// that ICustomFormatter will be null.
var customFormatter = (ICustomFormatter)_formatProvider.GetFormat(typeof(ICustomFormatter));
if (customFormatter != null)
{
var result = customFormatter.Format(format, arg, _formatProvider);
if (result != null)
{
return _encoder.Encode(result);
}
}
// Next check if 'arg' is an IFormattable (DateTime is an example).
//
// An IFormattable will likely call back into the IFormatterProvider and ask for more information
// about how to format itself. This is the typical case when IFormatterProvider is a CultureInfo.
var formattable = arg as IFormattable;
if (formattable != null)
{
var result = formattable.ToString(format, _formatProvider);
if (result != null)
{
return _encoder.Encode(result);
}
}
// If we get here then there's nothing really smart left to try.
if (arg != null)
{
var result = arg.ToString();
if (result != null)
{
return _encoder.Encode(result);
}
}
return string.Empty;
}
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
{
return this;
}
return null;
}
}
}
}

View File

@ -0,0 +1,217 @@
// 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.Globalization;
using System.IO;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.WebEncoders.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Html
{
public class HtmlFormattableStringTest
{
[Fact]
public void HtmlFormattableString_EmptyArgs()
{
// Arrange
var formattableString = new HtmlFormattableString("Hello, World!");
// Act
var result = HtmlContentToString(formattableString);
// Assert
Assert.Equal("Hello, World!", result);
}
[Fact]
public void HtmlFormattableString_EmptyArgsAndCulture()
{
// Arrange
var formattableString = new HtmlFormattableString(CultureInfo.CurrentCulture, "Hello, World!");
// Act
var result = HtmlContentToString(formattableString);
// Assert
Assert.Equal("Hello, World!", result);
}
[Fact]
public void HtmlFormattableString_MultipleArguments()
{
// Arrange
var formattableString = new HtmlFormattableString("{0} {1} {2} {3}!", "First", "Second", "Third", "Fourth");
// Act
var result = HtmlContentToString(formattableString);
// Assert
Assert.Equal(
"HtmlEncode[[First]] HtmlEncode[[Second]] HtmlEncode[[Third]] HtmlEncode[[Fourth]]!",
result);
}
[Fact]
public void HtmlFormattableString_WithHtmlEncodedString()
{
// Arrange
var formattableString = new HtmlFormattableString("{0}!", new HtmlEncodedString("First"));
// Act
var result = HtmlContentToString(formattableString);
// Assert
Assert.Equal("First!", result);
}
[Fact]
public void HtmlFormattableString_WithOtherIHtmlContent()
{
// Arrange
var builder = new HtmlContentBuilder();
builder.Append("First");
var formattableString = new HtmlFormattableString("{0}!", builder);
// Act
var result = HtmlContentToString(formattableString);
// Assert
Assert.Equal("HtmlEncode[[First]]!", result);
}
// This test is needed to ensure the shared StringWriter gets cleared.
[Fact]
public void HtmlFormattableString_WithMultipleHtmlContentArguments()
{
// Arrange
var formattableString = new HtmlFormattableString(
"Happy {0}, {1}!",
new HtmlEncodedString("Birthday"),
new HtmlContentBuilder().Append("Billy"));
// Act
var result = HtmlContentToString(formattableString);
// Assert
Assert.Equal("Happy Birthday, HtmlEncode[[Billy]]!", result);
}
[Fact]
public void HtmlFormattableString_WithHtmlEncodedString_AndOffset()
{
// Arrange
var formattableString = new HtmlFormattableString("{0, 20}!", new HtmlEncodedString("First"));
// Act
var result = HtmlContentToString(formattableString);
// Assert
Assert.Equal(" First!", result);
}
[Fact]
public void HtmlFormattableString_With3Arguments()
{
// Arrange
var formattableString = new HtmlFormattableString("0x{0:X} - {1} equivalent for {2}.", 50, "hex", 50);
// Act
var result = HtmlContentToString(formattableString);
// Assert
Assert.Equal(
"0xHtmlEncode[[32]] - HtmlEncode[[hex]] equivalent for HtmlEncode[[50]].",
result);
}
[Fact]
public void HtmlFormattableString_WithAlignmentComponent()
{
// Arrange
var formattableString = new HtmlFormattableString("{0, -25} World!", "Hello");
// Act
var result = HtmlContentToString(formattableString);
// Assert
Assert.Equal(
"HtmlEncode[[Hello]] World!", result);
}
[Fact]
public void HtmlFormattableString_WithFormatStringComponent()
{
// Arrange
var formattableString = new HtmlFormattableString("0x{0:X}", 50);
// Act
var result = HtmlContentToString(formattableString);
// Assert
Assert.Equal("0xHtmlEncode[[32]]", result);
}
[Fact]
public void HtmlFormattableString_WithCulture()
{
// Arrange
var formattableString = new HtmlFormattableString(
CultureInfo.InvariantCulture,
"Numbers in InvariantCulture - {0, -5:N} {1} {2} {3}!",
1.1,
2.98,
145.82,
32.86);
// Act
var result = HtmlContentToString(formattableString);
// Assert
Assert.Equal(
"Numbers in InvariantCulture - HtmlEncode[[1.10]] HtmlEncode[[2.98]] " +
"HtmlEncode[[145.82]] HtmlEncode[[32.86]]!",
result);
}
[Fact]
[ReplaceCulture("en-US", "en-US")]
public void HtmlFormattableString_UsesPassedInCulture()
{
// Arrange
var culture = new CultureInfo("fr-FR");
var formattableString = new HtmlFormattableString(culture, "{0} in french!", 1.21);
// Act
var result = HtmlContentToString(formattableString);
// Assert
Assert.Equal("HtmlEncode[[1,21]] in french!", result);
}
[Fact]
[ReplaceCulture("de-DE", "de-DE")]
public void HtmlFormattableString_UsesCurrentCulture()
{
// Arrange
var formattableString = new HtmlFormattableString("{0:D}", DateTime.Parse("01/02/2015"));
// Act
var result = HtmlContentToString(formattableString);
// Assert
Assert.Equal("HtmlEncode[[Sonntag, 1. Februar 2015]]", result);
}
private static string HtmlContentToString(IHtmlContent content)
{
using (var writer = new StringWriter())
{
content.WriteTo(writer, new HtmlTestEncoder());
return writer.ToString();
}
}
}
}