Skip to content

Commit

Permalink
Transition SelectTagHelper and OptionTagHelper to use `context.It…
Browse files Browse the repository at this point in the history
…ems`.

- Added functional tests to validate data created from a `SelectTagHelper` does not impact following `<select>` tags.
- Also moved the new `SelectTagHelper` communication flow into `TagHelper.Init`.

aspnet#3347
  • Loading branch information
NTaylorMullen committed Oct 23, 2015
1 parent c267ef3 commit 911dfc5
Show file tree
Hide file tree
Showing 11 changed files with 80 additions and 60 deletions.
8 changes: 3 additions & 5 deletions src/Microsoft.AspNet.Mvc.TagHelpers/OptionTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ public override int Order

/// <inheritdoc />
/// <remarks>
/// Does nothing unless <see cref="FormContext.FormData"/> contains a
/// <see cref="SelectTagHelper.SelectedValuesFormDataKey"/> entry and that entry is a non-empty
/// Does nothing unless <see cref="TagHelperContext.Items"/> contains a
/// <see cref="SelectTagHelper"/> <see cref="Type"/> entry and that entry is a non-empty
/// <see cref="ICollection{string}"/> instance. Also does nothing if the associated &lt;option&gt; is already
/// selected.
/// </remarks>
Expand All @@ -82,9 +82,7 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
{
// Is this <option/> element a child of a <select/> element the SelectTagHelper targeted?
object formDataEntry;
ViewContext.FormContext.FormData.TryGetValue(
SelectTagHelper.SelectedValuesFormDataKey,
out formDataEntry);
context.Items.TryGetValue(typeof(SelectTagHelper), out formDataEntry);

// ... And did the SelectTagHelper determine any selected values?
var selectedValues = formDataEntry as ICollection<string>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public class RenderAtEndOfFormTagHelper : TagHelper
/// <inheritdoc />
public override void Init(TagHelperContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

// Push the new FormContext.
ViewContext.FormContext = new FormContext
{
Expand Down
68 changes: 34 additions & 34 deletions src/Microsoft.AspNet.Mvc.TagHelpers/SelectTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,8 @@ public class SelectTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
private const string ItemsAttributeName = "asp-items";

/// <summary>
/// Key used for selected values in <see cref="FormContext.FormData"/>.
/// </summary>
/// <remarks>
/// Value for this dictionary entry will either be <c>null</c> (indicating no <see cref="SelectTagHelper"/> has
/// executed within this &lt;form/&gt;) or an <see cref="ICollection{string}"/> instance. Elements of the
/// collection are based on current <see cref="ViewDataDictionary.Model"/>.
/// </remarks>
public static readonly string SelectedValuesFormDataKey = nameof(SelectTagHelper) + "-SelectedValues";
private bool _allowMultiple;
private IReadOnlyCollection<string> _currentValues;

/// <summary>
/// Creates a new <see cref="SelectTagHelper"/>.
Expand Down Expand Up @@ -70,26 +62,16 @@ public override int Order
public IEnumerable<SelectListItem> Items { get; set; }

/// <inheritdoc />
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if <see cref="Items"/> is non-<c>null</c> but <see cref="For"/> is <c>null</c>.
/// </exception>
public override void Process(TagHelperContext context, TagHelperOutput output)
public override void Init(TagHelperContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

if (output == null)
{
throw new ArgumentNullException(nameof(output));
}

// Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient.
// IHtmlGenerator will enforce name requirements.
var metadata = For.Metadata;
if (metadata == null)
if (For.Metadata == null)
{
throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata(
"<select>",
Expand All @@ -102,36 +84,54 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
// "SelectExpressionNotEnumerable" InvalidOperationException during generation.
// Metadata.IsEnumerableType is similar but does not take runtime type into account.
var realModelType = For.ModelExplorer.ModelType;
var allowMultiple = typeof(string) != realModelType &&
_allowMultiple = typeof(string) != realModelType &&
typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(realModelType.GetTypeInfo());
_currentValues = Generator.GetCurrentValues(
ViewContext,
For.ModelExplorer,
expression: For.Name,
allowMultiple: _allowMultiple);

// Whether or not (not being highly unlikely) we generate anything, could update contained <option/>
// elements. Provide selected values for <option/> tag helpers.
context.Items[typeof(SelectTagHelper)] = _currentValues;
}

/// <inheritdoc />
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if <see cref="Items"/> is non-<c>null</c> but <see cref="For"/> is <c>null</c>.
/// </exception>
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

if (output == null)
{
throw new ArgumentNullException(nameof(output));
}

// Ensure GenerateSelect() _never_ looks anything up in ViewData.
var items = Items ?? Enumerable.Empty<SelectListItem>();

var currentValues = Generator.GetCurrentValues(
ViewContext,
For.ModelExplorer,
expression: For.Name,
allowMultiple: allowMultiple);
var tagBuilder = Generator.GenerateSelect(
ViewContext,
For.ModelExplorer,
optionLabel: null,
expression: For.Name,
selectList: items,
currentValues: currentValues,
allowMultiple: allowMultiple,
currentValues: _currentValues,
allowMultiple: _allowMultiple,
htmlAttributes: null);

if (tagBuilder != null)
{
output.MergeAttributes(tagBuilder);
output.PostContent.Append(tagBuilder.InnerHtml);
}

// Whether or not (not being highly unlikely) we generate anything, could update contained <option/>
// elements. Provide selected values for <option/> tag helpers. They'll run next.
ViewContext.FormContext.FormData[SelectedValuesFormDataKey] = currentValues;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
<option value="HtmlEncode[[Credit]]">Credit</option>
<option value="HtmlEncode[[Check]]" selected="HtmlEncode[[selected]]">Check</option>
</select>
<select>
<option value="HtmlEncode[[Check]]">Check</option>
</select>
</div>
<div>
<label class="order" for="HtmlEncode[[Customer_Number]]">HtmlEncode[[Number]]</label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
<option value="Credit">Credit</option>
<option value="Check" selected="selected">Check</option>
</select>
<select>
<option value="Check">Check</option>
</select>
</div>
<div>
<label class="order" for="Customer_Number">Number</label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
<select id="HtmlEncode[[PaymentMethod]]" multiple="HtmlEncode[[multiple]]" name="HtmlEncode[[PaymentMethod]]"><option value="HtmlEncode[[Credit]]">HtmlEncode[[Credit]]</option>
<option selected="HtmlEncode[[selected]]" value="HtmlEncode[[Check]]">HtmlEncode[[Check]]</option>
</select>
<select>
<option value="Check">Check</option>
</select>
</div>
<div>
<label class="HtmlEncode[[order]]" for="HtmlEncode[[Customer_Number]]">HtmlEncode[[Number]]</label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
<select id="PaymentMethod" multiple="multiple" name="PaymentMethod"><option value="Credit">Credit</option>
<option selected="selected" value="Check">Check</option>
</select>
<select>
<option value="Check">Check</option>
</select>
</div>
<div>
<label class="order" for="Customer_Number">Number</label>
Expand Down
10 changes: 5 additions & 5 deletions test/Microsoft.AspNet.Mvc.TagHelpers.Test/OptionTagHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
public class OptionTagHelperTest
{
// Original content, selected attribute, value attribute, selected values (to place in FormContext.FormData)
// Original content, selected attribute, value attribute, selected values (to place in TagHelperContext.Items)
// and expected tag helper output.
public static TheoryData<string, string, string, IEnumerable<string>, TagHelperOutput> GeneratesExpectedDataSet
{
Expand Down Expand Up @@ -346,7 +346,7 @@ public static TheoryData<string, string, string, IEnumerable<string>, TagHelperO
}
}

// Original content, selected attribute, value attribute, selected values (to place in FormContext.FormData)
// Original content, selected attribute, value attribute, selected values (to place in TagHelperContext.Items)
// and expected output (concatenation of TagHelperOutput generations). Excludes non-null selected attribute,
// null selected values, and empty selected values cases.
public static IEnumerable<object[]> DoesNotUseGeneratorDataSet
Expand All @@ -358,7 +358,7 @@ public static IEnumerable<object[]> DoesNotUseGeneratorDataSet
}
}

// Original content, selected attribute, value attribute, selected values (to place in FormContext.FormData)
// Original content, selected attribute, value attribute, selected values (to place in TagHelperContext.Items)
// and expected output (concatenation of TagHelperOutput generations). Excludes non-null selected attribute
// cases.
public static IEnumerable<object[]> DoesNotUseViewContextDataSet
Expand Down Expand Up @@ -420,7 +420,7 @@ public async Task ProcessAsync_GeneratesExpectedOutput(
model: null,
htmlGenerator: htmlGenerator,
metadataProvider: metadataProvider);
viewContext.FormContext.FormData[SelectTagHelper.SelectedValuesFormDataKey] = selectedValues;
tagHelperContext.Items[typeof(SelectTagHelper)] = selectedValues;
var tagHelper = new OptionTagHelper(htmlGenerator)
{
Value = value,
Expand Down Expand Up @@ -491,7 +491,7 @@ public async Task ProcessAsync_DoesNotUseGenerator_IfSelectedNullOrNoSelectedVal
model: null,
htmlGenerator: htmlGenerator,
metadataProvider: metadataProvider);
viewContext.FormContext.FormData[SelectTagHelper.SelectedValuesFormDataKey] = selectedValues;
tagHelperContext.Items[typeof(SelectTagHelper)] = selectedValues;
var tagHelper = new OptionTagHelper(htmlGenerator)
{
Value = value,
Expand Down
31 changes: 15 additions & 16 deletions test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ public async Task ProcessAsync_GeneratesExpectedOutput(
};

// Act
tagHelper.Init(tagHelperContext);
await tagHelper.ProcessAsync(tagHelperContext, output);

// Assert
Expand All @@ -245,10 +246,9 @@ public async Task ProcessAsync_GeneratesExpectedOutput(
Assert.Equal(expectedPostContent, output.PostContent.GetContent());
Assert.Equal(expectedTagName, output.TagName);

Assert.NotNull(viewContext.FormContext?.FormData);
Assert.Single(
viewContext.FormContext.FormData,
entry => entry.Key == SelectTagHelper.SelectedValuesFormDataKey);
tagHelperContext.Items,
entry => (Type)entry.Key == typeof(SelectTagHelper));
}

[Theory]
Expand Down Expand Up @@ -333,6 +333,7 @@ public async Task ProcessAsync_WithItems_GeneratesExpectedOutput_DoesNotChangeSe
};

// Act
tagHelper.Init(tagHelperContext);
await tagHelper.ProcessAsync(tagHelperContext, output);

// Assert
Expand All @@ -343,10 +344,9 @@ public async Task ProcessAsync_WithItems_GeneratesExpectedOutput_DoesNotChangeSe
Assert.Equal(expectedPostContent, HtmlContentUtilities.HtmlContentToString(output.PostContent));
Assert.Equal(expectedTagName, output.TagName);

Assert.NotNull(viewContext.FormContext?.FormData);
Assert.Single(
viewContext.FormContext.FormData,
entry => entry.Key == SelectTagHelper.SelectedValuesFormDataKey);
tagHelperContext.Items,
entry => (Type)entry.Key == typeof(SelectTagHelper));

Assert.Equal(savedDisabled, items.Select(item => item.Disabled));
Assert.Equal(savedGroup, items.Select(item => item.Group));
Expand Down Expand Up @@ -429,7 +429,6 @@ public async Task ProcessAsyncInTemplate_WithItems_GeneratesExpectedOutput_DoesN
var savedSelected = items.Select(item => item.Selected).ToList();
var savedText = items.Select(item => item.Text).ToList();
var savedValue = items.Select(item => item.Value).ToList();

var tagHelper = new SelectTagHelper(htmlGenerator)
{
For = modelExpression,
Expand All @@ -438,6 +437,7 @@ public async Task ProcessAsyncInTemplate_WithItems_GeneratesExpectedOutput_DoesN
};

// Act
tagHelper.Init(tagHelperContext);
await tagHelper.ProcessAsync(tagHelperContext, output);

// Assert
Expand All @@ -448,10 +448,9 @@ public async Task ProcessAsyncInTemplate_WithItems_GeneratesExpectedOutput_DoesN
Assert.Equal(expectedPostContent, HtmlContentUtilities.HtmlContentToString(output.PostContent));
Assert.Equal(expectedTagName, output.TagName);

Assert.NotNull(viewContext.FormContext?.FormData);
Assert.Single(
viewContext.FormContext.FormData,
entry => entry.Key == SelectTagHelper.SelectedValuesFormDataKey);
tagHelperContext.Items,
entry => (Type)entry.Key == typeof(SelectTagHelper));

Assert.Equal(savedDisabled, items.Select(item => item.Disabled));
Assert.Equal(savedGroup, items.Select(item => item.Group));
Expand Down Expand Up @@ -536,15 +535,15 @@ public async Task ProcessAsync_CallsGeneratorWithExpectedValues_ItemsAndAttribut
};

// Act
tagHelper.Init(tagHelperContext);
await tagHelper.ProcessAsync(tagHelperContext, output);

// Assert
htmlGenerator.Verify();

Assert.NotNull(viewContext.FormContext?.FormData);
var keyValuePair = Assert.Single(
viewContext.FormContext.FormData,
entry => entry.Key == SelectTagHelper.SelectedValuesFormDataKey);
tagHelperContext.Items,
entry => (Type)entry.Key == typeof(SelectTagHelper));
Assert.Same(currentValues, keyValuePair.Value);
}

Expand Down Expand Up @@ -610,15 +609,15 @@ public async Task TagHelper_CallsGeneratorWithExpectedValues_RealModelType(
};

// Act
tagHelper.Init(tagHelperContext);
await tagHelper.ProcessAsync(tagHelperContext, output);

// Assert
htmlGenerator.Verify();

Assert.NotNull(viewContext.FormContext?.FormData);
var keyValuePair = Assert.Single(
viewContext.FormContext.FormData,
entry => entry.Key == SelectTagHelper.SelectedValuesFormDataKey);
tagHelperContext.Items,
entry => (Type)entry.Key == typeof(SelectTagHelper));
Assert.Same(currentValues, keyValuePair.Value);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
<option value="Credit">Credit</option>
<option value="Check">Check</option>
</select>
<select>
<option value="Check">Check</option>
</select>
</div>
<div>
<label asp-for="Customer.Number" class="order"></label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ Html.BeginForm(actionName: "Submit", controllerName: "HtmlGeneration_Order"))
<div>
@Html.LabelFor(m => m.PaymentMethod, htmlAttributes: new { @class = "order" })
@Html.ListBoxFor(m => m.PaymentMethod, selectList: new SelectList(new[] { new { value = "Credit" }, new { value = "Check" } }, dataValueField: "value", dataTextField: "value"))
<select>
<option value="Check">Check</option>
</select>
</div>
<div>
@Html.LabelFor(m => m.Customer.Number, htmlAttributes: new { @class = "order" })
Expand Down

0 comments on commit 911dfc5

Please sign in to comment.