parent
f280353a56
commit
43e24b2aad
|
|
@ -1,26 +0,0 @@
|
||||||
// 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.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
|
||||||
{
|
|
||||||
public class DefaultInputFormatterSelector : IInputFormatterSelector
|
|
||||||
{
|
|
||||||
|
|
||||||
public IInputFormatter SelectFormatter(
|
|
||||||
IReadOnlyList<IInputFormatter> inputFormatters,
|
|
||||||
InputFormatterContext context)
|
|
||||||
{
|
|
||||||
foreach (var formatter in inputFormatters)
|
|
||||||
{
|
|
||||||
if (formatter.CanRead(context))
|
|
||||||
{
|
|
||||||
return formatter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
// 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.Collections.Generic;
|
|
||||||
using Microsoft.Framework.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
|
||||||
{
|
|
||||||
public interface IInputFormatterSelector
|
|
||||||
{
|
|
||||||
IInputFormatter SelectFormatter(
|
|
||||||
[NotNull] IReadOnlyList<IInputFormatter> inputFormatters,
|
|
||||||
[NotNull] InputFormatterContext context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -30,12 +30,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
||||||
|
|
||||||
var formatterSelector = requestServices.GetRequiredService<IInputFormatterSelector>();
|
|
||||||
var actionContext = requestServices.GetRequiredService<IScopedInstance<ActionContext>>().Value;
|
var actionContext = requestServices.GetRequiredService<IScopedInstance<ActionContext>>().Value;
|
||||||
var formatters = requestServices.GetRequiredService<IScopedInstance<ActionBindingContext>>().Value.InputFormatters;
|
var formatters = requestServices.GetRequiredService<IScopedInstance<ActionBindingContext>>().Value.InputFormatters;
|
||||||
|
|
||||||
var formatterContext = new InputFormatterContext(actionContext, bindingContext.ModelType);
|
var formatterContext = new InputFormatterContext(actionContext, bindingContext.ModelType);
|
||||||
var formatter = formatterSelector.SelectFormatter(formatters.ToList(), formatterContext);
|
var formatter = formatters.FirstOrDefault(f => f.CanRead(formatterContext));
|
||||||
|
|
||||||
if (formatter == null)
|
if (formatter == null)
|
||||||
{
|
{
|
||||||
|
|
@ -48,34 +47,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false);
|
return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
object model = null;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
model = await formatter.ReadAsync(formatterContext);
|
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);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
model = GetDefaultValueForType(bindingContext.ModelType);
|
|
||||||
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
|
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
|
||||||
|
|
||||||
// This model binder is the only handler for the Body binding source.
|
// This model binder is the only handler for the Body binding source.
|
||||||
// Always tell the model binding system to skip other model binders i.e. return non-null.
|
// Always tell the model binding system to skip other model binders i.e. return non-null.
|
||||||
return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false);
|
return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private object GetDefaultValueForType(Type modelType)
|
|
||||||
{
|
|
||||||
if (modelType.GetTypeInfo().IsValueType)
|
|
||||||
{
|
|
||||||
return Activator.CreateInstance(modelType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders);
|
return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders);
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddTransient<IInputFormatterSelector, DefaultInputFormatterSelector>();
|
|
||||||
services.AddInstance(new JsonOutputFormatter());
|
services.AddInstance(new JsonOutputFormatter());
|
||||||
|
|
||||||
// Razor, Views and runtime compilation
|
// Razor, Views and runtime compilation
|
||||||
|
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
// 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.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNet.Http;
|
|
||||||
using Microsoft.AspNet.Routing;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Core.Test
|
|
||||||
{
|
|
||||||
public class DefaultInputFormatterSelectorTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void DefaultInputFormatterSelectorTests_ReturnsFirstFormatterWhichReturnsTrue()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var actionContext = GetActionContext();
|
|
||||||
var inputFormatters = new List<IInputFormatter>()
|
|
||||||
{
|
|
||||||
new TestInputFormatter(false, 0),
|
|
||||||
new TestInputFormatter(false, 1),
|
|
||||||
new TestInputFormatter(true, 2),
|
|
||||||
new TestInputFormatter(true, 3)
|
|
||||||
};
|
|
||||||
|
|
||||||
var context = new InputFormatterContext(actionContext, typeof(int));
|
|
||||||
var selector = new DefaultInputFormatterSelector();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var selectedFormatter = selector.SelectFormatter(inputFormatters, context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var testFormatter = Assert.IsType<TestInputFormatter>(selectedFormatter);
|
|
||||||
Assert.Equal(2, testFormatter.Index);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ActionContext GetActionContext()
|
|
||||||
{
|
|
||||||
return new ActionContext(Mock.Of<HttpContext>(), new RouteData(), new ActionDescriptor());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestInputFormatter : IInputFormatter
|
|
||||||
{
|
|
||||||
private bool _canRead = false;
|
|
||||||
|
|
||||||
public TestInputFormatter(bool canRead, int index)
|
|
||||||
{
|
|
||||||
_canRead = canRead;
|
|
||||||
Index = index;
|
|
||||||
}
|
|
||||||
public int Index { get; set; }
|
|
||||||
|
|
||||||
public bool CanRead(InputFormatterContext context)
|
|
||||||
{
|
|
||||||
return _canRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<object> ReadAsync(InputFormatterContext context)
|
|
||||||
{
|
|
||||||
return Task.FromResult<object>(Index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,18 +4,18 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Http.Core;
|
using Microsoft.AspNet.Http.Core;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class BodyModelBinderTests
|
public class BodyModelBinderTests
|
||||||
{
|
{
|
||||||
|
|
@ -24,6 +24,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var mockInputFormatter = new Mock<IInputFormatter>();
|
var mockInputFormatter = new Mock<IInputFormatter>();
|
||||||
|
mockInputFormatter.Setup(f => f.CanRead(It.IsAny<InputFormatterContext>()))
|
||||||
|
.Returns(true)
|
||||||
|
.Verifiable();
|
||||||
mockInputFormatter.Setup(o => o.ReadAsync(It.IsAny<InputFormatterContext>()))
|
mockInputFormatter.Setup(o => o.ReadAsync(It.IsAny<InputFormatterContext>()))
|
||||||
.Returns(Task.FromResult<object>(new Person()))
|
.Returns(Task.FromResult<object>(new Person()))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
@ -32,7 +35,10 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var provider = new TestModelMetadataProvider();
|
var provider = new TestModelMetadataProvider();
|
||||||
provider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
provider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||||
|
|
||||||
var bindingContext = GetBindingContext(typeof(Person), inputFormatter, metadataProvider: provider);
|
var bindingContext = GetBindingContext(
|
||||||
|
typeof(Person),
|
||||||
|
new[] { inputFormatter },
|
||||||
|
metadataProvider: provider);
|
||||||
|
|
||||||
var binder = new BodyModelBinder();
|
var binder = new BodyModelBinder();
|
||||||
|
|
||||||
|
|
@ -40,6 +46,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
mockInputFormatter.Verify(v => v.CanRead(It.IsAny<InputFormatterContext>()), Times.Once);
|
||||||
mockInputFormatter.Verify(v => v.ReadAsync(It.IsAny<InputFormatterContext>()), Times.Once);
|
mockInputFormatter.Verify(v => v.ReadAsync(It.IsAny<InputFormatterContext>()), Times.Once);
|
||||||
Assert.NotNull(binderResult);
|
Assert.NotNull(binderResult);
|
||||||
Assert.True(binderResult.IsModelSet);
|
Assert.True(binderResult.IsModelSet);
|
||||||
|
|
@ -138,7 +145,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var bindingContext = GetBindingContext(
|
var bindingContext = GetBindingContext(
|
||||||
typeof(Person),
|
typeof(Person),
|
||||||
inputFormatter: new XyzFormatter(),
|
inputFormatters: new[] { new XyzFormatter() },
|
||||||
httpContext: httpContext,
|
httpContext: httpContext,
|
||||||
metadataProvider: provider);
|
metadataProvider: provider);
|
||||||
|
|
||||||
|
|
@ -170,7 +177,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var bindingContext = GetBindingContext(
|
var bindingContext = GetBindingContext(
|
||||||
typeof(Person),
|
typeof(Person),
|
||||||
inputFormatter: null,
|
inputFormatters: null,
|
||||||
httpContext: httpContext,
|
httpContext: httpContext,
|
||||||
metadataProvider: provider);
|
metadataProvider: provider);
|
||||||
|
|
||||||
|
|
@ -190,9 +197,35 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Assert.Equal("Unsupported content type 'text/xyz'.", errorMessage);
|
Assert.Equal("Unsupported content type 'text/xyz'.", errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindModelCoreAsync_UsesFirstFormatterWhichCanRead()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var canReadFormatter1 = new TestInputFormatter(canRead: true);
|
||||||
|
var canReadFormatter2 = new TestInputFormatter(canRead: true);
|
||||||
|
var inputFormatters = new List<IInputFormatter>()
|
||||||
|
{
|
||||||
|
new TestInputFormatter(canRead: false),
|
||||||
|
new TestInputFormatter(canRead: false),
|
||||||
|
canReadFormatter1,
|
||||||
|
canReadFormatter2
|
||||||
|
};
|
||||||
|
var provider = new TestModelMetadataProvider();
|
||||||
|
provider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Body);
|
||||||
|
var bindingContext = GetBindingContext(typeof(Person), inputFormatters, metadataProvider: provider);
|
||||||
|
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(binderResult.IsModelSet);
|
||||||
|
Assert.Same(canReadFormatter1, binderResult.Model);
|
||||||
|
}
|
||||||
|
|
||||||
private static ModelBindingContext GetBindingContext(
|
private static ModelBindingContext GetBindingContext(
|
||||||
Type modelType,
|
Type modelType,
|
||||||
IInputFormatter inputFormatter = null,
|
IEnumerable<IInputFormatter> inputFormatters = null,
|
||||||
HttpContext httpContext = null,
|
HttpContext httpContext = null,
|
||||||
IModelMetadataProvider metadataProvider = null)
|
IModelMetadataProvider metadataProvider = null)
|
||||||
{
|
{
|
||||||
|
|
@ -200,7 +233,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
httpContext = new DefaultHttpContext();
|
httpContext = new DefaultHttpContext();
|
||||||
}
|
}
|
||||||
UpdateServiceProvider(httpContext, inputFormatter);
|
|
||||||
|
UpdateServiceProvider(httpContext, inputFormatters ?? Enumerable.Empty<IInputFormatter>());
|
||||||
|
|
||||||
if (metadataProvider == null)
|
if (metadataProvider == null)
|
||||||
{
|
{
|
||||||
|
|
@ -227,21 +261,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return bindingContext;
|
return bindingContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateServiceProvider(HttpContext httpContext, IInputFormatter inputFormatter)
|
private static void UpdateServiceProvider(
|
||||||
|
HttpContext httpContext,
|
||||||
|
IEnumerable<IInputFormatter> inputFormatters)
|
||||||
{
|
{
|
||||||
var serviceProvider = new ServiceCollection();
|
var serviceProvider = new ServiceCollection();
|
||||||
var inputFormatterSelector = new Mock<IInputFormatterSelector>();
|
|
||||||
inputFormatterSelector
|
|
||||||
.Setup(o => o.SelectFormatter(
|
|
||||||
It.IsAny<IReadOnlyList<IInputFormatter>>(),
|
|
||||||
It.IsAny<InputFormatterContext>()))
|
|
||||||
.Returns(inputFormatter);
|
|
||||||
|
|
||||||
serviceProvider.AddInstance(inputFormatterSelector.Object);
|
|
||||||
|
|
||||||
var bindingContext = new ActionBindingContext()
|
var bindingContext = new ActionBindingContext()
|
||||||
{
|
{
|
||||||
InputFormatters = new List<IInputFormatter>(),
|
InputFormatters = inputFormatters.ToArray(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var bindingContextAccessor = new MockScopedInstance<ActionBindingContext>()
|
var bindingContextAccessor = new MockScopedInstance<ActionBindingContext>()
|
||||||
|
|
@ -296,5 +323,25 @@ namespace Microsoft.AspNet.Mvc
|
||||||
throw new InvalidOperationException("Your input is bad!");
|
throw new InvalidOperationException("Your input is bad!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestInputFormatter : IInputFormatter
|
||||||
|
{
|
||||||
|
private readonly bool _canRead;
|
||||||
|
|
||||||
|
public TestInputFormatter(bool canRead)
|
||||||
|
{
|
||||||
|
_canRead = canRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanRead(InputFormatterContext context)
|
||||||
|
{
|
||||||
|
return _canRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<object> ReadAsync(InputFormatterContext context)
|
||||||
|
{
|
||||||
|
return Task.FromResult<object>(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -126,6 +126,44 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal(expectedSampleIntValue.ToString(), responseBody);
|
Assert.Equal(expectedSampleIntValue.ToString(), responseBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("\"I'm a JSON string!\"")]
|
||||||
|
[InlineData("true")]
|
||||||
|
[InlineData("\"\"")] // Empty string
|
||||||
|
public async Task JsonInputFormatter_ReturnsDefaultValue_ForValueTypes(string input)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var content = new StringContent(input, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.PostAsync("http://localhost/JsonFormatter/ValueTypeAsBody/", content);
|
||||||
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
|
Assert.Equal("0", responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task JsonInputFormatter_ReadsPrimitiveTypes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "1773";
|
||||||
|
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var content = new StringContent(expected, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.PostAsync("http://localhost/JsonFormatter/ValueTypeAsBody/", content);
|
||||||
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal(expected, responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("{\"SampleInt\":10}")]
|
[InlineData("{\"SampleInt\":10}")]
|
||||||
[InlineData("{}")]
|
[InlineData("{}")]
|
||||||
|
|
|
||||||
|
|
@ -38,5 +38,16 @@ namespace FormatterWebSite.Controllers
|
||||||
}
|
}
|
||||||
return Content(dummyObject.SampleInt.ToString());
|
return Content(dummyObject.SampleInt.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult ValueTypeAsBody([FromBody] int value)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Content(value.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue