【问题标题】:ASP.NET Core 3.1: Query string parameter determines class of bodyASP.NET Core 3.1:查询字符串参数确定正文的类
【发布时间】:2020-10-13 20:26:29
【问题描述】:

我正在寻找一个包含两个曲折的 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();
}

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

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

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

LaunchData.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】:

这里有两个解决方案。

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.您也可以在Object body中添加stateId。并使用Model Binder。这样body的类由Object body中的stateId确定。 这是一个演示:

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

//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
    }
}

结果:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-29
    • 2020-09-28
    • 2014-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-03
    相关资源
    最近更新 更多