• ASP.NET Core WebAPI学习-4


    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。
    Binding Source Attributes 会告诉 Model 的绑定引擎从哪里找到绑定源。
    共有以下六种 Binding Source Attributes:
    [FromBody] 请求的 Body
    [FromForm] 请求的 Body 中的 form数据
    [FromHeader] 请求的 Header
    [FromQuery] Query string 参数
    [FromRoute] 当前请求中的路由数据
    [FromService] 作为 Action 参数而注入的服务
    默认情况下ASP.NET Core 会使用 Complex Object Model Binder,它会把数据从Value Providers那里提取出来,而Value Providers的顺序是定义好的。
    但是我们构建API时通常会使用 [ApiController] 这个属性,为了更好的适应API它改变了上面的规则。更改后的规则如下:
    [FromBody] 通常是用来推断复杂类型参数的。
    [FromForm] 通常用来推断IFormFile和IFormFileCollection类型的Action参数。
    [FromRoute] 用来推断Action的参数名和路由模板中的参数名一致的情况。
    [FromQuery] 用来推断其它的Action参数。
    按照这些规则,在Action的参数前面使用这些属性,就可以避免让我们手动去寻找绑定源。当默认的行为规则需要被重写的时候,也可以使用这些 Binding Source Attributes.

    过滤

    过滤集合的意思就是指根据条件限定返回的集合。
    例如我想返回所有类型为国有企业的欧洲公司。则URI为:GET /api/companies?type=State-owned&region=Europe
    所以过滤就是指:我们把某个字段的名字以及想要让该字段匹配的值一起传递给API,并将这些作为返回的集合的一部分。

    搜索

    针对集合进行搜索是指根据预定义的一些规则,把符合条件的数据添加到集合里面。
    搜索实际上超出了过滤的范围。针对搜索,通常不会把要匹配的字段名传递过去,通常会把要搜索的值传递给API,然后API自行决定应该对哪些字段来查找该值。经常会是全文搜索。
    例如:GET /api/companies?q=xxx

    过滤 vs 搜索

    可以看出来过滤和搜索是不同的。
    过滤:首先是一个完整的集合,然后根据条件把匹配/不匹配的数据项移除。
    搜索:首先是一个空的集合,然后根据条件把匹配/不匹配的数据项往里面添加。

    但需要注意的是:

    过滤和搜索这些参数并不是资源的一部分。
    只允许针对资源的字段进行过滤。
    过滤:

    [HttpGet]
    public async Task<ActionResult<IEnumerable<EmployeeDto>>>
        GetEmployeesForCompany(Guid companyId, [FromQuery(Name = "gender")]string genderDisplay)
    {
        if (!await companyRepository.CompanyExistsAsync(companyId))
        {
            return NotFound();
        }
        var employees = await companyRepository.GetEmployeesAsync(companyId, genderDisplay);
        var employeeDtos = mapper.Map<IEnumerable<EmployeeDto>>(employees);
        return Ok(employeeDtos);
    }
    
    public async Task<IEnumerable<Employee>> GetEmployeesAsync(Guid companyId, string gender)
    {
        if (companyId == Guid.Empty)
        {
            throw new ArgumentNullException(nameof(companyId));
        }
        if (string.IsNullOrWhiteSpace(gender))
        {
            return await context.Employees
                .Where(x => x.CompanyId == companyId)
                .OrderBy(x => x.EmployeeNo)
                .ToListAsync();
        }
    
        //判断Gender是否转换成功
        Gender gender1;
        var isGender = Enum.TryParse<Gender>(gender.Trim(), out gender1);
        if (isGender)
        {
            return await context.Employees
                .Where(x => x.CompanyId == companyId && x.Gender == gender1)
                .OrderBy(x => x.EmployeeNo)
                .ToListAsync();
        }
        else
        {
            throw new ArgumentNullException(nameof(gender));
        }
    
    }
    

    查询

    注意如果使用复杂类型查询,这里的CompanyDtoParameter时,API默认绑定的类型为从Body中获取,会出现415(Unsupported Media Type)错误,这时要在参数前面加上FromQuery特性,让参数从查询字符串中获取

    1. 因为ApiController的复杂类型参数默认从FromBody中推断,所以加入[FromQuery]即可
    2. 如果不指定Content-Type也会出现该错误,在Post的时候,在Header头部添加Content-Type:Application/json
    [HttpGet(Name = nameof(GetCompanies))]
    [HttpHead]
    public async Task<ActionResult<IEnumerable<CompanyDto>>> GetCompanies([FromQuery]CompanyDtoParameter parameter = null)
    {
        var companies = await companyRepository.GetCompaniesAsync(parameter);
        var previousPageLink = companies.HasPrevious
            ? CreateCompaniesResourceUri(parameter, ResourceUriType.PreviousePage)
            : null;
        var nextPageLink = companies.HasNext
            ? CreateCompaniesResourceUri(parameter, ResourceUriType.NextPage)
            : null;
        var paginationMetadata = new
        {
            totalCount = companies.TotalCount,
            pageSize = companies.PageSize,
            currentPage = companies.CurrentPage,
            totalPages = companies.TotalPages,
            previousPageLink,
            nextPageLink
        };
        Response.Headers.Add("X-Pagination", JsonSerializer.Serialize(paginationMetadata, new JsonSerializerOptions()
                                                                      {
                                                                          Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
                                                                      }));
    
        var companyDtos = mapper.Map<IEnumerable<CompanyDto>>(companies);
    
        return Ok(companyDtos);
    }
    

    创建资源

    注意几个方法的使用:
    UnprocessableEntity: 返回422错误
    BadRequest: 返回400错误
    CreatedAtRoute:返回创建成功的201状态码

    [HttpPost]
            public async Task<ActionResult<CompanyDto>> CreateCompany([FromBody]CompanyAddDto company)
            {
                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();
                }
                var entity = mapper.Map<Company>(company);
                companyRepository.AddCompany(entity);
                await companyRepository.SaveAsync();
                var returndto = mapper.Map<CompanyDto>(entity);
                //Creates a Microsoft.AspNetCore.Mvc.CreatedAtRouteResult object that produces
                //a Microsoft.AspNetCore.Http.StatusCodes.Status201Created response.
                return CreatedAtRoute(nameof(GetCompany), routeValues: new { companyId = returndto.Id }, value: returndto);
            }
    

    发送请求:
    在这里插入图片描述
    请求创建实体成功的响应:
    请求创建实体成功的响应
    请求创建实体时传递的参数错误时的响应:
    在这里插入图片描述
    请求创建实体时传递的参数错误时的响应:
    在这里插入图片描述

    自定义模型绑定器

    定义:

    using Microsoft.AspNetCore.Mvc.ModelBinding;
    using System;
    using System.ComponentModel;
    using System.Linq;
    using System.Reflection;
    using System.Threading.Tasks;
    
    namespace Routine.Api.Helpers
    {
        /// <summary>
        /// 自定义Model绑定器
        /// </summary>
        public class ArrayModelBinder : IModelBinder
        {
            public Task BindModelAsync(ModelBindingContext bindingContext)
            {
                //判断传递的参数的类型是否为枚举类型
                if (!bindingContext.ModelMetadata.IsEnumerableType)
                {
                    bindingContext.Result = ModelBindingResult.Failed();
                    return Task.CompletedTask;
                }
    
                //判断传递的值是否为空,如果为空的话直接返回,在Controller中返回BadRequest
                var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString();
                if (string.IsNullOrWhiteSpace(value))
                {
                    bindingContext.Result = ModelBindingResult.Success(null);
                    return Task.CompletedTask;
                }
    
                /*利用反射把string转换为guid数组*/
                //获取guid类型
                var elementType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];
                //获取转换器是为了把字符串类型转换为guid类型
                var converter = TypeDescriptor.GetConverter(elementType);
                //把string转换为object数组
                var values = value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
                    .Select(x => converter.ConvertFromString(x.Trim())).ToArray();
                //把object数组转换为guid数组
                var typedValues = Array.CreateInstance(elementType, values.Length);
                values.CopyTo(typedValues,0);
    
                bindingContext.Model = typedValues;
                bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
                return Task.CompletedTask;
            }
        }
    }
    
    

    使用:

    /// <summary>
    /// 使用自定义模型绑定器,查询公司集合
    /// </summary>
    /// <param name="ids"></param>
    /// <returns></returns>
    [HttpGet("({ids})", Name = nameof(GetCompanyCollection))]
    public async Task<IActionResult> GetCompanyCollection(
        [FromRoute]
        [ModelBinder(BinderType =typeof(ArrayModelBinder))]
        IEnumerable<Guid> ids)
    {
        if (ids == null)
        {
            return BadRequest();
        }
        var entities = await companyRepository.GetCompaniesAsync(ids);
        if (ids.Count() != entities.Count())
        {
            return NotFound();
        }
        var dtoToReturn = mapper.Map<IEnumerable<CompanyDto>>(entities);
        return Ok(dtoToReturn);
    }
    
    

    安全性和幂等性

    安全性是指方法执行后并不会改变资源的表述
    幂等性是指方法无论执行多少次都会得到同样的结果

    HTTP Options

    API消费者如何知道某个API是否允许被访问?
    OPTION请求可以获取针对某个Web API的通信选项的信息
    使用:

    [HttpOptions]
    public IActionResult GetCompaniesOptions()
    {
        Response.Headers.Add("Allow", "GET,POST,OPTIONS");
        return Ok();
    }
    

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

    415 Unsupported Media Type 错误信息

    这个是请求的数据格式不对或者没有,需要添加或更改Content-Type
    错误信息

  • 相关阅读:
    win10+PHP 安装memcache
    win10+PHP 安装redis
    一个github搞定微信小程序支付系列
    Linux下新建一个站点
    Linux下更改mysql版本
    零基础配置Linux服务器环境
    手把手教你使用ueditor
    react学习三
    react学习二 生命周期
    window.location.replace和window.location.href区别
  • 原文地址:https://www.cnblogs.com/AlexanderZhao/p/12878830.html
Copyright © 2020-2023  润新知