Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

binding a Guid parameter in asp.net mvc core

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?

like image 960
ghosttie Avatar asked Feb 24 '17 23:02

ghosttie


2 Answers

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'
});
like image 126
Daniel J.G. Avatar answered Nov 13 '22 13:11

Daniel J.G.


[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

like image 28
Ben Avatar answered Nov 13 '22 14:11

Ben