Simple ModelBinders and Simple ModelBinder Poco - 1

Covers simple scenario for each model binder.
Covers scenarios mixing a POCO model binder -> Simple Model binder.

This contains tests for
HeaderModel
ServicesModelBinder
CancellationTokenModelBinder
ByteArrayModelBinder
FormFileModelBinder

Part 2 Will contain similar tests for
FormCollectionModelBinder
BinderTypeBasedModelBinder
TypeConverterModelBinder
TypeMatchModelBinder
Any leftovers for BodyModelBinder
This commit is contained in:
Harsh Gupta 2015-04-28 11:39:21 -07:00
parent 30e54609cc
commit b5b37265e1
8 changed files with 927 additions and 43 deletions

30
Mvc.sln
View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.22822.1
VisualStudioVersion = 14.0.22810.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@ -15,6 +15,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Razor"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Core", "src\Microsoft.AspNet.Mvc.Core\Microsoft.AspNet.Mvc.Core.xproj", "{C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.ModelBinding", "src\Microsoft.AspNet.Mvc.ModelBinding\Microsoft.AspNet.Mvc.ModelBinding.xproj", "{FA915D3D-22C3-4478-97F2-A81D28B6C503}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Common", "src\Microsoft.AspNet.Mvc.Common\Microsoft.AspNet.Mvc.Common.xproj", "{F3DF6D0B-16FE-4402-B92C-7243A75CF1FD}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Razor.Test", "test\Microsoft.AspNet.Mvc.Razor.Test\Microsoft.AspNet.Mvc.Razor.Test.xproj", "{3F6E355E-4869-41D9-943B-D54771221A7F}"
@ -162,8 +164,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Integr
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Abstractions", "src\Microsoft.AspNet.Mvc.Abstractions\Microsoft.AspNet.Mvc.Abstractions.xproj", "{1154203C-7579-4525-906E-BC55268421C1}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Abstractions.Test", "test\Microsoft.AspNet.Mvc.Abstractions.Test\Microsoft.AspNet.Mvc.Abstractions.Test.xproj", "{DA000953-7532-4DF5-8DB9-8143DF98D999}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.ApiExplorer", "src\Microsoft.AspNet.Mvc.ApiExplorer\Microsoft.AspNet.Mvc.ApiExplorer.xproj", "{A2B72833-5D70-4C42-AE85-E0319926FB8A}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.ApiExplorer.Test", "test\Microsoft.AspNet.Mvc.ApiExplorer.Test\Microsoft.AspNet.Mvc.ApiExplorer.Test.xproj", "{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}"
@ -208,6 +208,16 @@ Global
{C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Release|x86.ActiveCfg = Release|Any CPU
{FA915D3D-22C3-4478-97F2-A81D28B6C503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA915D3D-22C3-4478-97F2-A81D28B6C503}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA915D3D-22C3-4478-97F2-A81D28B6C503}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{FA915D3D-22C3-4478-97F2-A81D28B6C503}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{FA915D3D-22C3-4478-97F2-A81D28B6C503}.Debug|x86.ActiveCfg = Debug|Any CPU
{FA915D3D-22C3-4478-97F2-A81D28B6C503}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA915D3D-22C3-4478-97F2-A81D28B6C503}.Release|Any CPU.Build.0 = Release|Any CPU
{FA915D3D-22C3-4478-97F2-A81D28B6C503}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{FA915D3D-22C3-4478-97F2-A81D28B6C503}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{FA915D3D-22C3-4478-97F2-A81D28B6C503}.Release|x86.ActiveCfg = Release|Any CPU
{F3DF6D0B-16FE-4402-B92C-7243A75CF1FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3DF6D0B-16FE-4402-B92C-7243A75CF1FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3DF6D0B-16FE-4402-B92C-7243A75CF1FD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@ -974,18 +984,6 @@ Global
{1154203C-7579-4525-906E-BC55268421C1}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{1154203C-7579-4525-906E-BC55268421C1}.Release|x86.ActiveCfg = Release|Any CPU
{1154203C-7579-4525-906E-BC55268421C1}.Release|x86.Build.0 = Release|Any CPU
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|x86.ActiveCfg = Debug|Any CPU
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|x86.Build.0 = Debug|Any CPU
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Any CPU.Build.0 = Release|Any CPU
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|x86.ActiveCfg = Release|Any CPU
{DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|x86.Build.0 = Release|Any CPU
{A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@ -1018,6 +1016,7 @@ Global
{079EFA1F-0B0A-4853-B27B-5780D111CD85} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{314E9AD6-2FFC-4A92-A8AD-510658C64F1E} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{C48DA9D7-ACB5-4408-AA79-27ECB60A67EF} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{FA915D3D-22C3-4478-97F2-A81D28B6C503} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{F3DF6D0B-16FE-4402-B92C-7243A75CF1FD} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{3F6E355E-4869-41D9-943B-D54771221A7F} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{A8AA326E-8EE8-4F11-B750-23028E0949D7} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
@ -1089,7 +1088,6 @@ Global
{DAB1252D-577C-4912-98BE-1A812BF83F86} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{864FA09D-1E48-403A-A6C8-4F079D2A30F0} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{1154203C-7579-4525-906E-BC55268421C1} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{DA000953-7532-4DF5-8DB9-8143DF98D999} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{A2B72833-5D70-4C42-AE85-E0319926FB8A} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
EndGlobalSection

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
public string Street { get; set; }
}
[Fact]
[Fact(Skip = "Extra entries in model state #2446.")]
public async Task FromBodyAndRequiredOnProperty_EmptyBody_AddsModelStateError()
{
// Arrange
@ -42,11 +42,18 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(Person)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext();
var httpContext = operationContext.HttpContext;
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{ \"Id\":1234 }"));
request.ContentType = "application/json";
});
ConfigureHttpRequest(httpContext.Request, string.Empty);
var modelState = new ModelStateDictionary();
var actionContext = operationContext
.HttpContext
.RequestServices
.GetRequiredService<IScopedInstance<ActionContext>>().Value;
var modelState = actionContext.ModelState;
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
@ -79,10 +86,14 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(Person)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext();
var httpContext = operationContext.HttpContext;
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{ \"Id\":1234 }"));
request.ContentType = "application/json";
});
ConfigureHttpRequest(httpContext.Request, "{ \"Id\":1234 }");
var httpContext = operationContext.HttpContext;
var modelState = new ModelStateDictionary();
// Act
@ -122,9 +133,14 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(Person4)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext();
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(string.Empty));
request.ContentType = "application/json";
});
var httpContext = operationContext.HttpContext;
ConfigureHttpRequest(httpContext.Request, string.Empty);
var actionContext = httpContext.RequestServices.GetRequiredService<IScopedInstance<ActionContext>>().Value;
var modelState = actionContext.ModelState;
@ -177,9 +193,13 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(Person2)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext();
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(inputText));
request.ContentType = "application/json";
});
var httpContext = operationContext.HttpContext;
ConfigureHttpRequest(httpContext.Request, inputText);
var modelState = new ModelStateDictionary();
// Act
@ -231,9 +251,13 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(Person3)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext();
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(inputText));
request.ContentType = "application/json";
});
var httpContext = operationContext.HttpContext;
ConfigureHttpRequest(httpContext.Request, inputText);
var actionContext = httpContext.RequestServices.GetRequiredService<IScopedInstance<ActionContext>>().Value;
var modelState = actionContext.ModelState;
@ -258,11 +282,5 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
"Required property 'Zip' not found in JSON. Path ''",
error.Exception.Message);
}
private static void ConfigureHttpRequest(HttpRequest request, string jsonContent)
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
request.ContentType = "application/json";
}
}
}

