TagHelpers attribute targeting - part 2

This commit is contained in:
Ajay Bhargav Baaskaran 2015-03-19 15:30:25 -07:00
parent bfdeda797d
commit e2058905ec
6 changed files with 51 additions and 180 deletions

View File

@ -90,22 +90,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
return string.Format(CultureInfo.CurrentCulture, GetString("InputTagHelper_ValueRequired"), p0, p1, p2, p3);
}
/// <summary>
/// Cannot determine body for {0}. '{2}' must be null if '{1}' is null.
/// </summary>
internal static string SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified
{
get { return GetString("SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified"); }
}
/// <summary>
/// Cannot determine body for {0}. '{2}' must be null if '{1}' is null.
/// </summary>
internal static string FormatSelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified"), p0, p1, p2);
}
/// <summary>
/// The {2} was unable to provide metadata about '{1}' expression value '{3}' for {0}.
/// </summary>

View File

@ -132,9 +132,6 @@
<data name="InputTagHelper_ValueRequired" xml:space="preserve">
<value>'{1}' must not be null for {0} if '{2}' is '{3}'.</value>
</data>
<data name="SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified" xml:space="preserve">
<value>Cannot determine body for {0}. '{2}' must be null if '{1}' is null.</value>
</data>
<data name="TagHelpers_NoProvidedMetadata" xml:space="preserve">
<value>The {2} was unable to provide metadata about '{1}' expression value '{3}' for {0}.</value>
</data>

View File

@ -14,6 +14,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;select&gt; elements with an <c>asp-for</c> attribute.
/// </summary>
[TargetElement("select", Attributes = ForAttributeName)]
public class SelectTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
@ -57,63 +58,47 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// </exception>
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (For == null)
// 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)
{
// Regular HTML <select/> element. Just make sure Items wasn't specified.
if (Items != null)
{
var message = Resources.FormatSelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified(
"<select>",
ForAttributeName,
ItemsAttributeName);
throw new InvalidOperationException(message);
}
throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata(
"<select>",
ForAttributeName,
nameof(IModelMetadataProvider),
For.Name));
}
else
// Base allowMultiple on the instance or declared type of the expression to avoid a
// "SelectExpressionNotEnumerable" InvalidOperationException during generation.
// Metadata.IsCollectionType() is similar but does not take runtime type into account.
var realModelType = For.ModelExplorer.ModelType;
var allowMultiple = typeof(string) != realModelType && typeof(IEnumerable).IsAssignableFrom(realModelType);
// Ensure GenerateSelect() _never_ looks anything up in ViewData.
var items = Items ?? Enumerable.Empty<SelectListItem>();
ICollection<string> selectedValues;
var tagBuilder = Generator.GenerateSelect(
ViewContext,
For.ModelExplorer,
optionLabel: null,
expression: For.Name,
selectList: items,
allowMultiple: allowMultiple,
htmlAttributes: null,
selectedValues: out selectedValues);
if (tagBuilder != null)
{
// 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)
{
throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata(
"<select>",
ForAttributeName,
nameof(IModelMetadataProvider),
For.Name));
}
// Base allowMultiple on the instance or declared type of the expression to avoid a
// "SelectExpressionNotEnumerable" InvalidOperationException during generation.
// Metadata.IsCollectionType() is similar but does not take runtime type into account.
var realModelType = For.ModelExplorer.ModelType;
var allowMultiple =
typeof(string) != realModelType && typeof(IEnumerable).IsAssignableFrom(realModelType);
// Ensure GenerateSelect() _never_ looks anything up in ViewData.
var items = Items ?? Enumerable.Empty<SelectListItem>();
ICollection<string> selectedValues;
var tagBuilder = Generator.GenerateSelect(
ViewContext,
For.ModelExplorer,
optionLabel: null,
expression: For.Name,
selectList: items,
allowMultiple: allowMultiple,
htmlAttributes: null,
selectedValues: out selectedValues);
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] = selectedValues;
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] = selectedValues;
}
}
}

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;textarea&gt; elements with an <c>asp-for</c> attribute.
/// </summary>
[TargetElement("textarea")]
[TargetElement("textarea", Attributes = ForAttributeName)]
public class TextAreaTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
@ -33,23 +33,20 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (For != null)
var tagBuilder = Generator.GenerateTextArea(
ViewContext,
For.ModelExplorer,
For.Name,
rows: 0,
columns: 0,
htmlAttributes: null);
if (tagBuilder != null)
{
var tagBuilder = Generator.GenerateTextArea(
ViewContext,
For.ModelExplorer,
For.Name,
rows: 0,
columns: 0,
htmlAttributes: null);
// Overwrite current Content to ensure expression result round-trips correctly.
output.Content.SetContent(tagBuilder.InnerHtml);
if (tagBuilder != null)
{
// Overwrite current Content to ensure expression result round-trips correctly.
output.Content.SetContent(tagBuilder.InnerHtml);
output.MergeAttributes(tagBuilder);
}
output.MergeAttributes(tagBuilder);
}
}
}

View File

@ -603,37 +603,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.Same(selectedValues, keyValuePair.Value);
}
[Fact]
public async Task ProcessAsync_Throws_IfForNotBoundButItemsIs()
{
// Arrange
var contextAttributes = new Dictionary<string, object>();
var originalAttributes = new Dictionary<string, string>();
var expectedTagName = "select";
var expectedMessage = "Cannot determine body for <select>. 'asp-items' must be null if 'asp-for' is null.";
var tagHelperContext = new TagHelperContext(
contextAttributes,
items: new Dictionary<object, object>(),
uniqueId: "test",
getChildContentAsync: () =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
});
var output = new TagHelperOutput(expectedTagName, originalAttributes);
var tagHelper = new SelectTagHelper
{
Items = Enumerable.Empty<SelectListItem>(),
};
// Act & Assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
() => tagHelper.ProcessAsync(tagHelperContext, output));
Assert.Equal(expectedMessage, exception.Message);
}
public class NameAndId
{
public NameAndId(string name, string id)

View File

@ -154,67 +154,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.Equal(expectedTagName, output.TagName);
}
[Fact]
public async Task TagHelper_LeavesOutputUnchanged_IfForNotBound()
{
// Arrange
var expectedAttributes = new Dictionary<string, string>
{
{ "class", "form-control" },
};
var expectedPreContent = "original pre-content";
var expectedContent = "original content";
var expectedPostContent = "original post-content";
var expectedTagName = "textarea";
var metadataProvider = new TestModelMetadataProvider();
var modelExplorer = metadataProvider
.GetModelExplorerForType(typeof(Model), model: null)
.GetExplorerForProperty(nameof(Model.Text));
var modelExpression = new ModelExpression(nameof(Model.Text), modelExplorer);
var tagHelper = new TextAreaTagHelper();
var tagHelperContext = new TagHelperContext(
allAttributes: new Dictionary<string, object>(),
items: new Dictionary<object, object>(),
uniqueId: "test",
getChildContentAsync: () =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
});
var output = new TagHelperOutput(expectedTagName, expectedAttributes)
{
SelfClosing = true,
};
output.PreContent.SetContent(expectedPreContent);
output.Content.SetContent(expectedContent);
output.PostContent.SetContent(expectedPostContent);
var htmlGenerator = new TestableHtmlGenerator(metadataProvider)
{
ValidationAttributes =
{
{ "valid", "from validation attributes" },
}
};
Model model = null;
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
tagHelper.ViewContext = viewContext;
tagHelper.Generator = htmlGenerator;
// Act
await tagHelper.ProcessAsync(tagHelperContext, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Equal(expectedContent, output.Content.GetContent());
Assert.True(output.SelfClosing);
Assert.Equal(expectedTagName, output.TagName);
}
public class NameAndId
{
public NameAndId(string name, string id)