Bläddra i källkod

.net core 2.2下返回自定义模型验证失败信息

master
qdjjx 5 år sedan
förälder
incheckning
182c8766d2

实践/后端/项目/15.InfluxDB操作.md → 实践/后端/项目/16.InfluxDB操作.md Visa fil


+ 180
- 0
实践/后端/项目/17.NET Core 2.2在API中返回自定义模型验证失败信息.md Visa fil

@@ -0,0 +1,180 @@
1
+在 .NET Core API中,如果想在接口中验证某个模型,首先需要在该模型(类)类的属性上打上来自于`System.ComponentModel.DataAnnotations`命名空间下的特性。
2
+
3
+```
4
+    public class SomeModel
5
+    {
6
+        [Required(ErrorMessage = "电话号码必填")]
7
+        [StringLength(30, ErrorMessage = "电话号码长度不能超过30")]
8
+        [RegularExpression(@"^1[3458][0-9]{9}$", ErrorMessage = "手机号格式不正确")]
9
+        public string Phone { get; set; }
10
+
11
+        [Required(ErrorMessage = "姓名必填")]
12
+        [StringLength(10, ErrorMessage = "姓名长度不能超过10")]
13
+        public string Name { get; set; }
14
+    }
15
+```
16
+
17
+然后在控制器方法中:
18
+
19
+```
20
+[HttpPost]
21
+public async Task<IActionResult> SomeActionMethod([FromBody]SomeModel command){}
22
+```
23
+
24
+此时请求`SomeActionMethod`这个接口,如果`SomeModel`的字段值不符合验证定义,就会返回如下报错:
25
+
26
+```
27
+{
28
+    "Name": [
29
+        "姓名必填"
30
+    ],
31
+    "Phone": [
32
+        "手机号格式不正确"
33
+    ]
34
+}
35
+
36
+```
37
+而在实际项目一般都有统一的返回格式,比如:
38
+
39
+```
40
+{
41
+    "success": true,
42
+    "message": "",
43
+    "errors": {
44
+        "name":[],
45
+        "phone":[]
46
+    }
47
+}
48
+```
49
+
50
+**如何让API返回的模型验证失败信息符合自定义格式呢**?
51
+
52
+# 错误的方式
53
+
54
+在以前的 ASP .NET MVC或者 .NET Core 2.0时代我们可以通过继承`ActionFilterAttribute`这个基类来实现。
55
+
56
+```
57
+public class ValidateModelAttribute : ActionFiltereAttribute
58
+{
59
+    public override void OnActionExecuting(ActionExecutingContext context)
60
+    {
61
+        if(!context.ModelState.IsValid)
62
+        {
63
+            context.Result = new BadRequestObjectResult(context.ModelState);
64
+        }
65
+    }
66
+}
67
+```
68
+
69
+在`Startup.cs`中配置。
70
+
71
+```
72
+services.AddMvcCore(options => {
73
+    options.Filters.Add(typeof(ValidateModelAttribute));
74
+});
75
+```
76
+
77
+最后在控制器方法上套用。
78
+
79
+```
80
+[ValidateModel]
81
+[HttpPost]
82
+public async Task<IActionResult> SomeActionMethod([FromBody]SomeModel command){}
83
+```
84
+
85
+**很不幸**,在 .NET Core 2.2下,虽然以上写法没有报错,但实际是走不通的。
86
+
87
+# 正确的方式
88
+
89
+阅读官网后发现在 .NET Core 2.2下可以通过实现`IResultFilter`接口来实现。在这个接口的`OnResultExecuting`方法中返回自定义的格式。
90
+
91
+> 在.NET Core 2.2 API中,当请求过来会来到请求管道,经过的路径大致包括:other middlewares → routing middleware  → action selection  → filter middlewares, 而在filter middleware中所经历的中间件依次是:authorization filter → resource filter → exception filter → model binding机制 → action filter  → result filter,而上面说的`IResultFilter`就属于result filter。
92
+>
93
+> 可以在一个类上增加多个过滤器。每个过滤器都有同步和异步方法,但不能同时使用同步或异步方法。如果既有同步方法也有异步方法会以异步方法优先。
94
+
95
+言归正传。首先实现`IResultFilter`接口。
96
+
97
+```
98
+    /// <summary>
99
+    /// 模型验证失败后返回的结果
100
+    /// </summary>
101
+    public class ValidateModelResultServiceFilter : IResultFilter
102
+    {
103
+        public void OnResultExecuted(ResultExecutedContext context)
104
+        {                      
105
+        }
106
+
107
+        public void OnResultExecuting(ResultExecutingContext context)
108
+        {
109
+            if (!context.ModelState.IsValid)
110
+            {
111
+                context.Result = new ValidationFailedResult(context.ModelState);
112
+            }
113
+        }
114
+    }
115
+```
116
+
117
+以上返回了自定义的`ValidationFailedResult`
118
+
119
+```
120
+public class ValidationFailedResult : ObjectResult
121
+{
122
+    public ValidationFailedResult(ModelStateDictionary modelState):base(new ValidationVM(modelState))
123
+    {
124
+        StatusCode = StatusCodes.Status422UnprocessableEntity;
125
+    }
126
+}
127
+```
128
+
129
+以上在`ValidationFailedResult`的构造函数中把`ValidationVM`实例交给了`ObjectResult`这个基类,`ObjectResult`负责把`ValidationVM`实例对象打印出来,即接口返回的格式。
130
+
131
+```
132
+   public class ValidationVM
133
+    {
134
+        /// <summary>
135
+        /// 返回数据是否成功
136
+        /// </summary>
137
+        public bool Success { get; set; }
138
+        /// <summary>
139
+        /// 返回描述
140
+        /// </summary>
141
+        public string Message { get; set; }
142
+
143
+        public List<VaidationError> Errors { get; }
144
+
145
+        public ValidationVM(ModelStateDictionary modelState)
146
+        {
147
+            Success = false;
148
+            Message = "输入模型无法通过验证";
149
+            Errors = modelState.Keys.SelectMany(key => modelState[key].Errors.Select(x => new VaidationError(key, x.ErrorMessage))).ToList();
150
+        }
151
+    }
152
+
153
+    public class VaidationError
154
+    {
155
+        [JsonProperty(NullValueHandling=NullValueHandling.Ignore)]
156
+        public string Field { get; }
157
+        public string Message { get; }
158
+
159
+        public VaidationError(string field, string message)
160
+        {
161
+            Field = string.IsNullOrEmpty(field) ? null : field;
162
+            Message = message;
163
+        }
164
+    }
165
+```
166
+
167
+需要在`Startup.cs`中放入DI容器。
168
+
169
+```
170
+services.AddScoped<ValidateModelResultServiceFilter>();
171
+```
172
+
173
+最后放置在某个控制器方法上。
174
+
175
+```
176
+[ServiceFilter(typeof(ValidateModelResultServiceFilter))]
177
+[HttpPost] 
178
+public async Task<IActionResult> SomeActionMethod([FromBody]SomeModel model){}
179
+```
180
+

Laddar…
Avbryt
Spara