ASP.NET Web API 中的模型验证
ASP.NET Web API 中的参数绑定 FromUri,FromBody
ASP.NET Core 中的模型绑定
ASP.NET Core ApiController 属性
ASP.NET Core 自动 HTTP 400 响应
ASP.NET Core 禁用自动 400 响应
ASP.NET Core MVC 和 Razor Pages 中的模型验证
将验证添加到 ASP.NET Core MVC 应用
添加验证
文档目录
.NET 文档
ASP.NET 文档
介绍如何对模型进行批注、 使用批注以进行数据验证和处理你的 web API 中的验证错误。 当客户端将数据发送到 web API 时,通常要执行任何处理之前验证数据。
在你的控制器操作,可以检查模型是否有效:
using MyApi.Models;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace MyApi.Controllers
{
public class ProductsController : ApiController
{
public HttpResponseMessage Post(Product product)
{
if (ModelState.IsValid)
{
// Do something with the product (not shown).
return new HttpResponseMessage(HttpStatusCode.OK);
}
else
{
// 模型验证失败,提示错误信息
string message = string.Empty;
foreach (var item in ModelState.Values)
{
foreach (var error in item.Errors)
message += error.ErrorMessage + "|";
}
return Json(new { code = 0, message = message, data = 0 });
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
}
}
}
处理验证错误
创建一个操作筛选器来调用控制器操作之前检查模型状态。 下面的代码演示一个示例
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Web.Http.ModelBinding;
namespace MyApi.Filters
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
/// 推荐
string message = string.Empty;
foreach (var item in actionContext.ModelState.Values)
{
foreach (var error in item.Errors)
message += error.ErrorMessage + "|";
}
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Accepted)
{
Content = new StringContent(JsonConvert.SerializeObject(new
{
code = HttpStatusCode.BadRequest,
message = message,
}),
System.Text.Encoding.GetEncoding("UTF-8"), "application/json")
};
/// 返回BadRequest
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
}
筛选器应用于所有的 Web API 控制器,将添加到筛选器的实例HttpConfiguration.Filters在配置期间的集合,\App_Start\WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new ValidateModelAttribute());
// ...
}
}
设置筛选器作为特性上各个控制器或控制器操作
public class ProductsController : ApiController
{
[ValidateModel]
public HttpResponseMessage Post(Product product)
{
// ...
}
}
*、模型
public class SignInRequest : RequestBase
{
/// <summary>
/// 用户名
/// </summary>
[Required(ErrorMessage = "用户名不允许为空")]
[StringLength(20, MinimumLength = 1, ErrorMessage = "用户名为{2}-{1}位字符")]
[RegularExpression(@"^[A-Za-z]+$", ErrorMessage = "登录名必须是字母!")]
public string UserName { get; set; }
/// <summary>
/// 密码
/// </summary>
[Required(ErrorMessage = "密码不允许为空")]
[StringLength(20, MinimumLength = 6, ErrorMessage = "密码为{2}-{1}位字符")]
[RegularExpression(@"(?!^(\d+|[a-zA-Z]+|[~!@#$%^&*?]+)$)^[\w~!@#$%^&*?]{8,16}$", ErrorMessage = "字母、数字、标点符号至少包含2种,8-16个字符!")]
[DataType(DataType.Password)]
public string Password { get; set; }
/// <summary>
/// 确认登录密码
/// </summary>
[Compare("Password", ErrorMessage = "两次密码输入不一致")]
public string ConfirmPassword { get; set; }
/// <summary>
/// 网址
/// </summary>
[Required(ErrorMessage = "网址尚未填写,请先完善")]
[StringLength(160, ErrorMessage = "网址太长了,最大为{1}位字符")]
[Url(ErrorMessage = "网址格式错误")]
public string Url { get; set; }
/// <summary>
/// 邮箱
/// </summary>
[Required(ErrorMessage = "邮箱尚未填写,请先完善")]
[StringLength(160, ErrorMessage = "邮箱太长了,最大为{1}位字符")]
[MinLength(160, ErrorMessage = "最小长度{1}位字符")]
[MaxLength(160, ErrorMessage = "最大长度{1}位字符")]
[EmailAddress(ErrorMessage = "邮箱格式错误")]
public string Email { get; set; }
[Required(ErrorMessage = "手机号不允许为空")]
[Phone(ErrorMessage = "手机号格式不正确")]
[RegularExpression(@"^0?(13[0-9]|15[012356789]|18[0123456789]|14[57]|17[1235678]|170|14[57]|166|19[189])[0-9]{8}$", ErrorMessage = "格式不正确")]
public string Phone { get; set; }
[Required(ErrorMessage = "身份证号不允许为空")]
[RegularExpression(@"^(11|12|13|14|15|21|22|23|31|32|33|34|35|36|37|41|42|43|44|45|46|50|51|52|53|54|61|62|63|64|65|71|81|82|91)(\d{13}|\d{15}[\dxX])$", ErrorMessage = "身份证号格式不正确")]
public string IDCard { get; set; }
/// <summary>
/// 日期验证
/// 只能验证 yyyy-MM-dd 格式日期,不能携带 HH:mm:ss 格式
/// </summary>
[Required(ErrorMessage = "日期不允许为空")]
[RegularExpression(@"^((((1[6-9]|[2-9]\d)\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\d|3[01]))|(((1[6-9]|[2-9]\d)\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\d|30))|(((1[6-9]|[2-9]\d)\d{2})-0?2-(0?[1-9]|1\d|2[0-9]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-))$", ErrorMessage = "日期格式不正确")]
public string Date { get; set; }
/// <summary>
/// 时间验证
/// 支持验证 yyyy-MM-dd 或 yyyy-MM-dd HH:mm 或 yyyy-MM-dd HH:mm:ss 格式
/// </summary>
[RegularExpression(@"^(19|20)\d\d[-/.]((0[1-9])|([1-9])|(1[0-2]))[-/.](([0-2][1-9])|([1-2]0)|(3[0-1])|([1-9])) (([0-1]\d)|([1-9])|(2[0-3]|(0))):(([0-5]\d)|([1-9])):(([0-5]\d)|([1-9]))$")]
public DateTime Time { get; set; }
/// <summary>
/// 时间验证
/// 支持验证 HH:mm:ss 或者 HH:mm 格式
/// </summary>
[RegularExpression(@"^(([0-1]\d)|([1-9])|(2[0-3]|(0))):(([0-5]\d)|([1-9])):(([0-5]\d)|([1-9]))$", ErrorMessage = "日期格式不正确")]
public TimeSpan? time { get; set; }
/// <summary>
/// 年份:1900 - 2099
/// </summary>
[Required(ErrorMessage = "年份不允许为空")]
[RegularExpression(@"^(19|20)\d{2}$", ErrorMessage = "年份格式不正确")]
public string year { get; set; }
/// <summary>
/// 年份:1900 - 9999
/// </summary>
[Required(ErrorMessage = "年份不允许为空")]
[RegularExpression(@"^(19|[2-9][0-9])\d{2}$", ErrorMessage = "年份格式不正确")]
public string year { get; set; }
/// <summary>
/// 月份:个位数月份前 可以加0 或者 不加0
/// </summary>
[RegularExpression(@"^0?[1-9]$|^1[0-2]$", ErrorMessage = "月份格式不正确")]
public string? month { get; set; }
/// <summary>
/// 性别
/// </summary>
[Required(ErrorMessage = "性别尚未填写,请先完善")]
[RegularExpression(@"[/^男$|^女&/]", ErrorMessage = "性别错误")]
[RegularExpression(@"^['男'|'女']$", ErrorMessage = "性别错误")]
[RegularExpression(@"(男|女|未知)", ErrorMessage = "性别错误")]
[RegularExpression(@"\b(男|女|未知)\b", ErrorMessage = "性别错误")]
public string Sex{ get; set; }
/// <summary>
/// 年龄
/// </summary>
[Required(ErrorMessage = "年龄尚未填写,请先完善")]
[Range(18, 60, ErrorMessage = "年龄范围在{1}至{2}之间")]
public string Age { get; set; }
[Range(1, 99999, ErrorMessage = "排序范围在{1}至{2}之间")]
public int Sort { get; set; }
[Range(typeof(decimal), "0.00", "49.99")]
public decimal Total { get; set; }
[Range(typeof(double), "0.00", "49.99")]
public double Amount { get; set; }
}
*、内置验证特性
[CreditCard]:验证属性是否有信用卡格式。
[Compare]:验证模型中的两个属性是否匹配。
[EmailAddress]:验证属性是否有电子邮件格式。
[Phone]:验证属性是否有电话号码格式。
[Range]:验证属性值是否在指定范围内。
[RegularExpression]:验证属性值是否与指定的正则表达式匹配。
[Required]:验证字段是否非 NULL。 请参阅 [必需] 特性,获取关于该特性的行为的详细信息。
[StringLength]:验证字符串属性值是否未超过指定长度限制。
[Url]:验证属性是否有 URL 格式。
[Remote]:通过调用服务器上的操作方法,验证客户端上的输入。 请参阅 [远程] 特性获取关于该特性的行为的详细信息。
在 System.ComponentModel.DataAnnotations 命名空间中可找到验证特性的完整列表
--------------------------------------------- 15位身份证号 ---------------------------------------------
15位身份证号:第7、8位为出生年份(两位数),第9、10位为出生月份,第11、12位为出生日期,第15位为性别,奇数为男,偶数为女。
【位数】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1 1 0 1 0 1 2 0 0 1 0 1 0 1 1
【15位身份证号】
110101200101011
【18位身份证截取出生日期】
string identityCard = "110106201901026789";
string year = identityCard.Substring(6, 4);
string month = identityCard.Substring(10, 2);
string date = identityCard.Substring(12, 2);
--------------------------------------------- ASP.Net Core ---------------------------------------------
Startup.cs、
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.Filters.Add<ValidateModelAttribute>();
options.MaxModelValidationErrors = 50;
})
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var result = new BadRequestObjectResult(context.ModelState);
result.ContentTypes.Add(MediaTypeNames.Application.Json);
result.ContentTypes.Add(MediaTypeNames.Application.Xml);
return result;
};
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[404].Link = "https://httpstatuses.com/404";
})
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
});
}
ValidateModelAttribute.cs、
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Linq;
using System.Text;
namespace Asset.WebAPI
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
#region 返回 Json 字符串
var result = context.ModelState.Keys.SelectMany(key => context.ModelState[key].Errors
.Select(x => new ValidationError(key, x.ErrorMessage))).ToList();
context.Result = new ObjectResult(new { code = (int)EnumCode.Fail, message = result });
#endregion
#region 返回 Json 字符串
var resultNew = context.ModelState.Where(x => x.Value.Errors.Count > 0)
.Select(x => new
{
Name = x.Key,
Message = x.Value.Errors?.FirstOrDefault().ErrorMessage
}).ToList();
//context.Result = new ObjectResult(new { code = (int)EnumCode.Fail, message = resultNew });
#endregion
#region 字符串追加
var errorNew = context.ModelState.GetValidationSummary();
//context.Result = new JsonResult(new { code = (int)EnumCode.Fail, message = errorNew });
#endregion
#region 字符串追加
string message = string.Empty;
foreach (var item in context.ModelState.Values)
{
foreach (var error in item.Errors)
message += error.ErrorMessage + "|";
}
//context.Result = new JsonResult(new { code = (int)EnumCode.Fail, message = message });
#endregion
#region 返回BadRequest
//context.Result = new BadRequestObjectResult(context.ModelState);
#endregion
}
}
}
public class ValidationError
{
public string Field { get; set; }
public string Message { get; set; }
public ValidationError(string field, string message)
{
Field = field;
Message = message;
}
}
public static class ModelStateExtensions
{
/// <summary>
/// 获取验证消息提示并格式化提示
/// </summary>
public static string GetValidationSummary(this ModelStateDictionary modelState, string separator = "\r\n")
{
if (modelState.IsValid) return null;
var error = new StringBuilder();
foreach (var item in modelState)
{
var state = item.Value;
var message = state.Errors.FirstOrDefault(p => !string.IsNullOrWhiteSpace(p.ErrorMessage))?.ErrorMessage;
if (string.IsNullOrWhiteSpace(message))
message = state.Errors.FirstOrDefault(o => o.Exception != null)?.Exception.Message;
if (string.IsNullOrWhiteSpace(message)) continue;
if (error.Length > 0)
error.Append(separator);
error.Append(message);
}
return error.ToString();
}
}
}
*
*
*