Skip to content

Commit

Permalink
Add Header ValueProvider
Browse files Browse the repository at this point in the history
New behavior mimics existing ASP.NET MVC behavior of QueryString and Form value providers.
Not really sure why this isn't included in that package.
Didn't do thorough testing, but did notice one gotcha if identical header-keys are submitted: the values are combined as a single comma-separated string.

Implements #23
  • Loading branch information
billbogaiv committed Jan 19, 2019
1 parent b17c978 commit bca16de
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 5 deletions.
6 changes: 4 additions & 2 deletions src/HybridModelBinding/DefaultHybridModelBinder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc.Formatters;
using HybridModelBinding.ModelBinding;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System.Collections.Generic;
Expand All @@ -23,7 +24,8 @@ public class DefaultHybridModelBinder : HybridModelBinder
.AddModelBinder(Body, new BodyModelBinder(formatters, readerFactory))
.AddValueProviderFactory(Form, new FormValueProviderFactory())
.AddValueProviderFactory(Route, new RouteValueProviderFactory())
.AddValueProviderFactory(QueryString, new QueryStringValueProviderFactory());
.AddValueProviderFactory(QueryString, new QueryStringValueProviderFactory())
.AddValueProviderFactory(Header, new HeaderValueProviderFactory());
}
}
}
6 changes: 4 additions & 2 deletions src/HybridModelBinding/DefaultPassthroughHybridModelBinder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc.Formatters;
using HybridModelBinding.ModelBinding;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System.Collections.Generic;
Expand All @@ -23,7 +24,8 @@ public class DefaultPassthroughHybridModelBinder : HybridModelBinder
.AddModelBinder(Body, new BodyModelBinder(formatters, readerFactory))
.AddValueProviderFactory(Form, new FormValueProviderFactory())
.AddValueProviderFactory(Route, new RouteValueProviderFactory())
.AddValueProviderFactory(QueryString, new QueryStringValueProviderFactory());
.AddValueProviderFactory(QueryString, new QueryStringValueProviderFactory())
.AddValueProviderFactory(Header, new HeaderValueProviderFactory());
}
}
}
2 changes: 1 addition & 1 deletion src/HybridModelBinding/HybridModelBinding.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.12.0</Version>
<Version>0.13.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.1.1" />
Expand Down
82 changes: 82 additions & 0 deletions src/HybridModelBinding/ModelBinding/HeaderValueProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Collections.Generic;
using System.Globalization;

namespace HybridModelBinding.ModelBinding
{
/// <summary>
/// Modified from https://github.com/aspnet/Mvc/blob/8d66f104f7f2ca42ee8b21f75b0e2b3e1abe2e00/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProvider.cs.
/// </summary>
public class HeaderValueProvider : BindingSourceValueProvider, IEnumerableValueProvider
{
public HeaderValueProvider(
BindingSource bindingSource,
IHeaderDictionary values,
CultureInfo culture)
: base(bindingSource)
{
if (bindingSource == null)
{
throw new ArgumentNullException(nameof(bindingSource));
}

this.values = values ?? throw new ArgumentNullException(nameof(values));
Culture = culture;
}

public CultureInfo Culture { get; private set; }

private readonly IHeaderDictionary values;
private PrefixContainer prefixContainer;

protected PrefixContainer PrefixContainer
{
get
{
if (prefixContainer == null)
{
prefixContainer = new PrefixContainer(values.Keys);
}

return prefixContainer;
}
}

public override bool ContainsPrefix(string prefix)
{
return PrefixContainer.ContainsPrefix(prefix);
}

public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix)
{
if (prefix == null)
{
throw new ArgumentNullException(nameof(prefix));
}

return PrefixContainer.GetKeysFromPrefix(prefix);
}

public override ValueProviderResult GetValue(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}

var values = this.values[key];

if (values.Count == 0)
{
return ValueProviderResult.None;
}
else
{
return new ValueProviderResult(values, Culture);
}
}
}
}
49 changes: 49 additions & 0 deletions src/HybridModelBinding/ModelBinding/HeaderValueProviderFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Globalization;
using System.Threading.Tasks;

namespace HybridModelBinding.ModelBinding
{
public class HeaderValueProviderFactory : IValueProviderFactory
{
/// <summary>
/// We need to re-create a Header BindingSource since the default ASP.NET MVC version
/// is greedy and that won't work as a ValueProvider.
///
/// Ref. https://github.com/aspnet/Mvc/blob/8d66f104f7f2ca42ee8b21f75b0e2b3e1abe2e00/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingSource.cs
/// Ref. https://github.com/aspnet/Mvc/blob/8d66f104f7f2ca42ee8b21f75b0e2b3e1abe2e00/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingSourceValueProvider.cs#L41
/// ArgumentException: The provided binding source 'Header' is a greedy data source. 'BindingSourceValueProvider' does not support greedy data sources. /// Parameter name: bindingSource
/// </summary>
private BindingSource Header = new BindingSource(
BindingSource.Header.Id,
BindingSource.Header.DisplayName,
isGreedy: false,
isFromRequest: BindingSource.Header.IsFromRequest);

public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

var headers = context.ActionContext.HttpContext.Request.Headers;
if (headers != null && headers.Count > 0)
{
var valueProvider = new HeaderValueProvider(
Header,
headers,
CultureInfo.InvariantCulture);

context.ValueProviders.Add(valueProvider);
}

#if NET451
return Task.FromResult(0);
#else
return Task.CompletedTask;
#endif
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/HybridModelBinding/Source.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public static class Source
{
public const string Body = nameof(Body);
public const string Form = nameof(Form);
public const string Header = nameof(Header);
public const string QueryString = nameof(QueryString);
public const string Route = nameof(Route);
}
Expand Down

0 comments on commit bca16de

Please sign in to comment.