1021 lines
46 KiB
C#
1021 lines
46 KiB
C#
// 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.Collections.Generic;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.Encodings.Web;
|
|
using Microsoft.AspNetCore.Antiforgery;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
|
using Microsoft.AspNetCore.Mvc.Internal;
|
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
using Microsoft.AspNetCore.Mvc.Routing;
|
|
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
|
using Microsoft.AspNetCore.Routing;
|
|
using Microsoft.AspNetCore.Testing;
|
|
using Microsoft.Extensions.Options;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|
{
|
|
public class DefaultHtmlGeneratorTest
|
|
{
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void GetCurrentValues_WithEmptyViewData_ReturnsNull(bool allowMultiple)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
|
|
// Act
|
|
var result = htmlGenerator.GetCurrentValues(
|
|
viewContext,
|
|
modelExplorer: null,
|
|
expression: nameof(Model.Name),
|
|
allowMultiple: allowMultiple);
|
|
|
|
// Assert
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void GetCurrentValues_WithNullExpressionResult_ReturnsNull(bool allowMultiple)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model: null);
|
|
|
|
// Act
|
|
var result = htmlGenerator.GetCurrentValues(
|
|
viewContext,
|
|
modelExplorer,
|
|
expression: nameof(Model.Name),
|
|
allowMultiple: allowMultiple);
|
|
|
|
// Assert
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetCurrentValues_WithNullExpression_DoesNotThrow()
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model: null);
|
|
|
|
// Act and Assert (does not throw).
|
|
htmlGenerator.GetCurrentValues(viewContext, modelExplorer, expression: null, allowMultiple: true);
|
|
}
|
|
|
|
[Fact]
|
|
public void GenerateSelect_WithNullExpression_Throws()
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model: null);
|
|
|
|
var expected = "The name of an HTML field cannot be null or empty. Instead use methods " +
|
|
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
|
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
|
|
|
// Act and Assert
|
|
ExceptionAssert.ThrowsArgument(
|
|
() => htmlGenerator.GenerateSelect(
|
|
viewContext,
|
|
modelExplorer,
|
|
"label",
|
|
expression: null,
|
|
selectList: new List<SelectListItem>(),
|
|
allowMultiple: true,
|
|
htmlAttributes: null),
|
|
"expression",
|
|
expected);
|
|
}
|
|
|
|
[Fact]
|
|
public void GenerateSelect_WithNullExpression_WithNameAttribute_DoesNotThrow()
|
|
{
|
|
// Arrange
|
|
var expected = "-expression-";
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model: null);
|
|
var htmlAttributes = new Dictionary<string, object>
|
|
{
|
|
{ "name", expected },
|
|
};
|
|
|
|
// Act
|
|
var tagBuilder = htmlGenerator.GenerateSelect(
|
|
viewContext,
|
|
modelExplorer,
|
|
"label",
|
|
expression: null,
|
|
selectList: new List<SelectListItem>(),
|
|
allowMultiple: true,
|
|
htmlAttributes: htmlAttributes);
|
|
|
|
// Assert
|
|
var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "name");
|
|
Assert.Equal(expected, attribute.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void GenerateTextArea_WithNullExpression_Throws()
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model: null);
|
|
|
|
var expected = "The name of an HTML field cannot be null or empty. Instead use methods " +
|
|
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
|
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
|
|
|
// Act and Assert
|
|
ExceptionAssert.ThrowsArgument(
|
|
() => htmlGenerator.GenerateTextArea(
|
|
viewContext,
|
|
modelExplorer,
|
|
expression: null,
|
|
rows: 1,
|
|
columns: 1,
|
|
htmlAttributes: null),
|
|
"expression",
|
|
expected);
|
|
}
|
|
|
|
[Fact]
|
|
public void GenerateTextArea_WithNullExpression_WithNameAttribute_DoesNotThrow()
|
|
{
|
|
// Arrange
|
|
var expected = "-expression-";
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model: null);
|
|
var htmlAttributes = new Dictionary<string, object>
|
|
{
|
|
{ "name", expected },
|
|
};
|
|
|
|
// Act
|
|
var tagBuilder = htmlGenerator.GenerateTextArea(
|
|
viewContext,
|
|
modelExplorer,
|
|
expression: null,
|
|
rows: 1,
|
|
columns: 1,
|
|
htmlAttributes: htmlAttributes);
|
|
|
|
// Assert
|
|
var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "name");
|
|
Assert.Equal(expected, attribute.Value);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithMaxLength), ModelWithMaxLengthMetadata.MaxLengthAttributeValue)]
|
|
[InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithStringLength), ModelWithMaxLengthMetadata.StringLengthAttributeValue)]
|
|
public void GenerateTextArea_RendersMaxLength(string expression, int expectedValue)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<ModelWithMaxLengthMetadata>(model: null, metadataProvider: metadataProvider);
|
|
var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), expression);
|
|
var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null);
|
|
var htmlAttributes = new Dictionary<string, object>
|
|
{
|
|
{ "name", "testElement" },
|
|
};
|
|
|
|
// Act
|
|
var tagBuilder = htmlGenerator.GenerateTextArea(viewContext, modelExplorer, expression, rows: 1, columns: 1, htmlAttributes);
|
|
|
|
// Assert
|
|
var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "maxlength");
|
|
Assert.Equal(expectedValue, Int32.Parse(attribute.Value));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithMaxLength), ModelWithMaxLengthMetadata.MaxLengthAttributeValue)]
|
|
[InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithStringLength), ModelWithMaxLengthMetadata.StringLengthAttributeValue)]
|
|
public void GeneratePassword_RendersMaxLength(string expression, int expectedValue)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<ModelWithMaxLengthMetadata>(model: null, metadataProvider: metadataProvider);
|
|
var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), expression);
|
|
var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null);
|
|
var htmlAttributes = new Dictionary<string, object>
|
|
{
|
|
{ "name", "testElement" },
|
|
};
|
|
|
|
// Act
|
|
var tagBuilder = htmlGenerator.GeneratePassword(viewContext, modelExplorer, expression, null, htmlAttributes);
|
|
|
|
// Assert
|
|
var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "maxlength");
|
|
Assert.Equal(expectedValue, Int32.Parse(attribute.Value));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithMaxLength), ModelWithMaxLengthMetadata.MaxLengthAttributeValue)]
|
|
[InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithStringLength), ModelWithMaxLengthMetadata.StringLengthAttributeValue)]
|
|
public void GenerateTextBox_RendersMaxLength(string expression, int expectedValue)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<ModelWithMaxLengthMetadata>(model: null, metadataProvider: metadataProvider);
|
|
var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), expression);
|
|
var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null);
|
|
var htmlAttributes = new Dictionary<string, object>
|
|
{
|
|
{ "name", "testElement" },
|
|
};
|
|
|
|
// Act
|
|
var tagBuilder = htmlGenerator.GenerateTextBox(viewContext, modelExplorer, expression, null, null, htmlAttributes);
|
|
|
|
// Assert
|
|
var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "maxlength");
|
|
Assert.Equal(expectedValue, Int32.Parse(attribute.Value));
|
|
}
|
|
|
|
[Fact]
|
|
public void GenerateTextBox_RendersMaxLength_WithMinimumValueFromBothAttributes()
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<ModelWithMaxLengthMetadata>(model: null, metadataProvider: metadataProvider);
|
|
var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), nameof(ModelWithMaxLengthMetadata.FieldWithBothAttributes));
|
|
var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null);
|
|
var htmlAttributes = new Dictionary<string, object>
|
|
{
|
|
{ "name", "testElement" },
|
|
};
|
|
|
|
// Act
|
|
var tagBuilder = htmlGenerator.GenerateTextBox(viewContext, modelExplorer, nameof(ModelWithMaxLengthMetadata.FieldWithBothAttributes), null, null, htmlAttributes);
|
|
|
|
// Assert
|
|
var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "maxlength");
|
|
Assert.Equal(Math.Min(ModelWithMaxLengthMetadata.MaxLengthAttributeValue, ModelWithMaxLengthMetadata.StringLengthAttributeValue), Int32.Parse(attribute.Value));
|
|
}
|
|
|
|
[Fact]
|
|
public void GenerateTextBox_DoesNotRenderMaxLength_WhenNoAttributesPresent()
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<ModelWithMaxLengthMetadata>(model: null, metadataProvider: metadataProvider);
|
|
var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), nameof(ModelWithMaxLengthMetadata.FieldWithoutAttributes));
|
|
var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null);
|
|
var htmlAttributes = new Dictionary<string, object>
|
|
{
|
|
{ "name", "testElement" },
|
|
};
|
|
|
|
// Act
|
|
var tagBuilder = htmlGenerator.GenerateTextBox(viewContext, modelExplorer, nameof(ModelWithMaxLengthMetadata.FieldWithoutAttributes), null, null, htmlAttributes);
|
|
|
|
// Assert
|
|
Assert.DoesNotContain(tagBuilder.Attributes, a => a.Key == "maxlength");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithMaxLength), ModelWithMaxLengthMetadata.MaxLengthAttributeValue)]
|
|
[InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithStringLength), ModelWithMaxLengthMetadata.StringLengthAttributeValue)]
|
|
public void GenerateTextBox_SearchType_RendersMaxLength(string expression, int expectedValue)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<ModelWithMaxLengthMetadata>(model: null, metadataProvider: metadataProvider);
|
|
var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), expression);
|
|
var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null);
|
|
var htmlAttributes = new Dictionary<string, object>
|
|
{
|
|
{ "name", "testElement" },
|
|
{ "type", "search"}
|
|
};
|
|
|
|
// Act
|
|
var tagBuilder = htmlGenerator.GenerateTextBox(viewContext, modelExplorer, expression, null, null, htmlAttributes);
|
|
|
|
// Assert
|
|
var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "maxlength");
|
|
Assert.Equal(expectedValue, Int32.Parse(attribute.Value));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithMaxLength))]
|
|
[InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithStringLength))]
|
|
public void GenerateHidden_DoesNotRenderMaxLength(string expression)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<ModelWithMaxLengthMetadata>(model: null, metadataProvider: metadataProvider);
|
|
var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), expression);
|
|
var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null);
|
|
var htmlAttributes = new Dictionary<string, object>
|
|
{
|
|
{ "name", "testElement" },
|
|
};
|
|
|
|
// Act
|
|
var tagBuilder = htmlGenerator.GenerateHidden(viewContext, modelExplorer, expression, null, false, htmlAttributes);
|
|
|
|
// Assert
|
|
Assert.DoesNotContain(tagBuilder.Attributes, a => a.Key == "maxlength");
|
|
}
|
|
|
|
[Fact]
|
|
public void GenerateValidationMessage_WithNullExpression_Throws()
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model: null);
|
|
|
|
var expected = "The name of an HTML field cannot be null or empty. Instead use methods " +
|
|
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
|
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
|
|
|
// Act and Assert
|
|
ExceptionAssert.ThrowsArgument(
|
|
() => htmlGenerator.GenerateValidationMessage(
|
|
viewContext,
|
|
modelExplorer: null,
|
|
expression: null,
|
|
message: "Message",
|
|
tag: "tag",
|
|
htmlAttributes: null),
|
|
"expression",
|
|
expected);
|
|
}
|
|
|
|
[Fact]
|
|
public void GenerateValidationMessage_WithNullExpression_WithValidationForAttribute_DoesNotThrow()
|
|
{
|
|
// Arrange
|
|
var expected = "-expression-";
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model: null);
|
|
var htmlAttributes = new Dictionary<string, object>
|
|
{
|
|
{ "data-valmsg-for", expected },
|
|
};
|
|
|
|
// Act
|
|
var tagBuilder = htmlGenerator.GenerateValidationMessage(
|
|
viewContext,
|
|
modelExplorer: null,
|
|
expression: null,
|
|
message: "Message",
|
|
tag: "tag",
|
|
htmlAttributes: htmlAttributes);
|
|
|
|
// Assert
|
|
var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "data-valmsg-for");
|
|
Assert.Equal(expected, attribute.Value);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void GetCurrentValues_WithSelectListInViewData_ReturnsNull(bool allowMultiple)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
viewContext.ViewData[nameof(Model.Name)] = Enumerable.Empty<SelectListItem>();
|
|
|
|
// Act
|
|
var result = htmlGenerator.GetCurrentValues(
|
|
viewContext,
|
|
modelExplorer: null,
|
|
expression: nameof(Model.Name),
|
|
allowMultiple: allowMultiple);
|
|
|
|
// Assert
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("some string")] // treated as if it were not IEnumerable
|
|
[InlineData(23)]
|
|
[InlineData(RegularEnum.Three)]
|
|
public void GetCurrentValues_AllowMultipleWithNonEnumerableInViewData_Throws(object value)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
viewContext.ViewData[nameof(Model.Name)] = value;
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<InvalidOperationException>(() => htmlGenerator.GetCurrentValues(
|
|
viewContext,
|
|
modelExplorer: null,
|
|
expression: nameof(Model.Name),
|
|
allowMultiple: true));
|
|
Assert.Equal(
|
|
"The parameter 'expression' must evaluate to an IEnumerable when multiple selection is allowed.",
|
|
exception.Message);
|
|
}
|
|
|
|
// rawValue, allowMultiple -> expected current values
|
|
public static TheoryData<string[], bool, IReadOnlyCollection<string>> GetCurrentValues_CollectionData
|
|
{
|
|
get
|
|
{
|
|
return new TheoryData<string[], bool, IReadOnlyCollection<string>>
|
|
{
|
|
// ModelStateDictionary converts arrays to single values if needed.
|
|
{ new [] { "some string" }, false, new [] { "some string" } },
|
|
{ new [] { "some string" }, true, new [] { "some string" } },
|
|
{ new [] { "some string", "some other string" }, false, new [] { "some string" } },
|
|
{
|
|
new [] { "some string", "some other string" },
|
|
true,
|
|
new [] { "some string", "some other string" }
|
|
},
|
|
// { new string[] { null }, false, null } would fall back to other sources.
|
|
{ new string[] { null }, true, new [] { string.Empty } },
|
|
{ new [] { string.Empty }, false, new [] { string.Empty } },
|
|
{ new [] { string.Empty }, true, new [] { string.Empty } },
|
|
{
|
|
new [] { null, "some string", "some other string" },
|
|
true,
|
|
new [] { string.Empty, "some string", "some other string" }
|
|
},
|
|
// ignores duplicates
|
|
{
|
|
new [] { null, "some string", null, "some other string", null, "some string", null },
|
|
true,
|
|
new [] { string.Empty, "some string", "some other string" }
|
|
},
|
|
// ignores case of duplicates
|
|
{
|
|
new [] { "some string", "SoMe StriNg", "Some String", "soME STRing", "SOME STRING" },
|
|
true,
|
|
new [] { "some string" }
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(GetCurrentValues_CollectionData))]
|
|
public void GetCurrentValues_WithModelStateEntryAndViewData_ReturnsModelStateEntry(
|
|
string[] rawValue,
|
|
bool allowMultiple,
|
|
IReadOnlyCollection<string> expected)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var model = new Model { Name = "ignored property value" };
|
|
|
|
var viewContext = GetViewContext<Model>(model, metadataProvider);
|
|
viewContext.ViewData[nameof(Model.Name)] = "ignored ViewData value";
|
|
viewContext.ModelState.SetModelValue(nameof(Model.Name), rawValue, attemptedValue: null);
|
|
|
|
// Act
|
|
var result = htmlGenerator.GetCurrentValues(
|
|
viewContext,
|
|
modelExplorer: null,
|
|
expression: nameof(Model.Name),
|
|
allowMultiple: allowMultiple);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal<string>(expected, result);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(GetCurrentValues_CollectionData))]
|
|
public void GetCurrentValues_WithModelStateEntryModelExplorerAndViewData_ReturnsModelStateEntry(
|
|
string[] rawValue,
|
|
bool allowMultiple,
|
|
IReadOnlyCollection<string> expected)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var model = new Model { Name = "ignored property value" };
|
|
|
|
var viewContext = GetViewContext<Model>(model, metadataProvider);
|
|
viewContext.ViewData[nameof(Model.Name)] = "ignored ViewData value";
|
|
viewContext.ModelState.SetModelValue(nameof(Model.Name), rawValue, attemptedValue: null);
|
|
|
|
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), "ignored model value");
|
|
|
|
// Act
|
|
var result = htmlGenerator.GetCurrentValues(
|
|
viewContext,
|
|
modelExplorer,
|
|
expression: nameof(Model.Name),
|
|
allowMultiple: allowMultiple);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal<string>(expected, result);
|
|
}
|
|
|
|
// rawValue -> expected current values
|
|
public static TheoryData<string[], string[]> GetCurrentValues_StringData
|
|
{
|
|
get
|
|
{
|
|
return new TheoryData<string[], string[]>
|
|
{
|
|
// 1. If given a ModelExplorer, GetCurrentValues does not use ViewData even if expression result is
|
|
// null.
|
|
// 2. Otherwise if ViewData entry exists, GetCurrentValue does not fall back to ViewData.Model even
|
|
// if entry is null.
|
|
// 3. Otherwise, GetCurrentValue does not fall back anywhere else even if ViewData.Model is null.
|
|
{ null, null },
|
|
{ new string[] { string.Empty }, new [] { string.Empty } },
|
|
{ new string[] { "some string" }, new [] { "some string" } },
|
|
};
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(GetCurrentValues_StringData))]
|
|
public void GetCurrentValues_WithModelExplorerAndViewData_ReturnsExpressionResult(
|
|
string[] rawValue,
|
|
IReadOnlyCollection<string> expected)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var model = new Model { Name = "ignored property value" };
|
|
|
|
var viewContext = GetViewContext<Model>(model, metadataProvider);
|
|
viewContext.ViewData[nameof(Model.Name)] = "ignored ViewData value";
|
|
viewContext.ModelState.SetModelValue(nameof(Model.Name), rawValue, attemptedValue: null);
|
|
|
|
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), rawValue);
|
|
|
|
// Act
|
|
var result = htmlGenerator.GetCurrentValues(
|
|
viewContext,
|
|
modelExplorer,
|
|
expression: nameof(Model.Name),
|
|
allowMultiple: false);
|
|
|
|
// Assert
|
|
Assert.Equal<string>(expected, result);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(GetCurrentValues_StringData))]
|
|
public void GetCurrentValues_WithViewData_ReturnsViewDataEntry(
|
|
string[] rawValue,
|
|
IReadOnlyCollection<string> expected)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var model = new Model { Name = "ignored property value" };
|
|
|
|
var viewContext = GetViewContext<Model>(model, metadataProvider);
|
|
viewContext.ViewData[nameof(Model.Name)] = rawValue;
|
|
viewContext.ModelState.SetModelValue(nameof(Model.Name), rawValue, attemptedValue: null);
|
|
|
|
// Act
|
|
var result = htmlGenerator.GetCurrentValues(
|
|
viewContext,
|
|
modelExplorer: null,
|
|
expression: nameof(Model.Name),
|
|
allowMultiple: false);
|
|
|
|
// Assert
|
|
Assert.Equal<string>(expected, result);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(GetCurrentValues_StringData))]
|
|
public void GetCurrentValues_WithModel_ReturnsModel(string[] rawValue, IReadOnlyCollection<string> expected)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var model = new Model { Name = rawValue?[0] };
|
|
|
|
var viewContext = GetViewContext<Model>(model, metadataProvider);
|
|
viewContext.ModelState.SetModelValue(nameof(Model.Name), rawValue, attemptedValue: null);
|
|
|
|
// Act
|
|
var result = htmlGenerator.GetCurrentValues(
|
|
viewContext,
|
|
modelExplorer: null,
|
|
expression: nameof(Model.Name),
|
|
allowMultiple: false);
|
|
|
|
// Assert
|
|
Assert.Equal<string>(expected, result);
|
|
}
|
|
|
|
// rawValue -> expected current values
|
|
public static TheoryData<string[], string[]> GetCurrentValues_StringCollectionData
|
|
{
|
|
get
|
|
{
|
|
return new TheoryData<string[], string[]>
|
|
{
|
|
{ new string[] { null }, new [] { string.Empty } },
|
|
{ new [] { string.Empty }, new [] { string.Empty } },
|
|
{ new [] { "some string" }, new [] { "some string" } },
|
|
{ new [] { "some string", "some other string" }, new [] { "some string", "some other string" } },
|
|
{
|
|
new [] { null, "some string", "some other string" },
|
|
new [] { string.Empty, "some string", "some other string" }
|
|
},
|
|
// ignores duplicates
|
|
{
|
|
new [] { null, "some string", null, "some other string", null, "some string", null },
|
|
new [] { string.Empty, "some string", "some other string" }
|
|
},
|
|
// ignores case of duplicates
|
|
{
|
|
new [] { "some string", "SoMe StriNg", "Some String", "soME STRing", "SOME STRING" },
|
|
new [] { "some string" }
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(GetCurrentValues_StringCollectionData))]
|
|
public void GetCurrentValues_CollectionWithModelExplorerAndViewData_ReturnsExpressionResult(
|
|
string[] rawValue,
|
|
IReadOnlyCollection<string> expected)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var model = new Model { Collection = { "ignored property value" } };
|
|
|
|
var viewContext = GetViewContext<Model>(model, metadataProvider);
|
|
viewContext.ViewData[nameof(Model.Collection)] = new[] { "ignored ViewData value" };
|
|
viewContext.ModelState.SetModelValue(nameof(Model.Collection), rawValue, attemptedValue: null);
|
|
|
|
var modelExplorer =
|
|
metadataProvider.GetModelExplorerForType(typeof(List<string>), new List<string>(rawValue));
|
|
|
|
// Act
|
|
var result = htmlGenerator.GetCurrentValues(
|
|
viewContext,
|
|
modelExplorer,
|
|
expression: nameof(Model.Collection),
|
|
allowMultiple: true);
|
|
|
|
// Assert
|
|
Assert.Equal<string>(expected, result);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(GetCurrentValues_StringCollectionData))]
|
|
public void GetCurrentValues_CollectionWithViewData_ReturnsViewDataEntry(
|
|
string[] rawValue,
|
|
IReadOnlyCollection<string> expected)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var model = new Model { Collection = { "ignored property value" } };
|
|
|
|
var viewContext = GetViewContext<Model>(model, metadataProvider);
|
|
viewContext.ViewData[nameof(Model.Collection)] = rawValue;
|
|
viewContext.ModelState.SetModelValue(nameof(Model.Collection), rawValue, attemptedValue: null);
|
|
|
|
// Act
|
|
var result = htmlGenerator.GetCurrentValues(
|
|
viewContext,
|
|
modelExplorer: null,
|
|
expression: nameof(Model.Collection),
|
|
allowMultiple: true);
|
|
|
|
// Assert
|
|
Assert.Equal<string>(expected, result);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(GetCurrentValues_StringCollectionData))]
|
|
public void GetCurrentValues_CollectionWithModel_ReturnsModel(
|
|
string[] rawValue,
|
|
IReadOnlyCollection<string> expected)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = new TestModelMetadataProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var model = new Model();
|
|
model.Collection.AddRange(rawValue);
|
|
|
|
var viewContext = GetViewContext<Model>(model, metadataProvider);
|
|
viewContext.ModelState.SetModelValue(
|
|
nameof(Model.Collection),
|
|
rawValue,
|
|
attemptedValue: null);
|
|
|
|
// Act
|
|
var result = htmlGenerator.GetCurrentValues(
|
|
viewContext,
|
|
modelExplorer: null,
|
|
expression: nameof(Model.Collection),
|
|
allowMultiple: true);
|
|
|
|
// Assert
|
|
Assert.Equal<string>(expected, result);
|
|
}
|
|
|
|
// property name, rawValue -> expected current values
|
|
public static TheoryData<string, object, string[]> GetCurrentValues_ValueToConvertData
|
|
{
|
|
get
|
|
{
|
|
return new TheoryData<string, object, string[]>
|
|
{
|
|
{ nameof(Model.FlagsEnum), FlagsEnum.All, new [] { "-1", "All" } },
|
|
{ nameof(Model.FlagsEnum), FlagsEnum.FortyTwo, new [] { "42", "FortyTwo" } },
|
|
{ nameof(Model.FlagsEnum), FlagsEnum.None, new [] { "0", "None" } },
|
|
{ nameof(Model.FlagsEnum), FlagsEnum.Two, new [] { "2", "Two" } },
|
|
{ nameof(Model.FlagsEnum), string.Empty, new [] { string.Empty } },
|
|
{ nameof(Model.FlagsEnum), "All", new [] { "-1", "All" } },
|
|
{ nameof(Model.FlagsEnum), "FortyTwo", new [] { "42", "FortyTwo" } },
|
|
{ nameof(Model.FlagsEnum), "None", new [] { "0", "None" } },
|
|
{ nameof(Model.FlagsEnum), "Two", new [] { "2", "Two" } },
|
|
{ nameof(Model.FlagsEnum), "Two, Four", new [] { "Two, Four", "6" } },
|
|
{ nameof(Model.FlagsEnum), "garbage", new [] { "garbage" } },
|
|
{ nameof(Model.FlagsEnum), "0", new [] { "0", "None" } },
|
|
{ nameof(Model.FlagsEnum), " 43", new [] { " 43", "43" } },
|
|
{ nameof(Model.FlagsEnum), "-5 ", new [] { "-5 ", "-5" } },
|
|
{ nameof(Model.FlagsEnum), 0, new [] { "0", "None" } },
|
|
{ nameof(Model.FlagsEnum), 1, new [] { "1", "One" } },
|
|
{ nameof(Model.FlagsEnum), 43, new [] { "43" } },
|
|
{ nameof(Model.FlagsEnum), -5, new [] { "-5" } },
|
|
{ nameof(Model.FlagsEnum), int.MaxValue, new [] { "2147483647" } },
|
|
{ nameof(Model.FlagsEnum), (uint)int.MaxValue + 1, new [] { "2147483648" } },
|
|
{ nameof(Model.FlagsEnum), uint.MaxValue, new [] { "4294967295" } }, // converted to string & used
|
|
|
|
{ nameof(Model.Id), string.Empty, new [] { string.Empty } },
|
|
{ nameof(Model.Id), "garbage", new [] { "garbage" } }, // no compatibility checks
|
|
{ nameof(Model.Id), "0", new [] { "0" } },
|
|
{ nameof(Model.Id), " 43", new [] { " 43" } },
|
|
{ nameof(Model.Id), "-5 ", new [] { "-5 " } },
|
|
{ nameof(Model.Id), 0, new [] { "0" } },
|
|
{ nameof(Model.Id), 1, new [] { "1" } },
|
|
{ nameof(Model.Id), 43, new [] { "43" } },
|
|
{ nameof(Model.Id), -5, new [] { "-5" } },
|
|
{ nameof(Model.Id), int.MaxValue, new [] { "2147483647" } },
|
|
{ nameof(Model.Id), (uint)int.MaxValue + 1, new [] { "2147483648" } }, // no limit checks
|
|
{ nameof(Model.Id), uint.MaxValue, new [] { "4294967295" } }, // no limit checks
|
|
|
|
{ nameof(Model.NullableEnum), RegularEnum.Zero, new [] { "0", "Zero" } },
|
|
{ nameof(Model.NullableEnum), RegularEnum.One, new [] { "1", "One" } },
|
|
{ nameof(Model.NullableEnum), RegularEnum.Two, new [] { "2", "Two" } },
|
|
{ nameof(Model.NullableEnum), RegularEnum.Three, new [] { "3", "Three" } },
|
|
{ nameof(Model.NullableEnum), string.Empty, new [] { string.Empty } },
|
|
{ nameof(Model.NullableEnum), "Zero", new [] { "0", "Zero" } },
|
|
{ nameof(Model.NullableEnum), "Two", new [] { "2", "Two" } },
|
|
{ nameof(Model.NullableEnum), "One, Two", new [] { "One, Two", "3", "Three" } },
|
|
{ nameof(Model.NullableEnum), "garbage", new [] { "garbage" } },
|
|
{ nameof(Model.NullableEnum), "0", new [] { "0", "Zero" } },
|
|
{ nameof(Model.NullableEnum), " 43", new [] { " 43", "43" } },
|
|
{ nameof(Model.NullableEnum), "-5 ", new [] { "-5 ", "-5" } },
|
|
{ nameof(Model.NullableEnum), 0, new [] { "0", "Zero" } },
|
|
{ nameof(Model.NullableEnum), 1, new [] { "1", "One" } },
|
|
{ nameof(Model.NullableEnum), 43, new [] { "43" } },
|
|
{ nameof(Model.NullableEnum), -5, new [] { "-5" } },
|
|
{ nameof(Model.NullableEnum), int.MaxValue, new [] { "2147483647" } },
|
|
{ nameof(Model.NullableEnum), (uint)int.MaxValue + 1, new [] { "2147483648" } },
|
|
{ nameof(Model.NullableEnum), uint.MaxValue, new [] { "4294967295" } },
|
|
|
|
{ nameof(Model.RegularEnum), RegularEnum.Zero, new [] { "0", "Zero" } },
|
|
{ nameof(Model.RegularEnum), RegularEnum.One, new [] { "1", "One" } },
|
|
{ nameof(Model.RegularEnum), RegularEnum.Two, new [] { "2", "Two" } },
|
|
{ nameof(Model.RegularEnum), RegularEnum.Three, new [] { "3", "Three" } },
|
|
{ nameof(Model.RegularEnum), string.Empty, new [] { string.Empty } },
|
|
{ nameof(Model.RegularEnum), "Zero", new [] { "0", "Zero" } },
|
|
{ nameof(Model.RegularEnum), "Two", new [] { "2", "Two" } },
|
|
{ nameof(Model.RegularEnum), "One, Two", new [] { "One, Two", "3", "Three" } },
|
|
{ nameof(Model.RegularEnum), "garbage", new [] { "garbage" } },
|
|
{ nameof(Model.RegularEnum), "0", new [] { "0", "Zero" } },
|
|
{ nameof(Model.RegularEnum), " 43", new [] { " 43", "43" } },
|
|
{ nameof(Model.RegularEnum), "-5 ", new [] { "-5 ", "-5" } },
|
|
{ nameof(Model.RegularEnum), 0, new [] { "0", "Zero" } },
|
|
{ nameof(Model.RegularEnum), 1, new [] { "1", "One" } },
|
|
{ nameof(Model.RegularEnum), 43, new [] { "43" } },
|
|
{ nameof(Model.RegularEnum), -5, new [] { "-5" } },
|
|
{ nameof(Model.RegularEnum), int.MaxValue, new [] { "2147483647" } },
|
|
{ nameof(Model.RegularEnum), (uint)int.MaxValue + 1, new [] { "2147483648" } },
|
|
{ nameof(Model.RegularEnum), uint.MaxValue, new [] { "4294967295" } },
|
|
};
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(GetCurrentValues_ValueToConvertData))]
|
|
public void GetCurrentValues_ValueConvertedAsExpected(
|
|
string propertyName,
|
|
object rawValue,
|
|
IReadOnlyCollection<string> expected)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
viewContext.ModelState.SetModelValue(
|
|
propertyName,
|
|
new string[] { rawValue.ToString() },
|
|
attemptedValue: null);
|
|
|
|
// Act
|
|
var result = htmlGenerator.GetCurrentValues(
|
|
viewContext,
|
|
modelExplorer: null,
|
|
expression: propertyName,
|
|
allowMultiple: false);
|
|
|
|
// Assert
|
|
Assert.Equal<string>(expected, result);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(true, "")]
|
|
[InlineData(false, "<input name=\"formFieldName\" type=\"hidden\" value=\"requestToken\" />")]
|
|
public void GenerateAntiforgery_GeneratesAntiforgeryFieldsOnlyIfRequired(
|
|
bool hasAntiforgeryToken,
|
|
string expectedAntiforgeryHtmlField)
|
|
{
|
|
// Arrange
|
|
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
viewContext.FormContext.CanRenderAtEndOfForm = true;
|
|
viewContext.FormContext.HasAntiforgeryToken = hasAntiforgeryToken;
|
|
|
|
// Act
|
|
var result = htmlGenerator.GenerateAntiforgery(viewContext);
|
|
|
|
// Assert
|
|
var antiforgeryField = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default);
|
|
Assert.Equal(expectedAntiforgeryHtmlField, antiforgeryField);
|
|
}
|
|
|
|
// This test covers use of the helper within literal <form> tags when tag helpers are not enabled e.g.
|
|
// <form action="/Home/Create">
|
|
// @Html.AntiForgeryToken()
|
|
// </form>
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void GenerateAntiforgery_AlwaysGeneratesAntiforgeryToken_IfCannotRenderAtEnd(bool hasAntiforgeryToken)
|
|
{
|
|
// Arrange
|
|
var expected = "<input name=\"formFieldName\" type=\"hidden\" value=\"requestToken\" />";
|
|
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
|
var htmlGenerator = GetGenerator(metadataProvider);
|
|
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
|
viewContext.FormContext.HasAntiforgeryToken = hasAntiforgeryToken;
|
|
|
|
// Act
|
|
var result = htmlGenerator.GenerateAntiforgery(viewContext);
|
|
|
|
// Assert
|
|
var antiforgeryField = HtmlContentUtilities.HtmlContentToString(result, HtmlEncoder.Default);
|
|
Assert.Equal(expected, antiforgeryField);
|
|
}
|
|
|
|
// GetCurrentValues uses only the IModelMetadataProvider passed to the DefaultHtmlGenerator constructor.
|
|
private static IHtmlGenerator GetGenerator(IModelMetadataProvider metadataProvider)
|
|
{
|
|
var mvcViewOptionsAccessor = new Mock<IOptions<MvcViewOptions>>();
|
|
mvcViewOptionsAccessor.SetupGet(accessor => accessor.Value).Returns(new MvcViewOptions() { AllowRenderingMaxLengthAttribute = true });
|
|
|
|
var htmlEncoder = Mock.Of<HtmlEncoder>();
|
|
var antiforgery = new Mock<IAntiforgery>();
|
|
antiforgery
|
|
.Setup(mock => mock.GetAndStoreTokens(It.IsAny<DefaultHttpContext>()))
|
|
.Returns(() =>
|
|
{
|
|
return new AntiforgeryTokenSet("requestToken", "cookieToken", "formFieldName", "headerName");
|
|
});
|
|
|
|
var attributeProvider = new DefaultValidationHtmlAttributeProvider(
|
|
mvcViewOptionsAccessor.Object,
|
|
metadataProvider,
|
|
new ClientValidatorCache());
|
|
|
|
return new DefaultHtmlGenerator(
|
|
antiforgery.Object,
|
|
mvcViewOptionsAccessor.Object,
|
|
metadataProvider,
|
|
new UrlHelperFactory(),
|
|
htmlEncoder,
|
|
attributeProvider);
|
|
}
|
|
|
|
// GetCurrentValues uses only the ModelStateDictionary and ViewDataDictionary from the passed ViewContext.
|
|
private static ViewContext GetViewContext<TModel>(TModel model, IModelMetadataProvider metadataProvider)
|
|
{
|
|
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
|
var viewData = new ViewDataDictionary<TModel>(metadataProvider, actionContext.ModelState)
|
|
{
|
|
Model = model,
|
|
};
|
|
|
|
return new ViewContext(
|
|
actionContext,
|
|
Mock.Of<IView>(),
|
|
viewData,
|
|
Mock.Of<ITempDataDictionary>(),
|
|
TextWriter.Null,
|
|
new HtmlHelperOptions());
|
|
}
|
|
|
|
public enum RegularEnum
|
|
{
|
|
Zero,
|
|
One,
|
|
Two,
|
|
Three,
|
|
}
|
|
|
|
public enum FlagsEnum
|
|
{
|
|
None = 0,
|
|
One = 1,
|
|
Two = 2,
|
|
Four = 4,
|
|
FortyTwo = 42,
|
|
All = -1,
|
|
}
|
|
|
|
private class ModelWithMaxLengthMetadata
|
|
{
|
|
internal const int MaxLengthAttributeValue = 77;
|
|
internal const int StringLengthAttributeValue = 7;
|
|
|
|
[MaxLength(MaxLengthAttributeValue)]
|
|
public string FieldWithMaxLength { get; set; }
|
|
|
|
[StringLength(StringLengthAttributeValue)]
|
|
public string FieldWithStringLength { get; set; }
|
|
|
|
public string FieldWithoutAttributes { get; set; }
|
|
|
|
[MaxLength(MaxLengthAttributeValue)]
|
|
[StringLength(StringLengthAttributeValue)]
|
|
public string FieldWithBothAttributes { get; set; }
|
|
}
|
|
|
|
private class Model
|
|
{
|
|
public int Id { get; set; }
|
|
|
|
public string Name { get; set; }
|
|
|
|
public RegularEnum RegularEnum { get; set; }
|
|
|
|
public FlagsEnum FlagsEnum { get; set; }
|
|
|
|
public RegularEnum? NullableEnum { get; set; }
|
|
|
|
public List<string> Collection { get; } = new List<string>();
|
|
}
|
|
}
|
|
}
|