简体   繁体   English

在 asp.net mvc core 中绑定 Guid 参数

[英]binding a Guid parameter in asp.net mvc core

I want to bind a Guid parameter to my ASP.NET MVC Core API:我想将 Guid 参数绑定到我的 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.如果我将参数更改为字符串并手动从字符串解析 Guid,它会起作用,所以我认为它不会将 Guid 检测为可转换类型。

In the documentation it says文档中它说

In MVC simple types are any .NET primitive type or type with a string type converter.在 MVC 中,简单类型是任何 .NET 原始类型或带有字符串类型转换器的类型。

There is a type converter for Guids ( GuidConverter ) but maybe ASP.NET MVC Core doesn't know about it. Guids ( GuidConverter ) 有一个类型转换器,但也许 ASP.NET MVC Core 不知道它。

Does anyone know how to bind a Guid parameter with ASP.NET MVC Core or how to tell it to use GuidConverter?有谁知道如何将 Guid 参数与 ASP.NET MVC Core 绑定或如何告诉它使用 GuidConverter?

I have just found out that basically ASP Core only supports binding header values to strings and collections of strings!我刚刚发现基本上 ASP Core 只支持将标头值绑定到字符串和字符串集合! (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:您可以在 Github 中查看HeaderModelBinderProvider 源代码并亲自查看:

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)我已经提交了一个新问题,但同时我建议您绑定到一个字符串或创建您自己的特定模型绑定器(将[FromHeader][ModelBinder]到您自己的绑定器中的东西)


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:您可以尝试,例如在浏览器中使用 jquery:

$.ajax({
  method: 'GET',
  headers: { 'my-guid': '70e9dfda-4982-4b88-96f9-d7d284a10cb4' }, 
  url: '/home/sampleaction'
});

[UPDATE] [更新]

This has been improved in 2.1.0-preview2.这已在 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看上面提到的同一个 Github 讨论: https : //github.com/aspnet/Mvc/issues/5859

I did it this way, which does not require an additional attribute on the controller action.我是这样做的,它不需要控制器操作的附加属性。

Model Binder模型绑定器

public class GuidHeaderModelBinder : IModelBinder
{
    public async Task BindModelAsync(ModelBindingContext BindingContext)
    {
        // Read HTTP header.
        string headerName = BindingContext.FieldName;
        if (BindingContext.HttpContext.Request.Headers.ContainsKey(headerName))
        {
            StringValues headerValues = BindingContext.HttpContext.Request.Headers[headerName];
            if (headerValues == StringValues.Empty)
            {
                // Value not found in HTTP header.  Substitute empty GUID.
                BindingContext.ModelState.SetModelValue(BindingContext.FieldName, headerValues, Guid.Empty.ToString());
                BindingContext.Result = ModelBindingResult.Success(Guid.Empty);
            }
            else
            {
                // Value found in HTTP header.
                string correlationIdText = headerValues[0];
                BindingContext.ModelState.SetModelValue(BindingContext.FieldName, headerValues, correlationIdText);
                // Parse GUID.
                BindingContext.Result = Guid.TryParse(correlationIdText, out Guid correlationId)
                    ? ModelBindingResult.Success(correlationId)
                    : ModelBindingResult.Failed();
            }
        }
        else
        {
            // HTTP header not found.
            BindingContext.Result = ModelBindingResult.Failed();
        }
        await Task.FromResult(default(object));
    }
}

Model Binder Provider (verifies conditions where model binding will succeed) Model Binder Provider (验证模型绑定成功的条件)

public class GuidHeaderModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext Context)
    {
        if (Context.Metadata.ModelType == typeof(Guid))
        {
            if (Context.BindingInfo.BindingSource == BindingSource.Header)
            {
                return new BinderTypeModelBinder(typeof(GuidHeaderModelBinder));
            }
        }
        return null;
    }
}

FooBar Controller Action FooBar 控制器动作

[HttpGet("getbars")]
public async Task<string> GetBarsAsync([FromHeader] Guid CorrelationId, int Count)
{
    Logger.Log(CorrelationId, $"Creating {Count} foo bars.");
    StringBuilder stringBuilder = new StringBuilder();
    for (int count = 0; count < Count; count++)
    {
        stringBuilder.Append("Bar! ");
    }
    return await Task.FromResult(stringBuilder.ToString());
}

Startup启动

// Add MVC and configure model binding.
Services.AddMvc(Options =>
{
    Options.ModelBinderProviders.Insert(0, new GuidHeaderModelBinderProvider());
});

The easiest way to do this is to remove the attribute before your parameter of type Guid in your controller action like this:最简单的方法是在控制器操作中删除 Guid 类型参数之前的属性,如下所示:

public async Task<IActionResult> UpdateAsync(Guid ApplicantId, [FromBody]UpdateApplicantRequest request) {}

Plain and simple, hope this helps.简单明了,希望这会有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM