Sanitize "id" attributes for HTML 4.0.1
- #704 part 2 of 2 - change `@Html.Id()` to sanitize return value; was identical to `@Html.Name()` Copied `TagBuilder.CreateSanitizedId()` and `TagBuilder.Html401IdUtil` from MVC 5.2 - except this `CreateSanitizedId()` returns a valid identifier if first `char` is not a letter - e.g. "[0].Name" nits: - expand variable names, use lots of `var`, put `public` members first - add doc comments for `CreateSanitizedId()` Note users will be able to apply different sanitization once we fix #1188.
This commit is contained in:
parent
f9e44ff7f9
commit
dd5da33a62
|
|
@ -679,7 +679,9 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
protected virtual string GenerateId(string expression)
|
||||
{
|
||||
var fullName = DefaultHtmlGenerator.GetFullHtmlFieldName(ViewContext, name: expression);
|
||||
return fullName;
|
||||
var id = TagBuilder.CreateSanitizedId(fullName, IdAttributeDotReplacement);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
protected virtual HtmlString GenerateLabel([NotNull] ModelMetadata metadata,
|
||||
|
|
|
|||
|
|
@ -49,32 +49,48 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
}
|
||||
}
|
||||
|
||||
public static string CreateSanitizedId(string originalId, [NotNull] string invalidCharReplacement)
|
||||
/// <summary>
|
||||
/// Return valid HTML 4.01 "id" attribute for an element with the given <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The original element name.</param>
|
||||
/// <param name="invalidCharReplacement">
|
||||
/// The <see cref="string"/> (normally a single <see cref="char"/>) to substitute for invalid characters in
|
||||
/// <paramref name="name"/>.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Valid HTML 4.01 "id" attribute for an element with the given <paramref name="name"/>.
|
||||
/// </returns>
|
||||
/// <remarks>Valid "id" attributes are defined in http://www.w3.org/TR/html401/types.html#type-id</remarks>
|
||||
public static string CreateSanitizedId(string name, [NotNull] string invalidCharReplacement)
|
||||
{
|
||||
if (string.IsNullOrEmpty(originalId))
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var firstChar = originalId[0];
|
||||
|
||||
var sb = new StringBuilder(originalId.Length);
|
||||
sb.Append(firstChar);
|
||||
|
||||
for (var i = 1; i < originalId.Length; i++)
|
||||
var firstChar = name[0];
|
||||
if (!Html401IdUtil.IsAsciiLetter(firstChar))
|
||||
{
|
||||
var thisChar = originalId[i];
|
||||
if (!char.IsWhiteSpace(thisChar))
|
||||
// The first character must be a letter according to the HTML 4.01 specification.
|
||||
firstChar = 'z';
|
||||
}
|
||||
|
||||
var stringBuffer = new StringBuilder(name.Length);
|
||||
stringBuffer.Append(firstChar);
|
||||
for (var index = 1; index < name.Length; index++)
|
||||
{
|
||||
var thisChar = name[index];
|
||||
if (Html401IdUtil.IsValidIdCharacter(thisChar))
|
||||
{
|
||||
sb.Append(thisChar);
|
||||
stringBuffer.Append(thisChar);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(invalidCharReplacement);
|
||||
stringBuffer.Append(invalidCharReplacement);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
return stringBuffer.ToString();
|
||||
}
|
||||
|
||||
public void GenerateId(string name, [NotNull] string idAttributeDotReplacement)
|
||||
|
|
@ -195,5 +211,39 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static class Html401IdUtil
|
||||
{
|
||||
public static bool IsAsciiLetter(char testChar)
|
||||
{
|
||||
return (('A' <= testChar && testChar <= 'Z') || ('a' <= testChar && testChar <= 'z'));
|
||||
}
|
||||
|
||||
public static bool IsValidIdCharacter(char testChar)
|
||||
{
|
||||
return (IsAsciiLetter(testChar) || IsAsciiDigit(testChar) || IsAllowableSpecialCharacter(testChar));
|
||||
}
|
||||
|
||||
private static bool IsAsciiDigit(char testChar)
|
||||
{
|
||||
return ('0' <= testChar && testChar <= '9');
|
||||
}
|
||||
|
||||
private static bool IsAllowableSpecialCharacter(char testChar)
|
||||
{
|
||||
switch (testChar)
|
||||
{
|
||||
case '-':
|
||||
case '_':
|
||||
case ':':
|
||||
// Note '.' is valid according to the HTML 4.01 specification. Disallowed here to avoid confusion
|
||||
// with CSS class selectors or when using jQuery.
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
var labelForResult = helper.LabelFor(m => m.Inner.Id);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("<label for=\"Inner.Id\">Id</label>", labelResult.ToString());
|
||||
Assert.Equal("<label for=\"Inner.Id\">Id</label>", labelForResult.ToString());
|
||||
Assert.Equal("<label for=\"Inner_Id\">Id</label>", labelResult.ToString());
|
||||
Assert.Equal("<label for=\"Inner_Id\">Id</label>", labelForResult.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -229,13 +229,14 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("A", "A")]
|
||||
[InlineData("A[23]", "A[23]")]
|
||||
[InlineData("A[0].B", "B")]
|
||||
[InlineData("A.B.C.D", "D")]
|
||||
[InlineData("A", "A", "A")]
|
||||
[InlineData("A[23]", "A[23]", "A_23_")]
|
||||
[InlineData("A[0].B", "B", "A_0__B")]
|
||||
[InlineData("A.B.C.D", "D", "A_B_C_D")]
|
||||
public void Label_DisplaysRightmostExpressionSegment_IfPropertiesNotFound(
|
||||
string expression,
|
||||
string expectedResult)
|
||||
string expectedText,
|
||||
string expectedId)
|
||||
{
|
||||
// Arrange
|
||||
var metadataHelper = new MetadataHelper();
|
||||
|
|
@ -246,7 +247,7 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
|
||||
// Assert
|
||||
// Label() falls back to expression name when DisplayName and PropertyName are null.
|
||||
Assert.Equal("<label for=\"" + expression + "\">" + expectedResult + "</label>", result.ToString());
|
||||
Assert.Equal("<label for=\"" + expectedId + "\">" + expectedText + "</label>", result.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -46,12 +46,12 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("A")]
|
||||
[InlineData("A[23]")]
|
||||
[InlineData("A[0].B")]
|
||||
[InlineData("A.B.C.D")]
|
||||
public void IdAndNameHelpers_ReturnPrefixForModel(string prefix)
|
||||
[InlineData("", "")]
|
||||
[InlineData("A", "A")]
|
||||
[InlineData("A[23]", "A_23_")]
|
||||
[InlineData("A[0].B", "A_0__B")]
|
||||
[InlineData("A.B.C.D", "A_B_C_D")]
|
||||
public void IdAndNameHelpers_ReturnPrefixForModel(string prefix, string expectedId)
|
||||
{
|
||||
// Arrange
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper();
|
||||
|
|
@ -66,9 +66,9 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
var nameForModelResult = helper.NameForModel();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(prefix, idResult);
|
||||
Assert.Equal(prefix, idForResult);
|
||||
Assert.Equal(prefix, idForModelResult);
|
||||
Assert.Equal(expectedId, idResult);
|
||||
Assert.Equal(expectedId, idForResult);
|
||||
Assert.Equal(expectedId, idForModelResult);
|
||||
Assert.Equal(prefix, nameResult);
|
||||
Assert.Equal(prefix, nameForResult);
|
||||
Assert.Equal(prefix, nameForModelResult);
|
||||
|
|
@ -94,16 +94,17 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, "Property1")]
|
||||
[InlineData("", "Property1")]
|
||||
[InlineData("A", "A.Property1")]
|
||||
[InlineData("A[23]", "A[23].Property1")]
|
||||
[InlineData("A[0].B", "A[0].B.Property1")]
|
||||
[InlineData("A.B.C.D", "A.B.C.D.Property1")]
|
||||
public void IdAndNameHelpers_ReturnPrefixAndPropertyName(string prefix, string expectedResult)
|
||||
[InlineData(null, "Property1", "Property1")]
|
||||
[InlineData("", "Property1", "Property1")]
|
||||
[InlineData("A", "A.Property1", "A_Property1")]
|
||||
[InlineData("A[23]", "A[23].Property1", "A_23__Property1")]
|
||||
[InlineData("A[0].B", "A[0].B.Property1", "A_0__B_Property1")]
|
||||
[InlineData("A.B.C.D", "A.B.C.D.Property1", "A_B_C_D_Property1")]
|
||||
public void IdAndNameHelpers_ReturnPrefixAndPropertyName(string prefix, string expectedName, string expectedId)
|
||||
{
|
||||
// Arrange
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper();
|
||||
helper.ViewData.TemplateInfo.HtmlFieldPrefix = prefix;
|
||||
|
||||
// Act
|
||||
var idResult = helper.Id("Property1");
|
||||
|
|
@ -112,10 +113,10 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
var nameForResult = helper.NameFor(m => m.Property1);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Property1", idResult);
|
||||
Assert.Equal("Property1", idForResult);
|
||||
Assert.Equal("Property1", nameResult);
|
||||
Assert.Equal("Property1", nameForResult);
|
||||
Assert.Equal(expectedId, idResult);
|
||||
Assert.Equal(expectedId, idForResult);
|
||||
Assert.Equal(expectedName, nameResult);
|
||||
Assert.Equal(expectedName, nameForResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -131,8 +132,8 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
var nameForResult = helper.NameFor(m => m.Inner.Id);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Inner.Id", idResult);
|
||||
Assert.Equal("Inner.Id", idForResult);
|
||||
Assert.Equal("Inner_Id", idResult);
|
||||
Assert.Equal("Inner_Id", idForResult);
|
||||
Assert.Equal("Inner.Id", nameResult);
|
||||
Assert.Equal("Inner.Id", nameForResult);
|
||||
}
|
||||
|
|
@ -194,10 +195,10 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("A")]
|
||||
[InlineData("A[0].B")]
|
||||
[InlineData("A.B.C.D")]
|
||||
public void IdAndName_ReturnExpression_EvenIfExpressionNotFound(string expression)
|
||||
[InlineData("A", "A")]
|
||||
[InlineData("A[0].B", "A_0__B")]
|
||||
[InlineData("A.B.C.D", "A_B_C_D")]
|
||||
public void IdAndName_ReturnExpression_EvenIfExpressionNotFound(string expression, string expectedId)
|
||||
{
|
||||
// Arrange
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper();
|
||||
|
|
@ -207,7 +208,7 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
var nameResult = helper.Name(expression);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expression, idResult);
|
||||
Assert.Equal(expectedId, idResult);
|
||||
Assert.Equal(expression, nameResult);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
public class InputTagHelperTest
|
||||
{
|
||||
// Model (List<Model> or Model instance), container type (Model or NestModel), model accessor,
|
||||
// property path, expected value.
|
||||
public static TheoryData<object, Type, Func<object>, string, string> TestDataSet
|
||||
// property path / id, expected value.
|
||||
public static TheoryData<object, Type, Func<object>, NameAndId, string> TestDataSet
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
@ -41,32 +41,32 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
modelWithText,
|
||||
};
|
||||
|
||||
return new TheoryData<object, Type, Func<object>, string, string>
|
||||
return new TheoryData<object, Type, Func<object>, NameAndId, string>
|
||||
{
|
||||
{ null, typeof(Model), () => null, "Text",
|
||||
{ null, typeof(Model), () => null, new NameAndId("Text", "Text"),
|
||||
string.Empty },
|
||||
|
||||
{ modelWithNull, typeof(Model), () => modelWithNull.Text, "Text",
|
||||
{ modelWithNull, typeof(Model), () => modelWithNull.Text, new NameAndId("Text", "Text"),
|
||||
string.Empty },
|
||||
{ modelWithText, typeof(Model), () => modelWithText.Text, "Text",
|
||||
{ modelWithText, typeof(Model), () => modelWithText.Text, new NameAndId("Text", "Text"),
|
||||
"outer text" },
|
||||
|
||||
{ modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text",
|
||||
string.Empty },
|
||||
{ modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text",
|
||||
"inner text" },
|
||||
{ modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text,
|
||||
new NameAndId("NestedModel.Text", "NestedModel_Text"), string.Empty },
|
||||
{ modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text,
|
||||
new NameAndId("NestedModel.Text", "NestedModel_Text"), "inner text" },
|
||||
|
||||
// Top-level indexing does not work end-to-end due to code generation issue #1345.
|
||||
// TODO: Remove above comment when #1345 is fixed.
|
||||
{ models, typeof(Model), () => models[0].Text, "[0].Text",
|
||||
string.Empty },
|
||||
{ models, typeof(Model), () => models[1].Text, "[1].Text",
|
||||
"outer text" },
|
||||
{ models, typeof(Model), () => models[0].Text,
|
||||
new NameAndId("[0].Text", "z0__Text"), string.Empty },
|
||||
{ models, typeof(Model), () => models[1].Text,
|
||||
new NameAndId("[1].Text", "z1__Text"), "outer text" },
|
||||
|
||||
{ models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text",
|
||||
string.Empty },
|
||||
{ models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text",
|
||||
"inner text" },
|
||||
{ models, typeof(NestedModel), () => models[0].NestedModel.Text,
|
||||
new NameAndId("[0].NestedModel.Text", "z0__NestedModel_Text"), string.Empty },
|
||||
{ models, typeof(NestedModel), () => models[1].NestedModel.Text,
|
||||
new NameAndId("[1].NestedModel.Text", "z1__NestedModel_Text"), "inner text" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
object model,
|
||||
Type containerType,
|
||||
Func<object> modelAccessor,
|
||||
string propertyPath,
|
||||
NameAndId nameAndId,
|
||||
string expectedValue)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -85,8 +85,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
{
|
||||
{ "class", "form-control" },
|
||||
{ "type", "text" },
|
||||
{ "id", propertyPath },
|
||||
{ "name", propertyPath },
|
||||
{ "id", nameAndId.Id },
|
||||
{ "name", nameAndId.Name },
|
||||
{ "valid", "from validation attributes" },
|
||||
{ "value", expectedValue },
|
||||
};
|
||||
|
|
@ -97,7 +97,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
|
||||
// Property name is either nameof(Model.Text) or nameof(NestedModel.Text).
|
||||
var metadata = metadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName: "Text");
|
||||
var modelExpression = new ModelExpression(propertyPath, metadata);
|
||||
var modelExpression = new ModelExpression(nameAndId.Name, metadata);
|
||||
|
||||
var tagHelperContext = new TagHelperContext(new Dictionary<string, object>());
|
||||
var htmlAttributes = new Dictionary<string, string>
|
||||
|
|
@ -185,6 +185,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
Assert.Equal(expectedTagName, output.TagName);
|
||||
}
|
||||
|
||||
public class NameAndId
|
||||
{
|
||||
public NameAndId(string name, string id)
|
||||
{
|
||||
Name = name;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string Id { get; private set; }
|
||||
}
|
||||
|
||||
private class Model
|
||||
{
|
||||
public string Text { get; set; }
|
||||
|
|
|
|||
|
|
@ -44,45 +44,45 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
return new TheoryData<object, Type, Func<object>, string, TagHelperOutputContent>
|
||||
{
|
||||
{ null, typeof(Model), () => null, "Text",
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text") },
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text", "Text") },
|
||||
|
||||
{ modelWithNull, typeof(Model), () => modelWithNull.Text, "Text",
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text") },
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text", "Text") },
|
||||
{ modelWithText, typeof(Model), () => modelWithText.Text, "Text",
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text") },
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text", "Text") },
|
||||
{ modelWithText, typeof(Model), () => modelWithNull.Text, "Text",
|
||||
new TagHelperOutputContent("Hello World", "Hello World") },
|
||||
new TagHelperOutputContent("Hello World", "Hello World", "Text") },
|
||||
{ modelWithText, typeof(Model), () => modelWithText.Text, "Text",
|
||||
new TagHelperOutputContent("Hello World", "Hello World") },
|
||||
new TagHelperOutputContent("Hello World", "Hello World", "Text") },
|
||||
|
||||
{ modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text",
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text") },
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text", "NestedModel_Text") },
|
||||
{ modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text",
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text") },
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text", "NestedModel_Text") },
|
||||
{ modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text",
|
||||
new TagHelperOutputContent("Hello World", "Hello World") },
|
||||
new TagHelperOutputContent("Hello World", "Hello World", "NestedModel_Text") },
|
||||
{ modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text",
|
||||
new TagHelperOutputContent("Hello World", "Hello World") },
|
||||
new TagHelperOutputContent("Hello World", "Hello World", "NestedModel_Text") },
|
||||
|
||||
// Note: Tests cases below here will not work in practice due to current limitations on indexing
|
||||
// into ModelExpressions. Will be fixed in https://github.com/aspnet/Mvc/issues/1345.
|
||||
{ models, typeof(Model), () => models[0].Text, "[0].Text",
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text") },
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text", "z0__Text") },
|
||||
{ models, typeof(Model), () => models[1].Text, "[1].Text",
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text") },
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text", "z1__Text") },
|
||||
{ models, typeof(Model), () => models[0].Text, "[0].Text",
|
||||
new TagHelperOutputContent("Hello World", "Hello World") },
|
||||
new TagHelperOutputContent("Hello World", "Hello World", "z0__Text") },
|
||||
{ models, typeof(Model), () => models[1].Text, "[1].Text",
|
||||
new TagHelperOutputContent("Hello World", "Hello World") },
|
||||
new TagHelperOutputContent("Hello World", "Hello World", "z1__Text") },
|
||||
|
||||
{ models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text",
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text") },
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text", "z0__NestedModel_Text") },
|
||||
{ models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text",
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text") },
|
||||
new TagHelperOutputContent(Environment.NewLine, "Text", "z1__NestedModel_Text") },
|
||||
{ models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text",
|
||||
new TagHelperOutputContent("Hello World", "Hello World") },
|
||||
new TagHelperOutputContent("Hello World", "Hello World", "z0__NestedModel_Text") },
|
||||
{ models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text",
|
||||
new TagHelperOutputContent("Hello World", "Hello World") },
|
||||
new TagHelperOutputContent("Hello World", "Hello World", "z1__NestedModel_Text") },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -100,7 +100,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var expectedAttributes = new Dictionary<string, string>
|
||||
{
|
||||
{ "class", "form-control" },
|
||||
{ "for", propertyPath }
|
||||
{ "for", tagHelperOutputContent.ExpectedId }
|
||||
};
|
||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
|
||||
|
|
@ -173,14 +173,18 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
|
||||
public class TagHelperOutputContent
|
||||
{
|
||||
public TagHelperOutputContent(string outputContent, string expectedContent)
|
||||
public TagHelperOutputContent(string outputContent, string expectedContent, string expectedId)
|
||||
{
|
||||
OriginalContent = outputContent;
|
||||
ExpectedContent = expectedContent;
|
||||
ExpectedId = expectedId;
|
||||
}
|
||||
|
||||
public string OriginalContent { get; set; }
|
||||
|
||||
public string ExpectedContent { get; set; }
|
||||
|
||||
public string ExpectedId { get; set; }
|
||||
}
|
||||
|
||||
private class Model
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
public class TextAreaTagHelperTest
|
||||
{
|
||||
// Model (List<Model> or Model instance), container type (Model or NestModel), model accessor,
|
||||
// property path, expected content.
|
||||
public static TheoryData<object, Type, Func<object>, string, string> TestDataSet
|
||||
// property path / id, expected content.
|
||||
public static TheoryData<object, Type, Func<object>, NameAndId, string> TestDataSet
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
@ -41,31 +41,40 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
modelWithText,
|
||||
};
|
||||
|
||||
return new TheoryData<object, Type, Func<object>, string, string>
|
||||
return new TheoryData<object, Type, Func<object>, NameAndId, string>
|
||||
{
|
||||
{ null, typeof(Model), () => null, "Text",
|
||||
{ null, typeof(Model), () => null,
|
||||
new NameAndId("Text", "Text"),
|
||||
Environment.NewLine },
|
||||
|
||||
{ modelWithNull, typeof(Model), () => modelWithNull.Text, "Text",
|
||||
{ modelWithNull, typeof(Model), () => modelWithNull.Text,
|
||||
new NameAndId("Text", "Text"),
|
||||
Environment.NewLine },
|
||||
{ modelWithText, typeof(Model), () => modelWithText.Text, "Text",
|
||||
{ modelWithText, typeof(Model), () => modelWithText.Text,
|
||||
new NameAndId("Text", "Text"),
|
||||
Environment.NewLine + "outer text" },
|
||||
|
||||
{ modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text",
|
||||
{ modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text,
|
||||
new NameAndId("NestedModel.Text", "NestedModel_Text"),
|
||||
Environment.NewLine },
|
||||
{ modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text",
|
||||
{ modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text,
|
||||
new NameAndId("NestedModel.Text", "NestedModel_Text"),
|
||||
Environment.NewLine + "inner text" },
|
||||
|
||||
// Top-level indexing does not work end-to-end due to code generation issue #1345.
|
||||
// TODO: Remove above comment when #1345 is fixed.
|
||||
{ models, typeof(Model), () => models[0].Text, "[0].Text",
|
||||
{ models, typeof(Model), () => models[0].Text,
|
||||
new NameAndId("[0].Text", "z0__Text"),
|
||||
Environment.NewLine },
|
||||
{ models, typeof(Model), () => models[1].Text, "[1].Text",
|
||||
{ models, typeof(Model), () => models[1].Text,
|
||||
new NameAndId("[1].Text", "z1__Text"),
|
||||
Environment.NewLine + "outer text" },
|
||||
|
||||
{ models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text",
|
||||
{ models, typeof(NestedModel), () => models[0].NestedModel.Text,
|
||||
new NameAndId("[0].NestedModel.Text", "z0__NestedModel_Text"),
|
||||
Environment.NewLine },
|
||||
{ models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text",
|
||||
{ models, typeof(NestedModel), () => models[1].NestedModel.Text,
|
||||
new NameAndId("[1].NestedModel.Text", "z1__NestedModel_Text"),
|
||||
Environment.NewLine + "inner text" },
|
||||
};
|
||||
}
|
||||
|
|
@ -77,15 +86,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
object model,
|
||||
Type containerType,
|
||||
Func<object> modelAccessor,
|
||||
string propertyPath,
|
||||
NameAndId nameAndId,
|
||||
string expectedContent)
|
||||
{
|
||||
// Arrange
|
||||
var expectedAttributes = new Dictionary<string, string>
|
||||
{
|
||||
{ "class", "form-control" },
|
||||
{ "id", propertyPath },
|
||||
{ "name", propertyPath },
|
||||
{ "id", nameAndId.Id },
|
||||
{ "name", nameAndId.Name },
|
||||
{ "valid", "from validation attributes" },
|
||||
};
|
||||
var expectedTagName = "textarea";
|
||||
|
|
@ -94,7 +103,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
|
||||
// Property name is either nameof(Model.Text) or nameof(NestedModel.Text).
|
||||
var metadata = metadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName: "Text");
|
||||
var modelExpression = new ModelExpression(propertyPath, metadata);
|
||||
var modelExpression = new ModelExpression(nameAndId.Name, metadata);
|
||||
var tagHelper = new TextAreaTagHelper
|
||||
{
|
||||
For = modelExpression,
|
||||
|
|
@ -178,6 +187,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
Assert.Equal(expectedTagName, output.TagName);
|
||||
}
|
||||
|
||||
public class NameAndId
|
||||
{
|
||||
public NameAndId(string name, string id)
|
||||
{
|
||||
Name = name;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string Id { get; private set; }
|
||||
}
|
||||
|
||||
private class Model
|
||||
{
|
||||
public string Text { get; set; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue