• ASP.NET Core WebAPI学习-5


    1. ASP.NET Core WebAPI学习-1
    2. ASP.NET Core WebAPI学习-2
    3. ASP.NET Core WebAPI学习-3
    4. ASP.NET Core WebAPI学习-4
    5. ASP.NET Core WebAPI学习-5
    6. ASP.NET Core WebAPI学习-6

    输入验证

    说到验证,那就需要做三件事:

    • 定义验证规则
    • 按验证规则进行检查
    • 报告验证的错误。
      在把错误报告给API消费者的时候,报告里并不包含到底是服务端还是API消费者引起的错误,这是状态码的工作。而通常响应的Body里面会包含一组验证错误信息,API消费者可以把这些信息展示给API消费者的用户。

    定义验证规则

    想要定义验证规则,我们可以使用ASP.NET Core内置的方式或者使用第三方库。
    在ASP.NET Core里面,验证规则可以通过以下的方式来进行定义:

    • Data Annotations。例如 [Required],[MaxLength]等等。
    • 自定义Atrribute。
    • 实现IValidatableObject接口 。

    1. Data Annotations:

    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    
    namespace Routine.Api.Dtos
    {
        public class CompanyAddDto
        {
            [Display(Name = "公司名")]
            [Required(ErrorMessage = "{0}这个字段是必填的")]
            [MaxLength(100, ErrorMessage = "{0}的最大长度不可以超过{1}")]
            public string Name { get; set; }
    
            [Display(Name = "公司名")]
            [StringLength(500, MinimumLength = 10, ErrorMessage = "{0}的长度范围从{2}到{1}")]
            public string Introduction { get; set; }
            public ICollection<EmployeeAddDto> Employees { get; set; } = new List<EmployeeAddDto>();
        }
    }
    

    请求响应:
    在这里插入图片描述

    注意: 在ASP.NET Core 3.0以后,不需要写下面的语句,在[APIController]这个特性里面,如果遇到验证错误,那么会自动返回400错误.

    if (!ModelState.IsValid)
    {
        //Creates an Microsoft.AspNetCore.Mvc.UnprocessableEntityObjectResult that produces
        //a Microsoft.AspNetCore.Http.StatusCodes.Status422UnprocessableEntity response.
        return UnprocessableEntity(ModelState);
    }
    if (company == null)
    {
        //Creates an Microsoft.AspNetCore.Mvc.BadRequestResult that produces 
        //a Microsoft.AspNetCore.Http.StatusCodes.Status400BadRequest response.
        return BadRequest();
    }
    

    2. IValidatableObject

    1. 实现IValidatableObject接口
    2. 定义Validate方法
      注意内置验证错误的级别要比IValidatableObject自定义验证错误规则高
    using Routine.Api.Entity;
    using Routine.Api.ValidationAttributes;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    
    namespace Routine.Api.Dtos
    {
        [EmployeeNoMustDifferentFromFirstName(ErrorMessage = "员工编号必须和名不一样")]
        public abstract class EmployeeAddOrUpdateDto : IValidatableObject
        {
            [Display(Name = "员工号")]
            [Required(ErrorMessage = "{0}是必填项")]
            [StringLength(10, MinimumLength = 10, ErrorMessage = "{0}的长度是{1}")]
            public string EmployeeNo { get; set; }
    
            [Display(Name = "名")]
            [Required(ErrorMessage = "{0}是必填项")]
            [MaxLength(50, ErrorMessage = "{0}的长度不能超过{1}")]
            public string FirstName { get; set; }
    
            [Display(Name = "姓")]
            [Required(ErrorMessage = "{0}是必填项")]
            [MaxLength(50, ErrorMessage = "{0}的长度不能超过{1}")]
            public string LastName { get; set; }
    
            [Display(Name = "性别")]
            public Gender Gender { get; set; }
    
            [Display(Name = "出生日期")]
            public DateTimeOffset DateOfBirth { get; set; }
    
            public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
            {
                if (FirstName == LastName)
                {
                    yield return new ValidationResult("姓和名不能一样", new[] { nameof(FirstName), nameof(LastName) });
                }
            }
        }
    }
    
    

    3. 自定义验证属性Attribute

    1. 继承自ValidationAttribute
    2. 重写IsValid方法
    using Routine.Api.Dtos;
    using System;
    using System.ComponentModel.DataAnnotations;
    
    namespace Routine.Api.ValidationAttributes
    {
        /// <summary>
        /// 员工编号不能与名重复,自定义输入验证属性,这是类级别的属性
        /// </summary>
        [AttributeUsage(AttributeTargets.Class)]
        public class EmployeeNoMustDifferentFromFirstNameAttribute : ValidationAttribute
        {
            protected override ValidationResult IsValid(object value, ValidationContext validationContext)
            {
                var addDto = (EmployeeAddOrUpdateDto)validationContext.ObjectInstance;
                if (addDto.EmployeeNo == addDto.FirstName)
                {
                	//这个ErrorMessage来自于EmployeeAddOrUpdateDto
                    //[EmployeeNoMustDifferentFromFirstName(ErrorMessage = "员工编号必须和名不一样")]
                    return new ValidationResult(ErrorMessage, new[] { nameof(EmployeeAddOrUpdateDto) });
                }
                return ValidationResult.Success;
            }
        }
    }
    
    

    验证什么?

    验证的是输入数据,而不是输出数据。例如POST请求Body里面的参数就需要进行验证,而GET请求返回响应里面的内容就不需要验证了。

    按验证规则进行检查

    ASP.NET Core 内置了一个 ModelState对象,它用来做验证规则检查。
    ModelState对象是一个Dictionary(字典),它既包含model的状态,又包含model的绑定验证信息。
    它也包含针对每个提交的属性值的错误信息的集合。每当有请求进来的时候,定义好的验证规则就会被检查。
    如果有一个规则验证不通过的话,那么ModelState.IsValid()方法就会返回false。而且如果传进来的属性的类型不正确的话,该方法也会返回false。

    报告验证错误信息

    由于验证错误肯定是由客户端引起的,所以返回的状态码肯定是4xx。针对验证错误,具体的就是422 Unprocessable entity 这个状态码。
    之前也讲过 422 表示服务器理解了entity的Content-Type,并且语法也正确,但是仍然无法处理所包含的结构数据。例如:语法正确,但是语义不正确。
    当报告验证错误信息的时候,我们不仅要使用正确的状态码,还需要在响应的body里面包含验证错误信息。
    REST并没有规定返回的错误信息的格式,但是有一个标准却规定了此事:Validation Problem Details RFC,它定义了这样的响应的body应该是什么样的。ASP.NET Core内置了对这个标准的支持,后续视频教程中可以看到。
    Validation Problem Details RFC:
    在这里插入图片描述

    在Startup.cs的ConfigureServices方法中自定义错误报告信息:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers(configure: setup =>
                                {
                                    setup.ReturnHttpNotAcceptable = true;
                                })
            .AddNewtonsoftJson(setup =>
                               {
                                   setup.SerializerSettings.ContractResolver =
                                       new CamelCasePropertyNamesContractResolver();
                               })
            .AddXmlDataContractSerializerFormatters()
            .ConfigureApiBehaviorOptions(setup =>
                                         {
                                             setup.InvalidModelStateResponseFactory = context =>
                                             {
                                                 var problemDetails = new ValidationProblemDetails(context.ModelState)
                                                 {
                                                     Type = "http://www.baidu.com",
                                                     Title = "有错误!!!",
                                                     Status = StatusCodes.Status422UnprocessableEntity,
                                                     Detail = "请看详细信息",
                                                     Instance = context.HttpContext.Request.Path
                                                 };
                                                 problemDetails.Extensions.Add("traceId", context.HttpContext.TraceIdentifier);
                                                 return new UnprocessableEntityObjectResult(problemDetails)
                                                 {
                                                     ContentTypes = { "application/problem+json" }
                                                 };
                                             };
                                         });
        //services.AddControllers(configure: setup =>
        // {
        //     setup.ReturnHttpNotAcceptable = true;
        //    //setup.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
        //    //设置默认的返回格式
        //    //setup.OutputFormatters.Insert(index:0,new XmlDataContractSerializerOutputFormatter());
        //});
    
        services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
        services.AddScoped<ICompanyRepository, CompanyRepository>();
        services.AddDbContext<RoutineDbContext>(optionsAction: option =>
                                                {
                                                    option.UseSqlite(connectionString: "Data Source=routine.db");
                                                });
    }
    

    第三方验证库Fluentvalidation

    参考:Fluentvalidation

    1. 很容易创建复杂的验证规则
    2. 验证规则与Model分离
    3. 容易进行单元测试
  • 相关阅读:
    [PHP] Which function can be used to format a local timestamp according to a specific locale?
    [Yii Framework] yii 如何使用第三方插件
    [PHP] Header to out put the image file.
    [PHP] PHP 格式化日期 format the datetime in PHP
    VC6工程中的文件类型
    VC6++ output :error executing c:\windows\system32\cmd.exe.
    根据选择计算Mask值
    .NET垃圾收集关键方法解析(转)
    JS访问表格的每行的每个TD的方法
    HTTP 状态消息详解
  • 原文地址:https://www.cnblogs.com/AlexanderZhao/p/12878829.html
Copyright © 2020-2023  润新知