I want to bind a Guid parameter to my ASP.NET MVC Core API:
[FromHeader] Guid id
but it's always null. If I change the parameter to a string and parse the Guid from the string manually it works, so I think it's not detecting Guid as a convertable type.
In the documentation it says
In MVC simple types are any .NET primitive type or type with a string type converter.
There is a type converter for Guids (GuidConverter) but maybe ASP.NET MVC Core doesn't know about it.
Does anyone know how to bind a Guid parameter with ASP.NET MVC Core or how to tell it to use GuidConverter?
I have just found out that basically ASP Core only supports binding header values to strings and collections of strings! (whereas binding from route values, query string and body supports any complex type)
You can check the HeaderModelBinderProvider
source in Github and see for yourself:
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.BindingInfo.BindingSource != null &&
context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Header))
{
// We only support strings and collections of strings. Some cases can fail
// at runtime due to collections we can't modify.
if (context.Metadata.ModelType == typeof(string) ||
context.Metadata.ElementType == typeof(string))
{
return new HeaderModelBinder();
}
}
return null;
}
I have submitted a new issue, but in the meantime I would suggest you either bind to a string or create your own specific model binder (something that combines [FromHeader]
and [ModelBinder]
into your own binder)
Edit
The sample model binder could look like this:
public class GuidHeaderModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(Guid)) return Task.CompletedTask;
if (!bindingContext.BindingSource.CanAcceptDataFrom(BindingSource.Header)) return Task.CompletedTask;
var headerName = bindingContext.ModelName;
var stringValue = bindingContext.HttpContext.Request.Headers[headerName];
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, stringValue, stringValue);
// Attempt to parse the guid
if (Guid.TryParse(stringValue, out var valueAsGuid))
{
bindingContext.Result = ModelBindingResult.Success(valueAsGuid);
}
return Task.CompletedTask;
}
}
And this would be an example using it:
public IActionResult SampleAction(
[FromHeader(Name = "my-guid")][ModelBinder(BinderType = typeof(GuidHeaderModelBinder))]Guid foo)
{
return Json(new { foo });
}
Which you can try, for example with jquery in the browser:
$.ajax({
method: 'GET',
headers: { 'my-guid': '70e9dfda-4982-4b88-96f9-d7d284a10cb4' },
url: '/home/sampleaction'
});
[UPDATE]
This has been improved in 2.1.0-preview2. Your code actually works now. You can bind a non-string type from header to your parameters. You only need to set compatibility version in your startup class.
Controller
[HttpGet]
public Task<JsonResult> Get([FromHeader] Guid id)
{
return new JsonResult(new {id});
}
Startup
Services
.AddMvc
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Look at the same Github discussion mentioned above: https://github.com/aspnet/Mvc/issues/5859
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With