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
错误信息

posted @ 2020-02-01 14:28  星空天宇  阅读(140)  评论(0编辑  收藏  举报