[Fix for #1929] HeaderModelBinder needs to honor explicit name.
This commit is contained in:
parent
2c3148fd25
commit
e805e67b4c
|
|
@ -1,47 +1,50 @@
|
|||
// 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.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="MetadataAwareBinder{IHeaderBinderMetadata}"/> which uses <see cref="Http.HttpRequest.Headers"/>
|
||||
/// to bind the model.
|
||||
/// </summary>
|
||||
public class HeaderModelBinder : MetadataAwareBinder<IHeaderBinderMetadata>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override Task<bool> BindAsync(
|
||||
[NotNull] ModelBindingContext bindingContext,
|
||||
[NotNull] ModelBindingContext bindingContext,
|
||||
[NotNull] IHeaderBinderMetadata metadata)
|
||||
{
|
||||
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
||||
var modelMetadata = bindingContext.ModelMetadata;
|
||||
|
||||
// Property name can be null if the model metadata represents a type (rahter than a property or parameter).
|
||||
var headerName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName ?? bindingContext.ModelName;
|
||||
if (bindingContext.ModelType == typeof(string))
|
||||
{
|
||||
var value = request.Headers.Get(bindingContext.ModelName);
|
||||
var value = request.Headers.Get(headerName);
|
||||
if (value != null)
|
||||
{
|
||||
bindingContext.Model = value;
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
else if (typeof(IEnumerable<string>).GetTypeInfo().IsAssignableFrom(
|
||||
bindingContext.ModelType.GetTypeInfo()))
|
||||
{
|
||||
var values = request.Headers.GetCommaSeparatedValues(bindingContext.ModelName);
|
||||
var values = request.Headers.GetCommaSeparatedValues(headerName);
|
||||
if (values != null)
|
||||
{
|
||||
bindingContext.Model = ModelBindingHelper.ConvertValuesToCollectionType(bindingContext.ModelType, values);
|
||||
bindingContext.Model =
|
||||
ModelBindingHelper.ConvertValuesToCollectionType(bindingContext.ModelType, values);
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
return Task.FromResult(false);
|
||||
// Always return true as header model binder is supposed to always handle IHeaderBinderMetadata.
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,57 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal(expected, result.HeaderValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FromHeader_BindHeader_ToString_OnProperty_CustomName()
|
||||
{
|
||||
// Arrange
|
||||
var title = "How to make really really good soup.";
|
||||
var tags = new string[] { "Cooking", "Recipes", "Awesome" };
|
||||
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Blog/BindToProperty/CustomName");
|
||||
request.Headers.TryAddWithoutValidation("BlogTitle", title);
|
||||
request.Headers.TryAddWithoutValidation("BlogTags", string.Join(", ", tags));
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<Result>(body);
|
||||
Assert.Equal(title, result.HeaderValue);
|
||||
Assert.Equal<string>(tags, result.HeaderValues);
|
||||
Assert.Empty(result.ModelStateErrors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FromHeader_NonExistingHeaderAddsValidationErrors_OnProperty_CustomName()
|
||||
{
|
||||
// Arrange
|
||||
var tags = new string[] { "Cooking", "Recipes", "Awesome" };
|
||||
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Blog/BindToProperty/CustomName");
|
||||
request.Headers.TryAddWithoutValidation("BlogTags", string.Join(", ", tags));
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<Result>(body);
|
||||
Assert.Equal<string>(tags, result.HeaderValues);
|
||||
var error = Assert.Single(result.ModelStateErrors);
|
||||
Assert.Equal("Title", error);
|
||||
}
|
||||
// The action that this test hits will echo back the model-bound value
|
||||
[Fact]
|
||||
public async Task FromHeader_BindHeader_ToString_OnParameter_CustomName()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
// 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.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||
{
|
||||
public class HeaderModelBinderTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(string))]
|
||||
[InlineData(typeof(string[]))]
|
||||
[InlineData(typeof(object))]
|
||||
[InlineData(typeof(int))]
|
||||
[InlineData(typeof(int[]))]
|
||||
[InlineData(typeof(TestFromHeader))]
|
||||
public async Task BindModelAsync_ReturnsTrue_ForAllTypes(Type type)
|
||||
{
|
||||
// Arrange
|
||||
var binder = new HeaderModelBinder();
|
||||
var modelBindingContext = GetBindingContext(type);
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(modelBindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderBinder_BindsHeaders_ToStringCollection()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(string[]);
|
||||
var header = "Accept";
|
||||
var headerValue = "application/json,text/json";
|
||||
var binder = new HeaderModelBinder();
|
||||
var modelBindingContext = GetBindingContext(type);
|
||||
|
||||
modelBindingContext.ModelName = header;
|
||||
modelBindingContext.OperationBindingContext.HttpContext.Request.Headers.Add(header, new[] { headerValue });
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(modelBindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Equal(headerValue.Split(','), modelBindingContext.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderBinder_BindsHeaders_ToStringType()
|
||||
{
|
||||
// Arrange
|
||||
var type = typeof(string);
|
||||
var header = "User-Agent";
|
||||
var headerValue = "UnitTest";
|
||||
var binder = new HeaderModelBinder();
|
||||
var modelBindingContext = GetBindingContext(type);
|
||||
|
||||
modelBindingContext.ModelName = header;
|
||||
modelBindingContext.OperationBindingContext.HttpContext.Request.Headers.Add(header, new[] { headerValue });
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(modelBindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Equal(headerValue, modelBindingContext.Model);
|
||||
}
|
||||
|
||||
private static ModelBindingContext GetBindingContext(Type modelType)
|
||||
{
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var bindingContext = new ModelBindingContext
|
||||
{
|
||||
ModelMetadata = metadataProvider.GetMetadataForType(null, modelType),
|
||||
ModelName = "modelName",
|
||||
OperationBindingContext = new OperationBindingContext
|
||||
{
|
||||
ModelBinder = new HeaderModelBinder(),
|
||||
MetadataProvider = metadataProvider,
|
||||
HttpContext = new DefaultHttpContext()
|
||||
}
|
||||
};
|
||||
|
||||
bindingContext.ModelMetadata.BinderMetadata = new TestFromHeader();
|
||||
return bindingContext;
|
||||
}
|
||||
|
||||
public class TestFromHeader : IHeaderBinderMetadata
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
|
|
@ -11,6 +12,17 @@ namespace ModelBindingWebSite.Controllers
|
|||
[Route("Blog")]
|
||||
public class FromHeader_BlogController : Controller
|
||||
{
|
||||
[HttpGet("BindToProperty/CustomName")]
|
||||
public object BindToProperty(BlogWithHeaderOnProperty blogWithHeader)
|
||||
{
|
||||
return new Result()
|
||||
{
|
||||
HeaderValue = blogWithHeader.Title,
|
||||
HeaderValues = blogWithHeader.Tags,
|
||||
ModelStateErrors = ModelState.Where(kvp => kvp.Value.Errors.Count > 0).Select(kvp => kvp.Key).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
// Echo back the header value
|
||||
[HttpGet("BindToStringParameter")]
|
||||
public object BindToStringParameter([FromHeader] string transactionId)
|
||||
|
|
@ -109,6 +121,18 @@ namespace ModelBindingWebSite.Controllers
|
|||
public string Author { get; set; }
|
||||
}
|
||||
|
||||
public class BlogWithHeaderOnProperty
|
||||
{
|
||||
[FromHeader(Name = "BlogTitle")]
|
||||
[Required]
|
||||
public string Title { get; set; }
|
||||
|
||||
[FromHeader(Name = "BlogTags")]
|
||||
public string[] Tags { get; set; }
|
||||
|
||||
public string Author { get; set; }
|
||||
}
|
||||
|
||||
public class BlogPostWithInitializedValue
|
||||
{
|
||||
[Required]
|
||||
|
|
|
|||
Loading…
Reference in New Issue