diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs
index 60725aeaf7..24c8a6dff5 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs
@@ -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
{
+ ///
+ /// A which uses
+ /// to bind the model.
+ ///
public class HeaderModelBinder : MetadataAwareBinder
{
///
protected override Task 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).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);
}
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingFromHeaderTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingFromHeaderTest.cs
index 6c2102562c..d68e1ddfcc 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingFromHeaderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingFromHeaderTest.cs
@@ -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(body);
+ Assert.Equal(title, result.HeaderValue);
+ Assert.Equal(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(body);
+ Assert.Equal(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()
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs
new file mode 100644
index 0000000000..baa92719bf
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs
@@ -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
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ModelBindingWebSite/Controllers/FromHeader_BlogController.cs b/test/WebSites/ModelBindingWebSite/Controllers/FromHeader_BlogController.cs
index 3a5bdae409..ac89820650 100644
--- a/test/WebSites/ModelBindingWebSite/Controllers/FromHeader_BlogController.cs
+++ b/test/WebSites/ModelBindingWebSite/Controllers/FromHeader_BlogController.cs
@@ -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]