Last part of #3676

Uses the correct IEnumerable<> in validation strategies
This commit is contained in:
Ryan Nowak 2015-12-11 15:01:34 -08:00
parent 8a310b35a4
commit ee6ef3f25f
4 changed files with 141 additions and 10 deletions

View File

@ -4,6 +4,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
@ -36,6 +37,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
/// </remarks>
public class DefaultCollectionValidationStrategy : IValidationStrategy
{
private static readonly MethodInfo _getEnumerator = typeof(DefaultCollectionValidationStrategy)
.GetMethod(nameof(GetEnumerator), BindingFlags.Static | BindingFlags.NonPublic);
/// <summary>
/// Gets an instance of <see cref="DefaultCollectionValidationStrategy"/>.
/// </summary>
@ -51,14 +55,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
string key,
object model)
{
return new Enumerator(metadata.ElementMetadata, key, (IEnumerable)model);
var enumerator = GetEnumeratorForElementType(metadata, model);
return new Enumerator(metadata.ElementMetadata, key, enumerator);
}
public static IEnumerator GetEnumeratorForElementType(ModelMetadata metadata, object model)
{
var getEnumeratorMethod = _getEnumerator.MakeGenericMethod(metadata.ElementType);
return (IEnumerator)getEnumeratorMethod.Invoke(null, new object[] { model });
}
// Called via reflection.
private static IEnumerator GetEnumerator<T>(object model)
{
return (model as IEnumerable<T>)?.GetEnumerator() ?? ((IEnumerable)model).GetEnumerator();
}
private class Enumerator : IEnumerator<ValidationEntry>
{
private readonly string _key;
private readonly ModelMetadata _metadata;
private readonly IEnumerable _model;
private readonly IEnumerator _enumerator;
private ValidationEntry _entry;
@ -67,13 +83,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
public Enumerator(
ModelMetadata metadata,
string key,
IEnumerable model)
IEnumerator enumerator)
{
_metadata = metadata;
_key = key;
_model = model;
_enumerator = _model.GetEnumerator();
_enumerator = enumerator;
_index = -1;
}
@ -116,7 +130,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
public void Reset()
{
throw new NotImplementedException();
_enumerator.Reset();
}
}
}

View File

@ -54,7 +54,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
string key,
object model)
{
return new Enumerator(metadata.ElementMetadata, key, ElementKeys, (IEnumerable)model);
var enumerator = DefaultCollectionValidationStrategy.GetEnumeratorForElementType(metadata, model);
return new Enumerator(metadata.ElementMetadata, key, ElementKeys, enumerator);
}
private class Enumerator : IEnumerator<ValidationEntry>
@ -70,13 +71,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
ModelMetadata metadata,
string key,
IEnumerable<string> elementKeys,
IEnumerable model)
IEnumerator enumerator)
{
_metadata = metadata;
_key = key;
_keyEnumerator = elementKeys.GetEnumerator();
_enumerator = model.GetEnumerator();
_enumerator = enumerator;
}
public ValidationEntry Current

View File

@ -1,6 +1,8 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using Xunit;
@ -84,6 +86,62 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
});
}
[Fact]
public void EnumerateElements_TwoEnumerableImplemenations()
{
// Arrange
var model = new TwiceEnumerable(new int[] { 2, 3, 5 });
var metadata = TestModelMetadataProvider.CreateDefaultProvider().GetMetadataForType(typeof(TwiceEnumerable));
var strategy = DefaultCollectionValidationStrategy.Instance;
// Act
var enumerator = strategy.GetChildren(metadata, "prefix", model);
// Assert
Assert.Collection(
BufferEntries(enumerator).OrderBy(e => e.Key),
e =>
{
Assert.Equal("prefix[0]", e.Key);
Assert.Equal(2, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[1]", e.Key);
Assert.Equal(3, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[2]", e.Key);
Assert.Equal(5, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
});
}
// 'int' is chosen by validation because it's declared on the more derived type.
private class TwiceEnumerable : List<string>, IEnumerable<int>
{
private readonly IEnumerable<int> _enumerable;
public TwiceEnumerable(IEnumerable<int> enumerable)
{
_enumerable = enumerable;
}
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
return _enumerable.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new InvalidOperationException();
}
}
private List<ValidationEntry> BufferEntries(IEnumerator<ValidationEntry> enumerator)
{
var entries = new List<ValidationEntry>();

View File

@ -1,6 +1,8 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using Xunit;
@ -85,6 +87,41 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
});
}
[Fact]
public void EnumerateElements_TwoEnumerableImplemenations()
{
// Arrange
var model = new TwiceEnumerable(new int[] { 2, 3, 5 });
var metadata = TestModelMetadataProvider.CreateDefaultProvider().GetMetadataForType(typeof(TwiceEnumerable));
var strategy = new ExplicitIndexCollectionValidationStrategy(new string[] { "zero", "one", "two" });
// Act
var enumerator = strategy.GetChildren(metadata, "prefix", model);
// Assert
Assert.Collection(
BufferEntries(enumerator).OrderBy(e => e.Key),
e =>
{
Assert.Equal("prefix[one]", e.Key);
Assert.Equal(3, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[two]", e.Key);
Assert.Equal(5, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[zero]", e.Key);
Assert.Equal(2, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
});
}
[Fact]
public void EnumerateElements_RunOutOfIndices()
{
@ -145,6 +182,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
});
}
// 'int' is chosen by validation because it's declared on the more derived type.
private class TwiceEnumerable : List<string>, IEnumerable<int>
{
private readonly IEnumerable<int> _enumerable;
public TwiceEnumerable(IEnumerable<int> enumerable)
{
_enumerable = enumerable;
}
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
return _enumerable.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new InvalidOperationException();
}
}
private List<ValidationEntry> BufferEntries(IEnumerator<ValidationEntry> enumerator)
{
var entries = new List<ValidationEntry>();