From 2028351bb8d81194da6c58b544f4a37b3288eb87 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 29 Sep 2015 17:11:26 -0700 Subject: [PATCH] React to WriteAttribute \ AddHtmlAttribute API changes --- .../MvcRazorHost.cs | 4 +- .../AttributeValue.cs | 45 -- .../PositionTagged.cs | 37 -- src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs | 396 +++++++++++------- .../TestFiles/Output/Runtime/Basic.cs | 5 +- .../RazorPageTest.cs | 202 ++++----- 6 files changed, 329 insertions(+), 360 deletions(-) delete mode 100644 src/Microsoft.AspNet.Mvc.Razor/AttributeValue.cs delete mode 100644 src/Microsoft.AspNet.Mvc.Razor/PositionTagged.cs diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs index 5b31b4accf..5d7de1693f 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs @@ -107,7 +107,9 @@ namespace Microsoft.AspNet.Mvc.Razor // Can't use nameof because IHtmlHelper is (also) not accessible here. MarkAsHtmlEncodedMethodName = HtmlHelperPropertyName + ".Raw", - AddHtmlAttributeValuesMethodName = "AddHtmlAttributeValues", + BeginAddHtmlAttributeValuesMethodName = "BeginAddHtmlAttributeValues", + EndAddHtmlAttributeValuesMethodName = "EndAddHtmlAttributeValues", + AddHtmlAttributeValueMethodName = "AddHtmlAttributeValue", HtmlEncoderPropertyName = "HtmlEncoder", TagHelperContentGetContentMethodName = nameof(TagHelperContent.GetContent), }) diff --git a/src/Microsoft.AspNet.Mvc.Razor/AttributeValue.cs b/src/Microsoft.AspNet.Mvc.Razor/AttributeValue.cs deleted file mode 100644 index 8b1d843708..0000000000 --- a/src/Microsoft.AspNet.Mvc.Razor/AttributeValue.cs +++ /dev/null @@ -1,45 +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 -{ - public class AttributeValue - { - public AttributeValue(PositionTagged prefix, PositionTagged value, bool literal) - { - Prefix = prefix; - Value = value; - Literal = literal; - } - - public PositionTagged Prefix { get; private set; } - - public PositionTagged Value { get; private set; } - - public bool Literal { get; private set; } - - public static AttributeValue FromTuple(Tuple, Tuple, bool> value) - { - return new AttributeValue(value.Item1, value.Item2, value.Item3); - } - - public static AttributeValue FromTuple(Tuple, Tuple, bool> value) - { - return new AttributeValue(value.Item1, - new PositionTagged(value.Item2.Item1, value.Item2.Item2), - value.Item3); - } - - public static implicit operator AttributeValue(Tuple, Tuple, bool> value) - { - return FromTuple(value); - } - - public static implicit operator AttributeValue(Tuple, Tuple, bool> value) - { - return FromTuple(value); - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Razor/PositionTagged.cs b/src/Microsoft.AspNet.Mvc.Razor/PositionTagged.cs deleted file mode 100644 index 4e2763809f..0000000000 --- a/src/Microsoft.AspNet.Mvc.Razor/PositionTagged.cs +++ /dev/null @@ -1,37 +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.Diagnostics; - -namespace Microsoft.AspNet.Mvc.Razor -{ - [DebuggerDisplay("({Position})\"{Value}\"")] - public class PositionTagged - { - public PositionTagged(T value, int offset) - { - Position = offset; - Value = value; - } - - public int Position { get; private set; } - - public T Value { get; private set; } - - public override string ToString() - { - return Value.ToString(); - } - - public static implicit operator T(PositionTagged value) - { - return value.Value; - } - - public static implicit operator PositionTagged(Tuple value) - { - return new PositionTagged(value.Item1, value.Item2); - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs index 9a6fed11b0..203ca246a3 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -34,6 +35,9 @@ namespace Microsoft.AspNet.Mvc.Razor private ITagHelperActivator _tagHelperActivator; private ITypeActivatorCache _typeActivatorCache; private bool _renderedBody; + private AttributeInfo _attributeInfo; + private TagHelperAttributeInfo _tagHelperAttributeInfo; + private StringCollectionTextWriter _valueBuffer; public RazorPage() { @@ -571,31 +575,25 @@ namespace Microsoft.AspNet.Mvc.Razor } } - public virtual void WriteAttribute( + public virtual void BeginWriteAttribute( string name, - PositionTagged prefix, - PositionTagged suffix, - params AttributeValue[] values) + string prefix, + int prefixOffset, + string suffix, + int suffixOffset, + int attributeValuesCount) { - if (prefix == null) - { - throw new ArgumentNullException(nameof(prefix)); - } - - if (suffix == null) - { - throw new ArgumentNullException(nameof(suffix)); - } - - WriteAttributeTo(Output, name, prefix, suffix, values); + BeginWriteAttributeTo(Output, name, prefix, prefixOffset, suffix, suffixOffset, attributeValuesCount); } - public virtual void WriteAttributeTo( + public virtual void BeginWriteAttributeTo( TextWriter writer, string name, - PositionTagged prefix, - PositionTagged suffix, - params AttributeValue[] values) + string prefix, + int prefixOffset, + string suffix, + int suffixOffset, + int attributeValuesCount) { if (writer == null) { @@ -612,112 +610,170 @@ namespace Microsoft.AspNet.Mvc.Razor throw new ArgumentNullException(nameof(suffix)); } - if (values.Length == 0) + _attributeInfo = new AttributeInfo(name, prefix, prefixOffset, suffix, suffixOffset, attributeValuesCount); + + // Single valued attributes might be omitted in entirety if it the attribute value strictly evaluates to + // null or false. Consequently defer the prefix generation until we encounter the attribute value. + if (attributeValuesCount != 1) { - // Explicitly empty attribute, so write the prefix - WritePositionTaggedLiteral(writer, prefix); + WritePositionTaggedLiteral(writer, prefix, prefixOffset); } - else if (IsSingleBoolFalseOrNullValue(values)) - { - // Value is either null or the bool 'false' with no prefix; don't render the attribute. - return; - } - else if (UseAttributeNameAsValue(values)) - { - var attributeValue = values[0]; - var positionTaggedAttributeValue = attributeValue.Value; - - WritePositionTaggedLiteral(writer, prefix); - - var sourceLength = suffix.Position - positionTaggedAttributeValue.Position; - var nameAttributeValue = new AttributeValue( - attributeValue.Prefix, - new PositionTagged(name, attributeValue.Value.Position), - literal: attributeValue.Literal); - - // The value is just the bool 'true', write the attribute name instead of the string 'True'. - WriteAttributeValue(writer, nameAttributeValue, sourceLength); - } - else - { - // This block handles two cases. - // 1. Single value with prefix. - // 2. Multiple values with or without prefix. - WritePositionTaggedLiteral(writer, prefix); - for (var i = 0; i < values.Length; i++) - { - var attributeValue = values[i]; - var positionTaggedAttributeValue = attributeValue.Value; - - if (positionTaggedAttributeValue.Value == null) - { - // Nothing to write - continue; - } - - var next = i == values.Length - 1 ? - suffix : // End of the list, grab the suffix - values[i + 1].Prefix; // Still in the list, grab the next prefix - - // Calculate length of the source span by the position of the next value (or suffix) - var sourceLength = next.Position - attributeValue.Value.Position; - - WriteAttributeValue(writer, attributeValue, sourceLength); - } - } - - WritePositionTaggedLiteral(writer, suffix); } - public void AddHtmlAttributeValues( - string attributeName, - TagHelperExecutionContext executionContext, - params AttributeValue[] values) + public void WriteAttributeValue( + string prefix, + int prefixOffset, + object value, + int valueOffset, + int valueLength, + bool isLiteral) { - if (IsSingleBoolFalseOrNullValue(values)) - { - // The first value was 'null' or 'false' indicating that we shouldn't render the attribute. The - // attribute is treated as a TagHelper attribute so it's only available in - // TagHelperContext.AllAttributes for TagHelper authors to see (if they want to see why the attribute - // was removed from TagHelperOutput.Attributes). - executionContext.AddTagHelperAttribute( - attributeName, - values[0].Value.Value?.ToString() ?? string.Empty); + WriteAttributeValueTo(Output, prefix, prefixOffset, value, valueOffset, valueLength, isLiteral); + } - return; - } - else if (UseAttributeNameAsValue(values)) + public void WriteAttributeValueTo( + TextWriter writer, + string prefix, + int prefixOffset, + object value, + int valueOffset, + int valueLength, + bool isLiteral) + { + if (_attributeInfo.AttributeValuesCount == 1) { - executionContext.AddHtmlAttribute(attributeName, attributeName); - } - else - { - var valueBuffer = new StringCollectionTextWriter(Output.Encoding); - - foreach (var value in values) + if (IsBoolFalseOrNullValue(prefix, value)) { - if (value.Value.Value == null) - { - // Skip null values - continue; - } - - if (!string.IsNullOrEmpty(value.Prefix)) - { - WriteLiteralTo(valueBuffer, value.Prefix); - } - - WriteUnprefixedAttributeValueTo(valueBuffer, value); + // Value is either null or the bool 'false' with no prefix; don't render the attribute. + _attributeInfo.Suppressed = true; + return; } - using (var stringWriter = new StringWriter()) - { - valueBuffer.Content.WriteTo(stringWriter, HtmlEncoder); + // We are not omitting the attribute. Write the prefix. + WritePositionTaggedLiteral(writer, _attributeInfo.Prefix, _attributeInfo.PrefixOffset); - var htmlString = new HtmlString(stringWriter.ToString()); - executionContext.AddHtmlAttribute(attributeName, htmlString); + if (IsBoolTrueWithEmptyPrefixValue(prefix, value)) + { + // The value is just the bool 'true', write the attribute name instead of the string 'True'. + value = _attributeInfo.Name; } } + + // This block handles two cases. + // 1. Single value with prefix. + // 2. Multiple values with or without prefix. + if (value != null) + { + if (!string.IsNullOrEmpty(prefix)) + { + WritePositionTaggedLiteral(writer, prefix, prefixOffset); + } + + BeginContext(valueOffset, valueLength, isLiteral); + + WriteUnprefixedAttributeValueTo(writer, value, isLiteral); + + EndContext(); + } + } + + public virtual void EndWriteAttribute() + { + EndWriteAttributeTo(Output); + } + + public virtual void EndWriteAttributeTo(TextWriter writer) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + if (!_attributeInfo.Suppressed) + { + WritePositionTaggedLiteral(writer, _attributeInfo.Suffix, _attributeInfo.SuffixOffset); + } + } + + public void BeginAddHtmlAttributeValues( + TagHelperExecutionContext executionContext, + string attributeName, + int attributeValuesCount) + { + _tagHelperAttributeInfo = new TagHelperAttributeInfo(executionContext, attributeName, attributeValuesCount); + _valueBuffer = null; + } + + public void AddHtmlAttributeValue( + string prefix, + int prefixOffset, + object value, + int valueOffset, + int valueLength, + bool isLiteral) + { + Debug.Assert(_tagHelperAttributeInfo.ExecutionContext != null); + if (_tagHelperAttributeInfo.AttributeValuesCount == 1) + { + if (IsBoolFalseOrNullValue(prefix, value)) + { + // The first value was 'null' or 'false' indicating that we shouldn't render the attribute. The + // attribute is treated as a TagHelper attribute so it's only available in + // TagHelperContext.AllAttributes for TagHelper authors to see (if they want to see why the + // attribute was removed from TagHelperOutput.Attributes). + _tagHelperAttributeInfo.ExecutionContext.AddTagHelperAttribute( + _tagHelperAttributeInfo.Name, + value?.ToString() ?? string.Empty); + _tagHelperAttributeInfo.Suppressed = true; + return; + } + else if (IsBoolTrueWithEmptyPrefixValue(prefix, value)) + { + _tagHelperAttributeInfo.ExecutionContext.AddHtmlAttribute( + _tagHelperAttributeInfo.Name, + _tagHelperAttributeInfo.Name); + _tagHelperAttributeInfo.Suppressed = true; + return; + } + } + + if (value != null) + { + if (_valueBuffer == null) + { + _valueBuffer = new StringCollectionTextWriter(Output.Encoding); + } + + if (!string.IsNullOrEmpty(prefix)) + { + WriteLiteralTo(_valueBuffer, prefix); + } + + WriteUnprefixedAttributeValueTo(_valueBuffer, value, isLiteral); + } + } + + public void EndAddHtmlAttributeValues(TagHelperExecutionContext executionContext) + { + if (!_tagHelperAttributeInfo.Suppressed) + { + HtmlString htmlString; + + if (_valueBuffer != null) + { + using (var stringWriter = new StringWriter()) + { + _valueBuffer.Content.WriteTo(stringWriter, HtmlEncoder); + htmlString = new HtmlString(stringWriter.ToString()); + } + } + else + { + htmlString = HtmlString.Empty; + } + + executionContext.AddHtmlAttribute(_tagHelperAttributeInfo.Name, htmlString); + } } public virtual string Href(string contentPath) @@ -735,33 +791,18 @@ namespace Microsoft.AspNet.Mvc.Razor return _urlHelper.Content(contentPath); } - private void WriteAttributeValue(TextWriter writer, AttributeValue attributeValue, int sourceLength) + private void WriteUnprefixedAttributeValueTo(TextWriter writer, object value, bool isLiteral) { - if (!string.IsNullOrEmpty(attributeValue.Prefix)) - { - WritePositionTaggedLiteral(writer, attributeValue.Prefix); - } - - BeginContext(attributeValue.Value.Position, sourceLength, isLiteral: attributeValue.Literal); - - WriteUnprefixedAttributeValueTo(writer, attributeValue); - - EndContext(); - } - - private void WriteUnprefixedAttributeValueTo(TextWriter writer, AttributeValue attributeValue) - { - var positionTaggedAttributeValue = attributeValue.Value; - var stringValue = positionTaggedAttributeValue.Value as string; + var stringValue = value as string; // The extra branching here is to ensure that we call the Write*To(string) overload where possible. - if (attributeValue.Literal && stringValue != null) + if (isLiteral && stringValue != null) { WriteLiteralTo(writer, stringValue); } - else if (attributeValue.Literal) + else if (isLiteral) { - WriteLiteralTo(writer, positionTaggedAttributeValue.Value); + WriteLiteralTo(writer, value); } else if (stringValue != null) { @@ -769,7 +810,7 @@ namespace Microsoft.AspNet.Mvc.Razor } else { - WriteTo(writer, positionTaggedAttributeValue.Value); + WriteTo(writer, value); } } @@ -780,11 +821,6 @@ namespace Microsoft.AspNet.Mvc.Razor EndContext(); } - private void WritePositionTaggedLiteral(TextWriter writer, PositionTagged value) - { - WritePositionTaggedLiteral(writer, value.Value, value.Position); - } - protected virtual HelperResult RenderBody() { if (RenderBodyDelegateAsync == null) @@ -1030,30 +1066,18 @@ namespace Microsoft.AspNet.Mvc.Razor return HtmlString.Empty; } - private bool IsSingleBoolFalseOrNullValue(AttributeValue[] values) + private bool IsBoolFalseOrNullValue(string prefix, object value) { - if (values.Length == 1 && string.IsNullOrEmpty(values[0].Prefix) && - (values[0].Value.Value is bool || values[0].Value.Value == null)) - { - var attributeValue = values[0]; - var positionTaggedAttributeValue = attributeValue.Value; - - if (positionTaggedAttributeValue.Value == null || !(bool)positionTaggedAttributeValue.Value) - { - return true; - } - } - - return false; + return string.IsNullOrEmpty(prefix) && + (value == null || + (value is bool && !(bool)value)); } - private bool UseAttributeNameAsValue(AttributeValue[] values) + private bool IsBoolTrueWithEmptyPrefixValue(string prefix, object value) { // If the value is just the bool 'true', use the attribute name as the value. - return values.Length == 1 && - string.IsNullOrEmpty(values[0].Prefix) && - values[0].Value.Value is bool && - (bool)values[0].Value.Value; + return string.IsNullOrEmpty(prefix) && + (value is bool && (bool)value); } private void EnsureMethodCanBeInvoked(string methodName) @@ -1063,5 +1087,63 @@ namespace Microsoft.AspNet.Mvc.Razor throw new InvalidOperationException(Resources.FormatRazorPage_MethodCannotBeCalled(methodName, Path)); } } + + private struct AttributeInfo + { + public AttributeInfo( + string name, + string prefix, + int prefixOffset, + string suffix, + int suffixOffset, + int attributeValuesCount) + { + Name = name; + Prefix = prefix; + PrefixOffset = prefixOffset; + Suffix = suffix; + SuffixOffset = suffixOffset; + AttributeValuesCount = attributeValuesCount; + + Suppressed = false; + } + + public int AttributeValuesCount { get; } + + public string Name { get; } + + public string Prefix { get; } + + public int PrefixOffset { get; } + + public string Suffix { get; } + + public int SuffixOffset { get; } + + public bool Suppressed { get; set; } + } + + private struct TagHelperAttributeInfo + { + public TagHelperAttributeInfo( + TagHelperExecutionContext tagHelperExecutionContext, + string name, + int attributeValuesCount) + { + ExecutionContext = tagHelperExecutionContext; + Name = name; + AttributeValuesCount = attributeValuesCount; + + Suppressed = false; + } + + public string Name { get; } + + public TagHelperExecutionContext ExecutionContext { get; } + + public int AttributeValuesCount { get; } + + public bool Suppressed { get; set; } + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/Basic.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/Basic.cs index 25eeaef83e..316cd0f546 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/Basic.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/Basic.cs @@ -32,8 +32,9 @@ namespace Asp BeginContext(0, 4, true); WriteLiteral("(logo, 12), false)); + BeginWriteAttribute("class", " class=\"", 4, "\"", 17, 1); + WriteAttributeValue("", 12, logo, 12, 5, false); + EndWriteAttribute(); BeginContext(18, 24, true); WriteLiteral(">\r\n Hello world\r\n "); EndContext(); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs index 60df2d0902..eaf79a6146 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs @@ -664,15 +664,10 @@ namespace Microsoft.AspNet.Mvc.Razor var page = CreatePage(p => { p.HtmlEncoder = new CommonTestEncoder(); - p.WriteAttribute("href", - new PositionTagged("prefix", 0), - new PositionTagged("suffix", 34), - new AttributeValue(new PositionTagged("prefix", 0), - new PositionTagged("attr1-value", 8), - literal: true), - new AttributeValue(new PositionTagged("prefix2", 22), - new PositionTagged("attr2", 29), - literal: false)); + p.BeginWriteAttribute("href", "prefix", 0, "suffix", 34, 2); + p.WriteAttributeValue("prefix", 0, "attr1-value", 8, 14, true); + p.WriteAttributeValue("prefix2", 22, "attr2", 29, 5, false); + p.EndWriteAttribute(); }); var context = new Mock(MockBehavior.Strict); var sequence = new MockSequence(); @@ -704,12 +699,9 @@ namespace Microsoft.AspNet.Mvc.Razor var page = CreatePage(p => { p.HtmlEncoder = new CommonTestEncoder(); - p.WriteAttribute("href", - new PositionTagged("prefix", 0), - new PositionTagged("suffix", 10), - new AttributeValue(new PositionTagged(string.Empty, 6), - new PositionTagged("true", 6), - literal: false)); + p.BeginWriteAttribute("href", "prefix", 0, "suffix", 10, 1); + p.WriteAttributeValue("", 6, "true", 6, 4, false); + p.EndWriteAttribute(); }); var context = new Mock(MockBehavior.Strict); var sequence = new MockSequence(); @@ -734,9 +726,8 @@ namespace Microsoft.AspNet.Mvc.Razor // Arrange var page = CreatePage(p => { - p.WriteAttribute("href", - new PositionTagged("prefix", 0), - new PositionTagged("tail", 7)); + p.BeginWriteAttribute("href", "prefix", 0, "tail", 7, 0); + p.EndWriteAttribute(); }); var context = new Mock(MockBehavior.Strict); var sequence = new MockSequence(); @@ -758,72 +749,51 @@ namespace Microsoft.AspNet.Mvc.Razor get { // attributeValues, expectedValue - return new TheoryData + return new TheoryData[], string> { { - new AttributeValue[] { - new AttributeValue( - new PositionTagged(string.Empty, 9), - new PositionTagged("Hello", 9), - literal: true) + new [] + { + Tuple.Create(string.Empty, 9, (object)"Hello", 9, true), }, "Hello" }, { - new AttributeValue[] { - new AttributeValue( - new PositionTagged(" ", 9), - new PositionTagged("Hello", 10), - literal: true) + new [] + { + Tuple.Create(" ", 9, (object)"Hello", 10, true) }, " Hello" }, { - new AttributeValue[] { - new AttributeValue( - new PositionTagged(" ", 9), - new PositionTagged(null, 10), - literal: false) + + new [] + { + Tuple.Create(" ", 9, (object)null, 10, false) }, string.Empty }, { - new AttributeValue[] { - new AttributeValue( - new PositionTagged(" ", 9), - new PositionTagged(false, 10), - literal: false) + new [] + { + Tuple.Create(" ", 9, (object)false, 10, false) }, " HtmlEncode[[False]]" }, { - new AttributeValue[] { - new AttributeValue( - new PositionTagged(" ", 9), - new PositionTagged(true, 11), - literal: false), - new AttributeValue( - new PositionTagged(" ", 15), - new PositionTagged("abcd", 17), - literal: true), + new [] + { + Tuple.Create(" ", 9, (object)true, 11, false), + Tuple.Create(" ", 9, (object)"abcd", 17, true) }, " HtmlEncode[[True]] abcd" }, - { - new AttributeValue[] { - new AttributeValue( - new PositionTagged(string.Empty, 9), - new PositionTagged("prefix", 9), - literal: true), - new AttributeValue( - new PositionTagged(" ", 15), - new PositionTagged(null, 17), - literal: false), - new AttributeValue( - new PositionTagged(" ", 21), - new PositionTagged("suffix", 22), - literal: false), + new [] + { + Tuple.Create(string.Empty, 9, (object)"prefix", 9, true), + Tuple.Create(" ", 15, (object)null, 17, false), + Tuple.Create(" ", 21, (object)"suffix", 22, false), }, "prefix HtmlEncode[[suffix]]" }, @@ -834,7 +804,7 @@ namespace Microsoft.AspNet.Mvc.Razor [Theory] [MemberData(nameof(AddHtmlAttributeValues_ValueData))] public void AddHtmlAttributeValues_AddsToHtmlAttributesAsExpected( - AttributeValue[] attributeValues, + Tuple[] attributeValues, string expectedValue) { // Arrange @@ -850,7 +820,12 @@ namespace Microsoft.AspNet.Mvc.Razor endTagHelperWritingScope: () => new DefaultTagHelperContent()); // Act - page.AddHtmlAttributeValues("someattr", executionContext, attributeValues); + page.BeginAddHtmlAttributeValues(executionContext, "someattr", attributeValues.Length); + foreach (var value in attributeValues) + { + page.AddHtmlAttributeValue(value.Item1, value.Item2, value.Item3, value.Item4, 0, value.Item5); + } + page.EndAddHtmlAttributeValues(executionContext); // Assert var htmlAttribute = Assert.Single(executionContext.HTMLAttributes); @@ -885,13 +860,9 @@ namespace Microsoft.AspNet.Mvc.Razor endTagHelperWritingScope: () => new DefaultTagHelperContent()); // Act - page.AddHtmlAttributeValues( - "someattr", - executionContext, - new AttributeValue( - prefix: new PositionTagged(string.Empty, 9), - value: new PositionTagged(attributeValue, 9), - literal: false)); + page.BeginAddHtmlAttributeValues(executionContext, "someattr", 1); + page.AddHtmlAttributeValue(string.Empty, 9, attributeValue, 9, valueLength: 0, isLiteral: false); + page.EndAddHtmlAttributeValues(executionContext); // Assert Assert.Empty(executionContext.HTMLAttributes); @@ -917,13 +888,9 @@ namespace Microsoft.AspNet.Mvc.Razor endTagHelperWritingScope: () => new DefaultTagHelperContent()); // Act - page.AddHtmlAttributeValues( - "someattr", - executionContext, - new AttributeValue( - prefix: new PositionTagged(string.Empty, 9), - value: new PositionTagged(true, 9), - literal: false)); + page.BeginAddHtmlAttributeValues(executionContext, "someattr", 1); + page.AddHtmlAttributeValue(string.Empty, 9, true, 9, valueLength: 0, isLiteral: false); + page.EndAddHtmlAttributeValues(executionContext); // Assert var htmlAttribute = Assert.Single(executionContext.HTMLAttributes); @@ -936,68 +903,53 @@ namespace Microsoft.AspNet.Mvc.Razor Assert.False(allAttribute.Minimized); } - public static TheoryData WriteAttributeData + public static TheoryData WriteAttributeData { get { // AttributeValues, ExpectedOutput - return new TheoryData + return new TheoryData[], string> { { - new AttributeValue[] { - new AttributeValue( - new PositionTagged(string.Empty, 9), - new PositionTagged(true, 9), - literal: false) + new[] + { + Tuple.Create(string.Empty, 9, (object)true, 9, false), }, "someattr=HtmlEncode[[someattr]]" }, { - new AttributeValue[] { - new AttributeValue( - new PositionTagged(string.Empty, 9), - new PositionTagged(false, 9), - literal: false) + new[] + { + Tuple.Create(string.Empty, 9, (object)false, 9, false), }, string.Empty }, { - new AttributeValue[] { - new AttributeValue( - new PositionTagged(string.Empty, 9), - new PositionTagged(null, 9), - literal: false) + new[] + { + Tuple.Create(string.Empty, 9, (object)null, 9, false), }, string.Empty }, { - new AttributeValue[] { - new AttributeValue( - new PositionTagged(" ", 9), - new PositionTagged(false, 11), - literal: false) + new[] + { + Tuple.Create(" ", 9, (object)false, 11, false), }, "someattr= HtmlEncode[[False]]" }, { - new AttributeValue[] { - new AttributeValue( - new PositionTagged(" ", 9), - new PositionTagged(null, 11), - literal: false) + new[] + { + Tuple.Create(" ", 9, (object)null, 11, false), }, "someattr=" }, { - new AttributeValue[] { - new AttributeValue( - new PositionTagged(" ", 9), - new PositionTagged(true, 11), - literal: false), - new AttributeValue( - new PositionTagged(" ", 15), - new PositionTagged("abcd", 17), - literal: true), + new[] + { + Tuple.Create(" ", 9, (object)true, 11, false), + Tuple.Create(" ", 15, (object)"abcd", 17, true), }, "someattr= HtmlEncode[[True]] abcd" }, @@ -1007,17 +959,31 @@ namespace Microsoft.AspNet.Mvc.Razor [Theory] [MemberData(nameof(WriteAttributeData))] - public void WriteAttributeTo_WritesAsExpected(AttributeValue[] attributeValues, string expectedOutput) + public void WriteAttributeTo_WritesAsExpected( + Tuple[] attributeValues, + string expectedOutput) { // Arrange var page = CreatePage(p => { }); page.HtmlEncoder = new CommonTestEncoder(); var writer = new StringWriter(); - var prefix = new PositionTagged("someattr=", 0); - var suffix = new PositionTagged(string.Empty, 0); + var prefix = "someattr="; + var suffix = string.Empty; // Act - page.WriteAttributeTo(writer, "someattr", prefix, suffix, attributeValues); + page.BeginWriteAttributeTo(writer, "someattr", prefix, 0, suffix, 0, attributeValues.Length); + foreach (var value in attributeValues) + { + page.WriteAttributeValueTo( + writer, + value.Item1, + value.Item2, + value.Item3, + value.Item4, + value.Item3?.ToString().Length ?? 0, + value.Item5); + } + page.EndWriteAttributeTo(writer); // Assert Assert.Equal(expectedOutput, writer.ToString());