簡體   English   中英

Web Api 2 中的 Mvc 風格參數綁定?

[英]Mvc-style parameter binding in Web Api 2?

我試圖在 web api 2 中同時使用 FromUri 和 FromBody 來填充傳入的請求模型。 我知道我需要編寫一個自定義模型綁定器來做到這一點。 下面是大家參考的例子 此解決方案已合並到 WebAPIContrib nuGet pacakge 中,其源代碼可在此處在 github 上查看

我無法讓 MvcActionValueBinder 處理應用程序/json 正文內容。 這是引發異常的源代碼的一部分。

class MvcActionBinding : HttpActionBinding
{
    // Read the body upfront , add as a ValueProvider
    public override Task ExecuteBindingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        HttpRequestMessage request = actionContext.ControllerContext.Request;
        HttpContent content = request.Content;
        if (content != null)
        {
            FormDataCollection fd = content.ReadAsAsync<FormDataCollection>().Result;
            if (fd != null)
            {
                IValueProvider vp = new NameValuePairsValueProvider(fd, CultureInfo.InvariantCulture);
                request.Properties.Add(Key, vp);
            }
        }

        return base.ExecuteBindingAsync(actionContext, cancellationToken);
    }
}

這一行拋出異常:

FormDataCollection fd = content.ReadAsAsync<FormDataCollection>().Result;

這是例外:

System.AggregateException

{"無法將當前 JSON 對象(例如 {\\"name\\":\\"value\\"})反序列化為類型“System.Net.Http.Formatting.FormDataCollection”,因為該類型需要一個 JSON 數組(例如 [1,2 ,3]) 以正確反序列化。\\r\\n要修復此錯誤,請將 JSON 更改為 JSON 數組(例如 [1,2,3])或更改反序列化類型,使其成為正常的 .NET 類型(例如不是可以從 JSON 對象反序列化的原始類型(如整數,而不是像數組或列表這樣的集合類型)。也可以將 JsonObjectAttribute 添加到該類型以強制其從 JSON 對象反序列化。\\r\\nPath 'creditLimit' ,第 2 行,位置 17。"}

如何讓模型綁定器使用 applciation/json 內容而不是 x-www-form-urlencoded 內容? 這是一個類似的問題,在 asp.net 論壇上沒有答案

更新:這是控制器方法:

[Route("{accountId:int}/creditlimit")]
[HttpPut]
public async Task<IHttpActionResult> UpdateAccountCreditLimit(int accountId, [FromBody] RequestObject request)
{
     // omitted for brevity
}

這是請求對象:

class RequestObject
{
    public int AccountId { get; set; }
    public decimal CreditLimit { get; set; }
}

這是要測試的郵遞員端點,它是一個 PUT:

http://localhost/api/accounts/47358/creditlimit

我已設置為 application/json 的主體。 這是示例內容。

{ "creditLimit": 125000.00 }

是的,我意識到我可以更改控制器方法以執行所有 FromUri 或所有 FromBody。 我沒有這樣做的自由。 謝謝。

我有同樣的問題,我想我終於弄清楚了。

這是代碼:

internal sealed class MvcActionValueBinder : DefaultActionValueBinder
{
    private static readonly Type stringType = typeof(string);

    // Per-request storage, uses the Request.Properties bag. We need a unique key into the bag.
    private const string Key = "5DC187FB-BFA0-462A-AB93-9E8036871EC8";

    private readonly JsonSerializerSettings serializerSettings;

    public MvcActionValueBinder(JsonSerializerSettings serializerSettings)
    {
        this.serializerSettings = serializerSettings;
    }

    public override HttpActionBinding GetBinding(HttpActionDescriptor actionDescriptor)
    {
        var actionBinding = new MvcActionBinding(serializerSettings);

        HttpParameterDescriptor[] parameters = actionDescriptor.GetParameters().ToArray();
        HttpParameterBinding[] binders = Array.ConvertAll(parameters, DetermineBinding);

        actionBinding.ParameterBindings = binders;

        return actionBinding;
    }

    private HttpParameterBinding DetermineBinding(HttpParameterDescriptor parameter)
    {
        HttpConfiguration config = parameter.Configuration;
        var attr = new ModelBinderAttribute(); // use default settings

        ModelBinderProvider provider = attr.GetModelBinderProvider(config);
        IModelBinder binder = provider.GetBinder(config, parameter.ParameterType);

        // Alternatively, we could put this ValueProviderFactory in the global config.
        var valueProviderFactories = new List<ValueProviderFactory>(attr.GetValueProviderFactories(config)) { new BodyValueProviderFactory() };
        return new ModelBinderParameterBinding(parameter, binder, valueProviderFactories);
    }

    // Derive from ActionBinding so that we have a chance to read the body once and then share that with all the parameters.
    private class MvcActionBinding : HttpActionBinding
    {
        private readonly JsonSerializerSettings serializerSettings;

        public MvcActionBinding(JsonSerializerSettings serializerSettings)
        {
            this.serializerSettings = serializerSettings;
        }

        // Read the body upfront, add as a ValueProvider
        public override Task ExecuteBindingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            HttpRequestMessage request = actionContext.ControllerContext.Request;
            HttpContent content = request.Content;
            if (content != null)
            {
                string result = request.Content.ReadAsStringAsync().Result;

                if (!string.IsNullOrEmpty(result))
                {
                    var jsonContent = JObject.Parse(result);
                    var values = new Dictionary<string, object>();

                    foreach (HttpParameterDescriptor parameterDescriptor in actionContext.ActionDescriptor.GetParameters())
                    {
                        object parameterValue = GetParameterValue(jsonContent, parameterDescriptor);
                        values.Add(parameterDescriptor.ParameterName, parameterValue);
                    }

                    IValueProvider valueProvider = new NameValuePairsValueProvider(values, CultureInfo.InvariantCulture);
                    request.Properties.Add(Key, valueProvider);
                }
            }

            return base.ExecuteBindingAsync(actionContext, cancellationToken);
        }

        private object GetParameterValue(JObject jsonContent, HttpParameterDescriptor parameterDescriptor)
        {
            string propertyValue = jsonContent.Property(parameterDescriptor.ParameterName)?.Value.ToString();

            if (IsSimpleParameter(parameterDescriptor))
            {
                // No deserialization needed for value type, a cast is enough
                return Convert.ChangeType(propertyValue, parameterDescriptor.ParameterType);
            }

            return JsonConvert.DeserializeObject(propertyValue, parameterDescriptor.ParameterType, serializerSettings);
        }

        private bool IsSimpleParameter(HttpParameterDescriptor parameterDescriptor)
        {
            return parameterDescriptor.ParameterType.IsValueType || parameterDescriptor.ParameterType == stringType;
        }
    }

    // Get a value provider over the body. This can be shared by all parameters.
    // This gets the values computed in MvcActionBinding.
    private class BodyValueProviderFactory : ValueProviderFactory
    {
        public override IValueProvider GetValueProvider(HttpActionContext actionContext)
        {
            actionContext.Request.Properties.TryGetValue(Key, out object vp);
            return (IValueProvider)vp; // can be null 
        }
    }
}

解釋一下,訣竅是首先將請求內容作為string讀取,然后將其加載到JObject 對於actionContext.ActionDescriptor存在的每個參數,都會使用參數名稱作為鍵填充字典,我們使用參數類型添加對象值。

根據參數類型,我們要么進行簡單的轉換,要么使用 Json.NET 將值反序列化為所需的類型。 請注意,您可能需要為值類型添加特殊情況以管理例如枚舉或Guid

在我的示例中,我傳遞了一個JsonSerializerSettings因為我有一些想要使用的自定義轉換器,但您可能不需要它。

您應該能夠使用 Web API 2 本身中的默認模型綁定功能來實現這一點。 您需要做的第一件事是將數據作為 JSON 字符串傳遞,如下所示。

data: JSON.stringify({ "creditLimit": 125000.00 })

accountId 將從 URL 中讀取,Web API 2 的默認 JsonFormatter 將嘗試綁定來自正文的第二個參數請求。 它將找到 creditLimit 並創建一個 RequestObject 的實例,其中填充了 creditLimit。

然后,您可以在控制器內部將 accountId 值分配給 RequestObject 其他屬性。 這樣您就不需要將 accountId 作為請求正文的一部分傳遞。 您僅將其作為 URL 端點的一部分傳遞。

以下鏈接是更深入細節的好資源。 http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM