首页 > 解决方案 > ASP.NET Core 3.1:查询字符串参数确定正文的类

问题描述

我正在寻找一个带有两个曲折的 ASP.NET Core 复杂模型绑定的示例。一种是body的类由查询字符串参数决定,但查询字符串参数不决定类型。

[HttpPut("api/Certificates/activities/state")]
public IActionResult PutState([FromQuery] string stateId, [FromQuery] int activityId, [FromQuery] string agent, [FromQuery] Guid registration, [FromBody] object body = null)
{
    ...
    return NoContent();
}

确定主体类的查询参数是stateId,但其值不完全包含类型名称。相反,场景如下:

  1. 如果stateId等于"LMS.LaunchData",则应将主体验证为LaunchData对象。
  2. 如果stateId等于"status",则应将主体验证为Status对象。
  3. 否则,body 应该是一个普通的对象。

第二个转折是,将发布到此端点的已知主体类没有任何共同点。在应用程序的其他部分,它们服务于完全不同的目的。我在下面发布了我的两种特殊体型,让您看到它们没有任何共同点。

启动数据.cs

    using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace EmploymentModules.Models
{
    public class LaunchData
    {
        [JsonPropertyName("registration")]
        public Guid Registration { get; set; }

        [JsonPropertyName("contextTemplate")]
        public Context ContextTemplate { get; set; } = new Context();
        [JsonPropertyName("launchMode")]
        public string LaunchMode { get; set; }
        [JsonPropertyName("launchMethod")]
        public string LaunchMethod { get; set; }
        [JsonPropertyName("returnURL")]
        public string ReturnURL { get; set; }
        [JsonPropertyName("launchParameters")]
        public string LaunchParameters { get; set; }
        [JsonPropertyName("entitlementKey")]
        public EntitlementKey EntitlementKey { get; set; } = new EntitlementKey();
        [JsonPropertyName("moveOn")]
        public string MoveOn { get; set; }
        
    }

    public class EntitlementKey
    {
        [JsonPropertyName("courseStructure")]
        public string CourseStructure { get; set; }
    }
}

Cmi5Result.cs

    
using EmploymentModules.Services;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Dynamic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using static EmploymentModules.Helpers.ScoreValidators;

namespace EmploymentModules.Models
{
    public class Cmi5Result
    {
        [JsonPropertyName("score")]
        [ScoreValidation]
        public Score Score { get; set; }
        [JsonPropertyName("success")]
        public bool? Success { get; set; }
        [JsonPropertyName("completion")]
        public bool? Completion { get; set; }
        [JsonPropertyName("duration"), RegularExpression("^(P((?<Years>\\d+)Y)?((?<Months>\\d+)M)?((?<Days>\\d+)D)?)(T((?<Hours>\\d+)H)?((?<Minutes>\\d+)M)?((?<Seconds>\\d+((.)?(\\d)?(\\d)?))S)?)$")]
        public string Duration { get; set; }
        [JsonPropertyName("extensions")]
        public ResultExtensions Extensions { get; set; }
    }

    [JsonConverter(typeof(ScoreConverter))]
    public class Score
    {
        [JsonPropertyName("scaled")]
        public decimal? Scaled { get; set; }
        [JsonPropertyName("raw")]
        public int? Raw { get; set; }
        [JsonPropertyName("min")]
        public int? Min { get; set; }
        [JsonPropertyName("max")]
        public int? Max { get; set; }
    }

    public class ResultExtensions : DynamicObject
    {
        [Range(0, 100), JsonPropertyName("https://w3id.org/xapi/cmi5/result/extensions/progress")]
        public int? Progress { get; set; }
        [RegularExpression("^(Tested Out|Equivalent AU|Equivalent Outside Activity|Administrative)$"), JsonPropertyName("https://w3id.org/xapi/cmi5/result/extensions/reason")]
        public string Reason { get; set; }

    }

    public class Status : Cmi5Result
    {
        [JsonPropertyName("launchModes")]
        public List<string> LaunchModes { get; set; }
    }
}

标签: c#asp.net-core

解决方案


这里有两个解决方案。

1.您可以反序列化对象,如下所示:

控制器:

public IActionResult PutState([FromQuery] string stateId, [FromQuery] int activityId, 

    [FromQuery] string agent, [FromQuery] Guid registration, [FromBody] object body)
            {
               
                    var model = new Object();
                    switch (stateId)
                    {
                        case "LMS.LaunchData":
                            model = JsonConvert.DeserializeObject<LaunchData>(body.ToString());
                            break;
                        case "status":
                            model = JsonConvert.DeserializeObject<Status>(body.ToString());
                        break;
                        default:
                        model = body;
                            break;
                    }
                return Ok(model);
        
            }

测试机构:

{
    "success": true,
    "score":{
        "raw":1,
        "min":0,
        "max":2
    }
}

结果: 在此处输入图像描述

2.您也可以添加stateId到Object body。并使用Model Binder。这样body的类由stateIdObject body中决定。这是一个演示:

控制器(添加[ModelBinder(typeof(DataBinder))]object body):

//use ModelBinder to Bind object body
public IActionResult PutState([FromQuery] string stateId, [FromQuery] int activityId, [FromQuery] string agent, [FromQuery] Guid registration, [FromBody][ModelBinder(typeof(DataBinder))] object body)
        {
            return Ok(body);
    
        }

数据绑定器:

 public class DataBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }


            var model = new Object();
            using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
            {
                var body = reader.ReadToEndAsync();

                var mydata = JsonConvert.DeserializeObject<JObject>(body.Result);
                
                var stateId = mydata["stateId"].ToString();
                switch (stateId)
                {
                    case "LMS.LaunchData":
                        model = mydata.ToObject<LaunchData>();
                        break;
                    case "status":
                        model = mydata.ToObject<Status>();
                        break;
                    default:
                        model = mydata.ToObject<Object>();
                        break;
                }
            }

            bindingContext.Result = ModelBindingResult.Success(model);
            return Task.CompletedTask;
        }
    }

测试正文(添加stateId到正文):

{
    "stateId":"status",
    "success": true,
    "score":{
        "raw":1,
        "min":0,
        "max":2
    }
}

结果: 在此处输入图像描述


推荐阅读