.net core RESTful Api笔记②
.net core RESTful Api笔记①中
写了建立api的工程,和restfulapi介绍和http请求问题,以及内容协商
Entity Model 和面向外部的Model
entity model:entity framework core使用entity model是用来表示数据库里面的记录。
面向外部model:面向外部的model表示传输的东西,这类model优势后叫dto,有时叫viewmodel
这样就更健壮,可靠,易于进化,同时也是页面最想获得的东西
修改controller
public async Task<IActionResult> GetCompanies() { var companies = await _companyRepository.GetCompaniesAsync(); var companyDto = new List<CompanyDto>(); foreach (var company in companies) { companyDto.Add(new CompanyDto { Id = company.Id, Name=company.Name }); } return Ok(companyDto); }
IActionResult:也可以写成具体的,可以配合swagger使用
public async Task<ActionResult<IEnumerable<CompanyDto>>> GetCompanies() { var companies = await _companyRepository.GetCompaniesAsync(); var companyDto = new List<CompanyDto>(); foreach (var company in companies) { companyDto.Add(new CompanyDto { Id = company.Id, Name=company.Name }); } return companyDto; }
AutoMapper:对象映射器
安装:
注册:
//获取当前的assembly程序集 services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
创建profile用来映射,修改了之前的dto内容
namespace Rountine.API.Profiles { public class CompanyProfile:Profile { public CompanyProfile() { //这里和注册的方式很像 //里面的参数是为了在名称映射不相同的情况下手动映射的 CreateMap<Company, CompanyDto>().ForMember( dest=>dest.CompanyName, opt=>opt.MapFrom(src=>src.Name)); } } }
最后修改controller内容
using AutoMapper; using Microsoft.AspNetCore.Mvc; using Rountine.API.Models; using Rountine.API.Services; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Rountine.API.Controllers { [ApiController] [Route("api/companies")] public class CompainesControllercs : ControllerBase { private readonly ICompanyRepository _companyRepository; private readonly IMapper _mapper; public CompainesControllercs(ICompanyRepository companyRepository, IMapper mapper) { _companyRepository = companyRepository ?? throw new ArgumentException(nameof(companyRepository)); _mapper = mapper ?? throw new ArgumentException(nameof(mapper)); } [HttpGet] public async Task<ActionResult<IEnumerable<CompanyDto>>> GetCompanies() { var companies = await _companyRepository.GetCompaniesAsync(); var companyDto = _mapper.Map<IEnumerable<CompanyDto>>(companies); return Ok(companyDto); } [HttpGet("{companyId}")] public async Task<ActionResult<CompanyDto>> GetCompanies(Guid companyId) { var company = await _companyRepository.GetCompanyAsync(companyId); if (company == null) { return NotFound(); } var companyDto = _mapper.Map<CompanyDto>(company); return Ok(companyDto); } } }
看下效果:
父子资源获取方式:
对于employee的获取不能直接获取它的资源,要体现出公司和员工的关系
创建employee的model和他的controller
这是employDto:
创建EmployeeProfile
using AutoMapper; using Rountine.API.Models; using Rountion.API.Eneities; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Rountine.API.Profiles { public class EmployeeProfile:Profile { public EmployeeProfile() { CreateMap<Employee, EmployeeDto>() .ForMember( dest=>dest.Name, opt=>opt.MapFrom(src=>$"{src.FirstName}{src.LastName}") ) .ForMember( dest=>dest.Gender, opt=>opt.MapFrom(src=>src.Gender.ToString()) ) .ForMember( dest=>dest.Age, opt=> opt.MapFrom(src=>DateTime.Now.Year-src.DateOfBirth.Year) ); } } }
创建controller
using AutoMapper; using Microsoft.AspNetCore.Mvc; using Rountine.API.Models; using Rountine.API.Services; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Rountine.API.Controllers { [ApiController] [Route("api/companies/{companyId}/employees")] public class EmployessController:ControllerBase { private readonly IMapper mapper; private readonly ICompanyRepository companyRepository; public EmployessController(IMapper mapper,ICompanyRepository companyRepository) { this.mapper = mapper ?? throw new ArgumentException(nameof(mapper)); this.companyRepository = companyRepository ??throw new ArgumentNullException(nameof(companyRepository)); } public async Task<ActionResult<IEnumerable<EmployeeDto>>> GetemployessFromCompany(Guid companyId) { //判断公司是否存在 if (!await this.companyRepository.CompanyExistsAsync(companyId)) { return NotFound(); } //这里直接将他们写道一块,本来不应该这样写的 var employees = await this.companyRepository.GetEmployeesAsynce(companyId); //转成dto var employessdto = this.mapper.Map<IEnumerable<EmployeeDto>>(employees); return Ok(employessdto); } } }
添加员工种子数据:
modelBuilder.Entity<Employee>().HasData( new Employee { Id= Guid.Parse("11CCF91A-5E6B-4397-A639-78A18853DDB1"), CompanyId = Guid.Parse("E6573877-FB7D-4BBE-A40A-44D24D8807FA"), EmployeeNo="182844" , FirstName="li", LastName="ming", Gender=Gender.男,
DateOfBirth=new DateTime(2000,1,2) }, new Employee { Id = Guid.Parse("6917B0AA-626D-434A-BDEA-C6E4F9EE35CC"), CompanyId = Guid.Parse("E6573877-FB7D-4BBE-A40A-44D24D8807FA"), EmployeeNo = "182845", FirstName = "liu", LastName = "ming", Gender = Gender.男,
DateOfBirth = new DateTime(1995, 1, 2) } );
运行PM>add-migration addEmployeeData
生成新的语句和快照文件,运行项目,我这里数据一直没生成成功,之后删除了库,重新生成了下数据。
添加单个查询
[HttpGet("{employeeId}")] public async Task<ActionResult<EmployeeDto>> GetemployesFromCompany(Guid companyId,Guid employeeId) { //判断公司是否存在 if (!await this.companyRepository.CompanyExistsAsync(companyId)) { return NotFound(); } var employee = await this.companyRepository.GetEmployeeAsync(companyId, employeeId); if (employee == null) { return NotFound(); } var employeedto= this.mapper.Map<EmployeeDto>(employee); return Ok(employeedto); }
处理故障
生产环境可以在管道中配置
if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler(appBuilder=> { appBuilder.Run(async context=> { context.Response.StatusCode = 500; await context.Response.WriteAsync("unException error"); });
保证生产环境不暴露api细节。
Http Head
head和get几乎一样
只是有一点不同:head的api不应该返回相应body
head可以获取资源上获取一些信息
只用在[httpget]下面加上[httphead]就行了;
过滤和搜索
如何绑定api传递数据:
数据通过多种方式来传递给api。
binging source attributes 会告诉model绑定引擎从哪绑定的
[FromBody]:请求的body
[FromForm]:请求的body中的form
[FromHeader]:请求的header
[FromQuery]:querystring中的参数
[FromRoute]:当前请求路由的数据
[FromService]:作为action参数注入的服务
[ApiController]
默认情况下asp.net core会从complex object model binder,它会把数据从value providers那里提取出来,而value providers的顺序定义好。
但是我们构建api时会使用[apiController]这个属性,为了是适应api改变的规则。
[FromBody]:复杂的参数类型
[FromForm]:推断IFormFile和IFormFileCollextion类型action参数
[FromRoute]:用来推断Action参数名和路由参数一致情况
[FromService]:用来推断action参数
过滤:我们把某个字段名字以及向要让该字段匹配的值一起传递给api,并将这些作为返回集合的一部分。
例:返回类型是国有企业的的欧洲公司
get 、api/companies?type=state-owned®ion=Europe
搜索:针对集合进行搜索是指预定义一些规则,把符合条件的数据添加到集合里
搜索实际超过过滤范围,搜索可能没有
例:get /api/companies?q=xxx
这里对员工查询进行修改
public async Task<IEnumerable<Employee>> GetEmployeesAsynce(Guid companyId,string gender,string q) { if (companyId == Guid.Empty) { throw new ArgumentNullException(nameof(companyId)); } if (string.IsNullOrEmpty(gender) && string.IsNullOrEmpty(q)) { return await _context.employees.Where(x => x.CompanyId == companyId).OrderBy(x=>x.EmployeeNo).ToListAsync(); } var items = _context.employees.Where(x => x.CompanyId == companyId); if (!String.IsNullOrEmpty(gender)) { gender = gender.Trim(); items=items.Where(x => x.Gender == Enum.Parse<Gender>(gender)); } if (!string.IsNullOrEmpty(q)) { q = q.Trim(); items = items.Where(x => x.EmployeeNo.Contains(q) ||x.FirstName.Contains(q) ||x.LastName.Contains(q)); } return await items.OrderBy(x => x.EmployeeNo).ToListAsync(); }
这里像拼接sql一样按存在拼接到sql上,在return时才去真正的查询。
上面查询参数都是写在action里,但是会遇到查询条件很多,而且会经常改变需求的查询条件,这样写就很头疼
所以优化下查询条件
建立DtoPrameters文件夹下面放上查询类
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Rountine.API.DtoParameters { public class CompanyDtoParameters { public string CompanyName { get; set; } public string searchTerm { get; set; } } }
controller:这里是类传入,要指向querystring才可以,否者默认是body里这样会415错误
public async Task<ActionResult<IEnumerable<CompanyDto>>> GetCompanies([FromQuery] CompanyDtoParameters parameters) { var companies = await _companyRepository.GetCompaniesAsync(parameters); var companyDto = _mapper.Map<IEnumerable<CompanyDto>>(companies); return Ok(companyDto); }
sevice:这个思想没变也是先判断都没的情况下,查询,再使用queryable表达式和参数存在情况下一个个判断
public async Task<IEnumerable<Company>> GetCompaniesAsync(CompanyDtoParameters parameters) { if (parameters == null) { throw new ArgumentException(nameof(parameters)); } if (string.IsNullOrWhiteSpace(parameters.CompanyName) && string.IsNullOrWhiteSpace(parameters.searchTerm)) { return await _context.companies.ToListAsync(); } var queryExpression = _context.companies as IQueryable<Company>; if (!string.IsNullOrEmpty(parameters.CompanyName)) { parameters.CompanyName = parameters.CompanyName.Trim(); queryExpression = queryExpression.Where(x => parameters.CompanyName == x.Name); } if (!string.IsNullOrEmpty(parameters.searchTerm)) { parameters.searchTerm = parameters.searchTerm.Trim(); queryExpression = queryExpression.Where(x => x.introduction.Contains(parameters.searchTerm) || x.Name.Contains(parameters.searchTerm)); } return await queryExpression.ToListAsync(); }
postman: