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. 容易进行单元测试
<think>嗯,用户想了解Excel中的VLOOKUP函数的用法和参数含义。首先,我需要回忆一下VLOOKUP的基本结构。VLOOKUP是垂直查找函数,用于在表格的首列查找指定的值,然后返回该行中指定列的数据。它的四个参数分别是查找值、表格范围、列索引号和匹配模式。 接下来,我得确认每个参数的具体作用。第一个参数是lookup_value,也就是用户要查找的值,比如产品ID或者姓名。第二个参数是table_array,即查找的范围,这里要注意的是,查找值必须位于这个范围的第一列,否则函数无法正确工作。第三个参数是col_index_num,也就是用户想要返回的数据在表格范围中的第几列,这个列数是从表格范围的第一列开始算起的,不是整个工作表的列。第四个参数是range_lookup,决定是精确匹配还是近似匹配,通常用FALSE或0来指定精确匹配,避免错误。 然后,用户可能需要一个具体的例子来理解如何使用。比如,假设有一个产品价格表,用户想根据产品ID查找对应的价格。这时候,VLOOKUP就可以在ID列查找,然后返回价格列的数据。同时,要提醒用户注意表格范围最好使用绝对引用,比如$A$2:$C$100,这样在拖动公式时范围不会变化。 可能遇到的问题也需要考虑。比如,如果查找值不在表格的第一列,或者列索引号超过了表格的列数,函数会返回错误。此外,数据格式不一致也可能导致问题,比如查找值是数字,而表格中是文本格式的数字,这时候需要统一格式。还有,当出现#N/A错误时,可能是查找值不存在,或者匹配模式设置不正确,需要检查参数是否正确。 用户可能还会问到如何避免常见的错误,比如部分数据匹配出错的情况。这时候需要检查是否有多余的空格,或者数据格式不一致,或者表格范围没有锁定导致拖动时偏移。此外,XLOOKUP作为VLOOKUP的升级版,可能在某些情况下更方便,比如可以向左查找或者返回多列数据,这也是可以提及的内容。 最后,整理步骤,从基础语法到参数说明,再到具体案例,逐步引导用户理解如何使用VLOOKUP函数。同时,提供常见错误和解决方法,帮助用户在实际应用中避免问题。如果有必要,还可以比较VLOOKUP和其他查找函数的区别,比如HLOOKUP或XLOOKUP,但根据用户的问题,主要集中在VLOOKUP本身即可。</think>### Excel VLOOKUP函数详解 #### 一、基础语法与参数说明 VLOOKUP函数的语法为: $$=VLOOKUP(lookup\_value, table\_array, col\_index\_num, [range\_lookup])$$ 包含4个参数: 1. **lookup_value**(必填):要查找的值(如单元格引用或具体值) 2. **table_array**(必填):包含数据的表格范围(必须包含查找列和返回列) 3. **col_index_num**(必填):返回值所在列的序号(从table_array第一列开始计数) 4. **range_lookup**(可选):匹配类型 - `TRUE`/`1`:近似匹配(默认值,需数据升序排列) - `FALSE`/`0`:精确匹配(常用选项) [^1][^2] #### 二、使用步骤演示(工资表查询案例) 假设需要根据员工编号查询工资: 1. 建立查询单元格(如`B12`) 2. 输入公式: ```excel =VLOOKUP(A12, $A$2:$D$100, 4, 0) ``` - `A12`:待查询的员工编号 - `$A$2:$D$100`:锁定数据区域(绝对引用) - `4`:返回第4列(工资列) - `0`:精确匹配 [^2][^3] #### 三、常见错误与解决方法 | 错误现象 | 原因 | 解决方案 | |---------|------|---------| | #N/A | 查找值不存在 | 检查数据源或改用`IFERROR`容错 | | #REF! | 列序号超出范围 | 确认col_index_num ≤ 表格列数 | | 部分匹配失败 | 数据格式不一致 | 统一数值/文本格式 | | 结果错位 | 表格未锁定 | 使用`$`符号固定区域引用 | [^3][^4] #### 四、进阶技巧 1. **多条件查询**: 使用辅助列合并多个条件字段 ```excel =VLOOKUP(A2&B2, $D$2:$F$100, 3, 0) ``` 2. **通配符匹配**: `"*"`匹配任意字符,`"?"`匹配单个字符 ```excel =VLOOKUP("张*", $A$2:$C$100, 3, 0) ``` 3. **跨表查询**: 引用其他工作表数据 ```excel =VLOOKUP(A2, Sheet2!$A$2:$D$100, 4, 0) ``` [^1][^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值