当前位置: 代码迷 >> 综合 >> .Net Core学习笔记(Alex)
  详细解决方案

.Net Core学习笔记(Alex)

热度:27   发布时间:2023-11-24 22:47:23.0

项目结构

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

  相关解决方案