View File

@ -0,0 +1,166 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
using Xunit;
namespace Microsoft.AspNet.Mvc.IntegrationTests
{
public class ByteArrayModelBinderIntegrationTest
{
private class Person
{
public byte[] Token { get; set; }
}
[Theory(Skip = "Extra entries in model state #2446, ModelState.Value not set due to #2445, #2447")]
[InlineData(true)]
[InlineData(false)]
public async Task BindProperty_WithData_GetsBound(bool fallBackScenario)
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo(),
ParameterType = typeof(Person)
};
var prefix = fallBackScenario ? string.Empty : "Parameter1";
var queryStringKey = fallBackScenario ? "Token" : prefix + "." + "Token";
// any valid base64 string
var expectedValue = new byte[] { 12, 13 };
var value = Convert.ToBase64String(expectedValue);
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
request =>
{
request.QueryString = new QueryString(queryStringKey, value);
});
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
// Model
var boundPerson = Assert.IsType<Person>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
Assert.NotNull(boundPerson.Token);
Assert.Equal(expectedValue, boundPerson.Token);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(2, modelState.Keys.Count); // Should be only two keys. bug #2446
Assert.Single(modelState.Keys, k => k == prefix);
Assert.Single(modelState.Keys, k => k == queryStringKey);
var key = Assert.Single(modelState.Keys, k => k == queryStringKey + "[0]");
Assert.NotNull(modelState[key].Value); // should be non null bug #2445.
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState); // Should be skipped. bug#2447
key = Assert.Single(modelState.Keys, k => k == queryStringKey + "[1]");
Assert.NotNull(modelState[key].Value); // should be non null bug #2445.
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState); // Should be skipped. bug#2447
}
[Fact]
public async Task BindParameter_NoData_DoesNotGetBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
{
BinderModelName = "CustomParameter",
},
ParameterType = typeof(byte[])
};
// No data is passed.
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(httpContext => { });
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.Null(modelBindingResult);
// ModelState
Assert.True(modelState.IsValid);
Assert.Empty(modelState.Keys);
}
[Fact(Skip = "ModelState.Value not set due to #2445, #2446")]
public async Task BindParameter_WithData_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
{
BinderModelName = "CustomParameter",
},
ParameterType = typeof(byte[])
};
// any valid base64 string
var value = "four";
var expectedValue = Convert.FromBase64String(value);
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
request =>
{
request.QueryString = new QueryString("CustomParameter", value);
});
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<byte[]>(modelBindingResult.Model);
// Model
Assert.Equal(expectedValue, model);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(3, modelState.Count);
Assert.Single(modelState.Keys, k => k == "CustomParameter[0]");
Assert.Single(modelState.Keys, k => k == "CustomParameter[1]");
var key = Assert.Single(modelState.Keys, k => k == "CustomParameter[2]");
Assert.NotNull(modelState[key].Value);
Assert.Equal(value, modelState[key].Value.AttemptedValue);
Assert.Equal(expectedValue, modelState[key].Value.RawValue);
Assert.Empty(modelState[key].Errors);
}
}
}

View File

@ -0,0 +1,152 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
using Xunit;
namespace Microsoft.AspNet.Mvc.IntegrationTests
{
public class CancellationTokenModelBinderIntegrationTest
{
private class Person
{
public CancellationToken Token { get; set; }
}
[Fact(Skip = "CancellationToken should not be validated #2447.")]
public async Task BindProperty_WithData__WithPrefix_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
{
BinderModelName = "CustomParameter",
},
ParameterType = typeof(Person)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(httpContext => { });
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
// Model
var boundPerson = Assert.IsType<Person>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
Assert.NotNull(boundPerson.Token);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(2, modelState.Keys.Count);
Assert.Single(modelState.Keys, k => k == "CustomParameter");
var key = Assert.Single(modelState.Keys, k => k == "CustomParameter.Token");
Assert.Null(modelState[key].Value);
Assert.Empty(modelState[key].Errors);
// This Assert Fails.
Assert.Equal(ModelValidationState.Skipped, modelState[key].ValidationState);
}
[Fact(Skip = "CancellationToken should not be validated #2447,Extra entries in model state dictionary. #2466")]
public async Task BindProperty_WithData__WithEmptyPrefix_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo(),
ParameterType = typeof(Person)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(httpContext => { });
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
// Model
var boundPerson = Assert.IsType<Person>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
Assert.NotNull(boundPerson.Token);
// ModelState
Assert.True(modelState.IsValid);
var key = Assert.Single(modelState.Keys);
Assert.Equal("Token", key);
Assert.Null(modelState[key].Value);
Assert.Empty(modelState[key].Errors);
// This Assert Fails.
Assert.Equal(ModelValidationState.Skipped, modelState[key].ValidationState);
}
[Fact(Skip = "CancellationToken should not be validated #2447.")]
public async Task BindParameter_WithData_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
{
BinderModelName = "CustomParameter",
},
ParameterType = typeof(CancellationToken)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(httpContext => { });
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
// Model
var token = Assert.IsType<CancellationToken>(modelBindingResult.Model);
Assert.NotNull(token);
// ModelState
Assert.True(modelState.IsValid);
var key = Assert.Single(modelState.Keys);
Assert.Equal("CustomParameter", key);
Assert.Null(modelState[key].Value);
Assert.Empty(modelState[key].Errors);
// This assert fails.
Assert.Equal(ModelValidationState.Skipped, modelState[key].ValidationState);
}
}
}

