项目结构
controller
路由名
[Route(“api/touristRoutes/{touristRouteId}/pictures”)]
[ApiController]
构造函数
定义私有仓库、私有mapper
构造函数赋值
http请求
具体功能函数
DataBase
定义数据库上下文
数据库上下文构造函数
映射表
每一个数据模型都要有一个DbSet来映射数据库的表
public DbSet<TouristPicturesRoute> TouristRoutes { get; set; }
OnModelCreating函数
DTO和Model
创建类 如TouristRouteDto
里面全是数据类型
Profile
创建类 如TouristRoutePictureProfile:Profile
接下来中定义一个映射函数
Services 仓库
有声明的文件,和实现的文件
- get
实现的文件,主要的作用就是表的连接和表的查找
运用lamda表达式
(t => t.Id == touristRouteId)
这里的t就是表示数据库中正在查找的对象
第三章 数据模型与数据库设计
3-6 EF组件
最底层:数据库
最上层:数据模型及其映射
? Model+Mapping
? EDM实体数据模型
LinQ:面对对象的方式转换为Sql Statement,查询实体对象,返回的结果也是实体对象
Entity SQL:相当于SQL statement,对SQL操作
通过LinQ和Entity SQL,将对象提供给对象服务 Object Service
Object Service(访问数据库并返回数据的主要入口,负责数据的实例化,传递给下一层DataProvider数据供应)
DataProvider数据供应,将真正的LINQ及Entity SQL语言转换为SQL语言
最后通过ADO.Net进行数据库通讯
3-7 安装ENtity Framework
1.安装插件(插件1)
右击[项目名].API
管理Nuget程序包
安装Microsoft.EntityFrameworkCore 3.1.3
2.建立数据库文件夹 (DataBase)
在文件夹中创建APPDBContext类文件
加入using Microsoft.EntityFrameworkCore
在定义类后面加上:DbContext继承基类
加上using[项目名](FakeXiecheng.API.Models)
编写AppDbContext
public AppDbContext(DbContextOptions<AppDbContext> options):base(options){}//每一个数据模型都要有一个DBSet来映射数据库的表public DbSet<TouristRoute> TouristRoutes { get; set; }public DbSet<TouristRoutePicture> touristRoutePictures { get; set; }
3.使用拓展框架配置数据库信息(插件2)
和插件1过程相同
只不过名字为Microsoft.EntityFrameworkCore.sqlserver 3.1.3
Microsoft.EntityFrameworkCore.Tools 3.1.3(后期3-8要用)数据库创建工具
引用拓展包时只需要引用EntityFramework就够了
4.将APPDBContext对象注入到系统的ioc容器中
打开Startup文件
找到ConfigureServices函数
services.AddDbContext<AppDbContext>(option =>{ option.UseSqlServer("server=localhost;Database=FakeXiechengDb;User Id=sa;Password=PaSSword12!");//接受string作为参数});
5.appsettings.json文件
"Dbcontext": {
"ConnectionString": "server=localhost;Database=FakeXiechengDb;User Id=sa;Password=PaSSword12!"}
回到Startup引用此文件
using Microsoft.Extensions.Configuration;
创建一个私有变量储存这一信息
public IConfiguration Configuration { get; }
在构建函数中给他赋值
public Startup(IConfiguration configuration){Configuration= configuration;}
6.数据库的配置信息从hardcore?转换为配置文件
Startup文件中
ConfigureServices函数
services.AddDbContext<AppDbContext>(option =>option.UseSqlServer(Configuration["DbContext:ConnectionString"]);});
7.创建返回数据的仓库TouristRouteRepository.cs
using FakeXiecheng.API.DataBase;
using FakeXiecheng.API.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace FakeXiecheng.API.Services
{public class TouristRouteRepository:ITouristRouteRepository{private readonly AppDbContext _context;public TouristRouteRepository(AppDbContext context){_context = context;}//需要返回的数据是一条单独的旅游路线//如果能找到,就返回对应的旅游路线,如果找不到,就返回空public TouristRoute GetTouristRoute(Guid touristRouteId){return _context.TouristRoutes.FirstOrDefault(n => n.Id == touristRouteId);}//返回Context中的所有旅游路线public IEnumerable<TouristRoute> GetTouristRoutes(){return _context.TouristRoutes;}}
}
8.去startup里更新注入依赖
services.AddTransient<ITouristRouteRepository, MockTouristRouteRepository>();//依赖注入 假数据库定义 假数据库实现
把Mock去掉就变成真的数据库啦
3-8 使用EntityFramework创建数据库
验证数据模型:添加数据库限制,主键信息,以及外键联系信息等等
数据库限制
引入using System.ComponentModel.DataAnnotations;
[Key]//主键public Guid Id { get; set; }[Required][MaxLength(100)]public string Title { get; set; }[Required, MaxLength(1500)]public string Description { get; set; }[Column(TypeName = "decimal(18,2)")]public decimal OriginalPrice{ get; set; }[Range(0.0,1.0)]public double? DiscountPresent { get; set; }//?代表可空public DateTime CreateTime { get; set; }public DateTime? UpdateTime { get; set; }public DateTime? DepartureTime { get; set; }[MaxLength]public string Features { get; set; }[MaxLength]public string Fees { get; set; }[MaxLength]public string Notes { get; set; }public ICollection<TouristRoutePicture> TouristRoutePictures { get; set; }=new List<TouristRoutePicture>();
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FakeXiecheng.API.Models
{public class TouristRoutePicture { [Key][DatabaseGenerated(DatabaseGeneratedOption.Identity)]//自增字段public int ID { get; set; }[MaxLength(100)]public string url { get; set; }[ForeignKey("TouristRouteId")]//外键public Guid TouristRouteId { get; set; }public TouristRoute TouristRoute { get; set; }}
}
tools在这里安装
创建数据库
视图-其他窗口-程序包管理器控制台
-
创建数据库迁移代码
add-migration initialMigration
-
创建数据库
update-database
要在MSSQLLocalDb-安全性-登录名-sa-查看源代码-更改对应密码与本地数据库相同
原密码(N’d{jlwp8ur6}j|+YxcLzp7dM3msFT7_&#KaTeX parse error: Expected '}', got 'EOF' at end of input: !~<j{k,fynkwpf’)其中要修改{}内的
3-9 添加初始化数据
重写OnModelCreating()
protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);}
创建数据表映射关系的时候补充说明用的
可以自定义表名
protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.Entity<TouristRoute>().HasData(new TouristRoute(){Id = Guid.NewGuid(),Title = "TestTitle",Description = "veryNB",OriginalPrice = 0,CreateTime = DateTime.UtcNow}); base.OnModelCreating(modelBuilder);}
手动增加数据到数据库
cd到项目目录
[D:\project.net core\FakeXiecheng.API]
-
先全局安装ef工具(一次就好,这次装了下次在同一电脑上就不用装了)
dotnet tool install --global dotnet-ef
-
添加新的数据迁移,名字叫DataSeeding
dotnet ef migrations add DataSeeding
-
更新数据库中的数据
dotnet of database update
添加文件中的数据
注释掉手动添加的数据
/*modelBuilder.Entity<TouristRoute>().HasData(new TouristRoute(){Id = Guid.NewGuid(),Title = "TestTitle",Description = "veryNB",OriginalPrice = 0,CreateTime = DateTime.UtcNow}); */
-
引入IO框架
using System.IO;
-
通过文件地址读取文件
File.ReadAllText(@"/DataBase/touristRoutesMockData.json");
@表示后面的为C#的字符串
-
获得当前项目的文件夹位置(程序集地址)保存在一个变量中
var touristRouteJsonData = File.ReadAllText(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)+@"/DataBase/touristRoutesMockData.json");
-
在nuget中安装Microsoft.VisualStudio.Web.CodeGeneration,Newtonsoft.Json
using Newtonsoft.Json;
-
新建一个旅游路线的列表
IList<TouristRoute>touristRoutes = JsonConvert.DeserializeObject<IList<TouristRoute>>(touristRouteJsonData);
反序列化
touristRouteJsonData是要反序列的具体数据
IListtouristRoutes是反序列后的类型
-
传入处理过的数据
modelBuilder.Entity<TouristRoute>().HasData(touristRoutes);
-
添加新的数据迁移,名字叫DataSeeding
dotnet ef migrations add DataSeeding2
-
更新数据库中的数据
dotnet of database update
3.10-更新数据库
增加属性(列)
-
double? Rating
-
天数:enum,在Models新建一个TravelDays文件
using System; using System.Collections.Generic; using System.Linq; using System.Threading; namespace FakeXiecheng.API.Models {public enum TravelDays{One,Two,Three,Four,Five,Six,Seven,Eight,EightPlus} }
在TouristRoute文件中加入
public TravelDays? TravelDays { get; set; }
TripType、DepartureCity与之同理
数据更新
-
添加新的数据迁移,名字叫DataSeeding
dotnet ef migrations add DataSeeding2
-
更新数据库中的数据
dotnet of database update
第四章
4-1 hello,Rest
Restful
- 历史的必然选择
- 语义明确、轻量级、且结构简单
全称:REpresentational State Transfer 表征性状态转移
Restful基本特点
- 无状态
- 面向“资源”,有名词无动词
- 使用HTTP的动词
- GET 查看
- POST 创建
- PUT 更新
- PATCH 部分更新
- DELETE 删除
- HATOAS 超媒体即应用状态引擎
RESTFul API到底好用吗?
某些情况好用,某些情况非常不好用
- 好用:面对对象(资源),如增删改查
- 不好用:面对过程,如登录
4-2Restful的6个约束和最佳实践
- Client-Server 前后端分离
- 无状态 请求独立
- 分层系统 代码分层
- 统一接口 数据统一,API自我发现
- 可缓存
- 按需代码 不重要
4-3 HTTP请求方法与资源交互
HTTPMethod请求方法 | Request Payload请求主体 | URL用例 | 响应主体 |
---|---|---|---|
GET | - | /api/touristRoutes | 旅游路线列表,单条路有路线 |
/api/touristRoute/{id} | |||
POST | 单一的旅游路线 | /api/touristRoutes | 单条路有路线 |
PUT | 单一的旅游路线 | /api/touristRoute/{id} | 单条路有路线或空 |
PATCH | 旅游路线的JsonPatchDocument | /api/touristRoute/{id} | 单条路有路线或空 |
DELETE | - | /api/touristRoute/{id} | |
HEAD | - | /api/touristRoutes | |
/api/touristRoute/{id} | |||
OPTIONS | - | /api/… |
4-4Richardson成熟度模型与HATOAS
只有使用了超媒体的才能算是真正的REST
理查逊成熟度模型(RIchardson Maturity Model)
level0:
只要有个api,通过http传输数据
比如:简单对象访问协议SOAP
level1:资源
面向资源
level2:HTTP动词
使用HTTP的动词
- GET 查看
- POST 创建
- PUT 更新
- PATCH 部分更新
- DELETE 删除
level3:超媒体控制
api的自我发现机制
超媒体=多媒体+超文本
第五章 GET
5-1 HTTP GET获取资源
完善controller的GET
TouristRoutesController中
增加通过ID的GET请求
//api/touristroutes/{touristRouteId}[HttpGet("{touristRouteId}")]public IActionResult GetTouristRouteById(Guid touristRouteId){return Ok(_touristRouteRepository.GetTouristRoute(touristRouteId));}
postman的使用
推荐教程:https://blog.csdn.net/CatStarXcode/article/details/115951827?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164965309816780255273847%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=164965309816780255273847&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-115951827.142v7pc_search_result_control_group,157v4control&utm_term=postman%E5%AE%89%E8%A3%85&spm=1018.2226.3001.4187
5-2 Status Code的重要性
HTTP状态码
- 用户可以知道服务器端是正确处理了请求,还是出现了什么错误
- 一个三位数字的状态码和一个字符串格式状态消息组成
- 数字码便于程序进行处理,而消息字符串更方便程序员理解
级别 | 概述 | 描述 |
---|---|---|
1** | Informational | 信息性状态码,表示接受的请求正在处理 |
2** | Success | 成功状态码,表示请求正常处理完毕 |
3** | Redirection | 重定向状态码,表示需要客户端进行附加操作 |
4** | Client Error | 客户端错误状态码,表示服务器无法处理请求 |
5** | Server Error | 服务器错误状态码,表示服务器处理请求出错 |
级别 | 常见状态码 |
---|---|
1** | - |
2** | 200OK,201Created,204No Content |
3** | 301/302 Moved Permanently,304Not Modified |
4** | 400 Bad Request,401 Unauthorized,403 Forbidden,404 NOT Found |
5** | 500 Internal Server Error,502 Bad Gateway… |
5-3 返回正确的Status Codes
将未找到的状态码改为404
[HttpGet]public IActionResult GetTouristRoutes()//调用私有仓库中函数获取数据{var touristRoutesFromRepo = _touristRouteRepository.GetTouristRoutes();if(touristRoutesFromRepo == null||touristRoutesFromRepo.Count()<=0)return NotFound("没有旅游路线");return Ok(touristRoutesFromRepo);}//api/touristroutes/{touristRouteId}[HttpGet("{touristRouteId}")]public IActionResult GetTouristRouteById(Guid touristRouteId){var touristRouteByIdFromRepo = _touristRouteRepository.GetTouristRoute(touristRouteId);if (touristRouteByIdFromRepo == null) return NotFound("找不到对应的旅游路线");return Ok(touristRouteByIdFromRepo);}
5-4内容协商与数据格式
-
请求头部的媒体类型定义“accept“与”Content-type“
application/JSON、application/xml
遇到无法识别的格式,返回错误代码406 unacceptable
-
ASP.NET Core 支持内容协商,自动化处理
5-5 实现内容协商
在postman中,增加header:{key:Accept;value:application/json}
Get到的内容不变
接下来:
- 加上对不支持 media type 的处理
- 添加对XML的支持
在startup文件中的ConfigureServices函数中修改AddControllers函数
services.AddControllers(setupAction =>{//setupAction.ReturnHttpNotAcceptable = false;//所有api会省略请求的头部,统一回复默认的数据结构setupAction.ReturnHttpNotAcceptable = true;});
这样使得不会统一回复默认的数据结构
当用postman对于value:application/xml进行Get请求时
返回状态码406 Not Acceptable
修改上述函数
使得添加对XML的支持
services.AddControllers(setupAction =>{//setupAction.ReturnHttpNotAcceptable = false;//所有api会省略请求的头部,统一回复默认的数据结构setupAction.ReturnHttpNotAcceptable = true;/*setupAction.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());*///旧方法对的xml的支持}).AddXmlDataContractSerializerFormatters();//新方法对xml支持
5-6 数据模型(model)vs数据传输对象(DTO)
使用数据模型带来了两个不稳定因素
- 直接向前端返回数据模型,会暴露系统的业务核心
- 颗粒度太粗,也就是输出数据无法进行精细调整
DTO 数据传输对象
- Data Transfer Object
- View Mode 视图模型
把该返回的数据返回,增强安全隐私性
-
Model是面向业务
-
DTO是面向界面,面向UI
-
使用DTO的时候可以屏蔽我们不希望暴露的核心业务
-
通过不同dto的组合,又可以调整输出数据的结果,从而解决颗粒度太粗的问题
5-7 分离Model与DTO
建立DTO文件夹
创建TouristRouteDto.cs文件
从对应Model中复制信息
-
对database的描述可以不复制
类似[key] [Required]
-
列表先不复制,5-10会讲
加入员工价格,注释掉原价和折扣
public decimal Price { get; set; }//public decimal OriginalPrice { get; set; }//public double? DiscountPresent { get; set; }//?代表可空
价格的计算,在后面将数据映射的时候再讲解
models与Dto依靠controller连接
在TouristRoutesController.cs中,修改GetTouristRouteByID函数
public IActionResult GetTouristRouteById(Guid touristRouteId){var touristRouteByIdFromRepo = _touristRouteRepository.GetTouristRoute(touristRouteId);if (touristRouteByIdFromRepo == null) return NotFound("找不到对应的旅游路线");var touristRouteDto = new TouristRouteDto(){Id = touristRouteByIdFromRepo.Id,Title=touristRouteByIdFromRepo.Title,Description=touristRouteByIdFromRepo.Description,Price=touristRouteByIdFromRepo.OriginalPrice*(decimal)(touristRouteByIdFromRepo.Rating),CreateTime=touristRouteByIdFromRepo.CreateTime,UpdateTime=touristRouteByIdFromRepo.UpdateTime,Features=touristRouteByIdFromRepo.Features,Fees=touristRouteByIdFromRepo.Fees,Notes=touristRouteByIdFromRepo.Notes,Rating=touristRouteByIdFromRepo.Rating,TravelDays=touristRouteByIdFromRepo.TravelDays.ToString(),Triptype=touristRouteByIdFromRepo.Triptype.ToString(),DepartureCity=touristRouteByIdFromRepo.DepartureCity.ToString(),};return Ok(touristRouteDto);}
5-8使用AutoMapper自动映射数据
NuGet AutoMapper.Extensions.Microsoft.DependencyInjection 7.0
在startup文件ConfigureServices函数中加入
//扫描profile文件services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
新建Profiles文件夹,创建TouristRouteProfile.cs类文件
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
//引用Automapper和model、dto
using AutoMapper;
using FakeXiecheng.API.Models;
using FakeXiecheng.API.DTO;
namespace FakeXiecheng.API.Profiles
{public class TouristRouteProfile:Profile{public TouristRouteProfile(){//<model,dto>CreateMap<TouristRoute, TouristRouteDto>().ForMember(dest => dest.Price,opt => opt.MapFrom(src => src.OriginalPrice * (decimal)(src.DiscountPresent ?? 1))).ForMember(dest => dest.TravelDays,opt => opt.MapFrom(src => src.TravelDays.ToString())).ForMember(dest => dest.Triptype,opt => opt.MapFrom(src => src.Triptype.ToString())).ForMember(dest => dest.DepartureCity,opt => opt.MapFrom(src => src.DepartureCity.ToString()));}}
}
回到TouristRouteController.cs文件
using Automapper
创建私有变量
private readonly IMapper _mapper;
仿照数据仓库,对mapper配置
public TouristRoutesController(ITouristRouteRepository touristRouteRepository,//构建函数,构建数据仓库服务IMapper mapper){_touristRouteRepository = touristRouteRepository;//给私有仓库赋值_mapper = mapper;}
在GetTouristRouteById函数中
注释掉之前的映射,改为一行代码即可
public IActionResult GetTouristRouteById(Guid touristRouteId){var touristRouteByIdFromRepo = _touristRouteRepository.GetTouristRoute(touristRouteId);if (touristRouteByIdFromRepo == null) return NotFound("找不到对应的旅游路线");/*var touristRouteDto = new TouristRouteDto(){Id = touristRouteByIdFromRepo.Id,Title=touristRouteByIdFromRepo.Title,Description=touristRouteByIdFromRepo.Description,Price=touristRouteByIdFromRepo.OriginalPrice*(decimal)(touristRouteByIdFromRepo.Rating),CreateTime=touristRouteByIdFromRepo.CreateTime,UpdateTime=touristRouteByIdFromRepo.UpdateTime,Features=touristRouteByIdFromRepo.Features,Fees=touristRouteByIdFromRepo.Fees,Notes=touristRouteByIdFromRepo.Notes,Rating=touristRouteByIdFromRepo.Rating,TravelDays=touristRouteByIdFromRepo.TravelDays.ToString(),Triptype=touristRouteByIdFromRepo.Triptype.ToString(),DepartureCity=touristRouteByIdFromRepo.DepartureCity.ToString(),};*/var touristRouteDto = _mapper.Map<TouristRouteDto>(touristRouteByIdFromRepo);return Ok(touristRouteDto);}
同时将,GetTouristRoutes函数也进行修改,改为映射dto
public IActionResult GetTouristRoutes()//调用私有仓库中函数获取数据{var touristRoutesFromRepo = _touristRouteRepository.GetTouristRoutes();if(touristRoutesFromRepo == null||touristRoutesFromRepo.Count()<=0)return NotFound("没有旅游路线");var touristRoutesDto = _mapper.Map<IEnumerable<TouristRouteDto>>(touristRoutesFromRepo);return Ok(touristRoutesDto);}
5-9获取嵌套对象关系型数据
建立新的旅游路线图片controller
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using FakeXiecheng.API.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using FakeXiecheng.API.Services;
using FakeXiecheng.API.DTO;
using AutoMapper;
namespace FakeXiecheng.API.controllers
{[ApiController]public class TouristRoutePicturesController:ControllerBase{private ITouristRouteRepository _touristRouteRepository;//定义私有仓库,由于私有,定义时加上下划线private readonly IMapper _mapper;public TouristRoutePicturesController(ITouristRouteRepository touristRouteRepository,//构建函数,构建数据仓库服务IMapper mapper){_touristRouteRepository = touristRouteRepository?? throw new ArgumentNullException(nameof(touristRouteRepository));//给私有仓库赋值_mapper = mapper ??throw new ArgumentNullException(nameof(mapper)); }}
}
建立新的旅游路线图片Dto
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace FakeXiecheng.API.DTO
{public class TouristRoutePictureDto{public int ID { get; set; }public string url { get; set; }public Guid TouristRouteId { get; set; }}
}
建立新的旅游路线图片profile
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
//引用Automapper和model、dto
using AutoMapper;
using FakeXiecheng.API.Models;
using FakeXiecheng.API.DTO;
namespace FakeXiecheng.API.Profiles
{public class TouristRoutePictureProfile:Profile{public TouristRoutePictureProfile(){CreateMap<TouristRoutePicture, TouristRoutePictureDto>();}}
}
补充建立新的旅游路线图片controller
(TouristRoutePicturesController)
是否存在对应的旅游路线
伪代码
[HttpGet]public IActionResult GetpictureListForTouristRoute(Guid touristRouteId){if(路线不存在){return NotFound("旅游路线不存在");}}
开始编写仓库代码(面向数据库)
首先在ITouristRouteRepository中声明:判断旅游路线是否存在的函数
bool TouristRouteExists(Guid touristRouteId);
再在TouristRouteRepository中实现
public bool TouristRouteExists(Guid touristRouteId){return _context.TouristRoutes.Any(t => t.Id == touristRouteId);}
实现TouristRoutePicturesController文件中
获得旅游路线函数(GetPictureListForTouristRoute)
public IActionResult GetPictureListForTouristRoute(Guid touristRouteId){if (!_touristRouteRepository.TouristRouteExists(touristRouteId)){return NotFound("旅游路线不存在");}}
如果存在就返回对应的图片
在仓库ITouristRouteRepository中声明
IEnumerable<TouristRoutePicture> GetPicturesByTouristRouteId(Guid touristRouteId);//根据id返回图片
在仓库TouristRouteRepository中实现
public IEnumerable<TouristRoutePicture> GetPicturesByTouristRouteId(Guid touristRouteId){return _context.touristRoutePictures.Where(p=>p.TouristRouteId == touristRouteId);}//根据id返回图片
在控制器TouristRoutePicturesController中
public IActionResult GetPictureListForTouristRoute(Guid touristRouteId){if (!_touristRouteRepository.TouristRouteExists(touristRouteId)){return NotFound("旅游路线不存在");}var PicturesFromRepo = _touristRouteRepository.GetPicturesByTouristRouteId(touristRouteId);if(PicturesFromRepo == null|| PicturesFromRepo.Count()<=0){return NotFound("照片不存在");}return Ok(_mapper.Map<IEnumerable<TouristRoutePictureDto>>(PicturesFromRepo));}
考虑到以后都不会使用假数据仓库
就把MockTouristRouteREpository全体注释掉
用postman测试
https://localhost:5001/api/touristRoutes/39996f34-013c-4fc6-b1b3-0c1036c47110/pictures
成功
5-10单独获取子资源
在TouristRoutePicturesController.cs文件中
添加寻找单个图片的函数
一般是要先判断父资源是否存在,再去寻找子资源
[HttpGet("{pictureId}")]public IActionResult GetPicture(Guid touristRouteId,int pictureId){//如果父资源不存在if (!_touristRouteRepository.TouristRouteExists(touristRouteId)){return NotFound("旅游路线不存在");}}
下面我们需要给数据仓库添加一个新的方法来获得图片
先在ITouristRouteRepository.cs文件中添加对应声明
TouristRoutePicture GetPicture(int pictureId);//根据图片id返回对应单张照片
在TouristRouteRepository.cs文件中实现上下文仓库传输功能
public TouristRoutePicture GetPicture(int pictureId){return _context.TouristRoutePictures.Where(p => p.Id == pictureId).FirstOrDefault();}
下面回到控制器,完成获取图片的代码
[HttpGet("{pictureId}")]public IActionResult GetPicture(Guid touristRouteId,int pictureId){//如果父资源不存在if (!_touristRouteRepository.TouristRouteExists(touristRouteId)){return NotFound("旅游路线不存在");}var pictureFromRepo=_touristRouteRepository.GetPicture(pictureId);if (pictureFromRepo == null) {return NotFound("相片不存在");}return Ok(_mapper.Map<TouristRoutePictureDto>(pictureFromRepo));//使用Automapper把数据类型从touristpicture转换为touristpicturedto}
5-11完善automapper的嵌套映射
在实际应用的过程中
获得旅游路线的同时,也要获得其中的图片信息
进入TouristRouteRepository.cs文件中(仓库实现文件)
更改GetTouristRoute和GetTouristRoutes文件(获得单条或全部旅游路线函数)
public TouristPicturesRoute GetTouristRoute(Guid touristRouteId){return _context.TouristRoutes.Include(t => t.TouristRoutePictures).FirstOrDefault(n => n.Id == touristRouteId);}//返回Context中的所有旅游路线public IEnumerable<TouristPicturesRoute> GetTouristRoutes(){return _context.TouristRoutes.Include(t => t.TouristRoutePictures);//这里的include就是ef框架中连接两张表的方式//join 另一种连接方式,不通过外键}
在TouristRouteDto.cs文件中,增加对图片的映射
public ICollection<TouristRoutePictureDto> TouristRoutePictures { get; set; }//ICollection是列表属性
注意这里的TouristRoutePictures
一定要与模型中(TouristRoute.cs文件)中对应部分的名字相同
使用postman测试
发现获得旅游路线时,同时也能获取到对应的图片信息
此环节automapper作用:
当dto与model字段相同时
在profile中注册过映射的相应属性
会自动进行automapper映射
5-12使用http的HEAD请求
- HEAD与Get类似,但没有响应主体
- 检测缓存(获得的信息是否有效,最近信息是否有被修改过)
- 探测资源是否存在(存在返回200,不存在返回404)
不过HEAD请求并不能提高api性能
他只是返回原action的头部而已
用来检索响应头部的信息,不用传输响应主体
使用时,只需要在[HttpGet]下面加一个[HttpHead]即可
第六章 深入理解GET请求
6-1 向API传入参数
- 传递api的参数
- 关键词搜索
- 数据过滤
- 封装参数,统一管理
向API传入参数
使用attribute
attribute | 参数来源 |
---|---|
FromQuery | 请求url的参数字符串 |
FromBody | 请求主体数据 |
FromForm | 请求主体的表单数据(IFormFile、IFormFileCollection) |
FromRoute | MVC架构下的Route路由URL的参数 |
FromService | 数据来源于已注入的服务依赖 |
[FromQuery] vs [FromRoute]
[FromQuery] :参数来自地址栏
https://api.fakexiecheng.com/search?pageNumber=1&query=埃及
?pageNumber=1&query=埃及 部分
[FromRoute] :url片段的一部分
https://api.fakexiecheng.com/touristRoutes/594960eb84cd453380655bc9
594960eb84cd453380655bc9 部分
6-2 关键词搜索
在TouristRouteController.cs文件中,添加关键词搜索功能
修改GetTouristRoutes函数
先添加参数string keyword
为了确保这个参数能被action函数接收到
我们在前面加入[FromQuery]
它的路由就是//api/touristRoutes?keyword=传入的参数
调整后函数
//api/touristRoutes?keyword=传入的参数[HttpGet][HttpHead]public IActionResult GetTouristRoutes([FromQuery] string keyword)//调用私有仓库中函数获取数据{var touristRoutesFromRepo = _touristRouteRepository.GetTouristRoutes(keyword);if (touristRoutesFromRepo == null || touristRoutesFromRepo.Count() <= 0){ return NotFound("没有旅游路线"); }var touristRoutesDto = _mapper.Map<IEnumerable<TouristRouteDto>>(touristRoutesFromRepo);return Ok(touristRoutesDto);}
更新数据仓库
ITouristRouteRepository文件中
GetTouristRoutes(string keyword)(给函数加上keyword参数)
TouristRouteRepository文件同理
调整后的GetTouristRoutes函数如下
public IEnumerable<TouristPicturesRoute> GetTouristRoutes(string keyword){IQueryable<TouristPicturesRoute> result= _context.TouristRoutes.Include(t => t.TouristRoutePictures);//这里的include就是ef框架中连接两张表的方式//join 另一种连接方式,不通过外键if (!string.IsNullOrWhiteSpace(keyword))//如果不是空{keyword=keyword.Trim();//去除空格result=result.Where(t=>t.Title.Contains(keyword));//查找标题是否包含关键词}return result.ToList();//Tolist()和FirstOrDefault()语句都是将sql语句转为实际数据//只不过FirstOrDefault()语句是单条数据,Tolist()是列表数据}
6-3 延迟执行 IQueryable
IQueryable的执行逻辑
linq表达式 -> 创建sql语句
|
IQueryable -> 延迟执行
|
聚合操作 -> 执行数据库操作
(.ToList(),.SingleOrDefault(),.Count())
延迟执行的目的
- 为后续动态表达提供可能
比如判空操作,得等判断完了之后,再进行查询等操作
- 减少数据库的执行次数
如果连接、查询这些步分步操作,对性能消耗较大(IO操作资源消耗大)
IQueryable使得这些操作确定后,再最后一块执行,增加效率
EF的最佳工具:linq
6-4 数据过滤
给GET请求加入过滤器
首先是TouristRouteController.cs文件
改变GetTouristRoutes函数
public IActionResult GetTouristRoutes([FromQuery] string keyword,string rating //小于 lessThan 大于largeThan 等于equalTo)//调用私有仓库中函数获取数据 {Regex regex = new Regex(@"([A-Za-z0-9\-]+)(\d+)");//正则表达式string operatorType="";//接受运算符int ratingValue = -1; ;//接收比较数值Match match=regex.Match(rating);//将rating拆分成“运算符”“数值”两个部分if (match.Success){operatorType = match.Groups[1].Value;ratingValue = Int32.Parse(match.Groups[2].Value);}var touristRoutesFromRepo = _touristRouteRepository.GetTouristRoutes(keyword,operatorType,ratingValue);if (touristRoutesFromRepo == null || touristRoutesFromRepo.Count() <= 0){ return NotFound("没有旅游路线"); }var touristRoutesDto = _mapper.Map<IEnumerable<TouristRouteDto>>(touristRoutesFromRepo);return Ok(touristRoutesDto);}
ITouristRouteRepository.cs仓库声明文件
IEnumerable<TouristPicturesRoute> GetTouristRoutes(string keyword,string ratingOperator,int ratingValue);//返回所有旅游路线
加上两个新拆分的运算符与比较值参数
TouristRouteRepository.cs仓库实现文件
public IEnumerable<TouristPicturesRoute> GetTouristRoutes(string keyword, string ratingOperator, int ratingValue){IQueryable<TouristPicturesRoute> result= _context.TouristRoutes.Include(t => t.TouristRoutePictures);//这里的include就是ef框架中连接两张表的方式//join 另一种连接方式,不通过外键if (!string.IsNullOrWhiteSpace(keyword))//如果不是空{keyword=keyword.Trim();//去除空格result=result.Where(t=>t.Title.Contains(keyword));//查找标题是否包含关键词}if(ratingValue>=0)//被比较值必须大于等于0才有意义,不然不进行任何操作{result = ratingOperator switch{"largerThan" => result.Where(t => t.Rating >= ratingValue),//大于"lessThan" => result.Where(t => t.Rating <= ratingValue),//小于_ => result.Where(t => t.Rating == ratingValue),//等于};//函数式写法实现Switch语句}return result.ToList();//Tolist()和FirstOrDefault()语句都是将sql语句转为实际数据//只不过FirstOrDefault()语句是单条数据,Tolist()是列表数据}
用postman测试
https://localhost:5001/api/touristRoutes?keyword=埃及&rating=largerThan2
这是>2的结果,正常只有一条
https://localhost:5001/api/touristRoutes?keyword=埃及&rating=largerThan4
这是>4的结果,正常结果应为“没有旅游路线”
6-5 封装资源过滤器
新创建ResourceParameters文件夹
新建TouristRouteResourceParames.cs文件
public class TouristRouteResourceParameters{//把api中的数据放在这里public string Keyword { get; set; }public string Rating { get; set; }}
将TouristRouteController.cs文件中
GetTouristRoutes参数改成
public IActionResult GetTouristRoutes([FromQuery] TouristRouteResourceParameters parameters)//调用私有仓库中函数获取数据
原来的rating改为parameters.Rating
keyword改为parameters.Keyword
封装与参数处理相关的代码
更新TouristRouteResourceParames.cs文件
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;namespace FakeXiecheng.API.ResourceParameters
{public class TouristRouteResourceParameters{//把api中的数据放在这里public string Keyword { get; set; }//不需要做预处理public string RatingOperator { get; set; }public int? RatingValue { get; set; }//?表示可空变量private string _rating;public string Rating { get { return _rating; }set{if (!string.IsNullOrWhiteSpace(value))//避免正则表达式match“null”产生报错{Regex regex = new Regex(@"([A-Za-z0-9\-]+)(\d+)");//正则表达式Match match = regex.Match(value);//将rating拆分成“运算符”“数值”两个部分if (match.Success){RatingOperator = match.Groups[1].Value;RatingValue = Int32.Parse(match.Groups[2].Value);//运算符和数值都已在上面定义过}}_rating = value;//value负责接受外界数据}}//要进行预处理}
}
之后把TouristRoutesController中的相应报错变量改成
TouristRouteResourceParames.cs文件中定义的变量即可
另外,在ITouristRouteRepository与TouristRouteRepository文件中
也要把ratingValue的变量类型改为int?(非空)
打开postman
测试
https://localhost:5001/api/touristRoutes?keyword=埃及&rating=largerThan2