ASP.NET Core – Custom Input formatters For Partial Update and Handle Under-posting
前言
之前的文章有谈过关于 ASP.NET Core 处理 under-posting 的方式.
它会使用 class default value. 许多时候这可能不是我们期望的. 比如当我们想要 patch update resource 的时候.
一种解决方法是把 DTO 改成 nullable 来表示 under-posting, 但这也不总是正确的, 毕竟也有可能它是想把 value set to null.
另一个方法是使用 JSON Patch, 但这个方法会让前端比较麻烦. 那为了实现需求只能做一些 Custom 的方案了
Model Binding
参考: Model Binding in ASP.NET Core
在说解决思路之前, 先了解一些基础知识. Model Binding 是 ASP.NET Core 把 Request 的信息投影到 Action parameter 的一个执行过程.
这就是为什么在 Action 阶段获取到的不是 JSON string 而是已经 deserialize 好的 instance. 就是这个 Model Binding 干的事儿.
Input Formatters
而在整个 Model Binding 中, Input Formatters 则是最终负责 deserialize JSON 的.
解决思路
想解决 under-posting 的问题, 就必须在 ASP.NET Core 处理 JSON 之前, 一旦它 deserialize JSON 后, 我们就失去了 under-posting 的信息了.
所以效仿 JsonPatch 的做法就是 Custom Input Formatters.
我们可以让 deserialize 的 class implement 特定的 interface, 比如叫 IUnderPosting, 里面则拥有一个 UnderPostingPropertyNames.
拦截 format 以后, 先做一轮原生的 JSON deserialize, 然后在做一个 JsonNode parse, 然后把 under-posting 的 property 记入到对象中.
这个概念和 JSON overflow 类似 Handle overflow JSON.
于是我们就拥有了 under-posting 的信息. 往后, 无论是 validation, mapping 都可以利用这个信息去做事情, 比如 under-posting 的 property 就 ignore validation 同时也不需要 update entity.
预想的结果
按思路走大概是这样的
public interface IUnderPosting
{
List<string> UnderPostingPropertyNames { get; set; }
} public class UpdateProductDTO : IUnderPosting
{
public string Name { get; set; } = "";
public decimal Amount { get; set; }
public List<string> UnderPostingPropertyNames { get; set; } = new();
}
在 Controller 可以依据 UnderPostingPropertyNames 去做逻辑处理.
[ApiController]
[Route("api/products")]
public class ProductController : ControllerBase
{
[HttpPatch]
public IActionResult UpdateProduct(UpdateProductDTO dto)
{
return Ok();
}
}
探路
看源码
既然我们是想替代 ASP.NET Core build-in 的 JSON formatter, 那就必须先看看它长什么样. 要如何 customize.
SystemTextJsonInputFormatter.cs
TextInputFormatter 好理解, Custom formatters in ASP.NET Core Web API 里也用这个, IInputFormatterExceptionPolicy 就不太清楚了.
最基本需要实现的接口
public class MyInputFormatter2 : TextInputFormatter, IInputFormatterExceptionPolicy
{
public InputFormatterExceptionPolicy ExceptionPolicy => throw new NotImplementedException();
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
throw new NotImplementedException();
}
}
所以整个核心代码就在 ReadRequestBodyAsync 里
可以大致看出它的实现方式, 从 HttpContext 读取 body stream, 然后 deserialze, ModelType 就是要 binding 到的 class, 如果有 exception 比如 type mismatch, 就写入到 ModelError.
显然它没有允许扩展, 没有办法简单的继承 override 去 customize 它. 另外还有一个难题.
它有 Dependency Injection 的, 但是 formatter 是不允许有 Dependency Injection 的
想要依赖 service 需要靠 context 去拿才正确.
怎么办... wrap 它!
能想到的方法就是 wrap 它来 customize
public class MyInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy
{
public MyInputFormatter()
{
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
SupportedEncodings.Add(UTF16EncodingLittleEndian);
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json").CopyAsReadOnly());
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/json").CopyAsReadOnly());
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/*+json").CopyAsReadOnly());
}
InputFormatterExceptionPolicy IInputFormatterExceptionPolicy.ExceptionPolicy => InputFormatterExceptionPolicy.MalformedInputExceptions; public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
var serviceProvider = context.HttpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<SystemTextJsonInputFormatter>>();
var jsonOptionsAccessor = serviceProvider.GetRequiredService<IOptions<JsonOptions>>();
var defaultFormatter = new SystemTextJsonInputFormatter(jsonOptionsAccessor.Value, logger);
InputFormatterResult result;
if (StgUtil.HasImplementInterface(context.ModelType, typeof(IUnderPosting)))
{
context.HttpContext.Request.EnableBuffering();
result = await defaultFormatter.ReadRequestBodyAsync(context, encoding);
context.HttpContext.Request.Body.Position = 0;
var model = (IUnderPosting)result.Model!;
using var document = await JsonDocument.ParseAsync(context.HttpContext.Request.Body);
context.HttpContext.Request.Body.Position = 0;
var rootElement = document.RootElement;
var properties = rootElement.EnumerateObject().Select(p => p.Name).ToList();
model.UnderPostingPropertyNames = properties;
}
else {
result = await defaultFormatter.ReadRequestBodyAsync(context, encoding);
}
return result;
}
}
这样我们就可以控制最终的 binding 结果了. 这里只是一个大概, 具体实现我懒得写了. 还有一个需要注意的是 body stream 默认只能 read 一次, 如果要多次需要打开设定.
参考:
Re-reading ASP.Net Core request bodies with EnableBuffering()
How to read request body in an asp.net core webapi controller?
ASP.NET Core – Custom Input formatters For Partial Update and Handle Under-posting的更多相关文章
- ASP.NET Core MVC 之局部视图(Partial Views)
1.什么是局部视图 局部视图是在其他视图中呈现的视图.通过执行局部视图生成的HTML输出呈现在调用视图中.与视图一样,局部视图使用 .cshtml 文件扩展名.当希望在不同视图之间共享网页的可重用部分 ...
- ASP.NET Core 中文文档 第四章 MVC(3.7 )局部视图(partial)
原文:Partial Views 作者:Steve Smith 翻译:张海龙(jiechen).刘怡(AlexLEWIS) 校对:许登洋(Seay).何镇汐.魏美娟(初见) ASP.NET Core ...
- [转]Writing Custom Middleware in ASP.NET Core 1.0
本文转自:https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/ One of the new ...
- [转]How do you create a custom AuthorizeAttribute in ASP.NET Core?
问: I'm trying to make a custom authorization attribute in ASP.NET Core. In previous versions it was ...
- 在ASP.NET Core使用Middleware模拟Custom Error Page功能
一.使用场景 在传统的ASP.NET MVC中,我们可以使用HandleErrorAttribute特性来具体指定如何处理Action抛出的异常.只要某个Action设置了HandleErrorAtt ...
- 在ASP.NET Core中实现自定义验证特性(Custom Validation Attribute)
这是我们在实际ASP.NET Core项目中用到的,验证用户名中是否包含空格. 开始是这么实现的(继承ValidationAttribute,重写IsValid方法): public class No ...
- [转]Using NLog for ASP.NET Core to write custom information to the database
本文转自:https://github.com/NLog/NLog/issues/1366 In the previous versions of NLog it was easily possibl ...
- [转]Create Custom Exception Filter in ASP.NET Core
本文转自:http://www.binaryintellect.net/articles/5df6e275-1148-45a1-a8b3-0ba2c7c9cea1.aspx In my previou ...
- [转]在ASP.NET Core使用Middleware模拟Custom Error Page功能
本文转自:http://www.cnblogs.com/maxzhang1985/p/5974429.html 阅读目录 一.使用场景 二..NET Core实现 三.源代码 回到目录 一.使用场景 ...
- [译]Writing Custom Middleware in ASP.NET Core 1.0
原文: https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/ Middleware是ASP. ...
随机推荐
- 网易数帆开源贡献获业界肯定,轻舟API网关获OSCAR尖峰开源技术创新奖
2020年10月16日,由中国信息通信研究院主办的"2020开源产业大会"在北京线下与线上同步召开,主办方在会上公布了"OSCAR尖峰开源奖项"各个奖项的评选结 ...
- Netcode for Entities如何添加自定义序列化,让GhostField支持任意类型?以int3为例(1.2.3版本)
一句话省流:很麻烦也很抽象,能用内置支持的类型就尽量用. 首先看文档.官方文档里一开头就列出了所有内置的支持的类型:Ghost Type Templates 其中Entity类型需要特别注意一下:在同 ...
- 踩坑记录:windows11下使用 VS2022 和 PCL1.14.1 配置点云开发环境
闲话不多说,具体在windows下下载PCL与解压pcl可以看https://www.yuque.com/huangzhongqing/pcl/这位大佬的文章,那我就具体说一下踩过点坑: 踩坑点1: ...
- LeetCode513. 找树左下角的值
题目链接:https://leetcode.cn/problems/find-bottom-left-tree-value/description/ 题目叙述: 给定一个二叉树的 根节点 root,请 ...
- 巧用 QLineF 从 QTransform 提取角度
我们在对 QGraphicsItem 进行变换时,QT 提供了很多便捷的方法.但当我们想获取当前变换的角度时却有些困难,因为 QTransform 没有提供获取角度的方法.在文章Qt 从 QTrans ...
- ChatGPT的作用(附示例)
ChatGPT介绍(内容由ChatGPT生成) ChatGPT是一款基于GPT(生成式预测网络)的聊天机器人,它可以根据用户输入自动生成相应的回复. GPT是由OpenAI开发的一种预测网络模型,其中 ...
- (六)Redis 消息队列 List、Streams
Redis 适合做消息队列吗?有什么解决方案?首先要明白消息队列的消息存取需求和工作流程. 1.消息队列 我们一般把消息队列中发送消息的组件称为生产者,把接收消息的组件称为消费者,下图是一个通用的消息 ...
- Android 性能稳定性测试工具 mobileperf 开源 (天猫精灵 Android 性能测试-线下篇)
Android 性能稳定性测试工具 mobileperf 开源 (天猫精灵 Android 性能测试-线下篇) 这篇文章写得很好!感谢阿里云开发者社区!!! 原文地址: https://develop ...
- 【Java】Spring注入静态Bean的几种写法
单例模式在Spring注解上的一种拓展用法 写法一,先配置自身Bean,作为静态成员,然后目标Bean作为自身Bean的实例成员' Spring初始化自身Bean时自动装配数据源Bean,从而附属到静 ...
- Git的分支管理和标签操作
分支操作 分支是Git使用过程中非常重要的概念.使用分支意味着你可以把你的工作从开发主线上分离出来,以免影响开发主线. 同意一个仓库可以有多个分支,各个分支相互独立,互不干扰.通过git init 命 ...