diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index af813aeb3e..f6ea5814ff 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22710.0 +VisualStudioVersion = 14.0.22808.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" EndProject @@ -64,6 +64,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.JsonPatch. EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.JsonPatch", "src\Microsoft.AspNet.JsonPatch\Microsoft.AspNet.JsonPatch.xproj", "{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.IntegrationTests", "test\Microsoft.AspNet.Mvc.IntegrationTests\Microsoft.AspNet.Mvc.IntegrationTests.xproj", "{864FA09D-1E48-403A-A6C8-4F079D2A30F0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -345,6 +347,18 @@ Global {4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|Mixed Platforms.Build.0 = Release|Any CPU {4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|x86.ActiveCfg = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|x86.ActiveCfg = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|x86.Build.0 = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Any CPU.Build.0 = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|x86.ActiveCfg = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -375,5 +389,6 @@ Global {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {81C20848-E063-4E12-AC40-0B55A532C16C} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {4D55F4D8-633B-462F-A5B1-FEB84BD2D534} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {864FA09D-1E48-403A-A6C8-4F079D2A30F0} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} EndGlobalSection EndGlobal diff --git a/Mvc.sln b/Mvc.sln index 487d3c64e4..917f504240 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22806.0 +VisualStudioVersion = 14.0.22808.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" EndProject @@ -162,6 +162,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.JsonPatch" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "JsonPatchWebSite", "test\WebSites\JsonPatchWebSite\JsonPatchWebSite.xproj", "{DAB1252D-577C-4912-98BE-1A812BF83F86}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.IntegrationTests", "test\Microsoft.AspNet.Mvc.IntegrationTests\Microsoft.AspNet.Mvc.IntegrationTests.xproj", "{864FA09D-1E48-403A-A6C8-4F079D2A30F0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -964,6 +966,18 @@ Global {DAB1252D-577C-4912-98BE-1A812BF83F86}.Release|Mixed Platforms.Build.0 = Release|Any CPU {DAB1252D-577C-4912-98BE-1A812BF83F86}.Release|x86.ActiveCfg = Release|Any CPU {DAB1252D-577C-4912-98BE-1A812BF83F86}.Release|x86.Build.0 = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|x86.ActiveCfg = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|x86.Build.0 = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Any CPU.Build.0 = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|x86.ActiveCfg = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1043,5 +1057,6 @@ Global {81C20848-E063-4E12-AC40-0B55A532C16C} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {4D55F4D8-633B-462F-A5B1-FEB84BD2D534} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {DAB1252D-577C-4912-98BE-1A812BF83F86} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {864FA09D-1E48-403A-A6C8-4F079D2A30F0} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} EndGlobalSection EndGlobal diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs index d272796a91..2957f1aa20 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs @@ -63,6 +63,41 @@ namespace Microsoft.AspNet.Mvc return actionArguments; } + public async Task BindModelAsync( + ParameterDescriptor parameter, + ModelStateDictionary modelState, + OperationBindingContext operationContext) + { + var metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterType); + var parameterType = parameter.ParameterType; + var modelBindingContext = GetModelBindingContext( + parameter.Name, + metadata, + parameter.BindingInfo, + modelState, + operationContext); + + var modelBindingResult = await operationContext.ModelBinder.BindModelAsync(modelBindingContext); + if (modelBindingResult != null && modelBindingResult.IsModelSet) + { + var key = modelBindingResult.Key; + var modelExplorer = new ModelExplorer( + _modelMetadataProvider, + metadata, + modelBindingResult.Model); + + var validationContext = new ModelValidationContext( + key, + modelBindingContext.BindingSource, + operationContext.ValidatorProvider, + modelState, + modelExplorer); + _validator.Validate(validationContext); + } + + return modelBindingResult; + } + private void ActivateProperties(object controller, Type containerType, Dictionary properties) { var propertyHelpers = PropertyHelper.GetProperties(controller); @@ -88,31 +123,10 @@ namespace Microsoft.AspNet.Mvc { foreach (var parameter in parameterMetadata) { - var metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterType); - var parameterType = parameter.ParameterType; - var modelBindingContext = GetModelBindingContext( - parameter.Name, - metadata, - parameter.BindingInfo, - modelState, - operationContext); - - var modelBindingResult = await operationContext.ModelBinder.BindModelAsync(modelBindingContext); + var modelBindingResult = await BindModelAsync(parameter, modelState, operationContext); if (modelBindingResult != null && modelBindingResult.IsModelSet) { - var modelExplorer = new ModelExplorer( - _modelMetadataProvider, - metadata, - modelBindingResult.Model); - arguments[parameter.Name] = modelBindingResult.Model; - var validationContext = new ModelValidationContext( - modelBindingResult.Key, - modelBindingContext.BindingSource, - operationContext.ValidatorProvider, - modelState, - modelExplorer); - _validator.Validate(validationContext); } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs index b233f20aee..bc09c834f1 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs @@ -51,8 +51,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { var model = await formatter.ReadAsync(formatterContext); - // key is empty to ensure that the model name is not used as a prefix for validation. - return new ModelBindingResult(model, key: string.Empty, isModelSet: true); + var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null; + + // For compatibility with MVC 5.0 for top level object we want to consider an empty key instead of + // the parameter name/a custom name. In all other cases (like when binding body to a property) we + // consider the entire ModelName as a prefix. + var modelBindingKey = isTopLevelObject ? string.Empty : bindingContext.ModelName; + return new ModelBindingResult(model, key: modelBindingKey, isModelSet: true); } catch (Exception ex) { diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultObjectValidator.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultObjectValidator.cs index f54ecadba8..bbe20104eb 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultObjectValidator.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultObjectValidator.cs @@ -187,6 +187,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var propertyBindingName = propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName; var childKey = ModelBindingHelper.CreatePropertyModelName(currentModelKey, propertyBindingName); + if (!ValidateNonVisitedNodeAndChildren( childKey, propertyValidationContext, diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs new file mode 100644 index 0000000000..63816190cf --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs @@ -0,0 +1,268 @@ +// 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.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.Framework.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNet.Mvc.IntegrationTests +{ + public class BodyValidationIntegrationTests + { + private class Person + { + [FromBody] + [Required] + public Address Address { get; set; } + } + + private class Address + { + public string Street { get; set; } + } + + [Fact] + public async Task FromBodyAndRequiredOnProperty_EmptyBody_AddsModelStateError() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "Parameter1", + BindingInfo = new BindingInfo() + { + BinderModelName = "CustomParameter", + }, + ParameterType = typeof(Person) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(); + var httpContext = operationContext.HttpContext; + + ConfigureHttpRequest(httpContext.Request, string.Empty); + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + var boundPerson = Assert.IsType(modelBindingResult.Model); + Assert.NotNull(boundPerson); + var key = Assert.Single(modelState.Keys); + Assert.Equal("CustomParameter.Address", key); + Assert.False(modelState.IsValid); + var error = Assert.Single(modelState[key].Errors); + Assert.Equal("The Address field is required.", error.ErrorMessage); + } + + [Fact] + public async Task FromBodyOnActionParameter_EmptyBody_AddsModelStateError() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "Parameter1", + BindingInfo = new BindingInfo() + { + BinderModelName = "CustomParameter", + BindingSource = BindingSource.Body + }, + ParameterType = typeof(Person) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(); + var httpContext = operationContext.HttpContext; + + ConfigureHttpRequest(httpContext.Request, "{ \"Id\":1234 }"); + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + var boundPerson = Assert.IsType(modelBindingResult.Model); + Assert.NotNull(boundPerson); + var key = Assert.Single(modelState.Keys); + Assert.Equal("Address", key); + Assert.False(modelState.IsValid); + var error = Assert.Single(modelState[key].Errors); + Assert.Equal("The Address field is required.",error.ErrorMessage); + } + + private class Person4 + { + [FromBody] + [Required] + public int Address { get; set; } + } + + [Fact] + public async Task FromBodyAndRequiredOnValueTypeProperty_EmptyBody_AddsModelStateError() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "Parameter1", + BindingInfo = new BindingInfo() + { + BinderModelName = "CustomParameter", + }, + ParameterType = typeof(Person4) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(); + var httpContext = operationContext.HttpContext; + ConfigureHttpRequest(httpContext.Request, string.Empty); + var actionContext = httpContext.RequestServices.GetRequiredService>().Value; + var modelState = actionContext.ModelState; + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + var boundPerson = Assert.IsType(modelBindingResult.Model); + Assert.NotNull(boundPerson); + Assert.False(modelState.IsValid); + + // The error with an empty key is a bug(#2416) in our implementation which does not append the prefix and + // use that along with the path. The expected key here would be CustomParameter.Address. + var key = Assert.Single(modelState.Keys, k => k == ""); + var error = Assert.Single(modelState[""].Errors); + Assert.StartsWith( + "No JSON content found and type 'System.Int32' is not nullable.", + error.Exception.Message); + } + + private class Person2 + { + [FromBody] + public Address2 Address { get; set; } + } + + private class Address2 + { + [Required] + public string Street { get; set; } + + public int Zip { get; set; } + } + + [Theory] + [InlineData("{ \"Zip\" : 123 }")] + [InlineData("{}")] + public async Task FromBodyOnTopLevelProperty_RequiredOnSubProperty_AddsModelStateError(string inputText) + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + BindingInfo = new BindingInfo() + { + BinderModelName = "CustomParameter", + }, + ParameterType = typeof(Person2) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(); + var httpContext = operationContext.HttpContext; + ConfigureHttpRequest(httpContext.Request, inputText); + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + var boundPerson = Assert.IsType(modelBindingResult.Model); + Assert.NotNull(boundPerson); + Assert.False(modelState.IsValid); + Assert.Equal(2, modelState.Keys.Count); + var zip = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Zip"); + Assert.Equal(ModelValidationState.Valid, modelState[zip].ValidationState); + + var street = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Street"); + Assert.Equal(ModelValidationState.Invalid, modelState[street].ValidationState); + var error = Assert.Single(modelState[street].Errors); + Assert.Equal("The Street field is required.", error.ErrorMessage); + } + + private class Person3 + { + [FromBody] + public Address3 Address { get; set; } + } + + private class Address3 + { + public string Street { get; set; } + + [Required] + public int Zip { get; set; } + } + + [Theory] + [InlineData("{ \"Street\" : \"someStreet\" }")] + [InlineData("{}")] + public async Task FromBodyOnProperty_RequiredOnValueTypeSubProperty_AddsModelStateError(string inputText) + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + BindingInfo = new BindingInfo() + { + BinderModelName = "CustomParameter", + }, + ParameterType = typeof(Person3) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(); + var httpContext = operationContext.HttpContext; + ConfigureHttpRequest(httpContext.Request, inputText); + var actionContext = httpContext.RequestServices.GetRequiredService>().Value; + var modelState = actionContext.ModelState; + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + var boundPerson = Assert.IsType(modelBindingResult.Model); + Assert.NotNull(boundPerson); + Assert.False(modelState.IsValid); + var street = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Street"); + Assert.Equal(ModelValidationState.Valid, modelState[street].ValidationState); + + // The error with an empty key is a bug(#2416) in our implementation which does not append the prefix and + // use that along with the path. The expected key here would be Address. + var zip = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Zip"); + Assert.Equal(ModelValidationState.Valid, modelState[zip].ValidationState); + var error = Assert.Single(modelState[""].Errors); + Assert.StartsWith( + "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"; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/Microsoft.AspNet.Mvc.IntegrationTests.xproj b/test/Microsoft.AspNet.Mvc.IntegrationTests/Microsoft.AspNet.Mvc.IntegrationTests.xproj new file mode 100644 index 0000000000..a93c34954b --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/Microsoft.AspNet.Mvc.IntegrationTests.xproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 864fa09d-1e48-403a-a6c8-4f079d2a30f0 + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/ModelBindingTestHelper.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/ModelBindingTestHelper.cs new file mode 100644 index 0000000000..11da5127e3 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/ModelBindingTestHelper.cs @@ -0,0 +1,81 @@ +// 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 Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Validation; +using Microsoft.AspNet.Routing; +using Microsoft.Framework.DependencyInjection; + +namespace Microsoft.AspNet.Mvc.IntegrationTests +{ + public static class ModelBindingTestHelper + { + public static OperationBindingContext GetOperationBindingContext() + { + var httpContext = ModelBindingTestHelper.GetHttpContext(); + var actionBindingContextAccessor = + httpContext.RequestServices.GetRequiredService>().Value; + return new OperationBindingContext() + { + BodyBindingState = BodyBindingState.NotBodyBased, + HttpContext = httpContext, + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), + ValidatorProvider = actionBindingContextAccessor.ValidatorProvider, + ValueProvider = actionBindingContextAccessor.ValueProvider, + ModelBinder = actionBindingContextAccessor.ModelBinder + }; + } + + public static DefaultControllerActionArgumentBinder GetArgumentBinder() + { + var options = new TestMvcOptions(); + options.Options.MaxModelValidationErrors = 5; + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + return new DefaultControllerActionArgumentBinder( + metadataProvider, + new DefaultObjectValidator( + options.Options.ValidationExcludeFilters, + metadataProvider)); + } + + public static HttpContext GetHttpContext() + { + var options = (new TestMvcOptions()).Options; + var httpContext = new DefaultHttpContext(); + var serviceCollection = MvcServices.GetDefaultServices(); + httpContext.RequestServices = serviceCollection.BuildServiceProvider(); + + var actionContext = new ActionContext(httpContext, new RouteData(), new ControllerActionDescriptor()); + + var actionContextAccessor = + httpContext.RequestServices.GetRequiredService>(); + actionContextAccessor.Value = actionContext; + + var actionBindingContextAccessor = + httpContext.RequestServices.GetRequiredService>(); + actionBindingContextAccessor.Value = GetActionBindingContext(options, actionContext); + return httpContext; + } + + private static ActionBindingContext GetActionBindingContext(MvcOptions options, ActionContext actionContext) + { + var valueProviderFactoryContext = new ValueProviderFactoryContext( + actionContext.HttpContext, + actionContext.RouteData.Values); + + var valueProvider = CompositeValueProvider.Create( + options.ValueProviderFactories, + valueProviderFactoryContext); + + return new ActionBindingContext() + { + InputFormatters = options.InputFormatters, + OutputFormatters = options.OutputFormatters, // Not required for model binding. + ValidatorProvider = new TestModelValidatorProvider(options.ModelValidatorProviders), + ModelBinder = new CompositeModelBinder(options.ModelBinders), + ValueProvider = valueProvider + }; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs new file mode 100644 index 0000000000..d10db14d1d --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs @@ -0,0 +1,24 @@ +// 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.Framework.OptionsModel; + +namespace Microsoft.AspNet.Mvc.IntegrationTests +{ + public class TestMvcOptions : IOptions + { + public TestMvcOptions() + { + Options = new MvcOptions(); + MvcOptionsSetup.ConfigureMvc(Options); + } + + public MvcOptions Options { get; } + + public MvcOptions GetNamedOptions(string name) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/project.json b/test/Microsoft.AspNet.Mvc.IntegrationTests/project.json new file mode 100644 index 0000000000..ee66b3d47f --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/project.json @@ -0,0 +1,20 @@ +{ + "compilationOptions": { + "warningsAsErrors": "true" + }, + "dependencies": { + "Microsoft.AspNet.Http.Core": "1.0.0-*", + "Microsoft.AspNet.Mvc":"6.0.0-*", + "Microsoft.AspNet.Mvc.TestCommon": { "version": "6.0.0-*", "type": "build" }, + "Microsoft.AspNet.Testing": "1.0.0-*", + "Moq": "4.2.1312.1622", + "xunit.runner.aspnet": "2.0.0-aspnet-*" + }, + "commands": { + "test": "xunit.runner.aspnet" + }, + "frameworks": { + "dnx451": { }, + "dnxcore50": { } + } +} diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs index 6ff37ac636..1da8cccf8b 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs @@ -977,7 +977,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata _attributes = attributes; } - protected override DefaultMetadataDetails CreateTypeDetails([NotNull]ModelMetadataIdentity key) + protected override DefaultMetadataDetails CreateTypeDetails(ModelMetadataIdentity key) { var entry = base.CreateTypeDetails(key); return new DefaultMetadataDetails( diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultModelValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultModelValidatorProviderTest.cs index 202d40b7e0..1b5cfdf065 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultModelValidatorProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultModelValidatorProviderTest.cs @@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider(); var metadata = metadataProvider.GetMetadataForProperty( - typeof(ModelValidatorAttributeOnProperty), + typeof(ModelValidatorAttributeOnProperty), nameof(ModelValidatorAttributeOnProperty.Property)); var context = new ModelValidatorProviderContext(metadata); @@ -117,7 +117,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation Assert.IsType(Assert.Single(validators)); } - + [Fact] public void GetValidators_DataAnnotationsAttribute_DefaultAdapter() {