View File

@ -0,0 +1,181 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Collections;
using Microsoft.AspNet.Mvc.ModelBinding;
using Xunit;
namespace Microsoft.AspNet.Mvc.IntegrationTests
{
public class FormFileModelBindingIntegrationTest
{
private class Person
{
public Address Address { get; set; }
}
private class Address
{
public int Zip { get; set; }
public IFormFile File { get; set; }
}
[Fact(Skip = "ModelState.Value not set due to #2445, Extra entries in model state #2446.")]
public async Task BindProperty_WithData_WithEmptyPrefix_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo(),
ParameterType = typeof(Person)
};
var data = "Some Data Is Better Than No Data.";
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
request =>
{
request.QueryString = new QueryString("Address.Zip", "12345");
UpdateRequest(request, data, "Address.File");
});
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
// Model
var boundPerson = Assert.IsType<Person>(modelBindingResult.Model);
Assert.NotNull(boundPerson.Address);
var file = Assert.IsAssignableFrom<IFormFile>(boundPerson.Address.File);
Assert.Equal("form-data; name=Address.File; filename=text.txt", file.ContentDisposition);
var reader = new StreamReader(boundPerson.Address.File.OpenReadStream());
Assert.Equal(data, reader.ReadToEnd());
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(2, modelState.Count);
Assert.Single(modelState.Keys, k => k == "Address.Zip");
var key = Assert.Single(modelState.Keys, k => k == "Address.File"); // Should be only one key. bug #2446
Assert.NotNull(modelState[key].Value); // should be non null bug #2445.
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
}
[Fact(Skip = "Extra entries in model state #2446.")]
public async Task BindParameter_WithData_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
{
// Setting a custom parameter prevents it from falling back to an empty prefix.
BinderModelName = "CustomParameter",
},
ParameterType = typeof(IFormFile)
};
var data = "Some Data Is Better Than No Data.";
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
request =>
{
UpdateRequest(request, data, "CustomParameter");
});
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
// Model
var file = Assert.IsType<FormFile>(modelBindingResult.Model);
Assert.NotNull(file);
Assert.Equal("form-data; name=CustomParameter; filename=text.txt", file.ContentDisposition);
var reader = new StreamReader(file.OpenReadStream());
Assert.Equal(data, reader.ReadToEnd());
// ModelState
Assert.True(modelState.IsValid);
// Validation should be skipped because we do not validate any parameters and since IFormFile is not
// IValidatableObject, we should have no entries in the model state dictionary.
Assert.Empty(modelState.Keys); // Enable when we fix #2446.
}
[Fact(Skip = "Extra entries in model state #2446.")]
public async Task BindParameter_NoData_DoesNotGetBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
{
BinderModelName = "CustomParameter",
},
ParameterType = typeof(IFormFile)
};
// No data is passed.
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.ContentType = "multipart/form-data";
});
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult); // Fails due to bug #2456
Assert.Null(modelBindingResult.Model);
// ModelState
Assert.True(modelState.IsValid);
Assert.Empty(modelState.Keys);
}
private void UpdateRequest(HttpRequest request, string data, string name)
{
var fileCollection = new FormFileCollection();
var formCollection = new FormCollection(new Dictionary<string, string[]>(), fileCollection);
var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(data));
request.Form = formCollection;
request.ContentType = "multipart/form-data";
request.Headers["Content-Disposition"] = "form-data; name=" + name + "; filename=text.txt";
fileCollection.Add(new FormFile(memoryStream, 0, data.Length)
{
Headers = request.Headers
});
}
}
}

View File

@ -0,0 +1,213 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
using Xunit;
namespace Microsoft.AspNet.Mvc.IntegrationTests
{
public class HeaderModelBinderIntegrationTest
{
private class Person
{
public Address Address { get; set; }
}
private class Address
{
[FromHeader(Name = "Header")]
[Required]
public string Street { get; set; }
}
[Fact]
public async Task BindPropertyFromHeader_NoData_UsesFullPathAsKeyForModelStateErrors()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
{
BinderModelName = "CustomParameter",
},
ParameterType = typeof(Person)
};
// Do not add any headers.
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { });
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
// Model
var boundPerson = Assert.IsType<Person>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
// ModelState
Assert.False(modelState.IsValid);
var key = Assert.Single(modelState.Keys);
Assert.Equal("CustomParameter.Address.Header", key);
var error = Assert.Single(modelState[key].Errors);
Assert.Equal("The Street field is required.", error.ErrorMessage);
}
[Fact(Skip = "ModelState.Value not set due to #2445")]
public async Task BindPropertyFromHeader_WithPrefix_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
{
BinderModelName = "prefix",
},
ParameterType = typeof(Person)
};
// Do not add any headers.
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => {
request.Headers.Add("Header", new[] { "someValue" });
});
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
// Model
var boundPerson = Assert.IsType<Person>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
Assert.NotNull(boundPerson.Address);
Assert.Equal("someValue", boundPerson.Address.Street);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(3, modelState.Count);
Assert.Single(modelState.Keys, k => k == "prefix.Address");
Assert.Single(modelState.Keys, k => k == "prefix");
var key = Assert.Single(modelState.Keys, k => k == "prefix.Address.Header");
Assert.NotNull(modelState[key].Value);
Assert.Equal("someValue", modelState[key].Value.RawValue);
Assert.Equal("someValue", modelState[key].Value.AttemptedValue);
}
// The scenario is interesting as we to bind the top level model we fallback to empty prefix,
// and hence the model state keys have an empty prefix.
[Fact(Skip = "ModelState.Value not set due to #2445. ModelState should not have empty key #2466.")]
public async Task BindPropertyFromHeader_WithData_WithEmptyPrefix_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo(),
ParameterType = typeof(Person)
};
// Do not add any headers.
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => {
request.Headers.Add("Header", new[] { "someValue" });
});
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
// Model
var boundPerson = Assert.IsType<Person>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
Assert.NotNull(boundPerson.Address);
Assert.Equal("someValue", boundPerson.Address.Street);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(2, modelState.Count);
Assert.Single(modelState.Keys, k => k == "Address");
var key = Assert.Single(modelState.Keys, k => k == "Address.Header");
Assert.NotNull(modelState[key].Value);
Assert.Equal("someValue", modelState[key].Value.RawValue);
Assert.Equal("someValue", modelState[key].Value.AttemptedValue);
}
[Theory(Skip = "Extra entries in model state #2446.")]
[InlineData(typeof(string[]), "value1, value2, value3")]
[InlineData(typeof(string), "value")]
public async Task BindParameterFromHeader_WithData_WithPrefix_ModelGetsBound(Type modelType, string value)
{
// Arrange
var expectedValue = value.Split(',').Select(v => v.Trim());
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
{
BinderModelName = "CustomParameter",
BindingSource = BindingSource.Header
},
ParameterType = modelType
};
Action<HttpRequest> action = (r) => r.Headers.Add("CustomParameter", new[] { value });
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(action);
// Do not add any headers.
var httpContext = operationContext.HttpContext;
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
// Model
Assert.NotNull(modelBindingResult.Model);
Assert.IsType(modelType, modelBindingResult.Model);
// ModelState
Assert.True(modelState.IsValid);
var key = Assert.Single(modelState.Keys);
Assert.Equal("CustomParameter", key);
Assert.NotNull(modelState[key].Value);
Assert.Equal(expectedValue, modelState[key].Value.RawValue);
Assert.Equal(value, modelState[key].Value.AttemptedValue);
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
@ -11,19 +12,19 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
{
public static class ModelBindingTestHelper
{
public static OperationBindingContext GetOperationBindingContext()
public static OperationBindingContext GetOperationBindingContext(Action<HttpRequest> updateRequest)
{
var httpContext = ModelBindingTestHelper.GetHttpContext();
var actionBindingContextAccessor =
var httpContext = ModelBindingTestHelper.GetHttpContext(updateRequest);
var actionBindingContext =
httpContext.RequestServices.GetRequiredService<IScopedInstance<ActionBindingContext>>().Value;
return new OperationBindingContext()
{
BodyBindingState = BodyBindingState.NotBodyBased,
HttpContext = httpContext,
MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(),
ValidatorProvider = actionBindingContextAccessor.ValidatorProvider,
ValueProvider = actionBindingContextAccessor.ValueProvider,
ModelBinder = actionBindingContextAccessor.ModelBinder
ValidatorProvider = actionBindingContext.ValidatorProvider,
ValueProvider = actionBindingContext.ValueProvider,
ModelBinder = actionBindingContext.ModelBinder
};
}
@ -39,10 +40,13 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
metadataProvider));
}
public static HttpContext GetHttpContext()
public static HttpContext GetHttpContext(Action<HttpRequest> updateRequest)
{
var options = (new TestMvcOptions()).Options;
var httpContext = new DefaultHttpContext();
updateRequest(httpContext.Request);
var serviceCollection = MvcServices.GetDefaultServices();
httpContext.RequestServices = serviceCollection.BuildServiceProvider();

View File

@ -0,0 +1,152 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
using Xunit;
namespace Microsoft.AspNet.Mvc.IntegrationTests
{
public class ServicesModelBinderIntegrationTest
{
private class Person
{
public Address Address { get; set; }
}
private class Address
{
// Using a service type already in defaults.
[FromServices]
public JsonOutputFormatter OutputFormatter { get; set; }
}
[Fact]
public async Task BindPropertyFromService_WithData_WithPrefix_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
{
BinderModelName = "CustomParameter",
},
ParameterType = typeof(Person)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(httpContext => { });
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
// Model
var boundPerson = Assert.IsType<Person>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
Assert.NotNull(boundPerson.Address.OutputFormatter);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(3, modelState.Keys.Count);
Assert.Single(modelState.Keys, k => k == "CustomParameter");
Assert.Single(modelState.Keys, k => k == "CustomParameter.Address");
var key = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.OutputFormatter");
Assert.Equal(ModelValidationState.Skipped, modelState[key].ValidationState);
Assert.Null(modelState[key].Value);
Assert.Empty(modelState[key].Errors);
}
[Fact(Skip = "ModelState should not have empty key #2466.")]
public async Task BindPropertyFromService_WithData_WithEmptyPrefix_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo(),
ParameterType = typeof(Person)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(httpContext => { });
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
// Model
var boundPerson = Assert.IsType<Person>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
Assert.NotNull(boundPerson.Address.OutputFormatter);
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(2, modelState.Keys.Count);
Assert.Single(modelState.Keys, k => k == "Address");
var key = Assert.Single(modelState.Keys, k => k == "Address.OutputFormatter");
Assert.Equal(ModelValidationState.Skipped, modelState[key].ValidationState);
Assert.Null(modelState[key].Value); // For non user bound models there should be no value.
Assert.Empty(modelState[key].Errors);
}
[Fact(Skip = "#2464 ModelState should not have entry for non request bound models.")]
public async Task BindParameterFromService_WithData_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
{
BinderModelName = "CustomParameter",
BindingSource = BindingSource.Services
},
// Using a service type already in defaults.
ParameterType = typeof(JsonOutputFormatter)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(httpContext => { });
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
// Model
var outputFormatter = Assert.IsType<JsonOutputFormatter>(modelBindingResult.Model);
Assert.NotNull(outputFormatter);
// ModelState
Assert.True(modelState.IsValid);
Assert.Empty(modelState.Keys);
}
}
}