关于HTTP HEAD 和 HTTP GET:数据库
从执行性能来讲,这两种其实并无什么区别。最大的不一样就是对于HTTP HEAD 来讲,Api消费者请求接口数据时,若是是经过HTTP HEAD的方式去请求,api
应该是不会把 Body返回回去的。那么它会返回什么呢? 好比说,Headers的一些响应头数据,例如Content-Type的一些资源信息。而HTTP GET是会将安全
Body里面的数据返回的。所以,能够经过HTTP HEAD去检测该 Api 是否存在资源,换一种说法就是该 Api 是否可用。 app
关于如何给 Api 传递数据:async
数据能够经过多种方式传递给 Api!ide
事实上,Bind Source Attribute 会告诉 Model 的绑定引擎从哪里去找到绑定。函数
Bind Source Attribute的六种方式:工具
默认状况下,ASP .NET Core会使用 Complex Object Model Binder,它会把数据从 Value Provides 那里提取出来性能
而 Value Provides的顺序是定义好的!测试
可是,咱们在构建 Api 时,一般会使用 [ApiController] 这个 特性类,目的就是为了更好的构建 RESTful Api。
更改后:
关于过滤和搜索:
过滤:
实际上这二者在实际的业务中一般应该是搭配使用的。
所谓过滤:就是过滤集合的一是,根据条件返回限定的集合数据
需求案例: 返回全部类型为国有企业的欧洲公司
分析:过滤条件天然是“国有企业”和“欧洲公司”
那么 uri 的设计就会是:GET api/companies?Type=State-owned®oin=Europe
因此过滤就是:咱们把某个字段的名字和与之匹配的值一块儿传递给 Api ,并将这些以集合的方式返回
搜索:
搜索实际上超出了过滤的范围,针对搜索咱们一般不会把要搜索的字段传递过去,而是只把要搜索的值传递给 Api,
而后 Api 自行决定应该对哪些字段来查找该值,通常是全文搜索
例如:api/companies?q=xxx
若是还不理解?
过滤:根据条件,将某一集合的数据按条件进行移除或选择
搜索:能够是空集合,根据要搜索的值,将数据添加到集合中,再返回
注意:过滤和搜索这些参数并非资源的一部分。
案例代码:过滤员工性别(参数: genderDisplay)、搜索匹配数据(参数:q)
实现类处理业务逻辑:
分析:
首先第二个if判断,若是都为空,那么就是返回所有数据,什么也没发生。
第二个if判断性别参数是否为空,若是不是,那么就编写过滤性别的代码,在这以前将定义的items就是查询到该公司下的全部员工而后在处理其余事件。
第三个if判断搜索的值是否为空,若是不是,就编写模糊查询的代码,这里是多字段模糊查询
以上if执行完毕后,实际上并无生成一个完整的 SQL 语句,实际上这样作就是为了性能,最后才经过 ToList返回集合,至于你是过滤仍是搜索都无所谓!
public async Task<IEnumerable<Employee>> GetEmployeesAsync(Guid companyId, string genderDisplay, string q) { if (companyId==Guid.Empty) { throw new ArgumentNullException(nameof(companyId)); } if (string.IsNullOrWhiteSpace(genderDisplay) || string.IsNullOrWhiteSpace(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.IsNullOrWhiteSpace(genderDisplay)) { genderDisplay = genderDisplay.Trim(); var gender = Enum.Parse<Gender>(genderDisplay); items = items.Where(x => x.Gender == gender); } if (!string.IsNullOrWhiteSpace(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(); }
控制器调用:
经过[FromQuery]的Name来指定参数匹配的名称是什么,好比:gender或者是genderDisplay
public async Task<ActionResult<IEnumerable<EmployeeDto>>> GetEmployeesForCompany(Guid companyId,[FromQuery(Name = "gender")] string genderDisplay,string q) { if (! await _companyRepository.CompanyExistsAsync(companyId)) { return NotFound(); } var employees =await _companyRepository.GetEmployeesAsync(companyId, genderDisplay,q); var employeeDtos = _mapper.Map<IEnumerable<EmployeeDto>>(employees); return Ok(employeeDtos); }
接口测试:
还须要考虑一种状况:
在实际业务当中呢,这种搜索过滤的条件确定不止一两个,通常是多个属性进行搜索或者过滤,这个时候,若是也按照查询字符串的方式传递给 Api ,那么就会显得很是的复杂也很容易写错。
那怎么办呢?
很简单,其实只须要写一个对应的类就行了,把须要查询的字段属性所有放到类里面。
这样就算后期想再增长条件属性只须要编写类里面的代码,无需在,Api 接口中在去增长参数。
添加一个CompanyParameters类:
分析:分别定义公司名称属性字段和全文搜索属性字段
namespace Routine.Api.ResoureParameters { public class CompanyDtoParameters { public string CompanyName { get; set; } public string SearchTerm { get; set; } } }
业务逻辑类:
public async Task<IEnumerable<Company>> GetCompaniesAsync(CompanyDtoParameters companyParameters) { if (companyParameters==null) { throw new ArgumentNullException(nameof(companyParameters)); } if (string.IsNullOrWhiteSpace(companyParameters.CompanyName) && string.IsNullOrWhiteSpace(companyParameters.SearchTerm)) { return await _context.Companies.ToListAsync(); } var queryableCompany = _context.Companies as IQueryable<Company>; if (!string.IsNullOrWhiteSpace(companyParameters.CompanyName)) { companyParameters.CompanyName = companyParameters.CompanyName.Trim(); queryableCompany = queryableCompany.Where(x => x.Name == companyParameters.CompanyName); } if (!string.IsNullOrWhiteSpace(companyParameters.SearchTerm)) { companyParameters.SearchTerm = companyParameters.SearchTerm.Trim(); queryableCompany = queryableCompany.Where(x => x.Name.Contains(companyParameters.SearchTerm) || x.Introduction.Contains(companyParameters.SearchTerm)); } return await queryableCompany.ToListAsync(); }
控制器调用:
注意:须要加上[FromQuery]标记,否则会出现状态码为 415 ,也就是不支持的媒体类型(MediaType)
分析:此时方法的参数是一个类,就至关于它是一个复杂的数据类型,这个时候请求 Api 的时候它可能会认为绑定源是来自于QueryString查询字符串。
因此咱们须要手动指定一些绑定源。
[HttpGet] public async Task<ActionResult<IEnumerable<CompanyDto>>> GetCompanies([FromQuery]CompanyDtoParameters companyDtoParameters) { var companies =await _companyRepository.GetCompaniesAsync(companyDtoParameters); var companyDtos = _mapper.Map<IEnumerable<CompanyDto>>(companies); return Ok(companyDtos); }
从新测试接口:
接口测试成功!
关于HTTP 方法的安全性与幂等性:
安全性是指方法执行后并不会改变资源的表述,例如 GET 它只是查询获取资源,它并不会改变资源的表述,因此它是安全的。
幂等性是指方法不管执行多少次都会返回获得一样的结果,例如对资源进行修改,而修改的内容是同样的,因此不管修改多少次获得的结果也是同样的,例如 HTTP PUT 就是幂等的。
案例1:如何建立 POST 父资源(Company):
在建立资源请求以前,首先要明确一个理念,那就是建立资源的 DTO 是否要和 GET 请求查询的 Dto 的属性字段
内容一致呢?
答案是不该该将POST,GET请求使用同一个 Dto 。
其实缘由很简单,仔细想一想,其实POST Action方法请求的资源大部分业务状况与 GET Action 请求的资源状况是不同的
尽管有时候可能做为查询的Dto属性和做为建立资源的POST Dto属性同样。这个时候也应该将它们分开使用。
由于在将来业务处理中 DTO 中的属性可能随时都在发生改变。
因此,这样分开写 DTO 的好处就是方便后期的重构。
简单点来讲就是针对 查询、建立、更新三大类小块咱们都应该使用不一样的 DTO。
对 Company 这个 Entity Model作一下 POST 建立资源的请求:
创建 CompanyAddDto类:
using System; namespace Routine.Api.Models { public class CompanyAddDto { public string Name { get; set; } public string Introduction { get; set; } } }
对比一下GET Action 请求的 Dto ,即 CompanyDto:
实际上二者通常来讲属性极可能根据业务状况不同!!!
using System; namespace Routine.Api.Models { public class CompanyDto { public Guid Id { get; set; } public string CompanyName { get; set; } } }
注意,别忘记添加 mapper 映射关系了,这里就是从 CompanyAddDto 映射到 Employee(Entity Model),由于是添加到数据库里面。
由于CompanyDto和Company属性字段并无什么改变,因此不须要对专门的字段进行配置
using AutoMapper; using Routine.Api.Entities; using Routine.Api.Models; namespace Routine.Api.Profiles { public class CompanyProfiles:Profile { public CompanyProfiles() { CreateMap<CompanyAddDto, Company>(); } } }
在控制器添加 POST 请求的方法:
须要注意在资源添加后还须要从新映射回 GET 请求查询资源的 Dto!
[HttpPost] public async Task<ActionResult<CompanyDto>> CreateCompany(CompanyAddDto company) //若是 companyAddDto为空,ASP.NET Core会自动返回 400错误,这是由于 [ApiController] Attribute的做用 { //须要将资源映射到 EntityModel var entity = _mapper.Map<Company>(company); _companyRepository.AddCompany(entity); await _companyRepository.SaveAsync(); //此时添加完成后,返回出去的仍是Dto,全部还须要进行一次映射 var returnDto = _mapper.Map<CompanyDto>(entity); //CreatedAtRoute,会返回一些响应头的资源运行咱们返回带着一个地址的head,而这个head含有一个uri,例如 201 表示添加成功,还有就是 uri,经过这个uri能够找到这个新建立的资源 //参数1:生成uri名称,与返回的GET方法名同样,参数2:路由值,参数3:对象值 return CreatedAtRoute(nameof(GetCompany), new { companyId= returnDto.Id}, returnDto); }
关于返回的 CreatedAtRoute方法,注释标注了做用和对应的参数,第一个参数 GetCompany 对应的就是GET Action标注的路由名称,以下:
接下来进行 POST 请求的接口测试,打开 Postman 工具。
返回状态码 201 表示 Post 成功!
再看看Headers里面给咱们带回了什么信息:
这实际上就是返回 CreatedAtRoute 方法的做用,会带着刚刚添加的资源的 uri 地址
案例2:如何建立 POST 子资源(Employee):
建立子资源其实和建立父资源差很少。
一样添加 EmployeeAddDto 类:
using System; using Routine.Api.Entities; namespace Routine.Api.Models { public class EmployeeAddDto { public string EmployeeNo { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public Gender Gender { get; set; } public DateTime DateOfBirth { get; set; } } }
再来对比 EmployeeDto:
能够看出这两个类的区别仍是很大的
using System; namespace Routine.Api.Models { public class EmployeeDto { public Guid Id { get; set; } public Guid CompanyId { get; set; } public string EmployeeNo { get; set; } public string Name { get; set; } public string GenderDispaly { get; set; } public int Age { get; set; } } }
在控制器添加 POST 请求的方法:
须要注意的是,由于Employee做为子资源因此须要带着 CompanyId 回去
[HttpPost] public async Task<ActionResult<EmployeeDto>> CreateEmployeeForCompany(Guid companyId, EmployeeAddDto employee) { if (!await _companyRepository.CompanyExistsAsync(companyId)) { return NotFound(); } var entity = _mapper.Map<Employee>(employee); _companyRepository.AddEmployee(companyId,entity); await _companyRepository.SaveAsync(); var dtoToReturn = _mapper.Map<EmployeeDto>(entity); return CreatedAtRoute(nameof(GetEmployeeForCompany), new { companyId, employeeId = dtoToReturn.Id }, dtoToReturn); }
接下来进行 POST 请求的接口测试,打开 Postman 工具。
返回201,接口测试成功!
一样来看看 Headers里面返回的一些资源:
将刚刚添加的资源以 uri 形式返回。
案例3:同时建立父子资源:
业务需求:同时建立多个子资源 employee
须要在建立POST Action的Company方法上扩展一下就好了,在 CompanyAddDto中添加Employees属性集合:
最好和Entity Model的 Employee中 Employee同样,这样就无效对这个属性在映射的时候作配置了。
接口测试:
POST 成功 !
案例4:刚刚添加了多个子资源,那么如何添加多个父资源呢?
这就须要从新写一个 uri ,由于当前 api/companies uri 是针对于单个 Company的 Post 建立资源。
既然从新写一个uri,那么直接建立一个新的控制器,标注 Attribute [ApiController]为 api/companycollections
ConpanyCollectionController控制器代码:
编写一个构造函数的依赖注入,分别注入 AutoMapper 以及 CompanyRepository 业务逻辑类
分析:既然是建立多个Company,那么返回的也是一个 IEnumerable的Dto集合,参数也是一个为 IEnumerable的 CompanyAddDto集合,这没有什么问题。
而后循环添加数据就行了。在此以前仍是同样的,须要将 CompanyAddDto 映射到 Entity Model对应的Company 里面!
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AutoMapper; using Microsoft.AspNetCore.Mvc; using Routine.Api.Entities; using Routine.Api.Models; using Routine.Api.Service; namespace Routine.Api.Controllers { [ApiController] [Route("api/companycollections")] public class CompanyCollectionsController:ControllerBase { private readonly IMapper _mapper; private readonly ICompanyRepository _companyRepository; public CompanyCollectionsController(IMapper mapper,ICompanyRepository companyRepository) { _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); _companyRepository = companyRepository ?? throw new ArgumentNullException(nameof(companyRepository)); } [HttpPost] public async Task<ActionResult<IEnumerable<CompanyDto>>> CreateCompanyCollection( IEnumerable<CompanyAddDto> companyCollection) { var companyEntities = _mapper.Map<IEnumerable<Company>>(companyCollection); foreach (var company in companyEntities) { _companyRepository.AddCompany(company); } await _companyRepository.SaveAsync(); return Ok(); } } }
这里先测试是否会返回一个 Ok 200 的状态码
接口测试:
返回 200,测试成功!