Merge branch 'release/3.0' => 'release/3.1' (#13653)
This commit is contained in:
commit
6e88aa200c
|
|
@ -16,7 +16,8 @@
|
||||||
<IsPackable Condition="'$(IsAspNetCoreApp)' == 'true' AND '$(IsShippingPackage)' != 'true'">false</IsPackable>
|
<IsPackable Condition="'$(IsAspNetCoreApp)' == 'true' AND '$(IsShippingPackage)' != 'true'">false</IsPackable>
|
||||||
|
|
||||||
<!-- Only build assemblies in Microsoft.AspNetCore.App in source build -->
|
<!-- Only build assemblies in Microsoft.AspNetCore.App in source build -->
|
||||||
<ExcludeFromSourceBuild Condition="'$(ExcludeFromSourceBuild)' == '' and '$(DotNetBuildFromSource)' == 'true' and '$(IsAspNetCoreApp)' != 'true'">true</ExcludeFromSourceBuild>
|
<!-- Analyzer package are needed in source build for WebSDK -->
|
||||||
|
<ExcludeFromSourceBuild Condition="'$(ExcludeFromSourceBuild)' == '' and '$(DotNetBuildFromSource)' == 'true' and '$(IsAspNetCoreApp)' != 'true' and '$(IsAnalyzersProject)' != 'true'">true</ExcludeFromSourceBuild>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Label="Resx settings">
|
<PropertyGroup Label="Resx settings">
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,10 @@ and are generated based on the last package release.
|
||||||
<LatestPackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCorePackageVersion)" />
|
<LatestPackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCorePackageVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Label="External dependencies available in source build" >
|
||||||
|
<LatestPackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Label="External dependencies" Condition="'$(DotNetBuildFromSource)' != 'true'">
|
<ItemGroup Label="External dependencies" Condition="'$(DotNetBuildFromSource)' != 'true'">
|
||||||
<LatestPackageReference Include="AngleSharp" Version="$(AngleSharpPackageVersion)" />
|
<LatestPackageReference Include="AngleSharp" Version="$(AngleSharpPackageVersion)" />
|
||||||
<LatestPackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetPackageVersion)" />
|
<LatestPackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetPackageVersion)" />
|
||||||
|
|
@ -171,7 +175,6 @@ and are generated based on the last package release.
|
||||||
<LatestPackageReference Include="Mono.Cecil" Version="$(MonoCecilPackageVersion)" />
|
<LatestPackageReference Include="Mono.Cecil" Version="$(MonoCecilPackageVersion)" />
|
||||||
<LatestPackageReference Include="Mono.WebAssembly.Interop" Version="$(MonoWebAssemblyInteropPackageVersion)" />
|
<LatestPackageReference Include="Mono.WebAssembly.Interop" Version="$(MonoWebAssemblyInteropPackageVersion)" />
|
||||||
<LatestPackageReference Include="Moq" Version="$(MoqPackageVersion)" />
|
<LatestPackageReference Include="Moq" Version="$(MoqPackageVersion)" />
|
||||||
<LatestPackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
|
|
||||||
<LatestPackageReference Include="Newtonsoft.Json.Bson" Version="$(NewtonsoftJsonBsonPackageVersion)" />
|
<LatestPackageReference Include="Newtonsoft.Json.Bson" Version="$(NewtonsoftJsonBsonPackageVersion)" />
|
||||||
<LatestPackageReference Include="NSwag.ApiDescription.Client" Version="$(NSwagApiDescriptionClientPackageVersion)" />
|
<LatestPackageReference Include="NSwag.ApiDescription.Client" Version="$(NSwagApiDescriptionClientPackageVersion)" />
|
||||||
<LatestPackageReference Include="Selenium.Support" Version="$(SeleniumSupportPackageVersion)" />
|
<LatestPackageReference Include="Selenium.Support" Version="$(SeleniumSupportPackageVersion)" />
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,7 @@
|
||||||
<MoqPackageVersion>4.10.0</MoqPackageVersion>
|
<MoqPackageVersion>4.10.0</MoqPackageVersion>
|
||||||
<MonoCecilPackageVersion>0.10.1</MonoCecilPackageVersion>
|
<MonoCecilPackageVersion>0.10.1</MonoCecilPackageVersion>
|
||||||
<NewtonsoftJsonBsonPackageVersion>1.0.2</NewtonsoftJsonBsonPackageVersion>
|
<NewtonsoftJsonBsonPackageVersion>1.0.2</NewtonsoftJsonBsonPackageVersion>
|
||||||
<NewtonsoftJsonPackageVersion>12.0.1</NewtonsoftJsonPackageVersion>
|
<NewtonsoftJsonPackageVersion>12.0.2</NewtonsoftJsonPackageVersion>
|
||||||
<NSwagApiDescriptionClientPackageVersion>13.0.4</NSwagApiDescriptionClientPackageVersion>
|
<NSwagApiDescriptionClientPackageVersion>13.0.4</NSwagApiDescriptionClientPackageVersion>
|
||||||
<SeleniumSupportPackageVersion>3.12.1</SeleniumSupportPackageVersion>
|
<SeleniumSupportPackageVersion>3.12.1</SeleniumSupportPackageVersion>
|
||||||
<SeleniumWebDriverMicrosoftDriverPackageVersion>17.17134.0</SeleniumWebDriverMicrosoftDriverPackageVersion>
|
<SeleniumWebDriverMicrosoftDriverPackageVersion>17.17134.0</SeleniumWebDriverMicrosoftDriverPackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests
|
||||||
|
|
||||||
public ShutdownTests(ITestOutputHelper output) : base(output) { }
|
public ShutdownTests(ITestOutputHelper output) : base(output) { }
|
||||||
|
|
||||||
[ConditionalFact]
|
[ConditionalFact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/2577")]
|
||||||
[OSSkipCondition(OperatingSystems.Windows)]
|
[OSSkipCondition(OperatingSystems.Windows)]
|
||||||
[OSSkipCondition(OperatingSystems.MacOSX)]
|
[OSSkipCondition(OperatingSystems.MacOSX)]
|
||||||
[Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2577", FlakyOn.All)]
|
[Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2577", FlakyOn.All)]
|
||||||
|
|
|
||||||
|
|
@ -333,7 +333,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
|
||||||
var contextAttributes = context.Attributes;
|
var contextAttributes = context.Attributes;
|
||||||
var contextAttributesCount = contextAttributes.Count;
|
var contextAttributesCount = contextAttributes.Count;
|
||||||
var attributes = new List<object>(contextAttributesCount);
|
var attributes = new List<object>(contextAttributesCount);
|
||||||
|
|
||||||
for (var i = 0; i < contextAttributesCount; i++)
|
for (var i = 0; i < contextAttributesCount; i++)
|
||||||
{
|
{
|
||||||
var attribute = contextAttributes[i];
|
var attribute = contextAttributes[i];
|
||||||
|
|
@ -367,15 +367,15 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
|
||||||
else if (context.Key.MetadataKind == ModelMetadataKind.Property)
|
else if (context.Key.MetadataKind == ModelMetadataKind.Property)
|
||||||
{
|
{
|
||||||
addInferredRequiredAttribute = IsNullableReferenceType(
|
addInferredRequiredAttribute = IsNullableReferenceType(
|
||||||
context.Key.ContainerType,
|
context.Key.ContainerType,
|
||||||
member: null,
|
member: null,
|
||||||
context.PropertyAttributes);
|
context.PropertyAttributes);
|
||||||
}
|
}
|
||||||
else if (context.Key.MetadataKind == ModelMetadataKind.Parameter)
|
else if (context.Key.MetadataKind == ModelMetadataKind.Parameter)
|
||||||
{
|
{
|
||||||
addInferredRequiredAttribute = IsNullableReferenceType(
|
addInferredRequiredAttribute = IsNullableReferenceType(
|
||||||
context.Key.ParameterInfo?.Member.ReflectedType,
|
context.Key.ParameterInfo?.Member.ReflectedType,
|
||||||
context.Key.ParameterInfo.Member,
|
context.Key.ParameterInfo.Member,
|
||||||
context.ParameterAttributes);
|
context.ParameterAttributes);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -494,6 +494,15 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
|
||||||
|
|
||||||
internal static bool IsNullableBasedOnContext(Type containingType, MemberInfo member)
|
internal static bool IsNullableBasedOnContext(Type containingType, MemberInfo member)
|
||||||
{
|
{
|
||||||
|
// For generic types, inspecting the nullability requirement additionally requires
|
||||||
|
// inspecting the nullability constraint on generic type parameters. This is fairly non-triviial
|
||||||
|
// so we'll just avoid calculating it. Users should still be able to apply an explicit [Required]
|
||||||
|
// attribute on these members.
|
||||||
|
if (containingType.IsGenericType)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// The [Nullable] and [NullableContext] attributes are not inherited.
|
// The [Nullable] and [NullableContext] attributes are not inherited.
|
||||||
//
|
//
|
||||||
// The [NullableContext] attribute can appear on a method or on the module.
|
// The [NullableContext] attribute can appear on a method or on the module.
|
||||||
|
|
@ -516,7 +525,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
|
||||||
}
|
}
|
||||||
|
|
||||||
type = type.DeclaringType;
|
type = type.DeclaringType;
|
||||||
}
|
}
|
||||||
while (type != null);
|
while (type != null);
|
||||||
|
|
||||||
// If we don't find the attribute on the declaring type then repeat at the module level
|
// If we don't find the attribute on the declaring type then repeat at the module level
|
||||||
|
|
|
||||||
|
|
@ -1339,6 +1339,38 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsNullableReferenceType_ReturnsFalse_ForKeyValuePairWithoutNullableConstraints()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(KeyValuePair<string, object>);
|
||||||
|
var property = type.GetProperty(nameof(KeyValuePair<string, object>.Key));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DataAnnotationsMetadataProvider.IsNullableReferenceType(type, member: null, property.GetCustomAttributes(inherit: true));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
[Fact]
|
||||||
|
public void IsNullableReferenceType_ReturnsTrue_ForKeyValuePairWithNullableConstraints()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var type = typeof(KeyValuePair<string, object>);
|
||||||
|
var property = type.GetProperty(nameof(KeyValuePair<string, object>.Key))!;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DataAnnotationsMetadataProvider.IsNullableReferenceType(type, member: null, property.GetCustomAttributes(inherit: true));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// While we'd like for result to be 'true', we don't have a very good way of actually calculating it correctly.
|
||||||
|
// This test is primarily here to document the behavior.
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
#nullable restore
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsNonNullable_FindsNullableProperty()
|
public void IsNonNullable_FindsNullableProperty()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1126,6 +1126,105 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
Assert.Equal(expectedMessage, exception.Message);
|
Assert.Equal(expectedMessage, exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CollectionModelBinder_CollectionOfSimpleTypes_DoesNotResultInValidationError()
|
||||||
|
{
|
||||||
|
// Regression test for https://github.com/aspnet/AspNetCore/issues/13512
|
||||||
|
// Arrange
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "parameter",
|
||||||
|
ParameterType = typeof(Collection<string>),
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(
|
||||||
|
request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?[0]=hello&[1]=");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
var metadata = testContext.MetadataProvider.GetMetadataForType(parameter.ParameterType);
|
||||||
|
var valueProvider = await CompositeValueProvider.CreateAsync(testContext);
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await parameterBinder.BindModelAsync(parameter, testContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
Assert.Equal(0, modelState.ErrorCount);
|
||||||
|
|
||||||
|
Assert.True(result.IsModelSet);
|
||||||
|
var model = Assert.IsType<Collection<string>>(result.Model);
|
||||||
|
Assert.Collection(
|
||||||
|
model,
|
||||||
|
item => Assert.Equal("hello", item),
|
||||||
|
item => Assert.Null(item));
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
modelState,
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("[0]", kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, kvp.Value.ValidationState);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("[1]", kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, kvp.Value.ValidationState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CollectionModelBinder_CollectionOfNonNullableTypes_AppliesImplicitRequired()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "parameter",
|
||||||
|
ParameterType = typeof(Collection<string>),
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(
|
||||||
|
request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?[0]=hello&[1]=");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
var metadata = testContext.MetadataProvider.GetMetadataForType(parameter.ParameterType);
|
||||||
|
var valueProvider = await CompositeValueProvider.CreateAsync(testContext);
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await parameterBinder.BindModelAsync(parameter, testContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
Assert.Equal(0, modelState.ErrorCount);
|
||||||
|
|
||||||
|
Assert.True(result.IsModelSet);
|
||||||
|
var model = Assert.IsType<Collection<string>>(result.Model);
|
||||||
|
Assert.Collection(
|
||||||
|
model,
|
||||||
|
item => Assert.Equal("hello", item),
|
||||||
|
item => Assert.Null(item));
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
modelState,
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("[0]", kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, kvp.Value.ValidationState);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("[1]", kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, kvp.Value.ValidationState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private class ClosedGenericCollection : Collection<string>
|
private class ClosedGenericCollection : Collection<string>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
|
@ -1162,6 +1163,211 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
Assert.Equal(expectedMessage, exception.Message);
|
Assert.Equal(expectedMessage, exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DictionaryModelBinder_DictionaryOfSimpleType_NullValue_DoesNotResultInRequiredValidation()
|
||||||
|
{
|
||||||
|
// Regression test for https://github.com/aspnet/AspNetCore/issues/13512
|
||||||
|
// Arrange
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "parameter",
|
||||||
|
ParameterType = typeof(Dictionary<string, string>)
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?parameter[key0]=");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
var model = Assert.IsType<Dictionary<string, string>>(modelBindingResult.Model);
|
||||||
|
Assert.Collection(
|
||||||
|
model.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("key0", kvp.Key);
|
||||||
|
Assert.Null(kvp.Value);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
modelState.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("parameter[key0]", kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, kvp.Value.ValidationState);
|
||||||
|
});
|
||||||
|
Assert.Equal(0, modelState.ErrorCount);
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
public class NonNullPerson
|
||||||
|
{
|
||||||
|
public int Age { get; set; }
|
||||||
|
|
||||||
|
// This should be implicitly required
|
||||||
|
public string Name { get; set; } = default!;
|
||||||
|
}
|
||||||
|
#nullable restore
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DictionaryModelBinder_ValuesIsNonNullableType_AppliesImplicitRequired()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "parameter",
|
||||||
|
ParameterType = typeof(Dictionary<string, NonNullPerson>)
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?parameter[key0].Age=¶meter[key0].Name=name0¶meter[key1].Age=27¶meter[key1].Name=");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
var model = Assert.IsType<Dictionary<string, NonNullPerson>>(modelBindingResult.Model);
|
||||||
|
Assert.Collection(
|
||||||
|
model.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("key0", kvp.Key);
|
||||||
|
var person = kvp.Value;
|
||||||
|
Assert.Equal(0, person.Age);
|
||||||
|
Assert.Equal("name0", person.Name);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("key1", kvp.Key);
|
||||||
|
var person = kvp.Value;
|
||||||
|
Assert.Equal(27, person.Age);
|
||||||
|
Assert.Null(person.Name);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
modelState.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("parameter[key0].Age", kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, kvp.Value.ValidationState);
|
||||||
|
Assert.Equal("The value '' is invalid.", Assert.Single(kvp.Value.Errors).ErrorMessage);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("parameter[key0].Name", kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, kvp.Value.ValidationState);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("parameter[key1].Age", kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, kvp.Value.ValidationState);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("parameter[key1].Name", kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, kvp.Value.ValidationState);
|
||||||
|
Assert.Equal("The Name field is required.", Assert.Single(kvp.Value.Errors).ErrorMessage);
|
||||||
|
});
|
||||||
|
Assert.Equal(2, modelState.ErrorCount);
|
||||||
|
Assert.False(modelState.IsValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
public class NonNullPersonWithRequiredProperties
|
||||||
|
{
|
||||||
|
public int Age { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string? Name { get; set; }
|
||||||
|
}
|
||||||
|
#nullable restore
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DictionaryModelBinder_ValuesNullableTypeWithRequiredAttributes_AppliesValidation()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "parameter",
|
||||||
|
ParameterType = typeof(Dictionary<string, NonNullPersonWithRequiredProperties>)
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?parameter[key0].Age=¶meter[key0].Name=name0¶meter[key1].Age=27¶meter[key1].Name=");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
var model = Assert.IsType<Dictionary<string, NonNullPersonWithRequiredProperties>>(modelBindingResult.Model);
|
||||||
|
Assert.Collection(
|
||||||
|
model.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("key0", kvp.Key);
|
||||||
|
var person = kvp.Value;
|
||||||
|
Assert.Equal(0, person.Age);
|
||||||
|
Assert.Equal("name0", person.Name);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("key1", kvp.Key);
|
||||||
|
var person = kvp.Value;
|
||||||
|
Assert.Equal(27, person.Age);
|
||||||
|
Assert.Null(person.Name);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
modelState.OrderBy(kvp => kvp.Key),
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("parameter[key0].Age", kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, kvp.Value.ValidationState);
|
||||||
|
Assert.Equal("The value '' is invalid.", Assert.Single(kvp.Value.Errors).ErrorMessage);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("parameter[key0].Name", kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, kvp.Value.ValidationState);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("parameter[key1].Age", kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, kvp.Value.ValidationState);
|
||||||
|
},
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("parameter[key1].Name", kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, kvp.Value.ValidationState);
|
||||||
|
Assert.Equal("The Name field is required.", Assert.Single(kvp.Value.Errors).ErrorMessage);
|
||||||
|
});
|
||||||
|
Assert.Equal(2, modelState.ErrorCount);
|
||||||
|
Assert.False(modelState.IsValid);
|
||||||
|
}
|
||||||
|
|
||||||
private class ClosedGenericDictionary : Dictionary<string, string>
|
private class ClosedGenericDictionary : Dictionary<string, string>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
<PackAsTool>true</PackAsTool>
|
<PackAsTool>true</PackAsTool>
|
||||||
<!-- This package is for internal use only. It contains a CLI which is bundled in the .NET Core SDK. -->
|
<!-- This package is for internal use only. It contains a CLI which is bundled in the .NET Core SDK. -->
|
||||||
<IsShippingPackage>false</IsShippingPackage>
|
<IsShippingPackage>false</IsShippingPackage>
|
||||||
|
<ExcludeFromSourceBuild>false</ExcludeFromSourceBuild>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
<PackAsTool>true</PackAsTool>
|
<PackAsTool>true</PackAsTool>
|
||||||
<!-- This package is for internal use only. It contains a CLI which is bundled in the .NET Core SDK. -->
|
<!-- This package is for internal use only. It contains a CLI which is bundled in the .NET Core SDK. -->
|
||||||
<IsShippingPackage>false</IsShippingPackage>
|
<IsShippingPackage>false</IsShippingPackage>
|
||||||
|
<ExcludeFromSourceBuild>false</ExcludeFromSourceBuild>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
<PackAsTool>true</PackAsTool>
|
<PackAsTool>true</PackAsTool>
|
||||||
<!-- This package is for internal use only. It contains a CLI which is bundled in the .NET Core SDK. -->
|
<!-- This package is for internal use only. It contains a CLI which is bundled in the .NET Core SDK. -->
|
||||||
<IsShippingPackage>false</IsShippingPackage>
|
<IsShippingPackage>false</IsShippingPackage>
|
||||||
|
<ExcludeFromSourceBuild>false</ExcludeFromSourceBuild>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue