文章目录
- 1.MVC映射为API
- 2.看 Program 这个类
- 3.看Starup这个类
- 4.更改配置文件,默认自托管
- 5.建立Model。这里是建立 Entities 类
- 6.建立DBcontext类
- 7.建立 Repository 仓储接口
- 8.配置 EF core 实现迁移
- 9.Rest风格
- 10.用 RestfullAPI 实现 Controller 里面的相关方法
- 11.内容协商
- 12.Dto
- 13.获得父子关系
- 14.处理服务器端的故障
- 15.Head请求
- 16.过滤和搜索
- 17. 查询参数
- 18. 安全性和幂等性
- 19.Post .添加 Company
- 20.添加子资源
- 21.同时创建父子资源
- 22.添加集合资源
- 23.自定义 Model 绑定器
- 24.Http Options 请求
- 25.输入验证
- 26. PUT 整体更新
- 27. Http Patch 局部更新
- 28.HttpDelete 删除 employee 资源
- 29.HttpDelete 删除 Company
- 30.翻页功能
- 31.排序
- 32.数据塑形
- 33.HateOAS
- 34. 添加 JWT 授权认证功能
- 注意!!!!
- 35.配置 Swagger
- 参考文献
1.MVC映射为API
- Model,它负责处理数据的逻辑;
- View,负责展示数据,在API里面一般是Json;
- Controller, 它负责View和Model之间的交互;
和API交互的用户,称为是Api的消费者:(比如Angular写的单页应用程序)
先看整个目录结构
2.看 Program 这个类
主要还是创建了一个宿主,建立一个宿主,然后运行:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
//因为它是一个web程序,所以它需要一个宿主。
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
//新建的类,它实现IHostBuilder的接口;
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
3.看Starup这个类
注意每个中间件都有可能触发短路机制:
public class Startup
{
public Startup(IConfiguration configuration)
//构造函数,引用配置信息。
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
//容器,放在这里面的东西,全局都在可以使用依赖注入的方法进行引用
{
services.AddControllers();
}
//管道里面的每个中间件,都有可能触发短路机制;
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
//配置请求的管道,要在ConfigureService后边引用
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
//这个通常是在ConfigureService配置。要放在http请求之前;
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
//访问controller
});
}
}
4.更改配置文件,默认自托管
{
//删除的时候,第一行和第二行要留着
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"RestfullApi": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
5.建立Model。这里是建立 Entities 类
5.1先建立 Company 类(注意 list 类可以用快捷键来生成)
public class Company
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Introduction { get; set; }
public List<Employee> Employees { get; set; }
//用快捷键生成 Employee 类
}
5.2建立 Employee 类(注意指定导航属性和外键等)
public class Employee
{
public Guid Id { get; set; } //主键
public Guid CompanyId { get; set; } //外键。指向Company的主键
public Company Company { get; set; }//关联的导航属性;
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; }
}
6.建立DBcontext类
指定 EF core 的类,(建立数据库的表,指定类的成员的限制、一对多的关系、种子数据等)
注意 guid 可以在线生成:
public class RoutineDbContext : DbContext
{
//base 这个方法的具体用法,回去再查!
public RoutineDbContext(DbContextOptions<RoutineDbContext> options) : base(options)
{
}
//映射为两个数据表;
public DbSet<Company> Companies { get; set; }
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//设置对实体的限制
modelBuilder.Entity<Company>().Property(x => x.Name).IsRequired().HasMaxLength(100);
modelBuilder.Entity<Company>().Property(x => x.Introduction).IsRequired().HasMaxLength(500);
modelBuilder.Entity<Employee>().Property(x => x.EmployeeNo).IsRequired().HasMaxLength(10);
modelBuilder.Entity<Employee>().Property(x => x.FirstName).IsRequired().HasMaxLength(50);
modelBuilder.Entity<Employee>().Property(x => x.LastName).IsRequired().HasMaxLength(50);
//指定一下一对多,多对多的关系
modelBuilder.Entity<Employee>()
.HasOne(x => x.Company)
.WithMany(x => x.Employees)
//上边指定的是什么意思:一个employee对应一个Company,同时一个company对应多个Employees
.HasForeignKey(x => x.CompanyId)//指定外键
.OnDelete(DeleteBehavior.Restrict);//当删除的时候,如果company下边有员工的话,就无法删除;
//指定种子数据:
//Guid有在线生成工具,在线生成两个就行;
modelBuilder.Entity<Company>().HasData
(
new Company
{
Id = Guid.Parse("d0176f72-33d0-4b48-a5ac-2c748becf3a4"),
Name = "Microsoft",
Introduction = "Great Company"
},
new Company
{
Id = Guid.Parse("786cb431-4228-4489-8841-0ed6a16e13e4"),
Name = "Google",
Introduction = "Don't be evil",
},
new Company
{
Id = Guid.Parse("ac531f21-ccc8-4576-977a-5e3217236fa2"),
Name = "Alipapa",
Introduction = "Taobao Company"
} ); } }
7.建立 Repository 仓储接口
(实现对数据库的增删改查)
7.1接口的类
_context.Companies.Add(company);比如这句话,实际上 context.Companies 就是对对context 里面的 Companies 这个数据表
public interface ICompanRepository
{
//repository相当于过去的doc层;
//针对公司的增删改查;
Task<List<Company>> GetCompaniesAsync();
Task<Company> GetCompanyAsync(Guid companyId);
Task<List<Company>> GetCompaniesAsync(List<Guid> companyids);
void AddCompany(Company company);
void UpdateCompany(Company company);
void DeleteCompany(Company company);
Task<bool> CompanyExistsAsync(Guid companyId);
//对员工的增删改查:
Task<List<Employee>> GetEmployeesAsync(Guid companyId);
Task<Employee> GetEmployeeAsync(Guid companyId, Guid employeeId);
void AddEmployee(Guid companyid, Employee employee);
void UpdateEmployee(Employee employee);
void DeleteEmployee(Employee employee);
//保存的动作;
Task<bool> SavaAsync();
}
7.2实现这个接口:
public class CompanyRepository : ICompanRepository
{
//把DBconText,用构造函数的方式传进来;
private readonly RoutineDbContext _context;
public CompanyRepository(RoutineDbContext routineDbContext)
{
this._context = routineDbContext ??
throw new ArgumentNullException(nameof(routineDbContext));
//如果是null的话就抛出一个异常;
}
//一般用数据库操作的话,都是异步的;
//下边对数据库的所有操作。都是基于dbcontext的;
//获取所有的公司信息;
public async Task<List<Company>> GetCompaniesAsync()
{
return await _context.Companies.ToListAsync();
}
//和上边相当于重载
public async Task<Company> GetCompanyAsync(Guid companyId)
{
if (companyId==Guid.Empty)//guid判断空
{
throw new ArgumentNullException(nameof(companyId) );
}
return await _context.Companies.FirstOrDefaultAsync(x=>x.Id==companyId);
//注意是恒等
}
public async Task<List<Company>> GetCompaniesAsync(List<Guid> companyids)
{
if (companyids==null)//字符串判断空怎么判断的;
{
throw new ArgumentNullException(nameof(companyids));
}
//对数据库的数组进行操作;
return await _context.Companies.
Where(x => companyids.Contains(x.Id)).
OrderBy(x => x.Name).
ToListAsync();//上边的要求就是数组形式;
}
public void AddCompany(Company company)
{
if (company==null)//检查的应该是他传进来的参数
{
throw new ArgumentNullException(nameof(company));
}
company.Id = Guid.NewGuid();//Id 是当时生成的;
foreach (var employee in company.Employees)
{
employee.Id = Guid.NewGuid();
}
_context.Companies.Add(company);
}
public void UpdateCompany(Company company)
{
//这个是显示跟踪的,不用加也行。
// throw new NotImplementedException();
}
public void DeleteCompany(Company company)
{
if (company==null)
{
throw new ArgumentNullException(nameof(company));
}
_context.Companies.Remove(company);//删除是remove
}
//通过 Id 判断公司存不存在;
public async Task<bool> CompanyExistsAsync(Guid companyId)
{
if (companyId==Guid.Empty)
{
throw new ArgumentNullException(nameof(companyId));
}
//DBcontext怎么判断是否存在:
return await _context.Companies.AnyAsync(x => x.Id == companyId);
}
//根据公司ID寻找所有的员工信息;
public async Task<List<Employee>> GetEmployeesAsync(Guid companyId)
{
if (companyId==Guid.Empty)
{
throw new ArgumentNullException(nameof(companyId));
}
return await _context.Employees.
Where(x => x.CompanyId == companyId).
OrderBy(x => x.EmployeeNo).
ToListAsync(); //只要使用异步就要用await
}
//拿这个重点练习练习;
//获得公司下某一个员工的方法。这两个都要传进来
public async Task<Employee> GetEmployeeAsync(Guid companyId, Guid employeeId)
{
if (companyId==Guid.Empty)
{
throw new ArgumentNullException(nameof(companyId));
}
if (employeeId==Guid.Empty)
{
throw new ArgumentNullException(nameof(employeeId));
}
return await _context.Employees
.Where(x => x.CompanyId == companyId && x.Id == employeeId)
.FirstOrDefaultAsync();
}
//添加一个员工
public void AddEmployee(Guid companyid, Employee employee)
{
if (companyid==Guid.Empty)
{
throw new ArgumentNullException(nameof(companyid));
}
if (employee==null)
{
throw new ArgumentNullException(nameof(employee));
}
employee.CompanyId = companyid;
_context.Employees.Add(employee);
}
public void UpdateEmployee(Employee employee)
{
// 更新员工这个也不用写了;
}
public void DeleteEmployee(Employee employee)
{
_context.Employees.Remove(employee);
}
public async Task<bool> SavaAsync()
{
return await _context.SaveChangesAsync()>=0;
//它这返回的是保存的数量,一般不在这里写;
}
}
8.配置 EF core 实现迁移
8.1首先,引用两个 nuget 包
8.2 在 starup 类里面配置 EF core 链接字符串(指定数据库的)
public void ConfigureServices(IServiceCollection services)
//容器,放在这里面的东西,全局都在可以使用依赖注入的方法进行引用
{
services.AddControllers();
services.AddScoped<ICompanRepository,CompanyRepository>();
//把repositroy实现一下 scoped表示每一次http请求都是返回一个实例;
//对dbcontext有封装好的方法;
services.AddDbContext<RoutineDbContext>(option =>
{ //指定使用的是什么数据库
option.UseSqlServer //链接字符串;
("Server=(localdb)\\mssqllocaldb;Database=RestfulApi;Integrated Security=True");
} );}
8.3 在安装包程序管理控制台,执行迁移命名
8.3.1可以先用 get-help entityframework 命令,查看都是有哪些操作
8.3.2 为了避免每次都执行迁移命令,可在 Main 函数里面,宿主建立后,运行前,自动执行迁移命令;对 Main 函数的更改如下:(但是记住,第一次的迁移命令还是要添加的)
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build(); //因为它是一个web程序,所以它需要一个宿主。
using (var scope=host.Services.CreateScope())
{
try
{
var dbContext = scope.ServiceProvider.GetService<RoutineDbContext>(); //把容器内的服务给弄出来;
dbContext.Database.EnsureDeleted();//每次都是把容器的东西个删了;
dbContext.Database.Migrate();//自动迁移
}
catch (Exception e)
{
//如果发生错误的话,就记录一下日志;
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
logger.LogError(e,"Datebase Migration Error!");
}
}
host.Run();//在这个host运行之前搞点东西。
}
8.3.3迁移完成后生成相应的数据
执行 update-database -verbose 可以把执行的明细打印出来
EFcore也可以执行反向工程:即根据已经生成的数据库,反过来生成代码。
9.Rest风格
- 资源使用名词,而不是动词;
- 用 API 要体现资源的结构和关系;
- 建议路由做法: (至于到底是用复数还是单数,这个一直有争议,杨旭一直用复数)
GET api / companies / { companyId } / employees
自定义查询怎么命名:(获取所有的用户信息,并将结果按照年龄从大到小进行排序)
api / users ? orderby=name
10.用 RestfullAPI 实现 Controller 里面的相关方法
注意是如何json序列化的:
[Route("api/companies")]
[ApiController]//要求使用属性路由。。。自动http 400 相应。 frombody自动识别;
public class CompaniesController : ControllerBase
{
private readonly ICompanRepository companRepository;
public CompaniesController(ICompanRepository companRepository)
{
this.companRepository = companRepository;
}
[HttpGet]
public async Task<IActionResult> GetCompanies()
{
var companies = await companRepository.GetCompaniesAsync();
return new JsonResult(companies);//Json序列化;
}
状态码
- 1 开头的状态码是信息类的状态码,RestfulAPi不支持这种状态码;
- 2XX 的状态码表示请求已经成功。。200—OK。表示请求成功。。201——Created。表示请求成功并创建了资源。。。204——No Content 表示请求成功,但是不返回任何东西。例如删除操作。
- 3XX 开头的状态码,表示某个网页的网址已经永久的改变了,绝大多数的Web Api 都不需要这个使用这类的状态码。
- 4XX 表示客户端的错误。
- 5XX 表示服务器出现了错误,最常用的是500,表示服务器端出现了错误,客户端无能为力,只能以后再试了。
注意错误(error)和故障(Faults)的区别,错误是4开头的,故障是5开头的;
路由:
路由习惯建议使用 [ httpget (“api/companyies”) ] 这种写法
更改启动时的路径,如下图(注意不是直接在http//localhost:5000后边直接加!)
具体实现代码:
[Route("api/companies")]
[ApiController]
public class CompaniesController : ControllerBase
{
private readonly ICompanRepository companRepository;
public CompaniesController(ICompanRepository companRepository)
{
this.companRepository = companRepository
?? throw new ArgumentNullException(nameof(companRepository));//是throw一个异常。 ;
}
[HttpGet]
public async Task<IActionResult> GetCompanies()
{
var companies = await companRepository.GetCompaniesAsync();
// return new JsonResult(companies);
return Ok(companies);
}
[HttpGet("{companyId}")]
public async Task<IActionResult> GetCompany(Guid companyId)
{
var company = await companRepository.GetCompanyAsync(companyId);
if (company==null)
{
return NotFound();
}
return Ok(company);
}
}
11.内容协商
Restfull Api 并没有强制要求返回 Json 格式的返回结果,但是一般都默认Json,也可以选择其他的表述格式,如Xml,在多种表述格式中选择最佳的表述,这就是内容协商 。
Accept 当浏览器发送Http请求时,可以在 header 里面指定要返回的媒体类型:
- application/json
- application/xml
如果请求的格式不能被服务器支持,则返回 406 状态码
Content-Type 当浏览器发向服务器输入数据时,如 post 时候,需要指定媒体类型,相当于自描述,
用 postman 进行测试,如下:
让webApi支持XMl格式的文件:
1.因为默认情况,webapi,在返回不支持媒体类型时,返回Json,不报错。
2.让webapi 支持Json格式的文件:
在 services.AddControllers 里面完成相关代码
services.AddControllers(options =>
options.ReturnHttpNotAcceptable = true
//他默认是关闭的
).AddXmlDataContractSerializerFormatters();
//添加对XML的支持
12.Dto
1.在没有Dto之前,是如何做相关映射的:
- IActionResult 的返回类型,能明确尽量明确,这样在做swagger的时候可以更方便;
- 注意建立 companyDto1 的时候犯的几个错误;
public async Task<ActionResult<List<CompanyDto>>> GetCompanies()
{
var companies = await companRepository.GetCompaniesAsync();
// return new JsonResult(companies);
var companyDto1 = new List<CompanyDto>();//这个地方要用 new
foreach (var company in companies)
{
companyDto1.Add( //往数组里添加数据使用Add
new CompanyDto
{
Id = company.Id,
CompanyName = company.Name
});
}
return Ok(companyDto1);
}
2.使用 AutoMapper 映射器 将 Entity 里面的东西,映射到 Model 里面(Dto),映射相关的文件,具体命名目录如下;
如何用 AutoMapper 来实现映射:
1.引用 Nuget 包,此处引用第二个,能和 AutoMapper 更好的结合、
2.建立Mode类。里面建立 CompanyDto 这个类
public class CompanyDto
{
public Guid Id { get; set; }
public string CompanyName { get; set; }
}
相对应的是 Company 这个类
public class Company
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Introduction { get; set; }
public List<Employee> Employees { get; set; }
//用快捷键生成 Employee 类
}
建立 Profiles 这个文件夹,在文件夹里面建立 CompanyProfile 这个类:具体的代码如下:注意要继承于 Profile 这个类;
public class CompanyProfile:Profile
{
public CompanyProfile()
{
CreateMap<Company, CompanyDto>()//从Company映射到Dto
.ForMember(destination => destination.CompanyName,//为每个成员添加配置
option => option.MapFrom(source => source.Name));
//如果空引用的话会直接被忽略
//如果名字一样的话,可以直接映射;
//上边destination是目的地的意思:
//source是源头的意思;
} }}
在 Startup 类里面实现依赖注入:
// 对象映射器;
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
//获得当前域的集合
13.获得父子关系
先看创建后,新增和更改的文件夹:
1.或得父子关系,即根据路由模板
Companyee/{CompanyId}/Employees/{employessId} 来获得相关的信息
2.在 DBContext 里添加种子数据,种子数据要添加到 modelBuilder.Entity().hasData里面。
3.种子数据的 Id 等自动生成的数据都要手动添加
具体代码如下:
//种子数据要单独往里加
modelBuilder.Entity<Employee>().HasData(
new Employee
{
//种子数据中的ID也需要手动赋值
Id = Guid.Parse("6137cbc3-660f-4a53-a6cc-3de1a37cb6fa"),
CompanyId = Guid.Parse("d0176f72-33d0-4b48-a5ac-2c748becf3a4"),
DateOfBirth = new DateTime(1984, 11, 4),
EmployeeNo = "G006",
FirstName = "Mary",
LastName = "King",
Gender = Gender.女
},
new Employee
{
Id = Guid.Parse("1ad9b4ab-d892-4c1d-a8ea-51bced126d21"),
CompanyId = Guid.Parse("d0176f72-33d0-4b48-a5ac-2c748becf3a4"),
DateOfBirth = new DateTime(1994, 11, 24),
EmployeeNo = "G007",
FirstName = "aaa",
LastName = "zhao",
Gender = Gender.女
},
new Employee
{
Id = Guid.Parse("d9fdb98e-2601-4c39-8ed0-65e61fba0a73"),
CompanyId = Guid.Parse("d0176f72-33d0-4b48-a5ac-2c748becf3a4"),
DateOfBirth = new DateTime(1984, 11, 4),
EmployeeNo = "G026",
FirstName = "Aary",
LastName = "bng",
Gender = Gender.男
},
new Employee
{
Id = Guid.Parse("50b8ab5f-4a30-4d65-97c7-cf6c783e65bf"),
CompanyId = Guid.Parse("786cb431-4228-4489-8841-0ed6a16e13e4"),
DateOfBirth = new DateTime(1995, 8, 9),
EmployeeNo = "G015",
FirstName = "shighy",
LastName = "nhoie",
Gender = Gender.女
},
new Employee
{
Id = Guid.Parse("6782f6a9-3a2c-4316-a402-b66d525fcf0e"),
CompanyId = Guid.Parse("786cb431-4228-4489-8841-0ed6a16e13e4"),
DateOfBirth = new DateTime(1994, 11, 24),
EmployeeNo = "G497",
FirstName = "smgo",
LastName = "qinghi",
Gender = Gender.男
},
new Employee
{
Id = Guid.Parse("7adef789-951f-4830-b9b0-4f09441f165c"),
CompanyId = Guid.Parse("786cb431-4228-4489-8841-0ed6a16e13e4"),
DateOfBirth = new DateTime(1972, 6, 9),
EmployeeNo = "G04949",
FirstName = "BVer",
LastName = "EOheih",
Gender = Gender.男
},
new Employee
{
Id = Guid.Parse("dc32d25f-a390-4f09-8eac-bf92d8b5e09f"),
CompanyId = Guid.Parse("ac531f21-ccc8-4576-977a-5e3217236fa2"),
DateOfBirth = new DateTime(1997, 1, 9),
EmployeeNo = "G686",
FirstName = "shighi",
LastName = "Qian",
Gender = Gender.女
},
new Employee
{
Id = Guid.Parse("2a415a35-936e-4cdf-9239-653e9cdcb463"),
CompanyId = Guid.Parse("ac531f21-ccc8-4576-977a-5e3217236fa2"),
DateOfBirth = new DateTime(1994, 12, 24),
EmployeeNo = "Ghii7",
FirstName = "Sun",
LastName = "LI",
Gender = Gender.男
},
new Employee
{
Id = Guid.Parse("f93b3d61-318e-4123-9b84-ceb2759ea445"),
CompanyId = Guid.Parse("ac531f21-ccc8-4576-977a-5e3217236fa2"),
DateOfBirth = new DateTime(1993, 8, 8),
EmployeeNo = "G4987489",
FirstName = "BVa",
LastName = "Zhang",
Gender = Gender.女
}
);
4.更改了数据库文件以后,在 nuget 管理控制台 做数据迁移,即执行下边两个命令
- add-migration addemployeeNuber
- update-database
5.做 employee 的DTO。代码如下:
public class EmployeeDto
{
public Guid Id { get; set; } //主键
public Guid CompanyId { get; set; } //外键。指向Company的主键
//不能是company,可以是 companyDto 。
// public Company Company { get; set; }//关联的导航属性;
public string EmployeeNo { get; set; }
public string Name { get; set; }
public string GenderDisplay { get; set; } //枚举类型要先写
public int Age { get; set; }
}
6.在 EmploeeProfile 里面做对象映射
public EmpoyeeProfile()
{
CreateMap<Employee, EmployeeDto>( )
.ForMember(
//先写对象,然后写对象怎么来的
dest=>dest.Name,
opton=>opton.MapFrom(src=>$"{src.FirstName} {src.LastName}"))
.ForMember(dest=>dest.GenderDisplay,opt=>opt.MapFrom(src=>src.Gender.ToString()))
.ForMember(dest=>dest.Age,opt=>opt.MapFrom(src=>DateTime.Now.Year-src.DateOfBirth.Year)) ;
}
7。建立Controller. 根据 companID 获得员工的所有的员工信息: 根据companyId 和 employeeId 获得单个员工的信息。具体代码如下:(emploeeId一定要加括号)
//获取某一个员工的根据公司Id 获得公司下的所有员工
[HttpGet]
public async Task<ActionResult<List<EmployeeDto>>> GetEmoloyeesForCompany(Guid companyId)
{
if (!await companRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var employees = await companRepository.GetEmployeesAsync(companyId);
var employeeDto = mapper.Map<List<EmployeeDto>>(employees);
//把employee映射成EmployeeDto这种类型
return Ok(employeeDto);
}
//employeeId上要加括号;
[HttpGet("{employeeId}")]
public async Task<ActionResult<EmployeeDto>> GetEmoloyeeForCompany(Guid companyId,Guid employeeId)
{
if (!await companRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var employee = await companRepository.GetEmployeeAsync(companyId,employeeId);
if (employee == null)
{
return NotFound();
}
var employeeDto = mapper.Map<EmployeeDto>(employee);
//把employee映射成EmployeeDto这种类型
return Ok(employeeDto);
}
14.处理服务器端的故障
1.例如在 Controller 里面抛出一个异常,代码如下,
public async Task<ActionResult<List<CompanyDto>>> GetCompanies()
{
throw new Exception("这个是个错误");
var companies = await companRepository.GetCompaniesAsync();
// return new JsonResult(companies);
var companyDto1 = new List<CompanyDto>();
//这个地方要用 new
foreach (var company in companies)
{
companyDto1.Add(
new CompanyDto
{
Id = company.Id,
CompanyName = company.Name
}); }
var companyDto = mapper.Map< List < CompanyDto >> (companies);
return Ok(companyDto);
}
当异常抛出的时候,会出现如下界面,因为这是开发环境,所以可以把异常来抛出,但是在实际的生产环境中,不会抛出这些详细的错误信息,不然会给系统带来安全隐患。
当把开发环境改成 production 的时候,会返回空白页
但是我们很多时候,还是希望在其他环境中,能返回一些错误信息。可在Configure 里面进行配置,代码如下
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(appBuilder=>
{
appBuilder.Run(async context =>
{
context.Response.StatusCode = 500;
await context.Response.WriteAsync(
"发生了未处理的异常错误,请联系管理员处理");
//这个在实际的生产环境中通常要记录日志
}); } );
}
运行效果如下:
15.Head请求
httpHead 和 httpget 基本上是一样的,只是不返回 body ,使用方法可以直接在 [httpget] 上边直接加一个 [httphead]
1.幂等性
幂等通俗来说是指不管进行多少次重复操作,都是实现相同的结果。
2.HTTP请求的区别
GET,PUT,DELETE都是幂等操作,而POST不是,以下进行分析:
首先GET请求很好理解,对资源做查询多次,此实现的结果都是一样的。
PUT请求的幂等性可以这样理解,将A修改为B,它第一次请求值变为了B,再进行多次此操作,最终的结果还是B,与一次执行的结果是一样的,所以PUT是幂等操作。
同理可以理解DELETE操作,第一次将资源删除后,后面多次进行此删除请求,最终结果是一样的,将资源删除掉了。
POST不是幂等操作,因为一次请求添加一份新资源,二次请求则添加了两份新资源,多次请求会产生不同的结果,因此POST不是幂等操作。
https://blog.csdn.net/lixiaoer757/article/details/80090848
3. Post - Put - Patch 的区别
- post:提交
- Put:修改(未指定的修改会变成默认值,所以一般不用)
- Patch:局部修改
Post 处理的路径 {api/students} ,
Put 的处理路径是 {api/students/id}
Patch 的处理路径是 {api/students/id}
16.过滤和搜索
1.如何给 Api 传递数据:
Restful 更改后的传递方式(Bingding source Attribute),有四种
- FromBody 用来推断复杂类型的参数
- FromForm 用来推断 IFormFile 和 IFormFileCOllection 类型的 Action 参数
- FromRoute 用来推断 Action 中的参数和路由模板中的参数
- FromQuery 用来推断其他类型的 Action 参数
1,fromBody:在cation方法传入参数后添加[frombody]属性,参数将以一个整体的josn对象的形式传递。
2,fromform:在cation方法传入参数后添加[frombody]属性,参数将以表单的形式提交。
下边是从路由模板中取得参数的例子
public async Task<ActionResult<CompanyDto>>
GetCompany([FromRoute]Guid companyId)
从Query中取得的例子
public async Task<ActionResult<CompanyDto>>
GetCompany([FromQuery]Guid companyId)
2.过滤和搜索的区别
过滤:首先是一个完整的集合,然后根据条件,把不匹配的数据移除。
搜索:首先是一个空的集合,然后根据条件,把匹配的的数据项往里添加。
注意过滤只能对DTO中的字段进行过滤。
3.以 EmployeesController 为例,建立过滤搜索:
代码如下:
public async Task<ActionResult<List<EmployeeDto>>> GetEmoloyeesForCompany
(Guid companyId,[FromQuery(Name ="gender")]string genderDisplay)
{
if (!await companRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var employees = await companRepository.GetEmployeesAsync(companyId,genderDisplay);
var employeeDto = mapper.Map<List<EmployeeDto>>(employees);
//把employee映射成EmployeeDto这种类型
return Ok(employeeDto);
}
4.更改 DBcontext 中相关的代码:即 CompanyRepository 的代码:
注意下边属性的用法:
- genderDisplay.Trim( )
- ar gender = Enum.Parse(genderStr)
- string.IsNullOrWhiteSpace(genderDisplay)
public async Task<List<Employee>> GetEmployeesAsync
(Guid companyId,string genderDisplay)
{
if (companyId==Guid.Empty)
{
throw new ArgumentNullException(nameof(companyId));
}
//IsNullOrWhiteSpace 比 IsNullOrEmpty 大
if (string.IsNullOrWhiteSpace(genderDisplay))//如果是空的
{
return await _context.Employees.
Where(x => x.CompanyId == companyId).
OrderBy(x => x.EmployeeNo).
ToListAsync(); //只要使用异步就要用await
}
var genderStr = genderDisplay.Trim();//这一行是就修剪掉空白字符串
var gender = Enum.Parse<Gender>(genderStr);//把genderstr解析成Gender格式
return await _context.Employees.
Where(x => x.CompanyId == companyId && x.Gender==gender).
OrderBy(x => x.EmployeeNo).
ToListAsync(); //只要使用异步就要用await
}
.trim() 的含义
5.用postman测试
在路由中直接加 ? gender=男
6.搜索: 搜索其实就是检索出来带关键字的结果。
controller 里面的东西不再演示,下边是更改后的 EmployeeRespository()里面的东西。
注意对于 item 的用法,每次 item.where 都相当于做了一次筛选
注意item=item的用法,名字相同的时候,不用写类型:
搜索是对Entity进行的。
{
if (companyId==Guid.Empty)
{
throw new ArgumentNullException(nameof(companyId));
}
var items = _context.Employees.Where(x=>x.CompanyId==companyId);
//IsNullOrWhiteSpace 比 IsNullOrEmpty 大
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();
}
17. 查询参数
当进行查询和搜索的时候,如果一次传入多组参数,每次都需要更改Action参数的方法,会很麻烦,所以直接传入一个类,这样只要更改一个类就可以,不需要对 Controller 中的 Action 参数进行更改。
具体实现如下:以Company(Guid Id)为例
1.预览:具体添加目录如下:
2.新建 DtoParameters 的类,
如果要有新的查询,就在这里面添加。
public class CompanyDtoParamaters
{
public string CompanyName { get; set; }
public string SearchTerm { get; set; }
}
3.在 Controller 的 Action 里面更改传入参数:(注意要声明来自于 【FromQuery】)
public async Task<ActionResult<List<CompanyDto>>>
GetCompanies([FromQuery]CompanyDtoParamaters companyDtoParamaters)
{
var companies = await companRepository.GetCompaniesAsync(companyDtoParamaters);
// return new JsonResult(companies);
var companyDto1 = new List<CompanyDto>();//这个地方要用 new
foreach (var company in companies)
{
companyDto1.Add(
new CompanyDto
{
Id = company.Id,
CompanyName = company.Name
});
}
var companyDto = mapper.Map< List < CompanyDto >> (companies);
return Ok(companyDto);
}
4.更改 Repository:
public async Task<List<Company>> GetCompaniesAsync
(CompanyDtoParamaters companyDtoParamaters)
{
if (companyDtoParamaters==null)
{
throw new ArgumentNullException(nameof(companyDtoParamaters));
}
if (string.IsNullOrWhiteSpace(companyDtoParamaters.CompanyName)
&&string.IsNullOrWhiteSpace(companyDtoParamaters.SearchTerm))
{
return await _context.Companies.ToListAsync();//遇到TolistAsnyc才会真正的开始查询数据库
}
var item = _context.Companies as IQueryable<Company>;//对数据进行过滤和搜索的时候要用
//IQueryable 以后再查
if (!string.IsNullOrEmpty(companyDtoParamaters.CompanyName))
{
companyDtoParamaters.CompanyName = companyDtoParamaters.CompanyName.Trim();
item = item.Where(x=>x.Name==companyDtoParamaters.CompanyName);
}
if (!string.IsNullOrEmpty(companyDtoParamaters.SearchTerm))
{
companyDtoParamaters.SearchTerm = companyDtoParamaters.SearchTerm.Trim();
item = item.Where(x => x.Name.Contains(companyDtoParamaters.SearchTerm)
&&x.Introduction.Contains(companyDtoParamaters.SearchTerm));
}
return await item.ToListAsync(); //Async 前边要加 await
}
18. 安全性和幂等性
- 安全性:方法执行后不会改变资源的表述
- 幂等性:无论方法执行多少次后都会得到同样的结果。
各种http请求的区别https://blog.csdn.net/weixin_43740223/article/details/85767838
GET,PUT,DELETE都是幂等操作,而POST不是,以下进行分析:
首先GET请求很好理解,对资源做查询多次,此实现的结果都是一样的。
PUT请求的幂等性可以这样理解,将A修改为B,它第一次请求值变为了B,再进行多次此操作,最终的结果还是B,与一次执行的结果是一样的,所以PUT是幂等操作。
同理可以理解DELETE操作,第一次将资源删除后,后面多次进行此删除请求,最终结果是一样的,将资源删除掉了。
POST不是幂等操作,因为一次请求添加一份新资源,二次请求则添加了两份新资源,多次请求会产生不同的结果,因此POST不是幂等操作。
19.Post .添加 Company
1.先设置 CompanyAddDto :因为 addDto 和entity 和 dto 都不一样,比如在出入的时候,CompanyAddDto都不需要传入参数:设置AddDTo如下:
public class CompanyAddDto
{
public string Name { get; set; }
public string Introduction { get; set; }
}
2.建立映射关系:名字都一样,不需要指定
CreateMap<CompanyAddDto, Company>();
3.在 CompanyController 中设置 Post
注意一下几点
- 只要有 Async 的方法和属性,前边就一定要有 await
- 注意最后,添加成功返回 201 状态码,并返回添加成功的Dto,代码怎么写?要先在 httpget 的路由方法中添加名字。
[HttpGet("{companyId}",Name = "GetCompany")]//给路由起个名字
,而后在CreatAtRoute()方法中添加相关参数。 - 注意来来回回的映射关系
[HttpPost]
public async Task<ActionResult<CompanyDto>>
CreatCompany([FromBody]CompanyAddDto companyAddDto)
{
var entityCompany = mapper.Map<Company>(companyAddDto);
companRepository.AddCompany(entityCompany);
await companRepository.SavaAsync();
//只要后边有 Async 前边就必须有 awati
var returnDto = mapper.Map<CompanyDto>(entityCompany);
//如果返回201的状态码,如何设置?
//return CreatedResult();
//如何让其返回路由地址
//路由的参数,路由的值,还有 values
return CreatedAtRoute(nameof(GetCompany), new { companyId = returnDto.Id },returnDto);
}
4.最后测试结果如下:
20.添加子资源
添加子资源,就是在已有的公司里面,添加新的员工
方法和 Company 执行post 请求差不多:
1.创建 employeeAddDto 代码如下:
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; }
}
2.添加映射关系
CreateMap<EmployeeAddDto,Employee>();
3.建立 CreatEmployeeForCompany
[HttpPost]
public async Task<ActionResult<EmployeeDto>>
CreatEmployeeForCompany(Guid companyId
,EmployeeAddDto employeeAddDto)
{
if (!await companRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var entity = mapper.Map<Employee>(employeeAddDto);
companRepository.AddEmployee(companyId,entity);
await companRepository.SavaAsync();
var resutltDto = mapper.Map<EmployeeDto>(entity);
return CreatedAtRoute(
"GetEmoloyeeForCompany",
new {
companyId=entity.CompanyId,
employeeId=entity.Id
},resutltDto);
}
4.测试结果如下:
返回的路由地址
21.同时创建父子资源
同时创建父子资源的意思:是在创建 Company 的时候,同时带着 employ
1.里面的东西不用改,因为源代码中有遍历。代码如下
public void AddCompany(Company company)
{
if (company == null)//检查的应该是他传进来的参数
{
throw new ArgumentNullException(nameof(company));
}
company.Id = Guid.NewGuid();//Id 是当时生成的;
if (company.Employees != null)
{
foreach (var employee in company.Employees)
{
employee.Id = Guid.NewGuid();
}
}
_context.Companies.Add(company);
}
2.使用 PostMan 进行测试
22.添加集合资源
即:以集合的形式添加 Company (添加多个Company)
1.创建新的 CompanyCollectionController 类:代码如下:
[Route("api/companyCollections")]
[ApiController]
public class CompanyCollectionController : ControllerBase
{
private readonly IMapper mapper;
private readonly ICompanRepository companRepository;
//依赖注入的是接口
public CompanyCollectionController
(IMapper mapper,ICompanRepository companRepository)
{
this.mapper = mapper;
this.companRepository = companRepository;
}
[HttpPost]
public async Task<ActionResult<List<Company>>>
CreatCompanyList(List<CompanyAddDto> companyCollection)
{
var entityCompanyList = mapper.Map<List<Company>>(companyCollection);
foreach (var company in entityCompanyList)
{
companRepository.AddCompany(company);
}
await companRepository.SavaAsync();
return Ok();
} }
2.使用postman进行测试:
23.自定义 Model 绑定器
即:使用 , 分开的 id 能够找到分别对应的 Company
代码没看懂,直接上图片吧
视频连接:自定义Model绑定器
24.Http Options 请求
具体代码如下:
[HttpOptions]
public IActionResult GetCompanyOptions()
{
Response.Headers.Add("Alllow","get ,post,options");
return Ok();
}
用 postman 测试结果如下:
25.输入验证
Data Annotations(注释)
输入验证,本来在 DbContext 里面有输入验证:
modelBuilder.Entity<Company>().Property(x => x.Name).IsRequired().HasMaxLength(100);
modelBuilder.Entity<Company>()
.Property(x => x.Introduction).IsRequired().HasMaxLength(500);
modelBuilder.Entity<Employee>()
.Property(x => x.EmployeeNo).IsRequired().HasMaxLength(10);
modelBuilder.Entity<Employee>()
.Property(x => x.FirstName).IsRequired().HasMaxLength(50);
modelBuilder.Entity<Employee>()
.Property(x => x.LastName).IsRequired().HasMaxLength(50);
但是如果使用这个验证:就是在返回的状态码中返回 500 状态码。
所以要在 AddDto 里面进行输入验证:代码如下:
注意 {0} {1} : {0}是表示名字,后边的 1 和 2 表示的最大值和最小值(往后拍)
public class CompanyAddDto
{
[Display(Name ="公司的名字")]
[Required(ErrorMessage ="{0}是必须要填的")]
[MaxLength(100)]
public string Name { get; set; }
[Display(Name="公司的简介")]
[Required(ErrorMessage ="{0}这个是必填的")]
[StringLength
(500,MinimumLength =10,ErrorMessage ="{0}这个参数的最大长度是{1},最小范围是{2}")]
public string Introduction { get; set; }
public List<EmployeeAddDto>
Employees { get; set; } = new List<EmployeeAddDto>();
}
验证结果如下:
Name 和 Introduction 都不一样的时候
使用 IValidatableObject 来实现输入验证:
IvalidatableObject 功能更强大,通常是要走完属性验证以后,才会进行 IvalidatableObjectio验证:
具体实现代码如下:
比如,在添加员工的时候,姓和名不能一样
注意要继承 IvalidatableObject 这个类
public class EmployeeAddDto:IValidatableObject
{
[Display(Name ="员工号")]
[Required(ErrorMessage ="{0}是必须要填的")]
[StringLength(10,MinimumLength =10,ErrorMessage ="{0}的长度要求是{1}")]
public string EmployeeNo { get; set; }
[Display(Name ="姓")]
public string FirstName { get; set; }
[Display(Name ="名")]
public string LastName { get; set; }
[Display(Name ="性别")]
public Gender Gender { get; set; } //枚举类型要先写
[Display(Name ="出生日期")]
public DateTime DateOfBirth { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (FirstName==LastName)
{
yield return
new ValidationResult("姓和名字不能一样"
,new[] { nameof(FirstName),nameof(LastName)});
}
}
}
用 postman 测试结果如下:
要想作用于类,可以使用自定义的验证特性
没仔细看,具体看视频
26. PUT 整体更新
put 是对数据进行整体的更新,如果没有设置的值,就会被设为默认值。所以这一般用的比较少。
注意:ld 的路由一定要加 {} !!
1.首先建立 UPdateDto,因为要 updateDto 和 其他的 Dto 可能会不一样,这是一样的;
public class EmployeeAddDto:IValidatableObject
{
[Display(Name ="员工号")]
[Required(ErrorMessage ="{0}是必须要填的")]
[StringLength(10,MinimumLength =10,ErrorMessage ="{0}的长度要求是{1}")]
public string EmployeeNo { get; set; }
[Display(Name ="姓")]
public string FirstName { get; set; }
[Display(Name ="名")]
public string LastName { get; set; }
[Display(Name ="性别")]
public Gender Gender { get; set; } //枚举类型要先写
[Display(Name ="出生日期")]
public DateTime DateOfBirth { get; set; }
public IEnumerable<ValidationResult>
Validate(ValidationContext validationContext)
{
if (FirstName==LastName)
{
yield return
new ValidationResult
("姓和名字不能一样",new[] { nameof(FirstName),nameof(LastName)});
}} }
2.添加 PUT 的 Controller
- 注意路由模板
- 注意 通过 noContent 获得 204状态码
注意:路由模板
[HttpPut("{employeeId}")]
public async Task<IActionResult> UpdateEmployeeForCompany
([FromRoute]Guid companyId,[FromRoute]Guid employeeId,
[FromBody]EmployeeUpdateDto employeeUpdateDto)
{
if (!await companRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var employeeEntity = await
companRepository.GetEmployeeAsync(companyId,employeeId);
if (employeeEntity==null)
{
return NotFound();
}
//把 entity 转化为 UpdateDto
//把 employee 更新到 updateDto
//再把 updateDto 映射回 entity
//在 AutoMapper 里面一句就实现了
mapper.Map(employeeUpdateDto,employeeEntity);
companRepository.UpdateEmployee(employeeEntity);
await companRepository.SavaAsync();
return NoContent();
}
3.用 Postman 检测效果:
27. Http Patch 局部更新
1.局更新的操作方法有以下几种
2.新建 EmployeeController 类
- 注意 map.map(A) 和map.map(A,B)
- 注意传入参数时候用的 JsonPatchDocument 类,引用该类型需要引入第三方的一个包:
代码如下:
[HttpPatch("{employeeId}")]
public async Task<IActionResult> PatchUpdataEmployeeForCompany
( Guid companyId
,Guid employeeId
,JsonPatchDocument<EmployeeUpdateDto> patchDocument )
{
if (!await companRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var employeeEntity = await companRepository.GetEmployeeAsync(companyId, employeeId);
if (employeeEntity == null)
{
return NotFound();
}
//只要是涉及到映射改变的,都是先映射回来,再映射回去
var dtoTopatch = mapper.Map<EmployeeUpdateDto>(employeeEntity);
//这个是转化类型
//验证错误;
patchDocument.ApplyTo(dtoTopatch);
mapper.Map(dtoTopatch,employeeEntity);
//这个是从一个实例映射到另一个实例
companRepository.UpdateEmployee(employeeEntity);
await companRepository.SavaAsync();
return NoContent();
}
3.但是在实际引用的时候,因为 .net core,Json的转化不太健全,所以可以使用 Newtonsoft.Json 转化一下:
引用包:
代码如下:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
options.ReturnHttpNotAcceptable = true
//他默认是关闭的
).AddNewtonsoftJson(setup=>
{
setup.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
})
.AddXmlDataContractSerializerFormatters();
//添加对XML的支持
4.使用postman进行测试:
- 注意 Content-Type 要使用 application/json-patch+json 这种类型
其他操作类似:
28.HttpDelete 删除 employee 资源
1.这个比较简单,代码如下:
[HttpDelete("{employeeId}")]
public async Task<IActionResult>
DeleteEmployeeForCompany(Guid companyId, Guid employeeId)
{
if (!await companRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var employeeEntity =
await companRepository.GetEmployeeAsync(companyId, employeeId);
if (employeeEntity == null)
{
return NotFound();
}
companRepository.DeleteEmployee(employeeEntity);
await companRepository.SavaAsync();
return NoContent();
}
2.用 Postman 的测试结果如下:
29.HttpDelete 删除 Company
在删除 Company 的同时,顺带着把 Company 下的 Employee 也删除了。
1.需要在 DBcontext 里边把级联属性打开:
.HasForeignKey(x => x.CompanyId)//指定外键
// .OnDelete(DeleteBehavior.Restrict);
//当删除的时候,如果company下边有员工的话,就无法删除;
.OnDelete(DeleteBehavior.Cascade);
2.在 CompanyController 里面,添加 httpDelete 方法:
注意:要添加
await companRepository.GetEmployeesAsync(companyId, null, null);
进行跟踪
[HttpDelete("{companyId}")]
public async Task<IActionResult>
DeleteEmployeeForCompany(Guid companyId)
{
var companyEntity =
await companRepository.GetCompanyAsync(companyId);
if (companyId==null)
{
return NotFound();
}
await companRepository.GetEmployeesAsync(companyId, null, null);
companRepository.DeleteCompany(companyEntity);
await companRepository.SavaAsync();
return NoContent();
}
3.用 Postman 进行测试:
30.翻页功能
翻页指在数据库层面的翻页,在查询完成以后,再执行翻页功能没有任何意义
下边以查询 Company 下边的员工为例。
1.因为有查询表达参数这个类,所以直接在类里面添加:
特别注意属性的用法:
- 属性没有储存功能
- 在类的内部相当于方法
public class CompanyDtoParamaters
{
private const int maxlength = 5;
public string CompanyName { get; set; }
public string SearchTerm { get; set; }
public int pageNumber { get; set; } = 1;
private int _pagesize=5;
public int PageSize
{ get=>_pagesize;
set=>_pagesize= (value> maxlength )? maxlength:value;
}
}
2.在 Repository 类里面添加一行代码:
- 注意代码一定要放在过滤和搜素以后
- 这个代码一定要放在正式查询之前,比如 .ToListAsync();
public async Task<List<Company>> GetCompaniesAsync
(CompanyDtoParamaters companyDtoParamaters)
{
if (companyDtoParamaters == null)
{
throw new ArgumentNullException(nameof(companyDtoParamaters));
}
//if (string.IsNullOrWhiteSpace(companyDtoParamaters.CompanyName)
// && string.IsNullOrWhiteSpace(companyDtoParamaters.SearchTerm))
//{
// return await _context.Companies.ToListAsync();//遇到TolistAsnyc才会真正的开始查询数据库
//}
var item = _context.Companies as IQueryable<Company>;//对数据进行过滤和搜索的时候要用
//IQueryable 以后再查
if (!string.IsNullOrEmpty(companyDtoParamaters.CompanyName))
{
companyDtoParamaters.CompanyName =
companyDtoParamaters.CompanyName.Trim();
item = item
.Where(x => x.Name == companyDtoParamaters.CompanyName);
}
if (!string.IsNullOrEmpty(companyDtoParamaters.SearchTerm))
{
companyDtoParamaters.SearchTerm
= companyDtoParamaters.SearchTerm.Trim();
item = item.Where(x => x.Name.Contains(companyDtoParamaters.SearchTerm)
&& x.Introduction.Contains(companyDtoParamaters.SearchTerm));
}
//翻页在过滤以后再执行;
item = item
.Skip
(companyDtoParamaters.PageSize * (companyDtoParamaters.pageNumber - 1))
.Take(companyDtoParamaters.PageSize);
return await item.ToListAsync(); //Async 前边要加 await
}
3.使用 Postman 进行测试的结果:
可以返回一些更加详细的信息,比如前一页和后一页的链接,当前页是第几页等。
这个比较复杂。我没看,详细看视频
31.排序
1.和 employeeDToParamter 一样,建立 EmployeeDtoParamters 类,建立添加OrederBy 的字段
内容如下:
public class EmployeeDtoparamters
{
private const int MaxPageSize = 20;
public string Gerder { get; set; }
public string Q { get; set; }
public int PageNumber { get; set; } = 1;
public int _PageSize = 5;
public int pageSize
{
get=>_PageSize;
set=>_PageSize=(value>MaxPageSize)?MaxPageSize:value;
}
public string OrderBy { get; set; } = "Name";
}
2.在 DBContext 里面添加关于查询的字符串,注意 OrederBy 和 ThenBy
//根据公司ID寻找所有的员工信息;
public async Task<List<Employee>> GetEmployeesAsync(Guid companyId, EmployeeDtoparamters dtoparamters)
{
if (companyId == Guid.Empty)
{
throw new ArgumentNullException(nameof(companyId));
}
var items = _context.Employees
.Where(x => x.CompanyId == companyId);
//IsNullOrWhiteSpace 比 IsNullOrEmpty 大
if (!string.IsNullOrWhiteSpace(dtoparamters.Gerder))
//如果非空
{
dtoparamters.Gerder = dtoparamters.Gerder.Trim();
//名字相同的话,不要写类型;
var gender = Enum.Parse<Gender>(dtoparamters.Gerder);
items = items.Where(x => x.Gender == gender);
}
if (!string.IsNullOrWhiteSpace(dtoparamters.Q))
{
dtoparamters.Q = dtoparamters.Q.Trim();
items = items
.Where(x => x.EmployeeNo.Contains(dtoparamters.Q)
|| x.FirstName.Contains(dtoparamters.Q)
|| x.LastName.Contains(dtoparamters.Q));
}
if (dtoparamters.OrderBy!=null)
{
if (dtoparamters.OrderBy.ToLowerInvariant()=="name")
{
items = items
.OrderBy(x=>x.LastName).ThenBy( x=>x.FirstName);
}
}
return await
items.ToListAsync();
}
3.用 postman 进行查询的结果:
这种简单粗暴的处理方式,不能使用复用,要实现复用的话,相对麻烦一点。我没看,相关部分请看视频。
32.数据塑形
- 数据塑形:允许 API 消费者选择要返回的资源的字段。
- /api/companies? fields = id ,name。
- 针对资源的字段,而不是其他更低层次的对象的字段。
33.HateOAS
翻译过来的意思:超媒体做为应用状态的引擎
HATEOAS 是 REST 架构风格中最复杂的约束,也是构成成熟REST架构风格的核心;他除了返回一些结果以外,还返回一个 links 的数组
有点复杂,我也没看,具体看视频;
34. 添加 JWT 授权认证功能
题外话: 如果在建立授权类的时候,对里面的某个方法不确定的话,可以先写个get string的函数试一试;
1.要新建一个项目,添加 AuthorizationServer 服务器
新建目录如下
2.JWT接口的内容如下
public interface IJWTService
{
string GetToken(string UserName);
}
3…JWTServer 类的内容如下
public class JWTServer : IJWTService
{
private readonly IConfiguration configuration;
public JWTServer(IConfiguration configuration)
{
this.configuration = configuration;
}
//查一下claim类
public string GetToken(string UserName)
{
Claim[] claims = new[]
//这里面的东西是要添加到payload里面的东西
{
new Claim(ClaimTypes.Name,UserName),
new Claim("NcikName","zhaodepeng"),
new Claim("Role","Admin"),
};
//用私钥加密
SymmetricSecurityKey key =
new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(configuration["SecurityKey"]));
//加密的字符串
SigningCredentials creds =
new SigningCredentials(key, SecurityAlgorithms.HmacSha256);//指定加密方式;
//JWT标准规定的一些字段
var token = new JwtSecurityToken(
issuer: configuration["issuer"],
audience: configuration["audience"],
claims: claims, //上边的其他声明
expires: DateTime.Now.AddMinutes(5),//五分钟有效期;
signingCredentials: creds//加密方法.
);
string returnToken =
new JwtSecurityTokenHandler().WriteToken(token);//把JWT写到Json里面
return returnToken;
}
}
4.AuthenticationController类的内容如下:
[Route("authen")]
[ApiController]
public class AuthenticationController : ControllerBase
{
#region 注入
private readonly ILoggerFactory factory;
private readonly ILogger<AuthenticationController> logger;
private readonly IConfiguration configuration;
private readonly IJWTService JWtser;
public AuthenticationController(
ILoggerFactory factory,
ILogger<AuthenticationController> logger,
IConfiguration configuration, IJWTService service)
{
this.factory = factory;
this.logger = logger;
this.configuration = configuration;
this.JWtser = service;
}
#endregion
[HttpGet("test")]
public string Gettest()
{
return "能成功的进入到方法里面";
}
public string Login(string name, string password)
{
//正常的话是要查询数据库
if ("zhao".Equals(name) && "123456".Equals(password))
{
string token = JWtser.GetToken(name);
return JsonConvert.SerializeObject(new
{
result = true,
token
});
}
else
{
return JsonConvert.SerializeObject(new
{
result = false,
token = ""
});
} } }
5.配置文件的相关内容如下:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"audience": "http://localhost:5000",
"issuer": "http://localhost:5000",
"SecurityKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0a
fyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uK
jVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L
/mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB"
}
6. 在server API中进行配置认证信息
(就是什么样的token可以进来)
#region Jwt授权认证
//1.Nuget引入程序包:Microsoft.AspNetCore.Authentication.JwtBearer
//services.AddAuthentication();//禁用
var ValidAudience = this.Configuration["audience"];
var ValidIssuer = this.Configuration["issuer"];
var SecurityKey = this.Configuration["SecurityKey"];
services.AddAuthentication
(JwtBearerDefaults.AuthenticationScheme) //默认授权机制名称;
.AddJwtBearer(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true,//是否验证Audience
ValidateLifetime = true,//是否验证失效时间
ValidateIssuerSigningKey = true,//是否验证SecurityKey
ValidAudience = ValidAudience,//Audience
ValidIssuer = ValidIssuer,//Issuer,
这两项和前面签发jwt的设置一致 表示谁签发的Token
IssuerSigningKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecurityKey))
//拿到SecurityKey
//AudienceValidator = (m, n, z) =>
//{
// return m != null && m.FirstOrDefault().Equals(this.Configuration["audience"]);
//},//自定义校验规则,可以新登录后将之前的无效
};
});
#endregion
7.添加两个中间件管道:
app.UseAuthentication();//鉴权授权;注意位置
app.UseAuthorization();
8.在要保护的 Controller 上边添加 [Authorize] 特性, 不授权保护的api上边添加 【AllowAnonymous】特性
9.使用 postman 进行测试的结果如下:
- 先从认证服务器上获得token
- 带着token去访问被保护的ApI
注意!!!!
token 是\ 里面的东西,不包括斜杠,别搞错了!!
35.配置 Swagger
1.引入 SwashBuckle.AspNetCore 包
我就是用这个包安装的。引入了以后,可以按 f12 ,查看具体的参数
2.把swagger 放到IOC容器里
services.AddSwaggerGen(options => //配置swagger...swaggerGen是swagger生成器.
{
options.SwaggerDoc("V1" ,new OpenApiInfo() //这个v1 是给机器看的。
{
Title = "test",
//下边的信息是给人看的。
Version = "Version--0",
Description = "公司-员工-信息查询系统"
});
});
3在中间件管道里使用
#region 使用Swagger中间件
app.UseSwagger();
app.UseSwaggerUI(s =>//swaggerUI 解释了Swagger JSon
{
s.SwaggerEndpoint("/swagger/V1/swagger.json", "test1");
//其中的V1和前边(给机器看的)的相对应;
});
#endregion