Generic ModelStateDictionary add and remove extensions
- Added generic add model error and remove extensions for ModelStateDictionary, to avoid using hard coded strings then specifying model state dictionary keys. - Added generic removal all extension for ModelStateDictionary, to support removing all the model state keys for given expression. aspnet/Mvc#3164
This commit is contained in:
parent
6a0a24481a
commit
b28debf442
|
|
@ -0,0 +1,114 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions methods for <see cref="ModelStateDictionary"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class ModelStateDictionaryExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the specified <paramref name="errorMessage"/> to the <see cref="ModelState.Errors"/> instance
|
||||||
|
/// that is associated with the specified <paramref name="expression"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TModel">The type of the model.</typeparam>
|
||||||
|
/// <param name="modelState">The <see cref="ModelStateDictionary"/> instance this method extends.</param>
|
||||||
|
/// <param name="expression">An expression to be evaluated against an item in the current model.</param>
|
||||||
|
/// <param name="errorMessage">The error message to add.</param>
|
||||||
|
public static void AddModelError<TModel>(this ModelStateDictionary modelState, Expression<Func<TModel, object>> expression, string errorMessage)
|
||||||
|
{
|
||||||
|
modelState.AddModelError(GetExpressionText(expression), errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the specified <paramref name="exception"/> to the <see cref="ModelState.Errors"/> instance
|
||||||
|
/// that is associated with the specified <paramref name="expression"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TModel">The type of the model.</typeparam>
|
||||||
|
/// <param name="modelState">The <see cref="ModelStateDictionary"/> instance this method extends.</param>
|
||||||
|
/// <param name="expression">An expression to be evaluated against an item in the current model.</param>
|
||||||
|
/// <param name="exception">The <see cref="Exception"/> to add.</param>
|
||||||
|
public static void AddModelError<TModel>(this ModelStateDictionary modelState, Expression<Func<TModel, object>> expression, Exception exception)
|
||||||
|
{
|
||||||
|
modelState.AddModelError(GetExpressionText(expression), exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the specified <paramref name="expression"/> from the <see cref="ModelStateDictionary"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TModel">The type of the model.</typeparam>
|
||||||
|
/// <param name="modelState">The <see cref="ModelStateDictionary"/> instance this method extends.</param>
|
||||||
|
/// <param name="expression">An expression to be evaluated against an item in the current model.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// true if the element is successfully removed; otherwise, false.
|
||||||
|
/// This method also returns false if <paramref name="expression"/> was not found in the model-state dictionary.
|
||||||
|
/// </returns>
|
||||||
|
public static bool Remove<TModel>(this ModelStateDictionary modelState, Expression<Func<TModel, object>> expression)
|
||||||
|
{
|
||||||
|
return modelState.Remove(GetExpressionText(expression));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all the entries for the specified <paramref name="expression"/> from the <see cref="ModelStateDictionary"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TModel">The type of the model.</typeparam>
|
||||||
|
/// <param name="modelState">The <see cref="ModelStateDictionary"/> instance this method extends.</param>
|
||||||
|
/// <param name="expression">An expression to be evaluated against an item in the current model.</param>
|
||||||
|
public static void RemoveAll<TModel>(this ModelStateDictionary modelState, Expression<Func<TModel, object>> expression)
|
||||||
|
{
|
||||||
|
string modelKey = GetExpressionText(expression);
|
||||||
|
if (string.IsNullOrEmpty(modelKey))
|
||||||
|
{
|
||||||
|
var modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(TModel));
|
||||||
|
|
||||||
|
foreach (var property in modelMetadata.Properties)
|
||||||
|
{
|
||||||
|
var childKey = property.BinderModelName ?? property.PropertyName;
|
||||||
|
var entries = modelState.FindKeysWithPrefix(childKey).ToArray();
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
modelState.Remove(entry.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var entries = modelState.FindKeysWithPrefix(modelKey).ToArray();
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
modelState.Remove(entry.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetExpressionText(LambdaExpression expression)
|
||||||
|
{
|
||||||
|
// We check if expression is wrapped with conversion to object expression
|
||||||
|
// and unwrap it if necessary, because Expression<Func<TModel, object>>
|
||||||
|
// automatically creates a convert to object expression for expresions
|
||||||
|
// returning value types
|
||||||
|
var unaryExpression = expression.Body as UnaryExpression;
|
||||||
|
|
||||||
|
if (IsConversionToObject(unaryExpression))
|
||||||
|
{
|
||||||
|
return ExpressionHelper.GetExpressionText(Expression.Lambda(unaryExpression.Operand, expression.Parameters[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExpressionHelper.GetExpressionText(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsConversionToObject(UnaryExpression expression)
|
||||||
|
{
|
||||||
|
return expression?.NodeType == ExpressionType.Convert &&
|
||||||
|
expression.Operand?.NodeType == ExpressionType.MemberAccess &&
|
||||||
|
expression.Type == typeof(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,332 @@
|
||||||
|
// 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 Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class ModelStateDictionaryExtensionsTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void AddModelError_ForSingleExpression_AddsExpectedMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.AddModelError<TestModel>(model => model.Text, "Message");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = Assert.Single(dictionary);
|
||||||
|
var modelError = Assert.Single(modelState.Value.Errors);
|
||||||
|
|
||||||
|
Assert.Equal("Text", modelState.Key);
|
||||||
|
Assert.Equal("Message", modelError.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddModelError_ForRelationExpression_AddsExpectedMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.AddModelError<TestModel>(model => model.Child.Text, "Message");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = Assert.Single(dictionary);
|
||||||
|
var modelError = Assert.Single(modelState.Value.Errors);
|
||||||
|
|
||||||
|
Assert.Equal("Child.Text", modelState.Key);
|
||||||
|
Assert.Equal("Message", modelError.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddModelError_ForImplicitlyCastedToObjectExpression_AddsExpectedMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.AddModelError<TestModel>(model => model.Child.Value, "Message");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = Assert.Single(dictionary);
|
||||||
|
var modelError = Assert.Single(modelState.Value.Errors);
|
||||||
|
|
||||||
|
Assert.Equal("Child.Value", modelState.Key);
|
||||||
|
Assert.Equal("Message", modelError.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddModelError_ForNotModelsExpression_AddsExpectedMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var variable = "Test";
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.AddModelError<TestModel>(model => variable, "Message");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = Assert.Single(dictionary);
|
||||||
|
var modelError = Assert.Single(modelState.Value.Errors);
|
||||||
|
|
||||||
|
Assert.Equal("variable", modelState.Key);
|
||||||
|
Assert.Equal("Message", modelError.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddModelError_ForSingleExpression_AddsExpectedException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var exception = new Exception();
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.AddModelError<TestModel>(model => model.Text, exception);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = Assert.Single(dictionary);
|
||||||
|
var modelError = Assert.Single(modelState.Value.Errors);
|
||||||
|
|
||||||
|
Assert.Equal("Text", modelState.Key);
|
||||||
|
Assert.Same(exception, modelError.Exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddModelError_ForRelationExpression_AddsExpectedException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var exception = new Exception();
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.AddModelError<TestModel>(model => model.Child.Text, exception);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = Assert.Single(dictionary);
|
||||||
|
var modelError = Assert.Single(modelState.Value.Errors);
|
||||||
|
|
||||||
|
Assert.Equal("Child.Text", modelState.Key);
|
||||||
|
Assert.Same(exception, modelError.Exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddModelError_ForImplicitlyCastedToObjectExpression_AddsExpectedException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var exception = new Exception();
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.AddModelError<TestModel>(model => model.Child.Value, exception);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = Assert.Single(dictionary);
|
||||||
|
var modelError = Assert.Single(modelState.Value.Errors);
|
||||||
|
|
||||||
|
Assert.Equal("Child.Value", modelState.Key);
|
||||||
|
Assert.Same(exception, modelError.Exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddModelError_ForNotModelsExpression_AddsExpectedException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var variable = "Test";
|
||||||
|
var exception = new Exception();
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.AddModelError<TestModel>(model => variable, exception);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = Assert.Single(dictionary);
|
||||||
|
var modelError = Assert.Single(modelState.Value.Errors);
|
||||||
|
|
||||||
|
Assert.Equal("variable", modelState.Key);
|
||||||
|
Assert.Same(exception, modelError.Exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Remove_ForSingleExpression_RemovesModelStateKey()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
dictionary.Add("Text", new ModelState());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.Remove<TestModel>(model => model.Text);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(dictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Remove_ForRelationExpression_RemovesModelStateKey()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
dictionary.Add("Child.Text", new ModelState());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.Remove<TestModel>(model => model.Child.Text);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(dictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Remove_ForImplicitlyCastedToObjectExpression_RemovesModelStateKey()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
dictionary.Add("Child.Value", new ModelState());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.Remove<TestModel>(model => model.Child.Value);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(dictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Remove_ForNotModelsExpression_RemovesModelStateKey()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var variable = "Test";
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
dictionary.Add("variable", new ModelState());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.Remove<TestModel>(model => variable);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(dictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RemoveAll_ForSingleExpression_RemovesModelStateKeys()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var state = new ModelState();
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
dictionary.Add("Key", state);
|
||||||
|
dictionary.Add("Text", new ModelState());
|
||||||
|
dictionary.Add("Text.Length", new ModelState());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.RemoveAll<TestModel>(model => model.Text);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = Assert.Single(dictionary);
|
||||||
|
|
||||||
|
Assert.Equal("Key", modelState.Key);
|
||||||
|
Assert.Same(state, modelState.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RemoveAll_ForRelationExpression_RemovesModelStateKeys()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var state = new ModelState();
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
dictionary.Add("Key", state);
|
||||||
|
dictionary.Add("Child", new ModelState());
|
||||||
|
dictionary.Add("Child.Text", new ModelState());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.RemoveAll<TestModel>(model => model.Child);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = Assert.Single(dictionary);
|
||||||
|
|
||||||
|
Assert.Equal("Key", modelState.Key);
|
||||||
|
Assert.Same(state, modelState.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RemoveAll_ForImplicitlyCastedToObjectExpression_RemovesModelStateKeys()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var state = new ModelState();
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
dictionary.Add("Child", state);
|
||||||
|
dictionary.Add("Child.Value", new ModelState());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.RemoveAll<TestModel>(model => model.Child.Value);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = Assert.Single(dictionary);
|
||||||
|
|
||||||
|
Assert.Equal("Child", modelState.Key);
|
||||||
|
Assert.Same(state, modelState.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RemoveAll_ForNotModelsExpression_RemovesModelStateKeys()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var variable = "Test";
|
||||||
|
var state = new ModelState();
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
dictionary.Add("Key", state);
|
||||||
|
dictionary.Add("variable", new ModelState());
|
||||||
|
dictionary.Add("variable.Text", new ModelState());
|
||||||
|
dictionary.Add("variable.Value", new ModelState());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.RemoveAll<TestModel>(model => variable);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = Assert.Single(dictionary);
|
||||||
|
|
||||||
|
Assert.Equal("Key", modelState.Key);
|
||||||
|
Assert.Same(state, modelState.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RemoveAll_ForModelExpression_RemovesModelPropertyKeys()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var state = new ModelState();
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
dictionary.Add("Key", state);
|
||||||
|
dictionary.Add("Text", new ModelState());
|
||||||
|
dictionary.Add("Child", new ModelState());
|
||||||
|
dictionary.Add("Child.Text", new ModelState());
|
||||||
|
dictionary.Add("Child.NoValue", new ModelState());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.RemoveAll<TestModel>(model => model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = Assert.Single(dictionary);
|
||||||
|
|
||||||
|
Assert.Equal("Key", modelState.Key);
|
||||||
|
Assert.Same(state, modelState.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestModel
|
||||||
|
{
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
public ChildModel Child { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ChildModel
|
||||||
|
{
|
||||||
|
public int Value { get; set; }
|
||||||
|
public string Text { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue