【问题标题】:Annotation for field transformation on HTTP requestsHTTP 请求的字段转换注解
【发布时间】:2021-06-12 13:41:09
【问题描述】:

给定我的 Api 控制器端点的 Dto

public class MyDto
{
    public int FirstProp { get; set; }
    public int SecondProp { get; set; }

    // Make it optional
    // On request set its value equal to the sum of FirstProp and SecondProp
    public int ThirdProp { get; set; }
}

字段ThirdProp 应该是可选的,因为您也可以通过计算其他道具的总和来设置它。这只是一个例子,我的实际用例有点困难,我必须从配置中读取值并基于它们,我可以在 HTTP 请求期间计算此属性。

出于测试目的,这是我正在使用的控制器

[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
    [HttpPost]
    public int ReturnThirdProp([FromBody] MyDto myDto) => myDto.ThirdProp;
}

NestJs 框架中的转换管道是展示我想要实现的目标的示例

https://docs.nestjs.com/pipes

有什么东西可以用于我的 C# Api 吗?

这只是一个伪实现,展示了我对类似于ValidationAttribute的解决方案的期望

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class MyCustomTransformAnnotation : OnHttpRequestAttribute
{
    protected override void Transform(object value, RequestContext requestContext)
    {
        MyDto myDto = requestContext.GetBodyValue<MyDto>();
        myDto.ThirdProp = myDto.FirstProp + myDto.SecondProp;
        // now the controller can work with the transformed DTO
    }
}

【问题讨论】:

    标签: c# asp.net-core-webapi


    【解决方案1】:

    您可以使用Custom Model Binding。众所周知,传入的请求数据和应用模型之间的映射是由模型绑定器处理的,通过使用自定义模型绑定,我们可以在将输入绑定到应用模型之前对其进行转换,我们还可以验证值。

    在您的场景中,您可以创建一个自定义活页夹,如下所示:

    //required using Microsoft.AspNetCore.Mvc.ModelBinding;
    //required using Newtonsoft.Json.Linq;
    //required using System.Threading.Tasks;
    //required using WebApplication.Models; //The MyDto class is in the Models folder.
    public class MyDtoEntityBinder : IModelBinder
    { 
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            } 
            string valueFromBody = string.Empty;  
            //get value from request body. 
            //if you want to get value from the form, try to get the value from the ValueProvider, code like this:  bindingContext.ValueProvider.GetValue("FirstProp");
    
            using (var sr = new StreamReader(bindingContext.HttpContext.Request.Body))
            {
                valueFromBody = sr.ReadToEnd();
            }
    
            if (string.IsNullOrEmpty(valueFromBody))
            {
                return Task.CompletedTask;
            }
            int firstProp = 0, secondProp =0;
            if(JObject.Parse(valueFromBody).ContainsKey("FirstProp"))
                firstProp = Convert.ToInt32(((JValue)JObject.Parse(valueFromBody)["FirstProp"]).Value);
            if(JObject.Parse(valueFromBody).ContainsKey("SecondProp"))
                secondProp = Convert.ToInt32(((JValue)JObject.Parse(valueFromBody)["SecondProp"]).Value);
    
            //create a new object.
            var result = new MyDto()
            {
                FirstProp = firstProp,
                SecondProp = secondProp,
                ThirdProp = firstProp + secondProp
            };
            bindingContext.Result = ModelBindingResult.Success(result);
    
            return Task.CompletedTask;
        }
    }
    

    然后,在模型上应用 ModelBinder 属性:

    [ModelBinder(BinderType = typeof(MyDtoEntityBinder))]
    public class MyDto
    {
        public int FirstProp { get; set; }
        public int SecondProp { get; set; }
    
        // Make it optional
        // On request set its value equal to the sum of FirstProp and SecondProp
        public int ThirdProp { get; set; }
    }
    

    API 控制器:

        // POST api/<MyController>
        [HttpPost]
        public IActionResult Post([FromBody]MyDto myDto)
        {
            return Ok(myDto);
        }
    

    结果如下:

    【注意】在调试上述示例代码时,如果遇到Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true错误。尝试在 Startup.cs 中允许同步 IO:

    public void ConfigureServices(IServiceCollection services)
    {
        // If using Kestrel:
        services.Configure<KestrelServerOptions>(options =>
        {
            options.AllowSynchronousIO = true;
        });
    
        // If using IIS:
        services.Configure<IISServerOptions>(options =>
        {
            options.AllowSynchronousIO = true;
        });
    }
    

    有关使用自定义模型绑定的更多详细信息,请参阅以下文章:

    Custom model binder sample

    Custom Model Binding In ASP.NET Core MVC

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-18
      • 1970-01-01
      • 2016-06-24
      • 1970-01-01
      相关资源
      最近更新 更多