Use a prefix tree as a backing store for ModelStateDictionary
This commit is contained in:
parent
432cfa0035
commit
f651f18d3a
|
|
@ -1,31 +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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// An entry in a <see cref="ModelStateDictionary"/>.
|
||||
/// </summary>
|
||||
public class ModelStateEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the raw value from the request associated with this entry.
|
||||
/// </summary>
|
||||
public object RawValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of values contained in <see cref="RawValue"/>, joined into a comma-separated string.
|
||||
/// </summary>
|
||||
public string AttemptedValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ModelErrorCollection"/> for this entry.
|
||||
/// </summary>
|
||||
public ModelErrorCollection Errors { get; } = new ModelErrorCollection();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ModelValidationState"/> for this entry.
|
||||
/// </summary>
|
||||
public ModelValidationState ValidationState { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,9 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
|
|
@ -12,15 +14,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// Represents the state of an attempt to bind values from an HTTP Request to an action method, which includes
|
||||
/// validation information.
|
||||
/// </summary>
|
||||
public class ModelStateDictionary : IDictionary<string, ModelStateEntry>
|
||||
public class ModelStateDictionary : IReadOnlyDictionary<string, ModelStateEntry>
|
||||
{
|
||||
// Make sure to update the doc headers if this value is changed.
|
||||
/// <summary>
|
||||
/// The default value for <see cref="MaxAllowedErrors"/> of <c>200</c>.
|
||||
/// </summary>
|
||||
public static readonly int DefaultMaxAllowedErrors = 200;
|
||||
private static readonly char[] Delimiters = new char[] { '.', '[' };
|
||||
|
||||
private readonly Dictionary<string, ModelStateEntry> _innerDictionary;
|
||||
private readonly ModelStateNode _root;
|
||||
private int _maxAllowedErrors;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -37,8 +40,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public ModelStateDictionary(int maxAllowedErrors)
|
||||
{
|
||||
MaxAllowedErrors = maxAllowedErrors;
|
||||
|
||||
_innerDictionary = new Dictionary<string, ModelStateEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
var emptySegment = new StringSegment(buffer: string.Empty);
|
||||
_root = new ModelStateNode(subKey: emptySegment)
|
||||
{
|
||||
Key = string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -47,21 +53,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// </summary>
|
||||
/// <param name="dictionary">The <see cref="ModelStateDictionary"/> to copy values from.</param>
|
||||
public ModelStateDictionary(ModelStateDictionary dictionary)
|
||||
: this(dictionary?.MaxAllowedErrors ?? DefaultMaxAllowedErrors)
|
||||
{
|
||||
if (dictionary == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dictionary));
|
||||
}
|
||||
|
||||
_innerDictionary = new Dictionary<string, ModelStateEntry>(
|
||||
dictionary,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
MaxAllowedErrors = dictionary.MaxAllowedErrors;
|
||||
ErrorCount = dictionary.ErrorCount;
|
||||
HasRecordedMaxModelError = dictionary.HasRecordedMaxModelError;
|
||||
Merge(dictionary);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Root entry for the <see cref="ModelStateDictionary"/>.
|
||||
/// </summary>
|
||||
public ModelStateEntry Root => _root;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum allowed model state errors in this instance of <see cref="ModelStateDictionary"/>.
|
||||
/// Defaults to <c>200</c>.
|
||||
|
|
@ -115,28 +121,23 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public int ErrorCount { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count
|
||||
{
|
||||
get { return _innerDictionary.Count; }
|
||||
}
|
||||
public int Count { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key sequence.
|
||||
/// </summary>
|
||||
public KeyEnumerable Keys => new KeyEnumerable(this);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return ((ICollection<KeyValuePair<string, ModelStateEntry>>)_innerDictionary).IsReadOnly; }
|
||||
}
|
||||
IEnumerable<string> IReadOnlyDictionary<string, ModelStateEntry>.Keys => Keys;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value sequence.
|
||||
/// </summary>
|
||||
public ValueEnumerable Values => new ValueEnumerable(this);
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<string> Keys
|
||||
{
|
||||
get { return _innerDictionary.Keys; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<ModelStateEntry> Values
|
||||
{
|
||||
get { return _innerDictionary.Values; }
|
||||
}
|
||||
IEnumerable<ModelStateEntry> IReadOnlyDictionary<string, ModelStateEntry>.Values => Values;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether any model state values in this model state dictionary is invalid or not validated.
|
||||
|
|
@ -150,14 +151,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ModelValidationState ValidationState
|
||||
{
|
||||
get
|
||||
{
|
||||
var entries = FindKeysWithPrefix(string.Empty);
|
||||
return GetValidity(entries, defaultState: ModelValidationState.Valid);
|
||||
}
|
||||
}
|
||||
public ModelValidationState ValidationState => GetValidity(_root) ?? ModelValidationState.Valid;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ModelStateEntry this[string key]
|
||||
|
|
@ -169,29 +163,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
ModelStateEntry value;
|
||||
_innerDictionary.TryGetValue(key, out value);
|
||||
return value;
|
||||
ModelStateEntry entry;
|
||||
TryGetValue(key, out entry);
|
||||
return entry;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
_innerDictionary[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// For unit testing
|
||||
internal IDictionary<string, ModelStateEntry> InnerDictionary
|
||||
{
|
||||
get { return _innerDictionary; }
|
||||
}
|
||||
|
||||
// Flag that indiciates if TooManyModelErrorException has already been added to this dictionary.
|
||||
|
|
@ -337,8 +312,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
ErrorCount++;
|
||||
var modelState = GetModelStateForKey(key);
|
||||
var modelState = GetOrAddNode(key);
|
||||
Count += !modelState.IsContainerNode ? 0 : 1;
|
||||
modelState.ValidationState = ModelValidationState.Invalid;
|
||||
modelState.MarkNonContainerNode();
|
||||
modelState.Errors.Add(errorMessage);
|
||||
|
||||
return true;
|
||||
|
|
@ -359,8 +336,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
var entries = FindKeysWithPrefix(key);
|
||||
return GetValidity(entries, defaultState: ModelValidationState.Unvalidated);
|
||||
var item = GetNode(key);
|
||||
return GetValidity(item) ?? ModelValidationState.Unvalidated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -398,12 +375,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
var modelState = GetModelStateForKey(key);
|
||||
var modelState = GetOrAddNode(key);
|
||||
if (modelState.ValidationState == ModelValidationState.Invalid)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.Validation_InvalidFieldCannotBeReset);
|
||||
}
|
||||
|
||||
Count += !modelState.IsContainerNode ? 0 : 1;
|
||||
modelState.MarkNonContainerNode();
|
||||
modelState.ValidationState = ModelValidationState.Valid;
|
||||
}
|
||||
|
||||
|
|
@ -419,12 +398,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
var modelState = GetModelStateForKey(key);
|
||||
var modelState = GetOrAddNode(key);
|
||||
if (modelState.ValidationState == ModelValidationState.Invalid)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.Validation_InvalidFieldCannotBeReset_ToSkipped);
|
||||
}
|
||||
|
||||
Count += !modelState.IsContainerNode ? 0 : 1;
|
||||
modelState.MarkNonContainerNode();
|
||||
modelState.ValidationState = ModelValidationState.Skipped;
|
||||
}
|
||||
|
||||
|
|
@ -440,9 +421,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
return;
|
||||
}
|
||||
|
||||
foreach (var entry in dictionary)
|
||||
foreach (var source in dictionary)
|
||||
{
|
||||
this[entry.Key] = entry.Value;
|
||||
var target = GetOrAddNode(source.Key);
|
||||
Count += !target.IsContainerNode ? 0 : 1;
|
||||
ErrorCount += source.Value.Errors.Count - target.Errors.Count;
|
||||
target.Copy(source.Value);
|
||||
target.MarkNonContainerNode();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -462,9 +447,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
var modelState = GetModelStateForKey(key);
|
||||
var modelState = GetOrAddNode(key);
|
||||
Count += !modelState.IsContainerNode ? 0 : 1;
|
||||
modelState.RawValue = rawValue;
|
||||
modelState.AttemptedValue = attemptedValue;
|
||||
modelState.MarkNonContainerNode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -515,46 +502,106 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
private ModelStateEntry GetModelStateForKey(string key)
|
||||
private ModelStateNode GetNode(string key) => GetNode(key, createIfNotExists: false);
|
||||
|
||||
private ModelStateNode GetOrAddNode(string key) => GetNode(key, createIfNotExists: true);
|
||||
|
||||
private ModelStateNode GetNode(string key, bool createIfNotExists)
|
||||
{
|
||||
if (key == null)
|
||||
Debug.Assert(key != null);
|
||||
if (key.Length == 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
return _root;
|
||||
}
|
||||
|
||||
ModelStateEntry entry;
|
||||
if (!TryGetValue(key, out entry))
|
||||
// For a key of the format, foo.bar[0].baz[qux] we'll create the following nodes:
|
||||
// foo
|
||||
// -> bar
|
||||
// -> [0]
|
||||
// -> baz
|
||||
// -> [qux]
|
||||
|
||||
var current = _root;
|
||||
var previousIndex = 0;
|
||||
int index;
|
||||
while ((index = key.IndexOfAny(Delimiters, previousIndex)) != -1)
|
||||
{
|
||||
entry = new ModelStateEntry();
|
||||
this[key] = entry;
|
||||
var keyStart = previousIndex == 0 || key[previousIndex - 1] == '.'
|
||||
? previousIndex
|
||||
: previousIndex - 1;
|
||||
var subKey = new StringSegment(key, keyStart, index - keyStart);
|
||||
current = current.GetNode(subKey, createIfNotExists);
|
||||
if (current == null)
|
||||
{
|
||||
// createIfNotExists is set to false and a node wasn't found. Exit early.
|
||||
return null;
|
||||
}
|
||||
|
||||
previousIndex = index + 1;
|
||||
}
|
||||
|
||||
return entry;
|
||||
if (previousIndex < key.Length)
|
||||
{
|
||||
var keyStart = previousIndex == 0 || key[previousIndex - 1] == '.'
|
||||
? previousIndex
|
||||
: previousIndex - 1;
|
||||
var subKey = new StringSegment(key, keyStart, key.Length - keyStart);
|
||||
current = current.GetNode(subKey, createIfNotExists);
|
||||
}
|
||||
|
||||
if (current != null && current.Key == null)
|
||||
{
|
||||
// Don't update the key if it's been previously assigned. This is to prevent change in key casing
|
||||
// e.g. modelState.SetModelValue("foo", .., ..);
|
||||
// var value = modelState["FOO"];
|
||||
current.Key = key;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
private static ModelValidationState GetValidity(PrefixEnumerable entries, ModelValidationState defaultState)
|
||||
private static ModelValidationState? GetValidity(ModelStateNode node)
|
||||
{
|
||||
|
||||
var hasEntries = false;
|
||||
var validationState = ModelValidationState.Valid;
|
||||
|
||||
foreach (var entry in entries)
|
||||
if (node == null)
|
||||
{
|
||||
hasEntries = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
var entryState = entry.Value.ValidationState;
|
||||
if (entryState == ModelValidationState.Unvalidated)
|
||||
ModelValidationState? validationState = null;
|
||||
if (!node.IsContainerNode)
|
||||
{
|
||||
validationState = ModelValidationState.Valid;
|
||||
if (node.ValidationState == ModelValidationState.Unvalidated)
|
||||
{
|
||||
// If any entries of a field is unvalidated, we'll treat the tree as unvalidated.
|
||||
return entryState;
|
||||
return ModelValidationState.Unvalidated;
|
||||
}
|
||||
else if (entryState == ModelValidationState.Invalid)
|
||||
|
||||
if (node.ValidationState == ModelValidationState.Invalid)
|
||||
{
|
||||
validationState = entryState;
|
||||
validationState = node.ValidationState;
|
||||
}
|
||||
}
|
||||
|
||||
return hasEntries ? validationState : defaultState;
|
||||
if (node.ChildNodes != null)
|
||||
{
|
||||
for (var i = 0; i < node.ChildNodes.Count; i++)
|
||||
{
|
||||
var entryState = GetValidity(node.ChildNodes[i]);
|
||||
|
||||
if (entryState == ModelValidationState.Unvalidated)
|
||||
{
|
||||
return entryState;
|
||||
}
|
||||
|
||||
if (validationState == null || entryState == ModelValidationState.Invalid)
|
||||
{
|
||||
validationState = entryState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return validationState;
|
||||
}
|
||||
|
||||
private void EnsureMaxErrorsReachedRecorded()
|
||||
|
|
@ -570,43 +617,23 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
private void AddModelErrorCore(string key, Exception exception)
|
||||
{
|
||||
var modelState = GetModelStateForKey(key);
|
||||
var modelState = GetOrAddNode(key);
|
||||
Count += !modelState.IsContainerNode ? 0 : 1;
|
||||
modelState.ValidationState = ModelValidationState.Invalid;
|
||||
modelState.MarkNonContainerNode();
|
||||
modelState.Errors.Add(exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(KeyValuePair<string, ModelStateEntry> item)
|
||||
{
|
||||
Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(string key, ModelStateEntry value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_innerDictionary.Add(key, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Removes all keys and values from ths instance of <see cref="ModelStateDictionary"/>.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_innerDictionary.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Contains(KeyValuePair<string, ModelStateEntry> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<string, ModelStateEntry>>)_innerDictionary).Contains(item);
|
||||
Count = 0;
|
||||
HasRecordedMaxModelError = false;
|
||||
ErrorCount = 0;
|
||||
_root.Reset();
|
||||
_root.ChildNodes.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -617,27 +644,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
return _innerDictionary.ContainsKey(key);
|
||||
return !GetNode(key)?.IsContainerNode ?? false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(KeyValuePair<string, ModelStateEntry>[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
((ICollection<KeyValuePair<string, ModelStateEntry>>)_innerDictionary).CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(KeyValuePair<string, ModelStateEntry> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<string, ModelStateEntry>>)_innerDictionary).Remove(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Removes the <see cref="ModelStateEntry"/> with the specified <paramref name="key"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns><c>true</c> if the element is successfully removed; otherwise <c>false</c>. This method also
|
||||
/// returns <c>false</c> if key was not found.</returns>
|
||||
public bool Remove(string key)
|
||||
{
|
||||
if (key == null)
|
||||
|
|
@ -645,7 +660,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
return _innerDictionary.Remove(key);
|
||||
var node = GetNode(key);
|
||||
if (node?.IsContainerNode == false)
|
||||
{
|
||||
Count--;
|
||||
ErrorCount -= node.Errors.Count;
|
||||
node.Reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -656,20 +680,29 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
return _innerDictionary.TryGetValue(key, out value);
|
||||
var result = GetNode(key);
|
||||
if (result?.IsContainerNode == false)
|
||||
{
|
||||
value = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<KeyValuePair<string, ModelStateEntry>> GetEnumerator()
|
||||
{
|
||||
return _innerDictionary.GetEnumerator();
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through this instance of <see cref="ModelStateDictionary"/>.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="Enumerator"/>.</returns>
|
||||
public Enumerator GetEnumerator() => new Enumerator(this, prefix: string.Empty);
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
IEnumerator<KeyValuePair<string, ModelStateEntry>>
|
||||
IEnumerable<KeyValuePair<string, ModelStateEntry>>.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public static bool StartsWithPrefix(string prefix, string key)
|
||||
{
|
||||
|
|
@ -698,7 +731,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (key.Length == prefix.Length)
|
||||
{
|
||||
// Exact match
|
||||
|
|
@ -724,6 +757,126 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
return new PrefixEnumerable(this, prefix);
|
||||
}
|
||||
|
||||
[DebuggerDisplay("SubKey={SubKey}, Key={Key}, ValidationState={ValidationState}")]
|
||||
private class ModelStateNode : ModelStateEntry
|
||||
{
|
||||
private bool _isContainerNode = true;
|
||||
|
||||
public ModelStateNode(StringSegment subKey)
|
||||
{
|
||||
SubKey = subKey;
|
||||
}
|
||||
|
||||
public List<ModelStateNode> ChildNodes { get; set; }
|
||||
|
||||
public override IReadOnlyList<ModelStateEntry> Children => ChildNodes;
|
||||
|
||||
public string Key { get; set; }
|
||||
|
||||
public StringSegment SubKey { get; }
|
||||
|
||||
public override bool IsContainerNode => _isContainerNode;
|
||||
|
||||
public void MarkNonContainerNode()
|
||||
{
|
||||
_isContainerNode = false;
|
||||
}
|
||||
|
||||
public void Copy(ModelStateEntry entry)
|
||||
{
|
||||
RawValue = entry.RawValue;
|
||||
AttemptedValue = entry.AttemptedValue;
|
||||
Errors.Clear();
|
||||
for (var i = 0; i < entry.Errors.Count; i++)
|
||||
{
|
||||
Errors.Add(entry.Errors[i]);
|
||||
}
|
||||
|
||||
ValidationState = entry.ValidationState;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_isContainerNode = true;
|
||||
RawValue = null;
|
||||
AttemptedValue = null;
|
||||
ValidationState = ModelValidationState.Unvalidated;
|
||||
Errors.Clear();
|
||||
}
|
||||
|
||||
public ModelStateNode GetNode(StringSegment subKey, bool createIfNotExists)
|
||||
{
|
||||
if (subKey.Length == 0)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var index = BinarySearch(subKey);
|
||||
ModelStateNode modelStateNode = null;
|
||||
if (index >= 0)
|
||||
{
|
||||
modelStateNode = ChildNodes[index];
|
||||
}
|
||||
else if (createIfNotExists)
|
||||
{
|
||||
if (ChildNodes == null)
|
||||
{
|
||||
ChildNodes = new List<ModelStateNode>(1);
|
||||
}
|
||||
|
||||
modelStateNode = new ModelStateNode(subKey);
|
||||
ChildNodes.Insert(~index, modelStateNode);
|
||||
}
|
||||
|
||||
return modelStateNode;
|
||||
}
|
||||
|
||||
public override ModelStateEntry GetModelStateForProperty(string propertyName)
|
||||
=> GetNode(new StringSegment(propertyName), createIfNotExists: false);
|
||||
|
||||
private int BinarySearch(StringSegment searchKey)
|
||||
{
|
||||
if (ChildNodes == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var low = 0;
|
||||
var high = ChildNodes.Count - 1;
|
||||
while (low <= high)
|
||||
{
|
||||
var mid = low + ((high - low) / 2);
|
||||
var midKey = ChildNodes[mid].SubKey;
|
||||
var result = midKey.Length - searchKey.Length;
|
||||
if (result == 0)
|
||||
{
|
||||
result = string.Compare(
|
||||
midKey.Buffer,
|
||||
midKey.Offset,
|
||||
searchKey.Buffer,
|
||||
searchKey.Offset,
|
||||
searchKey.Length,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
return mid;
|
||||
}
|
||||
if (result < 0)
|
||||
{
|
||||
low = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
high = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ~low;
|
||||
}
|
||||
}
|
||||
|
||||
public struct PrefixEnumerable : IEnumerable<KeyValuePair<string, ModelStateEntry>>
|
||||
{
|
||||
private readonly ModelStateDictionary _dictionary;
|
||||
|
|
@ -745,32 +898,23 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
_prefix = prefix;
|
||||
}
|
||||
|
||||
public PrefixEnumerator GetEnumerator()
|
||||
{
|
||||
return _dictionary == null ? new PrefixEnumerator() : new PrefixEnumerator(_dictionary, _prefix);
|
||||
}
|
||||
public Enumerator GetEnumerator() => new Enumerator(_dictionary, _prefix);
|
||||
|
||||
IEnumerator<KeyValuePair<string, ModelStateEntry>>
|
||||
IEnumerable<KeyValuePair<string, ModelStateEntry>>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
IEnumerable<KeyValuePair<string, ModelStateEntry>>.GetEnumerator() => GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
public struct PrefixEnumerator : IEnumerator<KeyValuePair<string, ModelStateEntry>>
|
||||
public struct Enumerator : IEnumerator<KeyValuePair<string, ModelStateEntry>>
|
||||
{
|
||||
private readonly ModelStateDictionary _dictionary;
|
||||
private string _prefix;
|
||||
private readonly ModelStateNode _rootNode;
|
||||
private ModelStateNode _modelStateNode;
|
||||
private List<ModelStateNode> _nodes;
|
||||
private int _index;
|
||||
private bool _visitedRoot;
|
||||
|
||||
private ExactMatchState _exactMatch;
|
||||
private Dictionary<string, ModelStateEntry>.Enumerator _enumerator;
|
||||
|
||||
public PrefixEnumerator(ModelStateDictionary dictionary, string prefix)
|
||||
public Enumerator(ModelStateDictionary dictionary, string prefix)
|
||||
{
|
||||
if (dictionary == null)
|
||||
{
|
||||
|
|
@ -782,23 +926,17 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(prefix));
|
||||
}
|
||||
|
||||
_dictionary = dictionary;
|
||||
_prefix = prefix;
|
||||
|
||||
_exactMatch = ExactMatchState.NotChecked;
|
||||
_enumerator = default(Dictionary<string, ModelStateEntry>.Enumerator);
|
||||
Current = default(KeyValuePair<string, ModelStateEntry>);
|
||||
_index = -1;
|
||||
_rootNode = dictionary.GetNode(prefix);
|
||||
_modelStateNode = null;
|
||||
_nodes = null;
|
||||
_visitedRoot = false;
|
||||
}
|
||||
|
||||
public KeyValuePair<string, ModelStateEntry> Current { get; private set; }
|
||||
public KeyValuePair<string, ModelStateEntry> Current =>
|
||||
new KeyValuePair<string, ModelStateEntry>(_modelStateNode.Key, _modelStateNode);
|
||||
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
return Current;
|
||||
}
|
||||
}
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
@ -806,61 +944,56 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_dictionary == null)
|
||||
if (_rootNode == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// ModelStateDictionary has a behavior where the first 'match' returned from iterating
|
||||
// prefixes is the exact match for the prefix (if present). Only after looking for an
|
||||
// exact match do we fall back to iteration to find 'starts-with' matches.
|
||||
if (_exactMatch == ExactMatchState.NotChecked)
|
||||
if (!_visitedRoot)
|
||||
{
|
||||
_enumerator = _dictionary._innerDictionary.GetEnumerator();
|
||||
|
||||
ModelStateEntry entry;
|
||||
if (_dictionary.TryGetValue(_prefix, out entry))
|
||||
// Visit the root node
|
||||
_visitedRoot = true;
|
||||
if (_rootNode.ChildNodes?.Count > 0)
|
||||
{
|
||||
// Mark exact match as found
|
||||
_exactMatch = ExactMatchState.Found;
|
||||
Current = new KeyValuePair<string, ModelStateEntry>(_prefix, entry);
|
||||
return true;
|
||||
_nodes = new List<ModelStateNode> { _rootNode };
|
||||
}
|
||||
else
|
||||
|
||||
if (!_rootNode.IsContainerNode)
|
||||
{
|
||||
// Mark exact match tested for
|
||||
_exactMatch = ExactMatchState.NotFound;
|
||||
_modelStateNode = _rootNode;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
while (_enumerator.MoveNext())
|
||||
if (_nodes == null)
|
||||
{
|
||||
var key = _enumerator.Current.Key;
|
||||
if (_exactMatch == ExactMatchState.NotFound)
|
||||
return false;
|
||||
}
|
||||
|
||||
while (_nodes.Count > 0)
|
||||
{
|
||||
var node = _nodes[0];
|
||||
if (_index == node.ChildNodes.Count - 1)
|
||||
{
|
||||
if (StartsWithPrefix(_prefix, key))
|
||||
{
|
||||
Current = _enumerator.Current;
|
||||
return true;
|
||||
}
|
||||
// We've exhausted the current sublist.
|
||||
_nodes.RemoveAt(0);
|
||||
_index = -1;
|
||||
continue;
|
||||
}
|
||||
else if (_exactMatch == ExactMatchState.ReferenceSet && Object.ReferenceEquals(_prefix, key))
|
||||
else
|
||||
{
|
||||
// Fast path skip this one. Is the exact string reference set below.
|
||||
_index++;
|
||||
}
|
||||
else if (_exactMatch == ExactMatchState.Found &&
|
||||
string.Equals(_prefix, key, StringComparison.OrdinalIgnoreCase))
|
||||
|
||||
var currentChild = node.ChildNodes[_index];
|
||||
if (currentChild.ChildNodes?.Count > 0)
|
||||
{
|
||||
// Update _prefix to be this exact string reference to enable fast path
|
||||
_prefix = key;
|
||||
// Mark exact reference set
|
||||
_exactMatch = ExactMatchState.ReferenceSet;
|
||||
// Skip this one. We've already handled the 'exact match' case.
|
||||
_nodes.Add(currentChild);
|
||||
}
|
||||
else if (StartsWithPrefix(_prefix, key))
|
||||
|
||||
if (!currentChild.IsContainerNode)
|
||||
{
|
||||
Current = _enumerator.Current;
|
||||
_modelStateNode = currentChild;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -870,17 +1003,120 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
|
||||
public void Reset()
|
||||
{
|
||||
_exactMatch = ExactMatchState.NotChecked;
|
||||
_enumerator = default(Dictionary<string, ModelStateEntry>.Enumerator);
|
||||
Current = default(KeyValuePair<string, ModelStateEntry>);
|
||||
_index = -1;
|
||||
_nodes.Clear();
|
||||
_visitedRoot = false;
|
||||
_modelStateNode = null;
|
||||
}
|
||||
}
|
||||
|
||||
public struct KeyEnumerable : IEnumerable<string>
|
||||
{
|
||||
private readonly ModelStateDictionary _dictionary;
|
||||
|
||||
public KeyEnumerable(ModelStateDictionary dictionary)
|
||||
{
|
||||
_dictionary = dictionary;
|
||||
}
|
||||
|
||||
private enum ExactMatchState
|
||||
public KeyEnumerator GetEnumerator() => new KeyEnumerator(_dictionary, prefix: string.Empty);
|
||||
|
||||
IEnumerator<string> IEnumerable<string>.GetEnumerator() => GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
public struct KeyEnumerator : IEnumerator<string>
|
||||
{
|
||||
private Enumerator _prefixEnumerator;
|
||||
|
||||
public KeyEnumerator(ModelStateDictionary dictionary, string prefix)
|
||||
{
|
||||
NotChecked,
|
||||
NotFound,
|
||||
Found,
|
||||
ReferenceSet
|
||||
_prefixEnumerator = new Enumerator(dictionary, prefix);
|
||||
Current = null;
|
||||
}
|
||||
|
||||
public string Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose() => _prefixEnumerator.Dispose();
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
var result = _prefixEnumerator.MoveNext();
|
||||
if (result)
|
||||
{
|
||||
var current = _prefixEnumerator.Current;
|
||||
Current = current.Key;
|
||||
}
|
||||
else
|
||||
{
|
||||
Current = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_prefixEnumerator.Reset();
|
||||
Current = null;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ValueEnumerable : IEnumerable<ModelStateEntry>
|
||||
{
|
||||
private readonly ModelStateDictionary _dictionary;
|
||||
|
||||
public ValueEnumerable(ModelStateDictionary dictionary)
|
||||
{
|
||||
_dictionary = dictionary;
|
||||
}
|
||||
|
||||
public ValueEnumerator GetEnumerator() => new ValueEnumerator(_dictionary, prefix: string.Empty);
|
||||
|
||||
IEnumerator<ModelStateEntry> IEnumerable<ModelStateEntry>.GetEnumerator() => GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
public struct ValueEnumerator : IEnumerator<ModelStateEntry>
|
||||
{
|
||||
private Enumerator _prefixEnumerator;
|
||||
|
||||
public ValueEnumerator(ModelStateDictionary dictionary, string prefix)
|
||||
{
|
||||
_prefixEnumerator = new Enumerator(dictionary, prefix);
|
||||
Current = null;
|
||||
}
|
||||
|
||||
public ModelStateEntry Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose() => _prefixEnumerator.Dispose();
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
var result = _prefixEnumerator.MoveNext();
|
||||
if (result)
|
||||
{
|
||||
var current = _prefixEnumerator.Current;
|
||||
Current = current.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Current = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_prefixEnumerator.Reset();
|
||||
Current = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
// 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.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// An entry in a <see cref="ModelStateDictionary"/>.
|
||||
/// </summary>
|
||||
public abstract class ModelStateEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the raw value from the request associated with this entry.
|
||||
/// </summary>
|
||||
public object RawValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of values contained in <see cref="RawValue"/>, joined into a comma-separated string.
|
||||
/// </summary>
|
||||
public string AttemptedValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ModelErrorCollection"/> for this entry.
|
||||
/// </summary>
|
||||
public ModelErrorCollection Errors { get; } = new ModelErrorCollection();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ModelValidationState"/> for this entry.
|
||||
/// </summary>
|
||||
public ModelValidationState ValidationState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that determines if the current instance of <see cref="ModelStateEntry"/> is a container node.
|
||||
/// Container nodes represent prefix nodes that aren't explicitly added to the
|
||||
/// <see cref="ModelStateDictionary"/>.
|
||||
/// </summary>
|
||||
public abstract bool IsContainerNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ModelStateEntry"/> for a sub-property with the specified <paramref name="propertyName"/>.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The property name to lookup.</param>
|
||||
/// <returns>The <see cref="ModelStateEntry"/> if a sub-property was found; otherwise <c>null</c>.</returns>
|
||||
/// <remarks>This method returns any existing entry, even those with <see cref="IsContainerNode"/> with value <c>true</c>..</remarks>
|
||||
public abstract ModelStateEntry GetModelStateForProperty(string propertyName);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ModelStateEntry"/> values for sub-properties.
|
||||
/// </summary>
|
||||
/// <remarks>This method returns all existing entries, even those with <see cref="IsContainerNode"/> with value <c>true</c>.</remarks>
|
||||
public abstract IReadOnlyList<ModelStateEntry> Children { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
|
|
@ -11,6 +10,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
{
|
||||
public static class ValidationHelpers
|
||||
{
|
||||
private static readonly ModelStateEntry[] EmptyModelStateEntries = new ModelStateEntry[0];
|
||||
|
||||
public static string GetModelErrorMessageOrDefault(ModelError modelError)
|
||||
{
|
||||
Debug.Assert(modelError != null);
|
||||
|
|
@ -44,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
}
|
||||
|
||||
// Returns non-null list of model states, which caller will render in order provided.
|
||||
public static IEnumerable<ModelStateEntry> GetModelStateList(
|
||||
public static IList<ModelStateEntry> GetModelStateList(
|
||||
ViewDataDictionary viewData,
|
||||
bool excludePropertyErrors)
|
||||
{
|
||||
|
|
@ -57,49 +58,59 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
{
|
||||
return new[] { ms };
|
||||
}
|
||||
|
||||
return Enumerable.Empty<ModelStateEntry>();
|
||||
}
|
||||
else
|
||||
else if (viewData.ModelState.Count > 0)
|
||||
{
|
||||
var metadata = viewData.ModelMetadata;
|
||||
var orderer = new ErrorsOrderer(metadata);
|
||||
var modelStateDictionary = viewData.ModelState;
|
||||
var entries = new List<ModelStateEntry>();
|
||||
Visit(modelStateDictionary, modelStateDictionary.Root, metadata, entries);
|
||||
|
||||
return viewData.ModelState
|
||||
.OrderBy(data => orderer.GetOrder(data.Key))
|
||||
.Select(ms => ms.Value);
|
||||
if (entries.Count < modelStateDictionary.Count)
|
||||
{
|
||||
// Account for entries in the ModelStateDictionary that do not have corresponding ModelMetadata values.
|
||||
foreach (var entry in modelStateDictionary)
|
||||
{
|
||||
if (!entries.Contains(entry.Value))
|
||||
{
|
||||
entries.Add(entry.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
return EmptyModelStateEntries;
|
||||
}
|
||||
|
||||
// Helper for sorting modelStates to respect the ordering in the metadata.
|
||||
// ModelState doesn't refer to ModelMetadata, but we can correlate via the property name.
|
||||
private class ErrorsOrderer
|
||||
private static void Visit(
|
||||
ModelStateDictionary dictionary,
|
||||
ModelStateEntry modelStateEntry,
|
||||
ModelMetadata metadata,
|
||||
List<ModelStateEntry> orderedModelStateEntries)
|
||||
{
|
||||
private readonly Dictionary<string, int> _ordering =
|
||||
new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public ErrorsOrderer(ModelMetadata metadata)
|
||||
if (metadata.ElementMetadata != null)
|
||||
{
|
||||
if (metadata == null)
|
||||
foreach (var indexEntry in modelStateEntry.Children)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(metadata));
|
||||
}
|
||||
|
||||
foreach (var data in metadata.Properties)
|
||||
{
|
||||
_ordering[data.PropertyName] = data.Order;
|
||||
Visit(dictionary, indexEntry, metadata.ElementMetadata, orderedModelStateEntries);
|
||||
}
|
||||
}
|
||||
|
||||
public int GetOrder(string key)
|
||||
for (var i = 0; i < metadata.Properties.Count; i++)
|
||||
{
|
||||
int value;
|
||||
if (_ordering.TryGetValue(key, out value))
|
||||
var propertyMetadata = metadata.Properties[i];
|
||||
var propertyModelStateEntry = modelStateEntry.GetModelStateForProperty(propertyMetadata.PropertyName);
|
||||
if (propertyModelStateEntry != null)
|
||||
{
|
||||
return value;
|
||||
Visit(dictionary, propertyModelStateEntry, propertyMetadata, orderedModelStateEntries);
|
||||
}
|
||||
}
|
||||
|
||||
return ModelMetadata.DefaultOrder;
|
||||
if (!modelStateEntry.IsContainerNode)
|
||||
{
|
||||
orderedModelStateEntries.Add(modelStateEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// 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.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Xunit;
|
||||
|
|
@ -12,20 +12,139 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public class ModelStateDictionaryTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(ModelValidationState.Valid)]
|
||||
[InlineData(ModelValidationState.Unvalidated)]
|
||||
public void MarkFieldSkipped_MarksFieldAsSkipped_IfStateIsNotInValid(ModelValidationState validationState)
|
||||
[InlineData("")]
|
||||
[InlineData("foo")]
|
||||
public void ContainsKey_ReturnsFalse_IfNodeHasNotBeenMutated(string key)
|
||||
{
|
||||
// Arrange
|
||||
var entry = new ModelStateEntry
|
||||
{
|
||||
ValidationState = validationState
|
||||
};
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.AddModelError("foo.bar", "some error");
|
||||
|
||||
var source = new ModelStateDictionary
|
||||
{
|
||||
{ "key", entry }
|
||||
};
|
||||
// Act
|
||||
var result = dictionary.ContainsKey(key);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("foo")]
|
||||
public void ContainsKey_ReturnsFalse_IfNodeHasBeenRemoved(string key)
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.AddModelError(key, "some error");
|
||||
|
||||
// Act
|
||||
var remove = dictionary.Remove(key);
|
||||
var containsKey = dictionary.ContainsKey(key);
|
||||
|
||||
// Assert
|
||||
Assert.True(remove);
|
||||
Assert.False(containsKey);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("foo")]
|
||||
[InlineData("foo.bar")]
|
||||
[InlineData("foo[bar]")]
|
||||
public void ContainsKey_ReturnsTrue_IfNodeHasBeenMutated(string key)
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.MarkFieldSkipped(key);
|
||||
|
||||
// Act
|
||||
var result = dictionary.ContainsKey(key);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("foo")]
|
||||
[InlineData("foo.bar")]
|
||||
[InlineData("foo.bar[10]")]
|
||||
public void IndexerDoesNotReturnIntermediaryNodes(string key)
|
||||
{
|
||||
// Arrange
|
||||
var modelStateDictionary = new ModelStateDictionary();
|
||||
modelStateDictionary.AddModelError("foo.bar[10].baz", "error-message");
|
||||
|
||||
// Act
|
||||
var result = modelStateDictionary[key];
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("prop")]
|
||||
[InlineData("first.second")]
|
||||
[InlineData("[0].prop")]
|
||||
[InlineData("[qux]")]
|
||||
[InlineData("[test].prop")]
|
||||
[InlineData("first[0].second")]
|
||||
[InlineData("first.second[0].third")]
|
||||
[InlineData("first[second][0]")]
|
||||
[InlineData("first.second.third[0]")]
|
||||
[InlineData("first.second.third[0].fourth")]
|
||||
[InlineData("first[0][second]")]
|
||||
public void Indexer_ReturnsValuesAddedUsingSetModelValue(string key)
|
||||
{
|
||||
// Arrange
|
||||
var value = "Hello world";
|
||||
var modelStateDictionary = new ModelStateDictionary();
|
||||
modelStateDictionary.SetModelValue(key, value, value);
|
||||
|
||||
// Act
|
||||
var result = modelStateDictionary[key];
|
||||
|
||||
// Assert
|
||||
Assert.Equal(value, result.RawValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Clear_RemovesAllEntries()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.AddModelError("a", "a-error");
|
||||
dictionary.AddModelError("b", "b-error");
|
||||
dictionary.AddModelError("c", "c-error");
|
||||
|
||||
// Act
|
||||
dictionary.Clear();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, dictionary.Count);
|
||||
Assert.Equal(0, dictionary.ErrorCount);
|
||||
Assert.Empty(dictionary);
|
||||
Assert.Equal(ModelValidationState.Valid, dictionary.ValidationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MarkFieldSkipped_MarksFieldAsSkipped_IfStateIsUnvalidated()
|
||||
{
|
||||
// Arrange
|
||||
var source = new ModelStateDictionary();
|
||||
source.SetModelValue("key", "value", "value");
|
||||
|
||||
// Act
|
||||
source.MarkFieldSkipped("key");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ModelValidationState.Skipped, source["key"].ValidationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MarkFieldSkipped_MarksFieldAsSkipped_IfStateIsValid()
|
||||
{
|
||||
// Arrange
|
||||
var source = new ModelStateDictionary();
|
||||
source.MarkFieldValid("key");
|
||||
|
||||
// Act
|
||||
source.MarkFieldSkipped("key");
|
||||
|
|
@ -38,11 +157,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void MarkFieldSkipped_MarksFieldAsSkipped_IfKeyIsNotPresent()
|
||||
{
|
||||
// Arrange
|
||||
var entry = new ModelStateEntry
|
||||
{
|
||||
ValidationState = ModelValidationState.Valid
|
||||
};
|
||||
|
||||
var source = new ModelStateDictionary();
|
||||
|
||||
// Act
|
||||
|
|
@ -58,15 +172,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void MarkFieldSkipped_Throws_IfStateIsInvalid()
|
||||
{
|
||||
// Arrange
|
||||
var entry = new ModelStateEntry
|
||||
{
|
||||
ValidationState = ModelValidationState.Invalid
|
||||
};
|
||||
|
||||
var source = new ModelStateDictionary
|
||||
{
|
||||
{ "key", entry }
|
||||
};
|
||||
var source = new ModelStateDictionary();
|
||||
source.AddModelError("key", "some error");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => source.MarkFieldSkipped("key"));
|
||||
|
|
@ -77,21 +184,26 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ModelValidationState.Skipped)]
|
||||
[InlineData(ModelValidationState.Unvalidated)]
|
||||
public void MarkFieldValid_MarksFieldAsValid_IfStateIsNotInvalid(ModelValidationState validationState)
|
||||
[Fact]
|
||||
public void MarkFieldValid_MarksFieldAsValid_IfStateIsUnvalidated()
|
||||
{
|
||||
// Arrange
|
||||
var entry = new ModelStateEntry
|
||||
{
|
||||
ValidationState = validationState
|
||||
};
|
||||
var source = new ModelStateDictionary();
|
||||
source.SetModelValue("key", "value", "value");
|
||||
|
||||
var source = new ModelStateDictionary
|
||||
{
|
||||
{ "key", entry }
|
||||
};
|
||||
// Act
|
||||
source.MarkFieldValid("key");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ModelValidationState.Valid, source["key"].ValidationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MarkFieldValid_MarksFieldAsValid_IfStateIsSkipped()
|
||||
{
|
||||
// Arrange
|
||||
var source = new ModelStateDictionary();
|
||||
source.MarkFieldSkipped("key");
|
||||
|
||||
// Act
|
||||
source.MarkFieldValid("key");
|
||||
|
|
@ -119,15 +231,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void MarkFieldValid_Throws_IfStateIsInvalid()
|
||||
{
|
||||
// Arrange
|
||||
var entry = new ModelStateEntry
|
||||
{
|
||||
ValidationState = ModelValidationState.Invalid
|
||||
};
|
||||
|
||||
var source = new ModelStateDictionary
|
||||
{
|
||||
{ "key", entry }
|
||||
};
|
||||
var source = new ModelStateDictionary();
|
||||
source.AddModelError("key", "some-error");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => source.MarkFieldValid("key"));
|
||||
|
|
@ -142,20 +247,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void CopyConstructor_CopiesModelStateData()
|
||||
{
|
||||
// Arrange
|
||||
var entry = new ModelStateEntry();
|
||||
var source = new ModelStateDictionary
|
||||
{
|
||||
{ "key", entry }
|
||||
};
|
||||
|
||||
var source = new ModelStateDictionary();
|
||||
source.SetModelValue("key", "attempted-value", "raw-value");
|
||||
var entry = source["key"];
|
||||
entry.AttemptedValue = "attempted-value";
|
||||
entry.RawValue = "raw-value";
|
||||
entry.Errors.Add(new ModelError(new InvalidOperationException()));
|
||||
entry.Errors.Add(new ModelError("error-message"));
|
||||
entry.ValidationState = ModelValidationState.Skipped;
|
||||
// Act
|
||||
var target = new ModelStateDictionary(source);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, target.ErrorCount);
|
||||
Assert.Equal(2, target.ErrorCount);
|
||||
Assert.Equal(1, target.Count);
|
||||
Assert.Same(entry, target["key"]);
|
||||
Assert.IsType<Dictionary<string, ModelStateEntry>>(target.InnerDictionary);
|
||||
var actual = target["key"];
|
||||
Assert.Equal(entry.RawValue, actual.RawValue);
|
||||
Assert.Equal(entry.AttemptedValue, actual.AttemptedValue);
|
||||
Assert.Equal(entry.Errors, actual.Errors);
|
||||
Assert.Equal(entry.ValidationState, actual.ValidationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -202,10 +312,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void ConstructorWithDictionaryParameter()
|
||||
{
|
||||
// Arrange
|
||||
var oldDictionary = new ModelStateDictionary()
|
||||
{
|
||||
{ "foo", new ModelStateEntry() { RawValue = "bar" } }
|
||||
};
|
||||
var oldDictionary = new ModelStateDictionary();
|
||||
oldDictionary.SetModelValue("foo", "bar", "bar");
|
||||
|
||||
// Act
|
||||
var newDictionary = new ModelStateDictionary(oldDictionary);
|
||||
|
|
@ -266,14 +374,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void GetFieldValidationState_ReturnsValidIfModelStateDoesNotContainErrors(string key)
|
||||
{
|
||||
// Arrange
|
||||
var validState = new ModelStateEntry
|
||||
{
|
||||
ValidationState = ModelValidationState.Valid
|
||||
};
|
||||
var dictionary = new ModelStateDictionary
|
||||
{
|
||||
{ key, validState }
|
||||
};
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.MarkFieldValid(key);
|
||||
|
||||
// Act
|
||||
var validationState = dictionary.GetFieldValidationState("foo");
|
||||
|
|
@ -304,14 +406,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void GetFieldValidationState_IndexedPrefix_ReturnsValidIfModelStateDoesNotContainErrors(string key)
|
||||
{
|
||||
// Arrange
|
||||
var validState = new ModelStateEntry
|
||||
{
|
||||
ValidationState = ModelValidationState.Valid
|
||||
};
|
||||
var dictionary = new ModelStateDictionary
|
||||
{
|
||||
{ key, validState }
|
||||
};
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.MarkFieldValid(key);
|
||||
|
||||
// Act
|
||||
var validationState = dictionary.GetFieldValidationState("[0].foo");
|
||||
|
|
@ -324,20 +420,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void IsValidPropertyReturnsFalseIfErrors()
|
||||
{
|
||||
// Arrange
|
||||
var errorState = new ModelStateEntry
|
||||
{
|
||||
ValidationState = ModelValidationState.Invalid
|
||||
};
|
||||
var validState = new ModelStateEntry
|
||||
{
|
||||
ValidationState = ModelValidationState.Valid
|
||||
};
|
||||
errorState.Errors.Add("some error");
|
||||
var dictionary = new ModelStateDictionary()
|
||||
{
|
||||
{ "foo", validState },
|
||||
{ "baz", errorState }
|
||||
};
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.MarkFieldValid("foo");
|
||||
dictionary.AddModelError("bar", "some error");
|
||||
|
||||
// Act
|
||||
var isValid = dictionary.IsValid;
|
||||
|
|
@ -352,19 +437,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void IsValidPropertyReturnsTrueIfNoErrors()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary()
|
||||
{
|
||||
{ "foo", new ModelStateEntry
|
||||
{
|
||||
ValidationState = ModelValidationState.Valid,
|
||||
}
|
||||
},
|
||||
{ "baz", new ModelStateEntry
|
||||
{
|
||||
ValidationState = ModelValidationState.Skipped,
|
||||
}
|
||||
}
|
||||
};
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.MarkFieldValid("foo");
|
||||
dictionary.MarkFieldSkipped("bar");
|
||||
|
||||
// Act
|
||||
var isValid = dictionary.IsValid;
|
||||
|
|
@ -379,21 +454,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void IsValidPropertyReturnsFalse_IfSomeFieldsAreNotValidated()
|
||||
{
|
||||
// Arrange
|
||||
var errorState = new ModelStateEntry
|
||||
{
|
||||
ValidationState = ModelValidationState.Invalid
|
||||
};
|
||||
var validState = new ModelStateEntry
|
||||
{
|
||||
ValidationState = ModelValidationState.Valid
|
||||
};
|
||||
errorState.Errors.Add("some error");
|
||||
var dictionary = new ModelStateDictionary()
|
||||
{
|
||||
{ "foo", validState },
|
||||
{ "baz", errorState },
|
||||
{ "qux", new ModelStateEntry() }
|
||||
};
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.MarkFieldValid("foo");
|
||||
dictionary.SetModelValue("qux", "value", "value");
|
||||
dictionary.AddModelError("baz", "some error");
|
||||
|
||||
// Act
|
||||
var isValid = dictionary.IsValid;
|
||||
|
|
@ -408,22 +472,70 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void MergeCopiesDictionaryEntries()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary1 = new ModelStateDictionary { { "foo", new ModelStateEntry() } };
|
||||
var dictionary2 = new ModelStateDictionary { { "bar", new ModelStateEntry() } };
|
||||
var dictionary1 = new ModelStateDictionary();
|
||||
dictionary1.SetModelValue("foo", "RawValue1", "AttemptedValue1");
|
||||
dictionary1.AddModelError("foo", "value1-Error1");
|
||||
dictionary1.AddModelError("foo", "value1-Error2");
|
||||
|
||||
var dictionary2 = new ModelStateDictionary();
|
||||
dictionary2.SetModelValue("bar", "RawValue2", "AttemptedValue2");
|
||||
dictionary2.AddModelError("bar", "value2-Error1");
|
||||
|
||||
// Act
|
||||
dictionary1.Merge(dictionary2);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, dictionary1.Count);
|
||||
Assert.Equal(dictionary2["bar"], dictionary1["bar"]);
|
||||
var item = dictionary1["foo"];
|
||||
Assert.Equal("AttemptedValue1", item.AttemptedValue);
|
||||
Assert.Equal("RawValue1", item.RawValue);
|
||||
|
||||
item = dictionary1["bar"];
|
||||
Assert.Equal("AttemptedValue2", item.AttemptedValue);
|
||||
Assert.Equal("RawValue2", item.RawValue);
|
||||
Assert.Collection(item.Errors,
|
||||
error => Assert.Equal("value2-Error1", error.ErrorMessage));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("key1")]
|
||||
public void MergeCopiesDictionaryOverwritesExistingValues(string key)
|
||||
{
|
||||
// Arrange
|
||||
var dictionary1 = new ModelStateDictionary();
|
||||
dictionary1.SetModelValue(key, "RawValue1", "AttemptedValue1");
|
||||
dictionary1.AddModelError(key, "value1-Error1");
|
||||
dictionary1.AddModelError(key, "value1-Error2");
|
||||
dictionary1.SetModelValue("other-key", null, null);
|
||||
|
||||
var dictionary2 = new ModelStateDictionary();
|
||||
dictionary2.SetModelValue(key, "RawValue2", "AttemptedValue2");
|
||||
dictionary2.AddModelError(key, "value2-Error1");
|
||||
|
||||
// Act
|
||||
dictionary1.Merge(dictionary2);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, dictionary1.Count);
|
||||
var item = dictionary1["other-key"];
|
||||
Assert.Null(item.AttemptedValue);
|
||||
Assert.Null(item.RawValue);
|
||||
Assert.Empty(item.Errors);
|
||||
|
||||
item = dictionary1[key];
|
||||
Assert.Equal("AttemptedValue2", item.AttemptedValue);
|
||||
Assert.Equal("RawValue2", item.RawValue);
|
||||
Assert.Collection(item.Errors,
|
||||
error => Assert.Equal("value2-Error1", error.ErrorMessage));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergeDoesNothingIfParameterIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary() { { "foo", new ModelStateEntry() } };
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.SetModelValue("foo", "value", "value");
|
||||
|
||||
// Act
|
||||
dictionary.Merge(null);
|
||||
|
|
@ -491,7 +603,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary["user.Address"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
dictionary.MarkFieldValid("user.Address");
|
||||
dictionary.SetModelValue("user.Name", new string[] { "some value" }, "some value");
|
||||
dictionary.AddModelError("user.Age", "Age is not a valid int");
|
||||
|
||||
|
|
@ -510,8 +622,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary["user.Address"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["user.Name"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
dictionary.MarkFieldValid("user.Address");
|
||||
dictionary.MarkFieldValid("user.Name");
|
||||
dictionary.AddModelError("user.Age", "Age is not a valid int");
|
||||
|
||||
// Act
|
||||
|
|
@ -529,9 +641,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
dictionary["[0].product.Name"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["[0].product.Age[0]"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
dictionary.MarkFieldValid("[0].product.Name");
|
||||
dictionary.MarkFieldValid("[0].product.Age[0]");
|
||||
dictionary.AddModelError("[0].product.Name", "Name is invalid");
|
||||
|
||||
// Act
|
||||
|
|
@ -546,8 +657,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary["user.Address"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["user.Name"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
dictionary.MarkFieldValid("user.Address");
|
||||
dictionary.MarkFieldValid("user.Name");
|
||||
|
||||
// Act
|
||||
var validationState = dictionary.GetFieldValidationState("user");
|
||||
|
|
@ -860,16 +971,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
dictionary["Property1"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
|
||||
dictionary["Property2"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.MarkFieldValid("Property1");
|
||||
dictionary.AddModelError("Property2", "Property2 invalid.");
|
||||
|
||||
dictionary["Property3"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Property3", "Property invalid.");
|
||||
|
||||
dictionary["Property4"] = new ModelStateEntry { ValidationState = ModelValidationState.Skipped };
|
||||
dictionary.MarkFieldSkipped("Property4");
|
||||
|
||||
// Act
|
||||
dictionary.ClearValidationState("Property1");
|
||||
|
|
@ -892,23 +997,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
dictionary["Product"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
|
||||
dictionary["Product.Detail1"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.MarkFieldValid("Product");
|
||||
dictionary.AddModelError("Product.Detail1", "Product Detail1 invalid.");
|
||||
|
||||
dictionary["Product.Detail2[0]"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Product.Detail2[0]", "Product Detail2[0] invalid.");
|
||||
|
||||
dictionary["Product.Detail2[1]"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Product.Detail2[1]", "Product Detail2[1] invalid.");
|
||||
|
||||
dictionary["Product.Detail2[2]"] = new ModelStateEntry { ValidationState = ModelValidationState.Skipped };
|
||||
|
||||
dictionary["Product.Detail3"] = new ModelStateEntry { ValidationState = ModelValidationState.Skipped };
|
||||
|
||||
dictionary["ProductName"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.MarkFieldSkipped("Product.Detail2[2]");
|
||||
dictionary.MarkFieldSkipped("Product.Detail3");
|
||||
dictionary.AddModelError("ProductName", "ProductName invalid.");
|
||||
|
||||
// Act
|
||||
|
|
@ -936,16 +1030,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
dictionary["Product"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
|
||||
dictionary["Product.Detail1"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.MarkFieldValid("Product");
|
||||
dictionary.AddModelError("Product.Detail1", "Product Detail1 invalid.");
|
||||
|
||||
dictionary["Product.Detail1.Name"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Product.Detail1.Name", "Product Detail1 Name invalid.");
|
||||
|
||||
dictionary["Product.Detail1Name"] = new ModelStateEntry { ValidationState = ModelValidationState.Skipped };
|
||||
dictionary.MarkFieldSkipped("Product.Detail1Name");
|
||||
|
||||
// Act
|
||||
dictionary.ClearValidationState("Product.Detail1");
|
||||
|
|
@ -966,16 +1054,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
dictionary["Property1"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
|
||||
dictionary["Property2"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.MarkFieldValid("Property1");
|
||||
dictionary.AddModelError("Property2", "Property2 invalid.");
|
||||
|
||||
dictionary["Property3"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Property3", "Property invalid.");
|
||||
|
||||
dictionary["Property4"] = new ModelStateEntry { ValidationState = ModelValidationState.Skipped };
|
||||
dictionary.MarkFieldSkipped("Property4");
|
||||
|
||||
// Act
|
||||
dictionary.ClearValidationState(modelKey);
|
||||
|
|
@ -990,5 +1072,220 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
Assert.Equal(0, dictionary["Property4"].Errors.Count);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Property4"].ValidationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEnumerable_ReturnsEmptySequenceWhenDictionaryIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Empty(dictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEnumerable_ReturnsAllNonContainerNodes()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.MarkFieldValid("Property1");
|
||||
dictionary.SetModelValue("Property1.Property2", "value", "value");
|
||||
dictionary.AddModelError("Property2", "Property invalid.");
|
||||
dictionary.AddModelError("Property2[Property3]", "Property2[Property3] invalid.");
|
||||
dictionary.MarkFieldSkipped("Property4");
|
||||
dictionary.Remove("Property2");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Collection(
|
||||
dictionary,
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal("Property1", entry.Key);
|
||||
Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState);
|
||||
Assert.Null(entry.Value.RawValue);
|
||||
Assert.Null(entry.Value.AttemptedValue);
|
||||
Assert.Empty(entry.Value.Errors);
|
||||
},
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal("Property4", entry.Key);
|
||||
Assert.Equal(ModelValidationState.Skipped, entry.Value.ValidationState);
|
||||
Assert.Null(entry.Value.RawValue);
|
||||
Assert.Null(entry.Value.AttemptedValue);
|
||||
Assert.Empty(entry.Value.Errors);
|
||||
},
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal("Property1.Property2", entry.Key);
|
||||
Assert.Equal(ModelValidationState.Unvalidated, entry.Value.ValidationState);
|
||||
Assert.Equal("value", entry.Value.RawValue);
|
||||
Assert.Equal("value", entry.Value.AttemptedValue);
|
||||
Assert.Empty(entry.Value.Errors);
|
||||
},
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal("Property2[Property3]", entry.Key);
|
||||
Assert.Equal(ModelValidationState.Invalid, entry.Value.ValidationState);
|
||||
Assert.Null(entry.Value.RawValue);
|
||||
Assert.Null(entry.Value.AttemptedValue);
|
||||
Assert.Collection(entry.Value.Errors,
|
||||
error => Assert.Equal("Property2[Property3] invalid.", error.ErrorMessage));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEnumerable_WorksCorrectlyWhenSiblingIsAPrefix()
|
||||
{
|
||||
// Arrange
|
||||
var modelStateDictionary = new ModelStateDictionary();
|
||||
modelStateDictionary.SetModelValue("prop", "value1", "value1");
|
||||
modelStateDictionary.SetModelValue("property_name", "value3", "value3");
|
||||
modelStateDictionary.SetModelValue("property", "value2", "value2");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Collection(modelStateDictionary,
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal("prop", entry.Key);
|
||||
Assert.Equal("value1", entry.Value.RawValue);
|
||||
},
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal("property", entry.Key);
|
||||
Assert.Equal("value2", entry.Value.RawValue);
|
||||
},
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal("property_name", entry.Key);
|
||||
Assert.Equal("value3", entry.Value.RawValue);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeysEnumerable_ReturnsEmptySequenceWhenDictionaryIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
// Act
|
||||
var keys = dictionary.Keys;
|
||||
|
||||
// Assert
|
||||
Assert.Empty(keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeysEnumerable_ReturnsAllKeys()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[] { "Property1", "Property4", "Property1.Property2", "Property2[Property3]" };
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.MarkFieldValid("Property1");
|
||||
dictionary.AddModelError("Property1.Property2", "Property2 invalid.");
|
||||
dictionary.AddModelError("Property2", "Property invalid.");
|
||||
dictionary.AddModelError("Property2[Property3]", "Property2[Property3] invalid.");
|
||||
dictionary.MarkFieldSkipped("Property4");
|
||||
dictionary.Remove("Property2");
|
||||
|
||||
// Act
|
||||
var keys = dictionary.Keys;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValuesEnumerable_ReturnsEmptySequenceWhenDictionaryIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
// Act
|
||||
var values = dictionary.Values;
|
||||
|
||||
// Assert
|
||||
Assert.Empty(values);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValuesEnumerable_ReturnsAllEntries()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.MarkFieldValid("Property1");
|
||||
dictionary.SetModelValue("Property1.Property2", "value", "value");
|
||||
dictionary.AddModelError("Property2", "Property invalid.");
|
||||
dictionary.AddModelError("Property2[Property3]", "Property2[Property3] invalid.");
|
||||
dictionary.MarkFieldSkipped("Property4");
|
||||
dictionary.Remove("Property2");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Collection(dictionary.Values,
|
||||
value =>
|
||||
{
|
||||
Assert.Equal(ModelValidationState.Valid, value.ValidationState);
|
||||
Assert.Null(value.RawValue);
|
||||
Assert.Null(value.AttemptedValue);
|
||||
Assert.Empty(value.Errors);
|
||||
},
|
||||
value =>
|
||||
{
|
||||
Assert.Equal(ModelValidationState.Skipped, value.ValidationState);
|
||||
Assert.Null(value.RawValue);
|
||||
Assert.Null(value.AttemptedValue);
|
||||
Assert.Empty(value.Errors);
|
||||
},
|
||||
value =>
|
||||
{
|
||||
Assert.Equal(ModelValidationState.Unvalidated, value.ValidationState);
|
||||
Assert.Equal("value", value.RawValue);
|
||||
Assert.Equal("value", value.AttemptedValue);
|
||||
Assert.Empty(value.Errors);
|
||||
},
|
||||
value =>
|
||||
{
|
||||
Assert.Equal(ModelValidationState.Invalid, value.ValidationState);
|
||||
Assert.Null(value.RawValue);
|
||||
Assert.Null(value.AttemptedValue);
|
||||
Assert.Collection(
|
||||
value.Errors,
|
||||
error => Assert.Equal("Property2[Property3] invalid.", error.ErrorMessage));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetModelStateForProperty_ReturnsModelStateForImmediateChildren()
|
||||
{
|
||||
// Arrange
|
||||
var modelStateDictionary = new ModelStateDictionary();
|
||||
modelStateDictionary.SetModelValue("property1", "value1", "value1");
|
||||
modelStateDictionary.SetModelValue("property1.property2", "value2", "value2");
|
||||
|
||||
// Act 1
|
||||
var property1 = modelStateDictionary.Root.GetModelStateForProperty("property1");
|
||||
var property2 = modelStateDictionary.Root.GetModelStateForProperty("property1.property2");
|
||||
|
||||
// Assert 1
|
||||
Assert.Equal("value1", property1.RawValue);
|
||||
Assert.Null(property2);
|
||||
|
||||
// Act 2
|
||||
property2 = property1.GetModelStateForProperty("property2");
|
||||
Assert.Equal("value2", property2.RawValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetModelStateForProperty_ReturnsModelStateForIndexedChildren()
|
||||
{
|
||||
// Arrange
|
||||
var modelStateDictionary = new ModelStateDictionary();
|
||||
modelStateDictionary.SetModelValue("[property]", "value1", "value1");
|
||||
|
||||
// Act
|
||||
var property = modelStateDictionary.Root.GetModelStateForProperty("[property]");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("value1", property.RawValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,9 +5,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
#if NETSTANDARDAPP1_5
|
||||
using System.Reflection;
|
||||
#endif
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
|
|
@ -749,9 +746,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var validator = CreateValidator();
|
||||
|
||||
modelState.Add("items[0]", new ModelStateEntry());
|
||||
modelState.Add("items[1]", new ModelStateEntry());
|
||||
modelState.Add("items[2]", new ModelStateEntry());
|
||||
modelState.SetModelValue("items[0]", "value1", "value1");
|
||||
modelState.SetModelValue("items[1]", "value2", "value2");
|
||||
modelState.SetModelValue("items[2]", "value3", "value3");
|
||||
validationState.Add(model, new ValidationStateEntry()
|
||||
{
|
||||
Key = "items",
|
||||
|
|
@ -797,10 +794,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{ "BarKey", "BarValue" }
|
||||
};
|
||||
|
||||
modelState.Add("items[0].Key", new ModelStateEntry());
|
||||
modelState.Add("items[0].Value", new ModelStateEntry());
|
||||
modelState.Add("items[1].Key", new ModelStateEntry());
|
||||
modelState.Add("items[1].Value", new ModelStateEntry());
|
||||
modelState.SetModelValue("items[0].Key", "key0", "key0");
|
||||
modelState.SetModelValue("items[0].Value", "value0", "value0");
|
||||
modelState.SetModelValue("items[1].Key", "key1", "key1");
|
||||
modelState.SetModelValue("items[1].Value", "value1", "value1");
|
||||
validationState.Add(model, new ValidationStateEntry() { Key = "items" });
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -691,14 +691,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var modelMetadata = metadataProvider.GetMetadataForType(typeof(Product));
|
||||
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary["Name"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Name", "MyProperty invalid.");
|
||||
dictionary["Id"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Id", "Id invalid.");
|
||||
dictionary.AddModelError("Id", "Id is required.");
|
||||
dictionary["Category"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
|
||||
dictionary["Unrelated"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.MarkFieldValid("Category");
|
||||
dictionary.AddModelError("Unrelated", "Unrelated is required.");
|
||||
|
||||
// Act
|
||||
|
|
@ -727,10 +723,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var modelMetadata = metadataProvider.GetMetadataForType(typeof(string));
|
||||
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary[string.Empty] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError(string.Empty, "MyProperty invalid.");
|
||||
|
||||
dictionary["Unrelated"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Unrelated", "Unrelated is required.");
|
||||
|
||||
// Act
|
||||
|
|
@ -754,19 +747,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var modelMetadata = metadataProvider.GetMetadataForType(typeof(List<Product>));
|
||||
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary["[0].Name"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("[0].Name", "Name invalid.");
|
||||
dictionary["[0].Id"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("[0].Id", "Id invalid.");
|
||||
dictionary.AddModelError("[0].Id", "Id required.");
|
||||
dictionary["[0].Category"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
dictionary.MarkFieldValid("[0].Category");
|
||||
|
||||
dictionary["[1].Name"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["[1].Id"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["[1].Category"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.MarkFieldValid("[1].Name");
|
||||
dictionary.MarkFieldValid("[1].Id");
|
||||
dictionary.AddModelError("[1].Category", "Category invalid.");
|
||||
|
||||
dictionary["Unrelated"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("Unrelated", "Unrelated is required.");
|
||||
|
||||
// Act
|
||||
|
|
@ -804,20 +792,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var modelMetadata = metadataProvider.GetMetadataForType(typeof(Product));
|
||||
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary["product.Name"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("product.Name", "Name invalid.");
|
||||
dictionary["product.Id"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("product.Id", "Id invalid.");
|
||||
dictionary.AddModelError("product.Id", "Id required.");
|
||||
dictionary["product.Category"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["product.Category.Name"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["product.Order[0].Name"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.MarkFieldValid("product.Category");
|
||||
dictionary.MarkFieldValid("product.Category.Name");
|
||||
dictionary.AddModelError("product.Order[0].Name", "Order name invalid.");
|
||||
dictionary["product.Order[0].Address.Street"] =
|
||||
new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.AddModelError("product.Order[0].Address.Street", "Street invalid.");
|
||||
dictionary["product.Order[1].Name"] = new ModelStateEntry { ValidationState = ModelValidationState.Valid };
|
||||
dictionary["product.Order[0]"] = new ModelStateEntry { ValidationState = ModelValidationState.Invalid };
|
||||
dictionary.MarkFieldValid("product.Order[1].Name");
|
||||
dictionary.AddModelError("product.Order[0]", "Order invalid.");
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -70,12 +69,8 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
{
|
||||
// Arrange
|
||||
var modelState = new ModelStateDictionary();
|
||||
modelState.Add(
|
||||
"key1",
|
||||
new ModelStateEntry());
|
||||
modelState.Add(
|
||||
"key2",
|
||||
new ModelStateEntry());
|
||||
modelState.SetModelValue("key1", "value1", "value1");
|
||||
modelState.SetModelValue("key2", "value2", "value2");
|
||||
|
||||
// Act
|
||||
var serializableError = new SerializableError(modelState);
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
<hr />
|
||||
<div class="text-danger validation-summary-errors" data-valmsg-summary="true">
|
||||
<ul><li>The field Age must be between 10 and 100.</li>
|
||||
<li>The value 'z' is not valid for Salary.</li>
|
||||
<li>The JoinDate field is required.</li>
|
||||
<li>The value 'z' is not valid for Salary.</li>
|
||||
</ul></div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-2" for="Age">Age</label>
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
Assert.Equal(1, modelState.Keys.Count);
|
||||
var key = Assert.Single(modelState.Keys, k => k == "Address[0].Street");
|
||||
var key = Assert.Single(modelState.Keys);
|
||||
Assert.Equal("Address[0].Street", key);
|
||||
Assert.Equal("SomeStreet", modelState[key].AttemptedValue);
|
||||
Assert.Equal("SomeStreet", modelState[key].RawValue);
|
||||
Assert.Empty(modelState[key].Errors);
|
||||
|
|
@ -141,6 +141,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
|
||||
var modelState = operationContext.ActionContext.ModelState;
|
||||
var model = new Person4();
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, operationContext) ?? default(ModelBindingResult);
|
||||
|
||||
|
|
@ -157,8 +158,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
Assert.Equal(1, modelState.Keys.Count);
|
||||
var key = Assert.Single(modelState.Keys, k => k == "Address[0].Street");
|
||||
var key = Assert.Single(modelState.Keys);
|
||||
Assert.Equal("Address[0].Street", key);
|
||||
Assert.Equal("SomeStreet", modelState[key].AttemptedValue);
|
||||
Assert.Equal("SomeStreet", modelState[key].RawValue);
|
||||
Assert.Empty(modelState[key].Errors);
|
||||
|
|
@ -243,8 +244,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
Assert.Equal(1, modelState.Keys.Count);
|
||||
var key = Assert.Single(modelState.Keys, k => k == "prefix.Address[0].Street");
|
||||
var key = Assert.Single(modelState.Keys);
|
||||
Assert.Equal("prefix.Address[0].Street", key);
|
||||
Assert.Equal("SomeStreet", modelState[key].AttemptedValue);
|
||||
Assert.Equal("SomeStreet", modelState[key].RawValue);
|
||||
Assert.Empty(modelState[key].Errors);
|
||||
|
|
@ -334,8 +335,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
Assert.Equal(1, modelState.Keys.Count);
|
||||
var key = Assert.Single(modelState.Keys, k => k == "prefix.Address[0].Street");
|
||||
var key = Assert.Single(modelState.Keys);
|
||||
Assert.Equal("prefix.Address[0].Street", key);
|
||||
Assert.Equal("SomeStreet", modelState[key].AttemptedValue);
|
||||
Assert.Equal("SomeStreet", modelState[key].RawValue);
|
||||
Assert.Empty(modelState[key].Errors);
|
||||
|
|
|
|||
|
|
@ -234,9 +234,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
|
||||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
Assert.Equal(1, modelState.Keys.Count);
|
||||
var key = Assert.Single(modelState.Keys, k => k == "Address.Street");
|
||||
var key = Assert.Single(modelState.Keys);
|
||||
Assert.Equal("Address.Street", key);
|
||||
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
|
||||
Assert.NotNull(modelState[key].RawValue); // Value is set by test model binder, no need to validate it.
|
||||
}
|
||||
|
|
@ -274,8 +273,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
|
||||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
Assert.Equal(1, modelState.Keys.Count);
|
||||
var key = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Street");
|
||||
var key = Assert.Single(modelState.Keys);
|
||||
Assert.Equal("CustomParameter.Address.Street", key);
|
||||
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
|
||||
Assert.NotNull(modelState[key].RawValue); // Value is set by test model binder, no need to validate it.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
|
|
@ -590,9 +591,9 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.NotNull(boundPerson);
|
||||
|
||||
Assert.False(modelState.IsValid);
|
||||
Assert.Equal(1, modelState.Keys.Count);
|
||||
|
||||
var street = Assert.Single(modelState, kvp => kvp.Key == "CustomParameter.Address.Street").Value;
|
||||
var entry = Assert.Single(modelState);
|
||||
Assert.Equal("CustomParameter.Address.Street", entry.Key);
|
||||
var street = entry.Value;
|
||||
Assert.Equal(ModelValidationState.Invalid, street.ValidationState);
|
||||
var error = Assert.Single(street.Errors);
|
||||
// Mono issue - https://github.com/aspnet/External/issues/19
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
Assert.Equal(0, modelState.Keys.Count);
|
||||
Assert.Empty(modelState.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
Assert.Equal(1, modelState.Keys.Count);
|
||||
Assert.Equal(1, modelState.Keys.Count());
|
||||
var key = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Zip");
|
||||
Assert.Equal("1", modelState[key].AttemptedValue);
|
||||
Assert.Equal("1", modelState[key].RawValue);
|
||||
|
|
@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
Assert.Equal(1, modelState.Keys.Count);
|
||||
Assert.Equal(1, modelState.Keys.Count());
|
||||
var key = Assert.Single(modelState.Keys, k => k == "Address.Zip");
|
||||
Assert.Equal("1", modelState[key].AttemptedValue);
|
||||
Assert.Equal("1", modelState[key].RawValue);
|
||||
|
|
@ -144,7 +144,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
Assert.Equal(1, modelState.Keys.Count);
|
||||
Assert.Equal(1, modelState.Keys.Count());
|
||||
var key = Assert.Single(modelState.Keys);
|
||||
Assert.Equal("Parameter1", key);
|
||||
Assert.Equal("someValue", modelState[key].AttemptedValue);
|
||||
|
|
@ -188,7 +188,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
Assert.Equal(1, modelState.Keys.Count);
|
||||
Assert.Equal(1, modelState.Keys.Count());
|
||||
var key = Assert.Single(modelState.Keys);
|
||||
Assert.Equal("Parameter1", key);
|
||||
Assert.Equal("someValue,otherValue", modelState[key].AttemptedValue);
|
||||
|
|
@ -512,7 +512,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
var modelState = operationContext.ActionContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, operationContext) ?? default(ModelBindingResult);
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, operationContext) ??
|
||||
default(ModelBindingResult);
|
||||
|
||||
// Assert
|
||||
// ModelBindingResult
|
||||
|
|
@ -529,7 +530,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
Assert.Equal(new[] { "Address.Lines", "Address.Zip", "Name" }, modelState.Keys.ToArray());
|
||||
Assert.Equal(new[] { "Address.Lines", "Address.Zip", "Name" }, modelState.Keys.OrderBy(p => p).ToArray());
|
||||
var entry = modelState["Address.Lines"];
|
||||
Assert.NotNull(entry);
|
||||
Assert.Empty(entry.Errors);
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.Add("Text", new ModelStateEntry());
|
||||
dictionary.SetModelValue("Text", "value", "value");
|
||||
|
||||
// Act
|
||||
dictionary.Remove<TestModel>(model => model.Text);
|
||||
|
|
@ -177,7 +177,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.Add("Child.Text", new ModelStateEntry());
|
||||
dictionary.SetModelValue("Child.Text", "value", "value");
|
||||
|
||||
// Act
|
||||
dictionary.Remove<TestModel>(model => model.Child.Text);
|
||||
|
|
@ -191,7 +191,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
// Arrange
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.Add("Child.Value", new ModelStateEntry());
|
||||
dictionary.SetModelValue("Child.Value", "value", "value");
|
||||
|
||||
// Act
|
||||
dictionary.Remove<TestModel>(model => model.Child.Value);
|
||||
|
|
@ -206,7 +206,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
// Arrange
|
||||
var variable = "Test";
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.Add("variable", new ModelStateEntry());
|
||||
dictionary.SetModelValue("variable", "value", "value");
|
||||
|
||||
// Act
|
||||
dictionary.Remove<TestModel>(model => variable);
|
||||
|
|
@ -219,12 +219,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public void RemoveAll_ForSingleExpression_RemovesModelStateKeys()
|
||||
{
|
||||
// Arrange
|
||||
var state = new ModelStateEntry();
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
dictionary.Add("Key", state);
|
||||
dictionary.Add("Text", new ModelStateEntry());
|
||||
dictionary.Add("Text.Length", new ModelStateEntry());
|
||||
dictionary.SetModelValue("Key", "value1", "value1");
|
||||
dictionary.SetModelValue("Text", "value2", "value2");
|
||||
dictionary.SetModelValue("Text.Length", "value3", "value3");
|
||||
var expected = dictionary["Key"];
|
||||
|
||||
// Act
|
||||
dictionary.RemoveAll<TestModel>(model => model.Text);
|
||||
|
|
@ -233,19 +233,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var modelState = Assert.Single(dictionary);
|
||||
|
||||
Assert.Equal("Key", modelState.Key);
|
||||
Assert.Same(state, modelState.Value);
|
||||
Assert.Same(expected, modelState.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveAll_ForRelationExpression_RemovesModelStateKeys()
|
||||
{
|
||||
// Arrange
|
||||
var state = new ModelStateEntry();
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
dictionary.Add("Key", state);
|
||||
dictionary.Add("Child", new ModelStateEntry());
|
||||
dictionary.Add("Child.Text", new ModelStateEntry());
|
||||
dictionary.SetModelValue("Key", "value1", "value1");
|
||||
dictionary.SetModelValue("Child", "value2", "value2");
|
||||
dictionary.SetModelValue("Child.Text", "value3", "value3");
|
||||
var expected = dictionary["Key"];
|
||||
|
||||
// Act
|
||||
dictionary.RemoveAll<TestModel>(model => model.Child);
|
||||
|
|
@ -254,18 +253,17 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var modelState = Assert.Single(dictionary);
|
||||
|
||||
Assert.Equal("Key", modelState.Key);
|
||||
Assert.Same(state, modelState.Value);
|
||||
Assert.Same(expected, modelState.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveAll_ForImplicitlyCastedToObjectExpression_RemovesModelStateKeys()
|
||||
{
|
||||
// Arrange
|
||||
var state = new ModelStateEntry();
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
dictionary.Add("Child", state);
|
||||
dictionary.Add("Child.Value", new ModelStateEntry());
|
||||
dictionary.SetModelValue("Child", "value1", "value1");
|
||||
dictionary.SetModelValue("Child.Value", "value2", "value2");
|
||||
var expected = dictionary["child"];
|
||||
|
||||
// Act
|
||||
dictionary.RemoveAll<TestModel>(model => model.Child.Value);
|
||||
|
|
@ -274,7 +272,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var modelState = Assert.Single(dictionary);
|
||||
|
||||
Assert.Equal("Child", modelState.Key);
|
||||
Assert.Same(state, modelState.Value);
|
||||
Assert.Same(expected, modelState.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -282,13 +280,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
// Arrange
|
||||
var variable = "Test";
|
||||
var state = new ModelStateEntry();
|
||||
var dictionary = new ModelStateDictionary();
|
||||
dictionary.SetModelValue("Key", "value1", "value1");
|
||||
dictionary.SetModelValue("variable", "value2", "value2");
|
||||
dictionary.SetModelValue("variable.Text", "value3", "value3");
|
||||
dictionary.SetModelValue("variable.Value", "value4", "value4");
|
||||
|
||||
dictionary.Add("Key", state);
|
||||
dictionary.Add("variable", new ModelStateEntry());
|
||||
dictionary.Add("variable.Text", new ModelStateEntry());
|
||||
dictionary.Add("variable.Value", new ModelStateEntry());
|
||||
var expected = dictionary["Key"];
|
||||
|
||||
// Act
|
||||
dictionary.RemoveAll<TestModel>(model => variable);
|
||||
|
|
@ -297,21 +295,20 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var modelState = Assert.Single(dictionary);
|
||||
|
||||
Assert.Equal("Key", modelState.Key);
|
||||
Assert.Same(state, modelState.Value);
|
||||
Assert.Same(expected, modelState.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveAll_ForModelExpression_RemovesModelPropertyKeys()
|
||||
{
|
||||
// Arrange
|
||||
var state = new ModelStateEntry();
|
||||
var dictionary = new ModelStateDictionary();
|
||||
|
||||
dictionary.Add("Key", state);
|
||||
dictionary.Add("Text", new ModelStateEntry());
|
||||
dictionary.Add("Child", new ModelStateEntry());
|
||||
dictionary.Add("Child.Text", new ModelStateEntry());
|
||||
dictionary.Add("Child.NoValue", new ModelStateEntry());
|
||||
dictionary.SetModelValue("Key", "value1", "value1");
|
||||
dictionary.SetModelValue("Text", "value2", "value2");
|
||||
dictionary.SetModelValue("Child", "value3", "value3");
|
||||
dictionary.SetModelValue("Child.Text", "value4", "value4");
|
||||
dictionary.SetModelValue("Child.NoValue", "value5", "value5");
|
||||
var expected = dictionary["Key"];
|
||||
|
||||
// Act
|
||||
dictionary.RemoveAll<TestModel>(model => model);
|
||||
|
|
@ -320,7 +317,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
var modelState = Assert.Single(dictionary);
|
||||
|
||||
Assert.Equal("Key", modelState.Key);
|
||||
Assert.Same(state, modelState.Value);
|
||||
Assert.Same(expected, modelState.Value);
|
||||
}
|
||||
|
||||
private class TestModel
|
||||
|
|
|
|||
|
|
@ -259,10 +259,10 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
viewData["FieldPrefix.Name"] = "View data dictionary value";
|
||||
viewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix";
|
||||
|
||||
var modelState = new ModelStateEntry();
|
||||
modelState.RawValue = new string[] { "Attempted name value" };
|
||||
modelState.AttemptedValue = "Attempted name value";
|
||||
viewData.ModelState["FieldPrefix.Name"] = modelState;
|
||||
viewData.ModelState.SetModelValue(
|
||||
"FieldPrefix.Name",
|
||||
"Attempted name value",
|
||||
"Attempted name value");
|
||||
|
||||
// Act
|
||||
var result = helper.DisplayText("Name");
|
||||
|
|
@ -284,10 +284,10 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
viewData["Name"] = "View data dictionary value";
|
||||
viewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix";
|
||||
|
||||
var modelState = new ModelStateEntry();
|
||||
modelState.RawValue = new string[] { "Attempted name value" };
|
||||
modelState.AttemptedValue = "Attempted name value";
|
||||
viewData.ModelState["FieldPrefix.Name"] = modelState;
|
||||
viewData.ModelState.SetModelValue(
|
||||
"FieldPrefix.Name",
|
||||
"Attempted name value",
|
||||
"Attempted name value");
|
||||
|
||||
// Act
|
||||
var result = helper.DisplayTextFor(m => m.Name);
|
||||
|
|
|
|||
|
|
@ -361,9 +361,18 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
idAttributeDotReplacement: "$");
|
||||
helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
|
||||
helper.ViewData.ModelState.Clear();
|
||||
helper.ViewData.ModelState.Add("Property1", GetModelStateEntry("modelstate-without-prefix"));
|
||||
helper.ViewData.ModelState.Add("MyPrefix.Property1", GetModelStateEntry("modelstate-with-prefix"));
|
||||
helper.ViewData.ModelState.Add("MyPrefix$Property1", GetModelStateEntry("modelstate-with-iddotreplacement"));
|
||||
helper.ViewData.ModelState.SetModelValue(
|
||||
"Property1",
|
||||
"modelstate-without-prefix",
|
||||
"modelstate-without-prefix");
|
||||
helper.ViewData.ModelState.SetModelValue(
|
||||
"MyPrefix.Property1",
|
||||
"modelstate-with-prefix",
|
||||
"modelstate-with-prefix");
|
||||
helper.ViewData.ModelState.SetModelValue(
|
||||
"MyPrefix$Property1",
|
||||
"modelstate-with-iddotreplacement",
|
||||
"modelstate-with-iddotreplacement");
|
||||
|
||||
// Act
|
||||
var result = helper.Hidden("Property1", "explicit-value", htmlAttributes: null);
|
||||
|
|
@ -411,7 +420,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
|
||||
// Act and Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => helper.Hidden(string.Empty, string.Empty, attributes),
|
||||
() => helper.Hidden(string.Empty, string.Empty, attributes),
|
||||
"expression",
|
||||
expected);
|
||||
}
|
||||
|
|
@ -660,9 +669,18 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
helper.ViewData.Model.Property1 = "propValue";
|
||||
helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
|
||||
helper.ViewData.ModelState.Clear();
|
||||
helper.ViewData.ModelState.Add("Property1", GetModelStateEntry("modelstate-without-prefix"));
|
||||
helper.ViewData.ModelState.Add("MyPrefix.Property1", GetModelStateEntry("modelstate-with-prefix"));
|
||||
helper.ViewData.ModelState.Add("MyPrefix$Property1", GetModelStateEntry("modelstate-with-iddotreplacement"));
|
||||
helper.ViewData.ModelState.SetModelValue(
|
||||
"Property1",
|
||||
"modelstate-without-prefix",
|
||||
"modelstate-without-prefix");
|
||||
helper.ViewData.ModelState.SetModelValue(
|
||||
"MyPrefix.Property1",
|
||||
"modelstate-with-prefix",
|
||||
"modelstate-with-prefix");
|
||||
helper.ViewData.ModelState.SetModelValue(
|
||||
"MyPrefix$Property1",
|
||||
"modelstate-with-iddotreplacement",
|
||||
"modelstate-with-iddotreplacement");
|
||||
|
||||
// Act
|
||||
var result = helper.HiddenFor(m => m.Property1, htmlAttributes: null);
|
||||
|
|
@ -802,9 +820,9 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
{
|
||||
// Arrange
|
||||
var viewData = GetViewDataWithNullModelAndNonNullViewData();
|
||||
viewData.ModelState.Add("pre.Property3[key]", GetModelStateEntry("Prop3Val"));
|
||||
viewData.ModelState.Add("pre.Property4.Property5", GetModelStateEntry("Prop5Val"));
|
||||
viewData.ModelState.Add("pre.Property4.Property6[0]", GetModelStateEntry("Prop6Val"));
|
||||
viewData.ModelState.SetModelValue("pre.Property3[key]", "Prop3Val", "Prop3Val");
|
||||
viewData.ModelState.SetModelValue("pre.Property4.Property5", "Prop5Val", "Prop5Val");
|
||||
viewData.ModelState.SetModelValue("pre.Property4.Property6[0]", "Prop6Val", "Prop6Val");
|
||||
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(viewData);
|
||||
viewData.TemplateInfo.HtmlFieldPrefix = "pre";
|
||||
|
|
@ -911,7 +929,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
{
|
||||
var viewData = GetViewDataWithNonNullModel();
|
||||
viewData["Property1"] = "view-data-val";
|
||||
viewData.ModelState.Add("Property1", GetModelStateEntry("ModelStateValue"));
|
||||
viewData.ModelState.SetModelValue("Property1", "ModelStateValue", "ModelStateValue");
|
||||
|
||||
return viewData;
|
||||
}
|
||||
|
|
@ -924,15 +942,6 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
return viewData;
|
||||
}
|
||||
|
||||
private static ModelStateEntry GetModelStateEntry(string value)
|
||||
{
|
||||
return new ModelStateEntry
|
||||
{
|
||||
RawValue = new string[] { value },
|
||||
AttemptedValue = value,
|
||||
};
|
||||
}
|
||||
|
||||
public class HiddenModel
|
||||
{
|
||||
public string Property1 { get; set; }
|
||||
|
|
|
|||
|
|
@ -324,9 +324,9 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
{
|
||||
// Arrange
|
||||
var viewData = GetViewDataWithModelStateAndModelAndViewDataValues();
|
||||
viewData.ModelState.Add("pre.Property3[key]", GetModelStateEntry("Property3Val"));
|
||||
viewData.ModelState.Add("pre.Property4.Property5", GetModelStateEntry("Property5Val"));
|
||||
viewData.ModelState.Add("pre.Property4.Property6[0]", GetModelStateEntry("Property6Val"));
|
||||
viewData.ModelState.SetModelValue("pre.Property3[key]", "Property3Val", "Property3Val");
|
||||
viewData.ModelState.SetModelValue("pre.Property4.Property5", "Property5Val", "Property5Val");
|
||||
viewData.ModelState.SetModelValue("pre.Property4.Property6[0]", "Property6Val", "Property6Val");
|
||||
viewData["pre.Property3[key]"] = "vdd-value1";
|
||||
viewData["pre.Property4.Property5"] = "vdd-value2";
|
||||
viewData["pre.Property4.Property6[0]"] = "vdd-value3";
|
||||
|
|
@ -426,7 +426,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
{
|
||||
var viewData = GetViewDataWithNullModelAndNonEmptyViewData();
|
||||
viewData.Model = new PasswordModel();
|
||||
viewData.ModelState.Add("Property1", GetModelStateEntry("ModelStateValue"));
|
||||
viewData.ModelState.SetModelValue("Property1", "ModelStateValue", "ModelStateValue");
|
||||
|
||||
return viewData;
|
||||
}
|
||||
|
|
@ -439,15 +439,6 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
return viewData;
|
||||
}
|
||||
|
||||
private static ModelStateEntry GetModelStateEntry(string value)
|
||||
{
|
||||
return new ModelStateEntry
|
||||
{
|
||||
RawValue = new string[] { value },
|
||||
AttemptedValue = value,
|
||||
};
|
||||
}
|
||||
|
||||
public class PasswordModel
|
||||
{
|
||||
public string Property1 { get; set; }
|
||||
|
|
|
|||
|
|
@ -381,7 +381,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<ArgumentException>(
|
||||
"expression",
|
||||
"expression",
|
||||
() => helper.DropDownList(null, selectList: null, optionLabel: null, htmlAttributes: null));
|
||||
Assert.Equal(expected, ex.Message);
|
||||
}
|
||||
|
|
@ -412,19 +412,15 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
// Arrange
|
||||
var expectedHtml = GetExpectedSelectElement(SelectSources.ModelStateEntry, allowMultiple: false);
|
||||
|
||||
var modelState = new ModelStateDictionary
|
||||
{
|
||||
["Property1"] = new ModelStateEntry
|
||||
{
|
||||
RawValue = new string[] { SelectSources.ModelStateEntry.ToString() },
|
||||
AttemptedValue = SelectSources.ModelStateEntry.ToString()
|
||||
},
|
||||
["Prefix.Property1"] = new ModelStateEntry
|
||||
{
|
||||
RawValue = new string[] { SelectSources.ModelStateEntryWithPrefix.ToString() },
|
||||
AttemptedValue = SelectSources.ModelStateEntryWithPrefix.ToString()
|
||||
},
|
||||
};
|
||||
var modelState = new ModelStateDictionary();
|
||||
modelState.SetModelValue(
|
||||
"Property1",
|
||||
SelectSources.ModelStateEntry,
|
||||
SelectSources.ModelStateEntry.ToString());
|
||||
modelState.SetModelValue(
|
||||
"Prefix.Property1",
|
||||
SelectSources.ModelStateEntryWithPrefix,
|
||||
SelectSources.ModelStateEntryWithPrefix.ToString());
|
||||
|
||||
var provider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var viewData = new ViewDataDictionary<ModelContainingSources>(provider, modelState)
|
||||
|
|
@ -453,19 +449,15 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
SelectSources.ModelStateEntryWithPrefix,
|
||||
allowMultiple: false);
|
||||
|
||||
var modelState = new ModelStateDictionary
|
||||
{
|
||||
["Property1"] = new ModelStateEntry
|
||||
{
|
||||
RawValue = new string[] { SelectSources.ModelStateEntry.ToString() },
|
||||
AttemptedValue = SelectSources.ModelStateEntry.ToString()
|
||||
},
|
||||
["Prefix.Property1"] = new ModelStateEntry
|
||||
{
|
||||
RawValue = new string[] { SelectSources.ModelStateEntryWithPrefix.ToString() },
|
||||
AttemptedValue = SelectSources.ModelStateEntryWithPrefix.ToString()
|
||||
},
|
||||
};
|
||||
var modelState = new ModelStateDictionary();
|
||||
modelState.SetModelValue(
|
||||
"Property1",
|
||||
SelectSources.ModelStateEntry,
|
||||
SelectSources.ModelStateEntry.ToString());
|
||||
modelState.SetModelValue(
|
||||
"Prefix.Property1",
|
||||
SelectSources.ModelStateEntryWithPrefix,
|
||||
SelectSources.ModelStateEntryWithPrefix.ToString());
|
||||
|
||||
var provider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var viewData = new ViewDataDictionary<ModelContainingSources>(provider, modelState)
|
||||
|
|
@ -823,19 +815,15 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
// Arrange
|
||||
var expectedHtml = GetExpectedSelectElement(SelectSources.ModelStateEntry, allowMultiple: true);
|
||||
|
||||
var modelState = new ModelStateDictionary
|
||||
{
|
||||
["Property1"] = new ModelStateEntry
|
||||
{
|
||||
RawValue = new string[] { SelectSources.ModelStateEntry.ToString() },
|
||||
AttemptedValue = SelectSources.ModelStateEntry.ToString()
|
||||
},
|
||||
["Prefix.Property1"] = new ModelStateEntry
|
||||
{
|
||||
RawValue = new string[] { SelectSources.ModelStateEntryWithPrefix.ToString() },
|
||||
AttemptedValue = SelectSources.ModelStateEntryWithPrefix.ToString()
|
||||
},
|
||||
};
|
||||
var modelState = new ModelStateDictionary();
|
||||
modelState.SetModelValue(
|
||||
"Property1",
|
||||
SelectSources.ModelStateEntry,
|
||||
SelectSources.ModelStateEntry.ToString());
|
||||
modelState.SetModelValue(
|
||||
"Prefix.Property1",
|
||||
SelectSources.ModelStateEntryWithPrefix,
|
||||
SelectSources.ModelStateEntryWithPrefix.ToString());
|
||||
|
||||
var provider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var viewData = new ViewDataDictionary<ModelContainingListOfSources>(provider, modelState)
|
||||
|
|
@ -864,19 +852,15 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
SelectSources.ModelStateEntryWithPrefix,
|
||||
allowMultiple: true);
|
||||
|
||||
var modelState = new ModelStateDictionary
|
||||
{
|
||||
["Property1"] = new ModelStateEntry
|
||||
{
|
||||
RawValue = new string[] { SelectSources.ModelStateEntry.ToString() },
|
||||
AttemptedValue = SelectSources.ModelStateEntry.ToString()
|
||||
},
|
||||
["Prefix.Property1"] = new ModelStateEntry
|
||||
{
|
||||
RawValue = new string[] { SelectSources.ModelStateEntryWithPrefix.ToString() },
|
||||
AttemptedValue = SelectSources.ModelStateEntryWithPrefix.ToString()
|
||||
},
|
||||
};
|
||||
var modelState = new ModelStateDictionary();
|
||||
modelState.SetModelValue(
|
||||
"Property1",
|
||||
SelectSources.ModelStateEntry,
|
||||
SelectSources.ModelStateEntry.ToString());
|
||||
modelState.SetModelValue(
|
||||
"Prefix.Property1",
|
||||
SelectSources.ModelStateEntryWithPrefix,
|
||||
SelectSources.ModelStateEntryWithPrefix.ToString());
|
||||
|
||||
var provider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var viewData = new ViewDataDictionary<ModelContainingListOfSources>(provider, modelState)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.TestCommon;
|
||||
|
|
@ -109,13 +110,13 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
"<li>HtmlEncode[[This is an error for Property3.]]</li>" + Environment.NewLine +
|
||||
"</ul></div>";
|
||||
var divWithAllErrors = "<div class=\"HtmlEncode[[validation-summary-errors]]\" data-valmsg-summary=\"HtmlEncode[[true]]\"><ul>" +
|
||||
"<li>HtmlEncode[[This is an error for Property3.Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.OrderedProperty3.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.OrderedProperty2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is another error for Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[The value '' is not valid for Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.OrderedProperty3.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.OrderedProperty2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for the model root.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is another error for the model root.]]</li>" + Environment.NewLine +
|
||||
"</ul></div>";
|
||||
|
|
@ -302,6 +303,166 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationSummary_OrdersCorrectlyWhenElementsAreRemovedFromDictionary()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "<div class=\"HtmlEncode[[validation-summary-errors]]\" data-valmsg-summary=\"HtmlEncode[[true]]\"><ul>" +
|
||||
"<li>HtmlEncode[[New error for Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.OrderedProperty3.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for the model root.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is another error for the model root.]]</li>" + Environment.NewLine +
|
||||
"</ul></div>";
|
||||
var model = new ValidationModel();
|
||||
var html = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
AddMultipleErrors(html.ViewData.ModelState);
|
||||
html.ViewData.ModelState.RemoveAll<ValidationModel>(m => m.Property2);
|
||||
html.ViewData.ModelState.Remove<ValidationModel>(m => m.Property3);
|
||||
html.ViewData.ModelState.Remove<ValidationModel>(m => m.Property3.OrderedProperty2);
|
||||
html.ViewData.ModelState.AddModelError("Property2", "New error for Property2.");
|
||||
|
||||
// Act
|
||||
var result = html.ValidationSummary(
|
||||
excludePropertyErrors: false,
|
||||
message: null,
|
||||
htmlAttributes: null,
|
||||
tag: null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationSummary_IncludesErrorsThatAreNotPartOfMetadata()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "<div class=\"HtmlEncode[[validation-summary-errors]]\" data-valmsg-summary=\"HtmlEncode[[true]]\"><ul>" +
|
||||
"<li>HtmlEncode[[This is an error for Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is another error for Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[The value '' is not valid for Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.OrderedProperty3.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.OrderedProperty2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for the model root.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is another error for the model root.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[non-existent-error1]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[non-existent-error2]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[non-existent-error3]]</li>" + Environment.NewLine +
|
||||
"</ul></div>";
|
||||
var model = new ValidationModel();
|
||||
var html = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
AddMultipleErrors(html.ViewData.ModelState);
|
||||
html.ViewData.ModelState.AddModelError("non-existent-property1", "non-existent-error1");
|
||||
html.ViewData.ModelState.AddModelError("non.existent.property2", "non-existent-error2");
|
||||
html.ViewData.ModelState.AddModelError("non.existent[0].property3", "non-existent-error3");
|
||||
|
||||
// Act
|
||||
var result = html.ValidationSummary(
|
||||
excludePropertyErrors: false,
|
||||
message: null,
|
||||
htmlAttributes: null,
|
||||
tag: null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationSummary_IncludesErrorsForCollectionProperties()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "<div class=\"HtmlEncode[[validation-summary-errors]]\" data-valmsg-summary=\"HtmlEncode[[true]]\"><ul>" +
|
||||
"<li>HtmlEncode[[Property1 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[Property2[0].OrderedProperty1 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[Property2[0].Property1 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[Property2[2].Property3 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[Property2[10].Property2 error]]</li>" + Environment.NewLine +
|
||||
"</ul></div>";
|
||||
var model = new ModelWithCollection();
|
||||
var html = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
html.ViewData.ModelState.AddModelError("Property1", "Property1 error");
|
||||
html.ViewData.ModelState.AddModelError("Property2[0].OrderedProperty1", "Property2[0].OrderedProperty1 error");
|
||||
html.ViewData.ModelState.AddModelError("Property2[10].Property2", "Property2[10].Property2 error");
|
||||
html.ViewData.ModelState.AddModelError("Property2[2].Property3", "Property2[2].Property3 error");
|
||||
html.ViewData.ModelState.AddModelError("Property2[0].Property1", "Property2[0].Property1 error");
|
||||
|
||||
// Act
|
||||
var result = html.ValidationSummary(
|
||||
excludePropertyErrors: false,
|
||||
message: null,
|
||||
htmlAttributes: null,
|
||||
tag: null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationSummary_IncludesErrorsForTopLevelCollectionProperties()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "<div class=\"HtmlEncode[[validation-summary-errors]]\" data-valmsg-summary=\"HtmlEncode[[true]]\"><ul>" +
|
||||
"<li>HtmlEncode[[[0].OrderedProperty2 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[[0].OrderedProperty1 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[[0].Property1 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[[2].OrderedProperty3 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[[2].Property3 error]]</li>" + Environment.NewLine +
|
||||
"</ul></div>";
|
||||
var model = new OrderedModel[5];
|
||||
var html = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
html.ViewData.ModelState.AddModelError("[0].OrderedProperty2", "[0].OrderedProperty2 error");
|
||||
html.ViewData.ModelState.AddModelError("[0].Property1", "[0].Property1 error");
|
||||
html.ViewData.ModelState.AddModelError("[0].OrderedProperty1", "[0].OrderedProperty1 error");
|
||||
html.ViewData.ModelState.AddModelError("[2].Property3", "[2].Property3 error");
|
||||
html.ViewData.ModelState.AddModelError("[2].OrderedProperty3", "[2].OrderedProperty3 error");
|
||||
|
||||
// Act
|
||||
var result = html.ValidationSummary(
|
||||
excludePropertyErrors: false,
|
||||
message: null,
|
||||
htmlAttributes: null,
|
||||
tag: null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationSummary_IncludesErrorsForPropertiesOnCollectionTypes()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "<div class=\"HtmlEncode[[validation-summary-errors]]\" data-valmsg-summary=\"HtmlEncode[[true]]\"><ul>" +
|
||||
"<li>HtmlEncode[[[0].OrderedProperty2 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[[0].OrderedProperty1 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[[0].Property1 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[[2].OrderedProperty3 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[[2].Property3 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[OrderedProperty1 error]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[OrderedProperty2 error]]</li>" + Environment.NewLine +
|
||||
"</ul></div>";
|
||||
var model = new OrderedModel[5];
|
||||
var html = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
html.ViewData.ModelState.AddModelError("[0].OrderedProperty2", "[0].OrderedProperty2 error");
|
||||
html.ViewData.ModelState.AddModelError("[0].Property1", "[0].Property1 error");
|
||||
html.ViewData.ModelState.AddModelError("[0].OrderedProperty1", "[0].OrderedProperty1 error");
|
||||
html.ViewData.ModelState.AddModelError("[2].Property3", "[2].Property3 error");
|
||||
html.ViewData.ModelState.AddModelError("[2].OrderedProperty3", "[2].OrderedProperty3 error");
|
||||
html.ViewData.ModelState.AddModelError("OrderedProperty1", "OrderedProperty1 error");
|
||||
html.ViewData.ModelState.AddModelError("OrderedProperty2", "OrderedProperty2 error");
|
||||
|
||||
// Act
|
||||
var result = html.ValidationSummary(
|
||||
excludePropertyErrors: false,
|
||||
message: null,
|
||||
htmlAttributes: null,
|
||||
tag: null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationSummary_ErrorsInModelUsingOrder_SortsErrorsAsExpected()
|
||||
{
|
||||
|
|
@ -313,10 +474,10 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
"<li>HtmlEncode[[This is yet-another error for OrderedProperty2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for OrderedProperty1.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property3.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is another error for Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property1.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is another error for Property1.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is another error for Property2.]]</li>" + Environment.NewLine +
|
||||
"<li>HtmlEncode[[This is an error for LastProperty.]]</li>" + Environment.NewLine +
|
||||
"</ul></div>";
|
||||
|
||||
|
|
@ -436,7 +597,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
helper.ViewData.ModelState.AddModelError("Property1", "Error for Property1");
|
||||
|
||||
// Act
|
||||
var validationSummaryResult = helper.ValidationSummary(message: "Custom Message", htmlAttributes: new { attr="value" });
|
||||
var validationSummaryResult = helper.ValidationSummary(message: "Custom Message", htmlAttributes: new { attr = "value" });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
|
|
@ -577,5 +738,21 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
[Display(Order = 23)]
|
||||
public string OrderedProperty1 { get; set; }
|
||||
}
|
||||
|
||||
private class ModelWithCollection
|
||||
{
|
||||
public string Property1 { get; set; }
|
||||
|
||||
public List<OrderedModel> Property2 { get; set; }
|
||||
}
|
||||
|
||||
private class CollectionType : Collection<OrderedModel>
|
||||
{
|
||||
[Display(Order = 1)]
|
||||
public string OrderedProperty2 { get; set; }
|
||||
|
||||
[Display(Order = 2)]
|
||||
public string OrderedProperty1 { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,15 +162,15 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
viewData["StringProperty"] = "ViewDataValue";
|
||||
viewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix";
|
||||
|
||||
var modelState = new ModelStateEntry();
|
||||
modelState.AttemptedValue = "StringPropertyAttemptedValue";
|
||||
modelState.RawValue = new string[] { "StringPropertyRawValue" };
|
||||
viewData.ModelState["FieldPrefix.StringProperty"] = modelState;
|
||||
viewData.ModelState.SetModelValue(
|
||||
"FieldPrefix.StringProperty",
|
||||
"StringPropertyRawValue",
|
||||
"StringPropertyAttemptedValue");
|
||||
|
||||
modelState = new ModelStateEntry();
|
||||
modelState.AttemptedValue = "ModelAttemptedValue";
|
||||
modelState.RawValue = new string[] { "ModelRawValue" };
|
||||
viewData.ModelState["FieldPrefix"] = modelState;
|
||||
viewData.ModelState.SetModelValue(
|
||||
"FieldPrefix",
|
||||
"ModelRawValue",
|
||||
"ModelAttemptedValue");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal("StringPropertyRawValue", helper.Value("StringProperty", format: null));
|
||||
|
|
@ -219,10 +219,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
var viewData = helper.ViewData;
|
||||
viewData["StringProperty"] = "ViewDataValue <\"\">";
|
||||
|
||||
var modelState = new ModelStateEntry();
|
||||
modelState.AttemptedValue = "ObjectPropertyAttemptedValue <\"\">";
|
||||
modelState.RawValue = new string[] { "ObjectPropertyRawValue <\"\">" };
|
||||
viewData.ModelState["ObjectProperty"] = modelState;
|
||||
viewData.ModelState.SetModelValue(
|
||||
"ObjectProperty",
|
||||
"ObjectPropertyRawValue <\"\">",
|
||||
"ObjectPropertyAttemptedValue <\"\">");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal(
|
||||
|
|
|
|||
|
|
@ -33,12 +33,10 @@ namespace System.Web.Http.Dispatcher
|
|||
yield return new[] { new HttpError() };
|
||||
yield return new[] { new HttpError("error") };
|
||||
yield return new[] { new HttpError(new NotImplementedException(), true) };
|
||||
yield return new[] { new HttpError(
|
||||
new ModelStateDictionary()
|
||||
{
|
||||
{ "key", new ModelStateEntry { Errors = { new ModelError("error") } } }
|
||||
},
|
||||
true) };
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
modelState.AddModelError("key", "error");
|
||||
yield return new[] { new HttpError(modelState, true) };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -247,7 +245,7 @@ namespace System.Web.Http.Dispatcher
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("ErrorKeyValue")]
|
||||
[MemberData(nameof(ErrorKeyValue))]
|
||||
public void HttpErrorStringProperties_UseCorrectHttpErrorKey(HttpError httpError, Func<string> productUnderTest, string key, string actualValue)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -289,7 +287,7 @@ namespace System.Web.Http.Dispatcher
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("HttpErrors")]
|
||||
[MemberData(nameof(HttpErrors))]
|
||||
public void HttpErrors_UseCaseInsensitiveComparer(HttpError httpError)
|
||||
{
|
||||
// Arrange
|
||||
|
|
|
|||
|
|
@ -83,8 +83,9 @@ namespace WebApiCompatShimWebSite
|
|||
var error = item.Value.Errors.SingleOrDefault();
|
||||
if (error != null)
|
||||
{
|
||||
var value = error.Exception != null ? error.Exception.Message :
|
||||
error.ErrorMessage;
|
||||
var value = error.Exception != null ?
|
||||
error.Exception.Message :
|
||||
error.ErrorMessage;
|
||||
result.Add(item.Key, value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue