TheGwa TheGwa - 2 months ago 7
C# Question

Bind from custom headers and request body

I am having a problem binding a model from both custom headers and the request body at the same time.

This is my model:


public class TestModel
{
public int Id { get; set; }
public string Name { get; set; }
public HeaderModel HeaderModel { get; set; }
}
public class HeaderModel
{
public int Version { get; set; }
public string Test { get; set; }
}


I have a simple test page that posts the following:


$(function() {
var go = function() {
$.ajax({
type: 'POST',
url: '/api/values',
data: { id: 1, name: 'theValue2' },
headers: { HeaderModel: JSON.stringify({version: 1, test:"roar"}) }
}).always();
};
$('input').click(go);
});


I have created the following header value provider:


public class HeaderValueProvider<T> : IValueProvider where T : class
{
private readonly HttpRequestHeaders _headers;

public HeaderValueProvider(HttpRequestHeaders headers)
{
_headers = headers;
}

public bool ContainsPrefix(string prefix)
{
var test = typeof (T).Name == prefix;
return test;
}

public ValueProviderResult GetValue(string key)
{
if (typeof (T).Name == key)
{
IEnumerable<string> headerStrings;
_headers.TryGetValues(key, out headerStrings);
var strings = headerStrings.ToArray();
if (headerStrings != null && strings.Any())
{
var value = strings.First();
var obj = JsonConvert.DeserializeObject<T>(value,
new JsonSerializerSettings {NullValueHandling = NullValueHandling.Ignore});
return new ValueProviderResult(obj, value, CultureInfo.InvariantCulture);
}
}
return null;
}
}

public class HeaderValueProviderFactory<T> : ValueProviderFactory where T : class
{
public override IValueProvider GetValueProvider(HttpActionContext actionContext)
{
var headers = actionContext.ControllerContext.Request.Headers;
return new HeaderValueProvider<T>(headers);
}
}


And it has been registered in the config:


config.Services.Add(typeof(ValueProviderFactory), new HeaderValueProviderFactory<HeaderModel>());


If I use my controller like the following I get my model bound, but not my header. It uses a media formatter:


public IHttpActionResult Post(TestModel model)
{
return Ok(model);
}


If I include the ModelBinder attribute I get the HeaderModel bound, but not the rest of the model from the request body:


public IHttpActionResult Post([ModelBinder]TestModel model)
{
return Ok(model);
}


What is the cleanest way to get both to work?

Answer

This was frustratingly easy to solve. As correctly stated by Badri, you cannot use the request body and headers for the same binding. You can however have two parameters and have them bound separately.

I have left the HeaderModel as its own separate class and added a ModelBinder attribute to it.

public class TestModel
{
    public int Id { get; set; }
    public string Name { get; set; }
}

[ModelBinder]
public class HeaderModel
{
    public int Version { get; set; }
    public string Test { get; set; }
}

I now have two parameters in my Post.

public IHttpActionResult Post(HeaderModel headerModel, TestModel model)
{
    return Ok();
}

I would not call this a perfect solution, but it works well.

Comments