Add AppendFormat extension methods on IHtmlContent
This commit is contained in:
parent
d82bc7ca9d
commit
a602b47e26
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.Framework.WebEncoders;
|
||||
|
||||
namespace Microsoft.AspNet.Html.Abstractions
|
||||
|
|
@ -13,6 +15,82 @@ namespace Microsoft.AspNet.Html.Abstractions
|
|||
/// </summary>
|
||||
public static class HtmlContentBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Appends the specified <paramref name="format"/> to the existing content after replacing each format
|
||||
/// item with the HTML encoded <see cref="string"/> representation of the corresponding item in the
|
||||
/// <paramref name="args"/> array.
|
||||
/// </summary>
|
||||
/// <param name="format">
|
||||
/// The composite format <see cref="string"/> (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx).
|
||||
/// The format string is assumed to be HTML encoded as-provided, and no further encoding will be performed.
|
||||
/// </param>
|
||||
/// <param name="args">
|
||||
/// The object array to format. Each element in the array will be formatted and then HTML encoded.
|
||||
/// </param>
|
||||
/// <returns>A reference to this instance after the append operation has completed.</returns>
|
||||
public static IHtmlContentBuilder AppendFormat(
|
||||
this IHtmlContentBuilder builder,
|
||||
string format,
|
||||
params object[] args)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (format == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(format));
|
||||
}
|
||||
|
||||
if (args == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
}
|
||||
|
||||
builder.Append(new HtmlFormatString(format, args));
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends the specified <paramref name="format"/> to the existing content with information from the
|
||||
/// <paramref name="provider"/> after replacing each format item with the HTML encoded <see cref="string"/>
|
||||
/// representation of the corresponding item in the <paramref name="args"/> array.
|
||||
/// </summary>
|
||||
/// <param name="formatProvider">An object that supplies culture-specific formatting information.</param>
|
||||
/// <param name="format">
|
||||
/// The composite format <see cref="string"/> (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx).
|
||||
/// The format string is assumed to be HTML encoded as-provided, and no further encoding will be performed.
|
||||
/// </param>
|
||||
/// <param name="args">
|
||||
/// The object array to format. Each element in the array will be formatted and then HTML encoded.
|
||||
/// </param>
|
||||
/// <returns>A reference to this instance after the append operation has completed.</returns>
|
||||
public static IHtmlContentBuilder AppendFormat(
|
||||
this IHtmlContentBuilder builder,
|
||||
IFormatProvider formatProvider,
|
||||
string format,
|
||||
params object[] args)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (format == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(format));
|
||||
}
|
||||
|
||||
if (args == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
}
|
||||
|
||||
builder.Append(new HtmlFormatString(formatProvider, format, args));
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends an <see cref="Environment.NewLine"/>.
|
||||
/// </summary>
|
||||
|
|
@ -132,5 +210,141 @@ namespace Microsoft.AspNet.Html.Abstractions
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
[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, IHtmlEncoder 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 IHtmlEncoder _encoder;
|
||||
private readonly IFormatProvider _formatProvider;
|
||||
|
||||
public EncodingFormatProvider(IFormatProvider formatProvider, IHtmlEncoder encoder)
|
||||
{
|
||||
Debug.Assert(formatProvider != null);
|
||||
Debug.Assert(encoder != null);
|
||||
|
||||
_formatProvider = formatProvider;
|
||||
_encoder = encoder;
|
||||
}
|
||||
|
||||
public string Format(string format, object arg, IFormatProvider formatProvider)
|
||||
{
|
||||
// This is the case we need to special case. We trust the IHtmlContent instance to do the
|
||||
// right thing with encoding.
|
||||
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.HtmlEncode(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.HtmlEncode(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.HtmlEncode(result);
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public object GetFormat(Type formatType)
|
||||
{
|
||||
if (formatType == typeof(ICustomFormatter))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
"dnx451": { },
|
||||
"dnxcore50": {
|
||||
"dependencies": {
|
||||
"System.Resources.ResourceManager": "4.0.1-beta-"
|
||||
"System.Resources.ResourceManager": "4.0.1-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"System.Diagnostics.Debug": "4.0.11-beta-*",
|
||||
"System.IO": "4.0.11-beta-*",
|
||||
"System.Reflection": "4.0.10-*",
|
||||
"System.Resources.ResourceManager": "4.0.1-beta-",
|
||||
"System.Resources.ResourceManager": "4.0.1-beta-*",
|
||||
"System.Runtime.Extensions": "4.0.11-beta-*",
|
||||
"System.Threading": "4.0.11-beta-*"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Microsoft.AspNet.Testing;
|
||||
using Microsoft.Framework.WebEncoders;
|
||||
using Microsoft.Framework.WebEncoders.Testing;
|
||||
using Xunit;
|
||||
|
|
@ -126,6 +128,220 @@ namespace Microsoft.AspNet.Html.Abstractions.Test
|
|||
entry => Assert.Equal("Hi", Assert.IsType<EncodedString>(entry).Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_AppendFormat()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TestHtmlContentBuilder();
|
||||
|
||||
// Act
|
||||
builder.AppendFormat("{0} {1} {2} {3}!", "First", "Second", "Third", "Fourth");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"HtmlEncode[[First]] HtmlEncode[[Second]] HtmlEncode[[Third]] HtmlEncode[[Fourth]]!",
|
||||
HtmlContentToString(builder));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_AppendFormat_HtmlContent()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TestHtmlContentBuilder();
|
||||
|
||||
// Act
|
||||
builder.AppendFormat("{0}!", new EncodedString("First"));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"First!",
|
||||
HtmlContentToString(builder));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_AppendFormatContent_With1Argument()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TestHtmlContentBuilder();
|
||||
|
||||
// Act
|
||||
builder.AppendFormat("0x{0:X} - hex equivalent for 50.", 50);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"0xHtmlEncode[[32]] - hex equivalent for 50.",
|
||||
HtmlContentToString(builder));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_AppendFormatContent_With2Arguments()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TestHtmlContentBuilder();
|
||||
|
||||
// Act
|
||||
builder.AppendFormat("0x{0:X} - hex equivalent for {1}.", 50, 50);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"0xHtmlEncode[[32]] - hex equivalent for HtmlEncode[[50]].",
|
||||
HtmlContentToString(builder));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_AppendFormatContent_With3Arguments()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TestHtmlContentBuilder();
|
||||
|
||||
// Act
|
||||
builder.AppendFormat("0x{0:X} - {1} equivalent for {2}.", 50, "hex", 50);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"0xHtmlEncode[[32]] - HtmlEncode[[hex]] equivalent for HtmlEncode[[50]].",
|
||||
HtmlContentToString(builder));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_AppendFormat_WithAlignmentComponent()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TestHtmlContentBuilder();
|
||||
|
||||
// Act
|
||||
builder.AppendFormat("{0, -25} World!", "Hello");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"HtmlEncode[[Hello]] World!",
|
||||
HtmlContentToString(builder));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_AppendFormat_WithFormatStringComponent()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TestHtmlContentBuilder();
|
||||
|
||||
// Act
|
||||
builder.AppendFormat("0x{0:X}", 50);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("0xHtmlEncode[[32]]", HtmlContentToString(builder));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_AppendFormat_WithCulture()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TestHtmlContentBuilder();
|
||||
|
||||
// Act
|
||||
builder.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Numbers in InvariantCulture - {0, -5:N} {1} {2} {3}!",
|
||||
1.1,
|
||||
2.98,
|
||||
145.82,
|
||||
32.86);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"Numbers in InvariantCulture - HtmlEncode[[1.10]] HtmlEncode[[2.98]] " +
|
||||
"HtmlEncode[[145.82]] HtmlEncode[[32.86]]!",
|
||||
HtmlContentToString(builder));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_AppendFormat_WithCulture_1Argument()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TestHtmlContentBuilder();
|
||||
|
||||
// Act
|
||||
builder.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Numbers in InvariantCulture - {0:N}!",
|
||||
1.1);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"Numbers in InvariantCulture - HtmlEncode[[1.10]]!",
|
||||
HtmlContentToString(builder));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_AppendFormat_WithCulture_2Arguments()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TestHtmlContentBuilder();
|
||||
|
||||
// Act
|
||||
builder.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Numbers in InvariantCulture - {0:N} {1}!",
|
||||
1.1,
|
||||
2.98);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"Numbers in InvariantCulture - HtmlEncode[[1.10]] HtmlEncode[[2.98]]!",
|
||||
HtmlContentToString(builder));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_AppendFormat_WithCulture_3Arguments()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TestHtmlContentBuilder();
|
||||
|
||||
// Act
|
||||
builder.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Numbers in InvariantCulture - {0:N} {1} {2}!",
|
||||
1.1,
|
||||
2.98,
|
||||
3.12);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"Numbers in InvariantCulture - HtmlEncode[[1.10]] HtmlEncode[[2.98]] HtmlEncode[[3.12]]!",
|
||||
HtmlContentToString(builder));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_AppendFormat_WithDifferentCulture()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TestHtmlContentBuilder();
|
||||
var culture = new CultureInfo("fr-FR");
|
||||
|
||||
// Act
|
||||
builder.AppendFormat(culture, "{0} in french!", 1.21);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"HtmlEncode[[1,21]] in french!",
|
||||
HtmlContentToString(builder));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[ReplaceCulture]
|
||||
public void Builder_AppendFormat_WithDifferentCurrentCulture()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TestHtmlContentBuilder();
|
||||
|
||||
// Act
|
||||
builder.AppendFormat(CultureInfo.CurrentCulture, "{0:D}", DateTime.Parse("01/02/2015"));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"HtmlEncode[[01 February 2015]]",
|
||||
HtmlContentToString(builder));
|
||||
}
|
||||
|
||||
private static string HtmlContentToString(IHtmlContent content)
|
||||
{
|
||||
using (var writer = new StringWriter())
|
||||
|
|
@ -164,7 +380,10 @@ namespace Microsoft.AspNet.Html.Abstractions.Test
|
|||
|
||||
public void WriteTo(TextWriter writer, IHtmlEncoder encoder)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
foreach (var entry in Entries)
|
||||
{
|
||||
entry.WriteTo(writer, encoder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -179,7 +398,7 @@ namespace Microsoft.AspNet.Html.Abstractions.Test
|
|||
|
||||
public void WriteTo(TextWriter writer, IHtmlEncoder encoder)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
writer.Write(Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +413,7 @@ namespace Microsoft.AspNet.Html.Abstractions.Test
|
|||
|
||||
public void WriteTo(TextWriter writer, IHtmlEncoder encoder)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
encoder.HtmlEncode(Value, writer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue