C# - 基于.NetCore开发的“寸金游”(后端开发)
一、前情提要
模块 or 架构
RESTful成熟度模型
二、【项目启动】ASP.Net Core API 上手指南
.Net Core的前世今生
Asp.Net Core MVC
- MVC架构设计模式。
- 基于.Net的应用框架。
创建实战项目
下载.Net6
CMD中输入dotnet --version
测试.Net是否安装成功
配置VS工作负荷ASP.NET
创建Hello World项目
namespace _00NET___HelloWorld
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
新建项目ASP.NET Core Web应用
运行测试
选择IIS服务器 或者 本地服务器
.NET Core的中间件、请求通道、以及环境设置
请求通道Request Pipeline
中间件Middelware
添加第一个API
注册MVC的框架依赖(Startup.cs)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace _01NET___CJ_ASP_Travel
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
新建Controllers文件夹
建立API控制器类TestApiController.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace _01NET___CJ_ASP_Travel.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TestApiController : ControllerBase
{
// GET: api/<controller>
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<controller>/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/<controller>
[HttpPost]
public void Post([FromBody] string value)
{
}
// PUT api/<controller>/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
访问https://localhost:7249/api/testapi
请求成功
新建ShoudongAPI.cs
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace _01NET___CJ_ASP_Travel.Controllers
{
[Route("api/shoudongapi")]
//[Controller]
//public class ShoudongAPIController
public class ShoudongAPI : Controller
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
}
访问https://localhost:7249/api/shoudongapi
普通的类文件如何识别为控制器类
[Controller]
xxController
xx : Controller
[Route("api/shoudongapi")]
[Controller]
public class ShoudongAPIController
public class
- 任选一种方式即可。
MVC架构
ASP.NET Core MVC框架
什么是MVC
视图View
模型Model
控制器Controller
MVC架构的数据是如何流动?
MVC的优点
- 耦合性低
- 可复用性高
- 可维护性高
三、【数据构建】数据模型与数据库设计
- 模型优先设计数据库结构、搭建数据仓库接口。
- 学习Entity Framework。
- 使用docker启动课程的数据库
- 使用dotnet CLI命令行来创建、初始化、更新数据库。
商城数据模型设计
数据模型设计指南
正确的项目启动思路
业务线(DDD)
根据业务模块建立数据模型
ER关系
逻辑 or 物理概念
数据模型与仓库模式
数据模型的作用
- 映射数据库,对象化数据。
- 获取数据、更新数据、传递数据、保存数据等。
- 处理业务逻辑,视为Business Layer业务层。
- View Model与视图数据绑定。
代码示例
如何从数据库中获取数据呢?
repository仓库模式
Model的文件结构
使用模型和数据仓库的好处
- 业务逻辑与数据模型紧密耦合,减少分层、降低代码数量。
- 完全剥离数据库业务,程序猿可以更专注于实现业务逻辑。
- 面向对象编程,数据转化为对象。
面向对象编程
创建数据模型和数据仓库
新建Models文件夹
创建数据模型
新增TouristRoute.cs类
namespace _01NET___CJ_ASP_Travel.Models
{
public class TouristRoute
{
public Guid Id { get; set; }
public string Title { get; set; }
//路线简介
public string Description { get; set; }
//原价
public decimal OriginalPrice { get; set; }
/* ?可控类型*//*折扣*/
public double? DiscountPresent { get; set; }
//线路发布时间
public DateTime CreateTime { get; set; }
//线路更新时间
public DateTime UpdateTime { get; set; }
//出发时间
public DateTime? DepartureTime { get; set; }
//说明
public string Features { get; set; }
//费用说明
public string Fees { get; set; }
public string Notes { get; set; }
//建立相应的外键关系TouristRoutePicture.cs
public ICollection<TouristRoutePicture> touristRoutePictures { get; set; }
}
}
新增TouristRoutePicture.cs
namespace _01NET___CJ_ASP_Travel.Models
{
public class TouristRoutePicture
{
public int Id { get; set; }
//照片属性
public string Url { get; set; }
//与旅游路线相关的外键关系
public Guid TouristRouteId { get; set; }
//连接关系
public TouristRoute TouristRoute { get; set; }
}
}
建立数据仓库
新建Services文件夹
新建接口(interface)ITouristRouteRepository
using _01NET___CJ_ASP_Travel.Models;
namespace _01NET___CJ_ASP_Travel.Services
{
public interface ITouristRouteRepository
{
//返回一组旅游路线
IEnumerable<TouristRoute> GetTouristRoutes();
//返回单独的旅游路线
TouristRoute GetTouristRoute(Guid touristRouteId);
}
}
新建类 (Mock假数据)MockTouristRouteRepository
using _01NET___CJ_ASP_Travel.Models;
namespace _01NET___CJ_ASP_Travel.Services
{
public class MockTouristRouteRepository : ITouristRouteRepository
{
private List<TouristRoute> _routes;
public MockTouristRouteRepository()
{
if (_routes == null)
{
InitializeTouristRoutes();
}
}
private void InitializeTouristRoutes()
{
_routes = new List<TouristRoute>
{
new TouristRoute {
Id = Guid.NewGuid(),
Title = "黄山",
Description="黄山真好玩",
OriginalPrice = 1299,
Features = "<p>吃住行游购娱</p>",
Fees = "<p>交通费用自理</p>",
Notes="<p>小心危险</p>"
},
new TouristRoute {
Id = Guid.NewGuid(),
Title = "华山",
Description="华山真好玩",
OriginalPrice = 1299,
Features = "<p>吃住行游购娱</p>",
Fees = "<p>交通费用自理</p>",
Notes="<p>小心危险</p>"
}
};
}
public TouristRoute GetTouristRoute(Guid touristRouteId)
{
//linq语法
return _routes.FirstOrDefault(n => n.Id == touristRouteId);
}
public IEnumerable<TouristRoute> GetTouristRoutes()
{
return _routes;
}
}
}
Startup.cs
注册数据仓库的服务依赖
services.AddSingleton
services.AddScoped
services.AddTransient
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//注册数据仓库的服务依赖
//以下三个Add都可以用于依赖注入
//services.AddSingleton
//services.AddScoped
services.AddTransient<ITouristRouteRepository, MockTouristRouteRepository>();
}
通过Api展示数据
Controllers - TouristRoutesController.cs
API控制器
using _01NET___CJ_ASP_Travel.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace _01NET___CJ_ASP_Travel.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TouristRoutesController : ControllerBase
{
private ITouristRouteRepository _touristRouteRepository;
public TouristRoutesController(ITouristRouteRepository touristRouteRepository)
{
_touristRouteRepository = touristRouteRepository;
}
public IActionResult GetTouristRoutes()
{
var route =_touristRouteRepository.GetTouristRoutes();
return Ok(route);
}
}
}
测试
输入https://localhost:7249/api/TouristRoutes
什么是Entity Framework
Entity Framework Core
EF的组件
添加Entity Framework Core
新建Database文件夹
数据库映射工具AppDbContext.cs
using _01NET___CJ_ASP_Travel.Models;
using Microsoft.EntityFrameworkCore;
namespace _01NET___CJ_ASP_Travel.Database
{
public class AppDbContext:DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<TouristRoute> TouristRoutes { get; set; }
public DbSet<TouristRoutePicture> TouristRoutePictures { get; set; }
}
}
Startup.cs
public class Startup
{
private IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//注册数据仓库的服务依赖
//以下三个Add都可以用于依赖注入
//services.AddSingleton
//services.AddScoped
services.AddTransient<ITouristRouteRepository, TouristRouteRepository>();
/* 数据库映射*/
services.AddDbContext<AppDbContext>
(options => {
options.UseSqlServer(Configuration["DbContext:ConnectionString"]);
});
}
//--------------------------------------
}
安装包Microsoft.EntityFrameworkCore.SqlServer
在appsettings.json中配置数据库连接字符串
ConnectionString
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"DbContext": {
"ConnectionString": "Data Source=DESKTOP-V6GQHHS\\SQLSERVER2012;Initial Catalog=master;Integrated Security=True"
}
}
Services - TouristRouteRepository.cs
using _01NET___CJ_ASP_Travel.Database;
using _01NET___CJ_ASP_Travel.Models;
namespace _01NET___CJ_ASP_Travel.Services
{
public class TouristRouteRepository : ITouristRouteRepository
{
private readonly AppDbContext _context;
public TouristRouteRepository(AppDbContext appDbContext)
{
_context = appDbContext;
}
public TouristRoute GetTouristRoute(Guid touristRouteId)
{
return _context.TouristRoutes.FirstOrDefault(n => n.Id == touristRouteId);
}
public IEnumerable<TouristRoute> GetTouristRoutes()
{
return _context.TouristRoutes;
}
}
}
创建数据库
使用EntityFramework创建数据库
验证数据模型
添加数据库限制,主键信息,以及外键联系信息等等。
TouristRoute.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using System.ComponentModel.DataAnnotations.Schema;
namespace _01NET___CJ_ASP_Travel.Models
{
public class TouristRoute
{
[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; }
//建立相应的外键关系TouristRoutePicture.cs
public ICollection<TouristRoutePicture> TouristRoutePictures { get; set; }
= new List<TouristRoutePicture>();
}
}
TouristRoutePicture.cs
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace _01NET___CJ_ASP_Travel.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; }
}
}
数据库创建工具:Entity Framework Core Tools
Package Manage Console (PMC)
- 如果报错,请降低包版本。
add-migration initialMigration
update-database
数据库创建成功
添加初始化数据
AppDbContext.cs - 重写OnModelCreating()
using System.Reflection;
using _01NET___CJ_ASP_Travel.Models;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
namespace _01NET___CJ_ASP_Travel.Database
{
public class AppDbContext:DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<TouristRoute> TouristRoutes { get; set; }
public DbSet<TouristRoutePicture> TouristRoutePictures { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var touristRouteJsonData = File.ReadAllText(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"/Database/touristRoutesMockData.json");
IList<TouristRoute> touristRoutes = JsonConvert.DeserializeObject<IList<TouristRoute>>(touristRouteJsonData);
modelBuilder.Entity<TouristRoute>().HasData(touristRoutes);
var touristRoutePictureJsonData = File.ReadAllText(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"/Database/touristRoutePicturesMockData.json");
IList<TouristRoutePicture> touristRoutePictures = JsonConvert.DeserializeObject<IList<TouristRoutePicture>>(touristRoutePictureJsonData);
modelBuilder.Entity<TouristRoutePicture>().HasData(touristRoutePictures);
base.OnModelCreating(modelBuilder);
}
}
}
更新数据请用Package Manage Console (PMC)
- 如果报错,请降低包版本。
add-migration initialMigration
update-database
测试
更新数据库
新增代码TouristRoute.cs
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using System.ComponentModel.DataAnnotations.Schema;
namespace _01NET___CJ_ASP_Travel.Models
{
public class TouristRoute
{
//--------------------------省略
//评分
public double? Rating { get; set; }
//旅行天数
public TravelDays TravelDays { get; set; }
//旅游类型
public TripType? TripType { get; set; }
//出发地
public DepartureCity? DepartureCity { get; set; }
}
}
Models - 新建TravelDays.cs
namespace _01NET___CJ_ASP_Travel.Models
{
public enum TravelDays
{
One,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
EightPlus
}
}
新建TripType.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace _01NET___CJ_ASP_Travel.Models
{
public enum TripType
{
HotelAndAttractions, //酒店+景点
Group, //跟团游
PrivateGroup, //私家团
BackPackTour, //自由行
SemiBackPackTour//半自助游
}
}
新建DepartureCity.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace _01NET___CJ_ASP_Travel.Models
{
public enum DepartureCity
{
Beijing, //北京
Shanghai, //上海
Canton, //广州
Shenzhen, //深圳
}
}
更新数据库
四、【API成熟度】通往真正REST的之路
RESTful的基础知识
RESTful的基本特点
RESTful的6个约束与最佳实践
6个约束限制
总结常用的HTTP请求方法
Richardson四个级别的成熟度模型 与 HATOAS
Richardson四个级别的成熟度模型
LEVEL 0
LEVEL 1 - 资源
LEVEL 2 - HTTP动词
LEVEL 3 - 超媒体控制
RESTfulAPI范例
五、【获取产品】RESTful面向资源入门
目标
Http Get 获取资源
修改 TouristRoutesController - GetTouristRouteById
// api/touristroutes/{touristRouteId}
[HttpGet("{touristRouteId}")]
public IActionResult GetTouristRouteById(Guid touristRouteId)
{
return Ok(_touristRouteRepository.GetTouristRoute(touristRouteId));
}
使用postman测试api
https://localhost:7249/api/TouristRoutes/39996f34-013c-4fc6-b1b3-0c1036c47110
Status Code 的重要性
HTTP状态码
常见的HTTP状态码
返回正确的 Status Codes
修改 TouristRoutesController - GetTouristRouteById 和 GetTouristRoutes
[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 touristRouteFromRepo = _touristRouteRepository.GetTouristRoute(touristRouteId);
if (touristRouteFromRepo == null)
{
return NotFound($"旅游路线{touristRouteId}找不到");
}
return Ok(touristRouteFromRepo);
}
测试404
内容协商与数据格式
内容协商
- 允许客户端和服务器通过协商来决定相互之间的数据传输格式、语言等内容。
内容协商与数据格式
实现内容协商
Startup.cs - services.AddControllers
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(setupAction =>
{
setupAction.ReturnHttpNotAcceptable = true;
}).AddXmlDataContractSerializerFormatters();
//注册数据仓库的服务依赖
//-------------------------省略
}
测试
header中xml的请求
反之,显示json结构
数据模型(Model) vs. 数据传输对象(DTO)
使用数据模型带来了两个不稳定因素
- 直接向前端返回数据模型,会暴露系统的业务核心。
- 颗粒度太粗,也就是输出数据无法精细调整。
数据传输对象(DTO)
数据模型(Model) vs. 数据传输对象(DTO)
- Model面向业务。
- DTO则是面向界面、面向UI。
- 使用DTO的时候可以屏蔽我们不希望暴露的核心业务。
- 通过不同dto的组合,又可以调整输出数据的结果,从而解决颗粒度太粗的问题。
分离Model与DTO
新建Dtos文件夹 - TouristRouteDto.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace _01NET___CJ_ASP_Travel.Dtos
{
public class TouristRouteDto
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
// 计算方式:原价 * 折扣
public decimal Price { get; set; }
//public decimal OriginalPrice { get; set; }
//public double? DiscountPresent { get; set; }
public DateTime CreateTime { get; set; }
public DateTime? UpdateTime { get; set; }
public DateTime? DepartureTime { get; set; }
public string Features { get; set; }
public string Fees { get; set; }
public string Notes { get; set; }
public double? Rating { get; set; }
public string TravelDays { get; set; }
public string TripType { get; set; }
public string DepartureCity { get; set; }
}
}
在TouristRoutesController.cs中修改GetTouristRouteById方法
// api/touristroutes/{touristRouteId}
[HttpGet("{touristRouteId}")]
public IActionResult GetTouristRouteById(Guid touristRouteId)
{
var touristRouteFromRepo = _touristRouteRepository.GetTouristRoute(touristRouteId);
if (touristRouteFromRepo == null)
{
return NotFound($"旅游路线{touristRouteId}找不到");
}
var touristRouteDto = new TouristRouteDto()
{
Id = touristRouteFromRepo.Id,
Title = touristRouteFromRepo.Title,
Description = touristRouteFromRepo.Description,
Price = touristRouteFromRepo.OriginalPrice * (decimal)(touristRouteFromRepo.DiscountPresent ?? 1),
CreateTime = touristRouteFromRepo.CreateTime,
UpdateTime = touristRouteFromRepo.UpdateTime,
Features = touristRouteFromRepo.Features,
Fees = touristRouteFromRepo.Fees,
Notes = touristRouteFromRepo.Notes,
Rating = touristRouteFromRepo.Rating,
TravelDays = touristRouteFromRepo.TravelDays.ToString(),
TripType = touristRouteFromRepo.TripType.ToString(),
DepartureCity = touristRouteFromRepo.DepartureCity.ToString()
};
return Ok(touristRouteFromRepo);
}
使用 AutoMapper 自动映射数据
注册到IOC容器中 - Startup.cs - ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
//------------------------省略
// 扫描profile文件
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
}
新建Profiles文件夹 - TouristRouteProfile.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
namespace _01NET___CJ_ASP_Travel.Profiles
{
public class TouristRouteProfile : Profile
{
public TouristRouteProfile()
{
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())
);
}
}
}
修改GetTouristRouteById方法 和 GetTouristRoutes方法(TouristRoutesController.cs)
var touristRoutesDto
private readonly IMapper _mapper;
[HttpGet]
public IActionResult GetTouristRoutes()
{
var touristRoutesFromRepo = _touristRouteRepository.GetTouristRoutes();
if (touristRoutesFromRepo == null || touristRoutesFromRepo.Count() <= 0)
{
return NotFound("没有旅游路线");
}
var touristRoutesDto = _mapper.Map<IEnumerable<TouristRouteDto>>(touristRoutesFromRepo);
return Ok(touristRoutesFromRepo);
}
// api/touristroutes/{touristRouteId}
[HttpGet("{touristRouteId}")]
public IActionResult GetTouristRouteById(Guid touristRouteId)
{
var touristRouteFromRepo = _touristRouteRepository.GetTouristRoute(touristRouteId);
if (touristRouteFromRepo == null)
{
return NotFound($"旅游路线{touristRouteId}找不到");
}
var touristRouteDto = _mapper.Map<TouristRouteDto>(touristRouteFromRepo);
return Ok(touristRouteFromRepo);
}
获取嵌套对象关系型数据
Controllers文件夹 - TouristRoutePicturesController.cs
using _01NET___CJ_ASP_Travel.Dtos;
using _01NET___CJ_ASP_Travel.Services;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
namespace _01NET___CJ_ASP_Travel.Controllers
{
[Route("api/touristRoutes/{touristRouteId}/pictures")]
[ApiController]
public class TouristRoutePicturesController : ControllerBase
{
private ITouristRouteRepository _touristRouteRepository;
private IMapper _mapper;
public TouristRoutePicturesController(
ITouristRouteRepository touristRouteRepository,
IMapper mapper
)
{
_touristRouteRepository = touristRouteRepository ??
throw new ArgumentNullException(nameof(touristRouteRepository));
_mapper = mapper ??
throw new ArgumentNullException(nameof(mapper));
}
[HttpGet]
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));
}
}
}
Dtos文件夹 - TouristRoutePictureDto.cs
namespace _01NET___CJ_ASP_Travel.Dtos
{
public class TouristRoutePictureDto
{
public int Id { get; set; }
public string Url { get; set; }
public Guid TouristRouteId { get; set; }
}
}
Profiles文件夹 - TouristRoutePictureProfile.cs
using _01NET___CJ_ASP_Travel.Dtos;
using _01NET___CJ_ASP_Travel.Models;
using AutoMapper;
namespace _01NET___CJ_ASP_Travel.Profiles
{
public class TouristRoutePictureProfile : Profile
{
public TouristRoutePictureProfile()
{
CreateMap<TouristRoutePicture, TouristRoutePictureDto>();
}
}
}
Services - ITouristRouteRepository.cs - 添加新的接口函数
bool TouristRouteExists(Guid touristRouteId);
IEnumerable<TouristRoutePicture> GetPicturesByTouristRouteId(Guid touristRouteId);
实现接口函数 - TouristRouteRepository.cs
public bool TouristRouteExists(Guid touristRouteId)
{
return _context.TouristRoutes.Any(t => t.Id == touristRouteId);
}
public IEnumerable<TouristRoutePicture> GetPicturesByTouristRouteId(Guid touristRouteId)
{
return _context.TouristRoutePictures
.Where(p => p.TouristRouteId == touristRouteId).ToList();
}
注释假数据代码文件(Mock)
测试
https://localhost:7249/api/TouristRoutes/39996f34-013c-4fc6-b1b3-0c1036c47110/pictures
单独获取子资源
TouristRoutePicturesController.cs
[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));
}
TouristRouteRepository.cs
public TouristRoutePicture GetPicture(int pictureId)
{
return _context.TouristRoutePictures.Where(p => p.Id == pictureId).FirstOrDefault();
}
ITouristRouteRepository.cs
TouristRoutePicture GetPicture(int pictureId);
测试
完善automapper的嵌套映射
(TouristRouteRepository.cs)修改GetTouristRoute
public TouristRoute GetTouristRoute(Guid touristRouteId)
{
return _context.TouristRoutes.Include(t => t.TouristRoutePictures).FirstOrDefault(n => n.Id == touristRouteId);
}
public IEnumerable<TouristRoute> GetTouristRoutes()
{
// include vs join
return _context.TouristRoutes.Include(t => t.TouristRoutePictures);
}
TouristRouteDto.cs
public ICollection<TouristRoutePictureDto> TouristRoutePictures { get; set; }
使用http的HEAD请求
HTTP HEAD
- HEAD与GET类似,但没有响应主体。
- 检测缓存。
- 探测资源是否存在。
TouristRoutesController.cs
- 直接加上
[HttpHead]
using _01NET___CJ_ASP_Travel.Dtos;
using _01NET___CJ_ASP_Travel.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using AutoMapper;
namespace _01NET___CJ_ASP_Travel.Controllers
{
[Route("api/[controller]")] // api/touristroute
[ApiController]
public class TouristRoutesController : ControllerBase
{
private ITouristRouteRepository _touristRouteRepository;
private readonly IMapper _mapper;
/* public TouristRoutesController(ITouristRouteRepository touristRouteRepository)
{
_touristRouteRepository = touristRouteRepository;
}*/
public TouristRoutesController(
ITouristRouteRepository touristRouteRepository,
IMapper mapper
)
{
_touristRouteRepository = touristRouteRepository;
_mapper = mapper;
}
[HttpGet]
[HttpHead]
public IActionResult GerTouristRoutes()
{
var touristRoutesFromRepo = _touristRouteRepository.GetTouristRoutes();
if (touristRoutesFromRepo == null || touristRoutesFromRepo.Count() <= 0)
{
return NotFound("没有旅游路线");
}
var touristRoutesDto = _mapper.Map<IEnumerable<TouristRouteDto>>(touristRoutesFromRepo);
return Ok(touristRoutesDto);
}
// api/touristroutes/{touristRouteId}
[HttpGet("{touristRouteId}")]
[HttpHead]
public IActionResult GetTouristRouteById(Guid touristRouteId)
{
var touristRouteFromRepo = _touristRouteRepository.GetTouristRoute(touristRouteId);
if (touristRouteFromRepo == null)
{
return NotFound($"旅游路线{touristRouteId}找不到");
}
var touristRouteDto = _mapper.Map<TouristRouteDto>(touristRouteFromRepo);
return Ok(touristRouteFromRepo);
}
}
}
六、【复合搜索】深入理解GET请求
向 API 传入参数
使用Attribute:[FromXXX]
[FromQuery] vs [Frompath]
关键词搜索
TouristRoutesController.cs - GerTouristRoutes方法添加参数
[FromQuery] string keyword
// api/touristRoutes?keyword=传入的参数
[HttpGet]
[HttpHead]
public IActionResult GerTouristRoutes([FromQuery] string keyword) // FromQuery vs FromBody
{
var touristRoutesFromRepo = _touristRouteRepository.GetTouristRoutes(keyword);
if (touristRoutesFromRepo == null || touristRoutesFromRepo.Count() <= 0)
{
return NotFound("没有旅游路线");
}
var touristRoutesDto = _mapper.Map<IEnumerable<TouristRouteDto>>(touristRoutesFromRepo);
return Ok(touristRoutesDto);
}
更新数据仓库ITouristRouteRepository.cs
GetTouristRoutes接口加上参数
//返回一组旅游路线
IEnumerable<TouristRoute> GetTouristRoutes(string keyword);
更新这个接口实现TouristRouteRepository.cs
GetTouristRoutes
使用IQueryable
public IEnumerable<TouristRoute> GetTouristRoutes(string keyword)
{
IQueryable<TouristRoute> result = _context.TouristRoutes
.Include(t => t.TouristRoutePictures);
if (!string.IsNullOrWhiteSpace(keyword))
{
keyword= keyword.Trim();
result = result.Where(t => t.Title.Contains(keyword));
}
// include vs join
return result.ToList();
}
测试
https://localhost:7249/api/TouristRoutes?keyword=埃及
延迟执行 IQueryable
IQueryable的执行逻辑
延迟执行的目的
- 为后续动态表达提供可能
- 减少数据库的执行次数
Entity Framework的最佳工具: linq
- Linq to sql
- Linq to object
- Linq to entity
数据过滤
TouristRoutesController.cs - GerTouristRoutes
string rating
// api/touristRoutes?keyword=传入的参数
[HttpGet]
[HttpHead]
public IActionResult GerTouristRoutes(
[FromQuery] string keyword,
string rating // 小于lessThan, 大于largerThan, 等于equalTo lessThan3, largerThan2, equalTo5
) // FromQuery vs FromBody
{
Regex regex = new Regex(@"([A-Za-z0-9\-]+)(\d+)");
string operatorType = "";
int raringVlaue = -1;
Match match = regex.Match(rating);
if (match.Success)
{
operatorType = match.Groups[1].Value;
raringVlaue = Int32.Parse(match.Groups[2].Value);
}
var touristRoutesFromRepo = _touristRouteRepository.GetTouristRoutes(keyword, operatorType, raringVlaue);
if (touristRoutesFromRepo == null || touristRoutesFromRepo.Count() <= 0)
{
return NotFound("没有旅游路线");
}
var touristRoutesDto = _mapper.Map<IEnumerable<TouristRouteDto>>(touristRoutesFromRepo);
return Ok(touristRoutesDto);
}
数据仓库 - 接口文件ITouristRouteRepository.cs - 添加两个参数
string ratingOperator, int ratingValue
//返回一组旅游路线
IEnumerable<TouristRoute> GetTouristRoutes(string keyword, string ratingOperator, int ratingValue);
更新代码实现 - TouristRouteRepository.cs - GetTouristRoutes
string ratingOperator, int ratingValue
if (ratingValue >= 0)
public IEnumerable<TouristRoute> GetTouristRoutes(string keyword, string ratingOperator, int ratingValue)
{
IQueryable<TouristRoute> result = _context.TouristRoutes
.Include(t => t.TouristRoutePictures);
if (!string.IsNullOrWhiteSpace(keyword))
{
keyword= keyword.Trim();
result = result.Where(t => t.Title.Contains(keyword));
}
if (ratingValue >= 0)
{
result = ratingOperator switch
{
"largerThan" => result.Where(t => t.Rating >= ratingValue),
"lessThan" => result.Where(t => t.Rating <= ratingValue),
_ => result.Where(t => t.Rating == ratingValue),
};
}
// include vs join
return result.ToList();
}
测试
https://localhost:7249/api/TouristRoutes?keyword=埃及&rating=largerThan2
https://localhost:7249/api/TouristRoutes?keyword=埃及&rating=largerThan4
封装资源过滤器
新建ResourceParameters文件夹 - TouristRouteResourceParamaters.cs
using System.Text.RegularExpressions;
namespace _01NET___CJ_ASP_Travel.ResourceParameters
{
public class TouristRouteResourceParamaters
{
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))
{
Regex regex = new Regex(@"([A-Za-z0-9\-]+)(\d+)");
Match match = regex.Match(value);
if (match.Success)
{
RatingOperator = match.Groups[1].Value;
RatingValue = Int32.Parse(match.Groups[2].Value);
}
}
_rating = value;
}
}
}
}
TouristRoutesController - GerTouristRoutes
// api/touristRoutes?keyword=传入的参数
[HttpGet]
[HttpHead]
public IActionResult GerTouristRoutes(
[FromQuery] TouristRouteResourceParamaters paramaters
/*[FromQuery] string keyword,
string rating // 小于lessThan, 大于largerThan, 等于equalTo lessThan3, largerThan2, equalTo5 */
) // FromQuery vs FromBody
{
var touristRoutesFromRepo = _touristRouteRepository.GetTouristRoutes(paramaters.Keyword, paramaters.RatingOperator, paramaters.RatingValue);
if (touristRoutesFromRepo == null || touristRoutesFromRepo.Count() <= 0)
{
return NotFound("没有旅游路线");
}
var touristRoutesDto = _mapper.Map<IEnumerable<TouristRouteDto>>(touristRoutesFromRepo);
return Ok(touristRoutesDto);
}
修改数据仓库GetTouristRoutes的传入参数
int? ratingValue
- TouristRouteRepository同理
//返回一组旅游路线
IEnumerable<TouristRoute> GetTouristRoutes(string keyword, string ratingOperator, int? ratingValue);
测试
https://localhost:7249/api/TouristRoutes?keyword=埃及&rating=largerThan2
七、【新建产品】POST 请求全面剖析
HTTP方法的安全性和幂等性
安全性
幂等性
HTTP方法的安全性和幂等性
创建旅游路线资源
TouristRoutesController.cs
// api/touristroutes/{touristRouteId}
[HttpGet("{touristRouteId}", Name = "GetTouristRouteById")]
/* [HttpHead]*/
public IActionResult GetTouristRouteById(Guid touristRouteId)
{
var touristRouteFromRepo = _touristRouteRepository.GetTouristRoute(touristRouteId);
if (touristRouteFromRepo == null)
{
return NotFound($"旅游路线{touristRouteId}找不到");
}
var touristRouteDto = _mapper.Map<TouristRouteDto>(touristRouteFromRepo);
return Ok(touristRouteFromRepo);
}
[HttpPost]
public IActionResult CreateTouristRoute([FromBody] TouristRouteForCreationDto touristRouteForCreationDto)
{
var touristRouteModel = _mapper.Map<TouristRoute>(touristRouteForCreationDto);
_touristRouteRepository.AddTouristRoute(touristRouteModel);
_touristRouteRepository.Save();
var touristRouteToReture = _mapper.Map<TouristRouteDto>(touristRouteModel);
return CreatedAtRoute(
"GetTouristRouteById",
new { touristRouteId = touristRouteToReture.Id },
touristRouteToReture
);
}
创建新的DTO用于反序列化数据 - TouristRouteForCreationDto.cs
namespace _01NET___CJ_ASP_Travel.Dtos
{
public class TouristRouteForCreationDto
{
public string Title { get; set; }
public string Description { get; set; }
// 计算方式:原价 * 折扣
public decimal Price { get; set; }
//public decimal OriginalPrice { get; set; }
//public double? DiscountPresent { get; set; }
public DateTime CreateTime { get; set; }
public DateTime? UpdateTime { get; set; }
public DateTime? DepartureTime { get; set; }
public string Features { get; set; }
public string Fees { get; set; }
public string Notes { get; set; }
public double? Rating { get; set; }
public string TravelDays { get; set; }
public string TripType { get; set; }
public string DepartureCity { get; set; }
}
}
TouristRouteProfile.cs
CreateMap<TouristRouteForCreationDto, TouristRoute>()
.ForMember(
dest => dest.Id,
opt => opt.MapFrom(src => Guid.NewGuid())
);
新增接口函数(ITouristRouteRepository)
void AddTouristRoute(TouristRoute touristRoute);
bool Save();
TouristRouteRepository
public void AddTouristRoute(TouristRoute touristRoute)
{
if (touristRoute == null)
{
throw new ArgumentNullException(nameof(touristRoute));
}
_context.TouristRoutes.Add(touristRoute);
//_context.SaveChanges();
}
public bool Save()
{
return (_context.SaveChanges() >= 0);
}
测试
{
"title": "哈哈哈哈",
"description": "哈哈哈哈",
"originalPrice": 15490.00,
"discountPercent": "0.9",
"coupons": null,
"points": 220
}
https://localhost:44381/api/TouristRoutes/0285b54e-8280-4e15-b62a-38c0738734df
即为他的请求
创建子资源:旅游路线图片
TouristRoutePicturesController.cs
[HttpPost]
public IActionResult CreateTouristRoutePicture(
[FromRoute] Guid touristRouteId,
[FromBody] TouristRoutePictureForCreationDto touristRoutePictureForCreationDto
)
{
if (!_touristRouteRepository.TouristRouteExists(touristRouteId))
{
return NotFound("旅游线路不存在");
}
var pictureModel = _mapper.Map<TouristRoutePicture>(touristRoutePictureForCreationDto);
_touristRouteRepository.AddTouristRoutePicture(touristRouteId, pictureModel);
_touristRouteRepository.Save();
var pictureToReturn = _mapper.Map<TouristRoutePictureDto>(pictureModel);
return CreatedAtRoute(
"GetPicture",
new
{
touristRouteId = pictureModel.TouristRouteId,
pictureId = pictureModel.Id
},
pictureToReturn
);
}
新建DTO - TouristRoutePictureForCreationDto.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace _02NET___CJ_ASP_Travel.Dtos
{
public class TouristRoutePictureForCreationDto
{
public string Url { get; set; }
}
}
TouristRoutePictureProfile.cs
namespace _02NET___CJ_ASP_Travel.Profiles
{
public class TouristRoutePictureProfile: Profile
{
public TouristRoutePictureProfile()
{
CreateMap<TouristRoutePicture, TouristRoutePictureDto>();
CreateMap<TouristRoutePictureForCreationDto, TouristRoutePicture>();
}
}
}
ITouristRouteRepository.cs
void AddTouristRoutePicture(Guid touristRouteId, TouristRoutePicture touristRoutePicture);
ITouristRouteRepository.cs
public void AddTouristRoutePicture(Guid touristRouteId, TouristRoutePicture touristRoutePicture)
{
if (touristRouteId == Guid.Empty)
{
throw new ArgumentNullException(nameof(touristRouteId));
}
if (touristRoutePicture == null)
{
throw new ArgumentNullException(nameof(touristRoutePicture));
}
touristRoutePicture.TouristRouteId = touristRouteId;
_context.TouristRoutePictures.Add(touristRoutePicture);
}
同时创建父子资源
TouristRouteForCreationDto.cs
public ICollection<TouristRoutePictureForCreationDto> TouristRoutePictures { get; set; }
= new List<TouristRoutePictureForCreationDto>();
ASP.NET Core 的数据验证
- 制定一个合理的数据检验规则。
- 检测数据。
- 提交错误信息。
制定数据检验规则
检测数据
提交错误信息
添加数据验证
TouristRouteForCreationDto.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace _02NET___CJ_ASP_Travel.Dtos
{
public class TouristRouteForCreationDto
{
[Required(ErrorMessage = "title 不可为空")]
[MaxLength(100)]
public string Title { get; set; }
[Required]
[MaxLength(1500)]
public string Description { get; set; }
// 计算方式:原价 * 折扣
public decimal Price { get; set; }
//public decimal OriginalPrice { get; set; }
//public double? DiscountPresent { get; set; }
public DateTime CreateTime { get; set; }
public DateTime? UpdateTime { get; set; }
public DateTime? DepartureTime { get; set; }
public string Features { get; set; }
public string Fees { get; set; }
public string Notes { get; set; }
public double? Rating { get; set; }
public string TravelDays { get; set; }
public string TripType { get; set; }
public string DepartureCity { get; set; }
public ICollection<TouristRoutePictureForCreationDto> TouristRoutePictures { get; set; }
= new List<TouristRoutePictureForCreationDto>();
public IEnumerable<ValidationResult> Validate(
ValidationContext validationContext)
{
if (Title == Description)
{
yield return new ValidationResult(
"路线名称必须与路线描述不同",
new[] { "TouristRouteForCreationDto" }
);
}
}
}
}
属性级别数据验证
(TouristRouteForCreationDto.cs)使用接口
public class TouristRouteForCreationDto : IValidatableObject
类级别数据验证
新建ValidationAttributes文件夹 - TouristRouteTitleMustBeDifferentFromDescriptionAttribute.cs
using _02NET___CJ_ASP_Travel.Dtos;
using System.ComponentModel.DataAnnotations;
namespace _02NET___CJ_ASP_Travel.ValidationAttributes
{
public class TouristRouteTitleMustBeDifferentFromDescriptionAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(
object value,
ValidationContext validationContext
)
{
var touristRouteDto = (TouristRouteForCreationDto)validationContext.ObjectInstance;
if (touristRouteDto.Title == touristRouteDto.Description)
{
return new ValidationResult(
"路线名称必须与路线描述不同",
new[] { "TouristRouteForCreationDto" }
);
}
return ValidationResult.Success;
}
}
}
TouristRouteForCreationDto
[TouristRouteTitleMustBeDifferentFromDescriptionAttribute]
输出状态码 422
Startup.cs
.ConfigureApiBehaviorOptions
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(setupAction => {
setupAction.ReturnHttpNotAcceptable = true;
//setupAction.OutputFormatters.Add(
// new XmlDataContractSerializerOutputFormatter()
//);
}).AddXmlDataContractSerializerFormatters()
.ConfigureApiBehaviorOptions(setupAction =>
{
setupAction.InvalidModelStateResponseFactory = context =>
{
var problemDetail = new ValidationProblemDetails(context.ModelState)
{
Type = "无所谓",
Title = "数据验证失败",
Status = StatusCodes.Status422UnprocessableEntity,
Detail = "请看详细说明",
Instance = context.HttpContext.Request.Path
};
problemDetail.Extensions.Add("traceId", context.HttpContext.TraceIdentifier);
return new UnprocessableEntityObjectResult(problemDetail)
{
ContentTypes = { "application/problem+json" }
};
};
});
//省略------------------------
}
八、【更新产品】PUT vs POST
PUT vs. PATCH
数据更新
PUT ?or? PATCH
使用put请求更新资源
TouristRoutesController.cs
[HttpPut("{touristRouteId}")]
public IActionResult UpdateTouristRoute(
[FromRoute]Guid touristRouteId,
[FromBody] TouristRouteForUpdateDto touristRouteForUpdateDto
)
{
if (!_touristRouteRepository.TouristRouteExists(touristRouteId))
{
return NotFound("旅游路线找不到");
}
var touristRouteFromRepo = _touristRouteRepository.GetTouristRoute(touristRouteId);
// 1. 映射dto
// 2. 更新dto
// 3. 映射model
_mapper.Map(touristRouteForUpdateDto, touristRouteFromRepo);
_touristRouteRepository.Save();
return NoContent();
}
Dto - TouristRouteForUpdateDto.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using _02NET___CJ_ASP_Travel.Dtos;
namespace _02NET___CJ_ASP_Travel.Dtos
{
public class TouristRouteForUpdateDto
{
[Required(ErrorMessage = "title 不可为空")]
[MaxLength(100)]
public string Title { get; set; }
[Required]
[MaxLength(1500)]
public string Description { get; set; }
// 计算方式:原价 * 折扣
public decimal Price { get; set; }
//public decimal OriginalPrice { get; set; }
//public double? DiscountPresent { get; set; }
public DateTime CreateTime { get; set; }
public DateTime? UpdateTime { get; set; }
public DateTime? DepartureTime { get; set; }
public string Features { get; set; }
public string Fees { get; set; }
public string Notes { get; set; }
public double? Rating { get; set; }
public string TravelDays { get; set; }
public string TripType { get; set; }
public string DepartureCity { get; set; }
public ICollection<TouristRoutePictureForCreationDto> TouristRoutePictures { get; set; }
= new List<TouristRoutePictureForCreationDto>();
}
}
PUT请求的数据验证
修改TouristRouteForUpdateDto.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using _02NET___CJ_ASP_Travel.Dtos;
namespace _02NET___CJ_ASP_Travel.Dtos
{
public class TouristRouteForUpdateDto : TouristRouteForManipulationDto
{
[Required(ErrorMessage = "更新必备")]
[MaxLength(1500)]
public override string Description { get; set; }
}
}
新建父类TouristRouteForManipulationDto.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using _02NET___CJ_ASP_Travel.ValidationAttributes;
namespace _02NET___CJ_ASP_Travel.Dtos
{
[TouristRouteTitleMustBeDifferentFromDescriptionAttribute]
public abstract class TouristRouteForManipulationDto
{
[Required(ErrorMessage = "title 不可为空")]
[MaxLength(100)]
public string Title { get; set; }
[Required]
[MaxLength(1500)]
public virtual string Description { get; set; }
// 计算方式:原价 * 折扣
public decimal Price { get; set; }
//public decimal OriginalPrice { get; set; }
//public double? DiscountPresent { get; set; }
public DateTime CreateTime { get; set; }
public DateTime? UpdateTime { get; set; }
public DateTime? DepartureTime { get; set; }
public string Features { get; set; }
public string Fees { get; set; }
public string Notes { get; set; }
public double? Rating { get; set; }
public string TravelDays { get; set; }
public string TripType { get; set; }
public string DepartureCity { get; set; }
public ICollection<TouristRoutePictureForCreationDto> TouristRoutePictures { get; set; }
= new List<TouristRoutePictureForCreationDto>();
}
}
TouristRouteForCreationDto.cs也继承TouristRouteForManipulationDto
public class TouristRouteForCreationDto : TouristRouteForManipulationDto
资源的局部更新PATCH
PATCH请求的主体 —— JSON Patch(JSON补丁文档)
JSON Patch操作
使用PATCH部分更新资源
TouristRoutesController.cs
[HttpPatch("{touristRouteId}")]
public IActionResult PartiallyUpdateTouristRoute(
[FromRoute]Guid touristRouteId,
[FromBody] JsonPatchDocument<TouristRouteForUpdateDto> patchDocument
)
{
if (!_touristRouteRepository.TouristRouteExists(touristRouteId))
{
return NotFound("旅游路线找不到");
}
var touristRouteFromRepo = _touristRouteRepository.GetTouristRoute(touristRouteId);
var touristRouteToPatch = _mapper.Map<TouristRouteForUpdateDto>(touristRouteFromRepo);
patchDocument.ApplyTo(touristRouteToPatch);
_mapper.Map(touristRouteToPatch, touristRouteFromRepo);
_touristRouteRepository.Save();
return NoContent();
}
配置nuget
microsoft.aspnetcore.jsonpatch
Microsoft.AspNetCore.Mvc.NewtonsoftJson
TouristRouteProfile.cs
CreateMap<TouristRouteForUpdateDto, TouristRoute>();
CreateMap<TouristRoute, TouristRouteForUpdateDto>();
TouristRoutePictureProfile.cs
CreateMap<TouristRoutePicture, TouristRoutePictureForCreationDto>();
引用NewtonsoftJson框架
Startup.cs
//新增部分 .AddNewtonsoftJson(setupAction => {
setupAction.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
//完整部分
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(setupAction => {
setupAction.ReturnHttpNotAcceptable = true;
})
.AddNewtonsoftJson(setupAction => {
setupAction.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
}).AddXmlDataContractSerializerFormatters()
.ConfigureApiBehaviorOptions(setupAction =>
{
setupAction.InvalidModelStateResponseFactory = context =>
{
var problemDetail = new ValidationProblemDetails(context.ModelState)
{
Type = "无所谓",
Title = "数据验证失败",
Status = StatusCodes.Status422UnprocessableEntity,
Detail = "请看详细说明",
Instance = context.HttpContext.Request.Path
};
problemDetail.Extensions.Add("traceId", context.HttpContext.TraceIdentifier);
return new UnprocessableEntityObjectResult(problemDetail)
{
ContentTypes = { "application/problem+json" }
};
};
});
//注册数据仓库的服务依赖
//以下三个Add都可以用于依赖注入
//services.AddSingleton
//services.AddScoped
services.AddTransient<ITouristRouteRepository, TouristRouteRepository>();
/* 数据库映射*/
services.AddDbContext<AppDbContext>(options => {
options.UseSqlServer(Configuration["DbContext:ConnectionString"]);
});
// 扫描profile文件
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
}
PATCH请求的数据验证
ASP数据验证:ModelState
if (!TryValidateModel(touristRouteToPatch))
{
return ValidationProblem(ModelState);
}
完整代码
[HttpPatch("{touristRouteId}")]
public IActionResult PartiallyUpdateTouristRoute(
[FromRoute]Guid touristRouteId,
[FromBody] JsonPatchDocument<TouristRouteForUpdateDto> patchDocument
)
{
if (!_touristRouteRepository.TouristRouteExists(touristRouteId))
{
return NotFound("旅游路线找不到");
}
var touristRouteFromRepo = _touristRouteRepository.GetTouristRoute(touristRouteId);
var touristRouteToPatch = _mapper.Map<TouristRouteForUpdateDto>(touristRouteFromRepo);
patchDocument.ApplyTo(touristRouteToPatch, ModelState);
if (!TryValidateModel(touristRouteToPatch))
{
return ValidationProblem(ModelState);
}
_mapper.Map(touristRouteToPatch, touristRouteFromRepo);
_touristRouteRepository.Save();
return NoContent();
}
九、【删除产品】解读http Delete
删除资源
TouristRoutesController.cs
[HttpDelete("{touristRouteId}")]
public IActionResult DeleteTouristRoute([FromRoute] Guid touristRouteId)
{
if (!_touristRouteRepository.TouristRouteExists(touristRouteId))
{
return NotFound("旅游路线找不到");
}
var touristRoute = _touristRouteRepository.GetTouristRoute(touristRouteId);
_touristRouteRepository.DeleteTouristRoute(touristRoute);
_touristRouteRepository.Save();
return NoContent();
}
新增接口函数(ITouristRouteRepository.cs)
void DeleteTouristRoute(TouristRoute touristRoute);
TouristRouteRepository.cs
public void DeleteTouristRoute(TouristRoute touristRoute)
{
_context.TouristRoutes.Remove(touristRoute);
}
删除嵌套子资源
TouristRoutePicturesController.cs
[HttpDelete("{pictureId}")]
public IActionResult DeletePicture(
[FromRoute]Guid touristRouteId,
[FromRoute]int pictureId
)
{
if (!_touristRouteRepository.TouristRouteExists(touristRouteId))
{
return NotFound("旅游线路不存在");
}
var picture = _touristRouteRepository.GetPicture(pictureId);
_touristRouteRepository.DeleteTouristRoutePicture(picture);
_touristRouteRepository.Save();
return NoContent();
}
新增接口函数(ITouristRouteRepository.cs)
void DeleteTouristRoutePicture(TouristRoutePicture picture);
TouristRouteRepository.cs
public void DeleteTouristRoutePicture(TouristRoutePicture picture)
{
_context.TouristRoutePictures.Remove(picture);
}
批量删除资源
TouristRouteController.cs
[HttpDelete("({touristIDs})")]
public IActionResult DeleteByIDs(
[ModelBinder( BinderType = typeof(ArrayModelBinder))][FromRoute]IEnumerable<Guid> touristIDs)
{
if(touristIDs == null)
{
return BadRequest();
}
var touristRoutesFromRepo = _touristRouteRepository.GetTouristRoutesByIDList(touristIDs);
_touristRouteRepository.DeleteTouristRoutes(touristRoutesFromRepo);
_touristRouteRepository.Save();
return NoContent();
}
新建工具类文件夹Helper
ArrayModelBinder.cs
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace _02NET___CJ_ASP_Travel.Helper
{
public class ArrayModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
// Our binder works only on enumerable types
if (!bindingContext.ModelMetadata.IsEnumerableType)
{
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
// Get the inputted value through the value provider
var value = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName).ToString();
// If that value is null or whitespace, we return null
if (string.IsNullOrWhiteSpace(value))
{
bindingContext.Result = ModelBindingResult.Success(null);
return Task.CompletedTask;
}
// The value isn't null or whitespace,
// and the type of the model is enumerable.
// Get the enumerable's type, and a converter
var elementType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];
var converter = TypeDescriptor.GetConverter(elementType);
// Convert each item in the value list to the enumerable type
var values = value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => converter.ConvertFromString(x.Trim()))
.ToArray();
// Create an array of that type, and set it as the Model value
var typedValues = Array.CreateInstance(elementType, values.Length);
values.CopyTo(typedValues, 0);
bindingContext.Model = typedValues;
// return a successful result, passing in the Model
bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
return Task.CompletedTask;
}
}
}
新增接口函数(ITouristRouteRepository.cs)
IEnumerable<TouristRoute> GetTouristRoutesByIDList(IEnumerable<Guid> ids);
void DeleteTouristRoutes(IEnumerable<TouristRoute> touristRoutes);
接口实现TouristRouteRepository.cs
public IEnumerable<TouristRoute> GetTouristRoutesByIDList(IEnumerable<Guid> ids)
{
return _context.TouristRoutes.Where(t => ids.Contains(t.Id)).ToList();
}
public void DeleteTouristRoutes(IEnumerable<TouristRoute> touristRoutes)
{
_context.TouristRoutes.RemoveRange(touristRoutes);
}
十、 【项目重构】异步async/await
项目存在的问题
解决方案 - 异步操作
百万级请求的处理
C#的异步async /await
同步方法
- 指程序调用某个方法,需要等待执行完成以后才进行下一步操作。
异步方法
- 指程序调用某个方法的时候,不做任何等待等待,在处理完成之前就返回该方法,继续执行接下来的操作。
异步方法是什么?
- 函数在执行完成前就可以先返回调用方,然后继续执行
的接下来的逻辑完成任务的函数。
代码示例
异步控制的流程
在函数声明中使用async关键词的意义是什么呢?
总结
项目重构异步模式
修改ITouristRouteRepository.cs
- 在接口前加上
Task
。 - 函数名后+
Async
,例如GetTouristRoute
命名为GetTouristRouteAsync
using _02NET___CJ_ASP_Travel.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace _02NET___CJ_ASP_Travel.Services
{
public interface ITouristRouteRepository
{
//返回一组旅游路线
Task<IEnumerable<TouristRoute>> GetTouristRoutesAsync(string keyword, string ratingOperator, int? ratingValue);
//返回单独的旅游路线
Task<TouristRoute> GetTouristRouteAsync(Guid touristRouteId);
Task<bool> TouristRouteExistsAsync(Guid touristRouteId);
Task<IEnumerable<TouristRoutePicture>> GetPicturesByTouristRouteIdAsync(Guid touristRouteId);
Task<TouristRoutePicture> GetPictureAsync(int pictureId);
Task<IEnumerable<TouristRoute>> GetTouristRoutesByIDListAsync(IEnumerable<Guid> ids);
void AddTouristRoute(TouristRoute touristRoute);
void AddTouristRoutePicture(Guid touristRouteId, TouristRoutePicture touristRoutePicture);
void DeleteTouristRoute(TouristRoute touristRoute);
void DeleteTouristRoutes(IEnumerable<TouristRoute> touristRoutes);
void DeleteTouristRoutePicture(TouristRoutePicture picture);
Task<bool> SaveAsync();
}
}
TouristRouteRepository.cs
- 加上关键词
async Task
- 函数名后+
Async
,例如GetTouristRoute
命名为GetTouristRouteAsync
FirstOrDefault();
修改为FirstOrDefaultAsync();
,即加上Async
。return await
using _02NET___CJ_ASP_Travel.Database;
using _02NET___CJ_ASP_Travel.Models;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace _02NET___CJ_ASP_Travel.Services
{
public class TouristRouteRepository : ITouristRouteRepository
{
private readonly AppDbContext _context;
public TouristRouteRepository(AppDbContext appDbContext)
{
_context = appDbContext;
}
public async Task<TouristRoute> GetTouristRouteAsync(Guid touristRouteId)
{
return await _context.TouristRoutes.Include(t => t.TouristRoutePictures).FirstOrDefaultAsync(n => n.Id == touristRouteId);
}
public async Task<IEnumerable<TouristRoute>> GetTouristRoutesAsync(
string keyword,
string ratingOperator,
int? ratingValue
)
{
IQueryable<TouristRoute> result = _context
.TouristRoutes
.Include(t => t.TouristRoutePictures);
if (!string.IsNullOrWhiteSpace(keyword))
{
keyword = keyword.Trim();
result = result.Where(t => t.Title.Contains(keyword));
}
if (ratingValue >= 0)
{
result = ratingOperator switch
{
"largerThan" => result.Where(t => t.Rating >= ratingValue),
"lessThan" => result.Where(t => t.Rating <= ratingValue),
_ => result.Where(t => t.Rating == ratingValue),
};
}
// include vs join
return await result.ToListAsync();
}
public async Task<bool> TouristRouteExistsAsync(Guid touristRouteId)
{
return await _context.TouristRoutes.AnyAsync(t => t.Id == touristRouteId);
}
public async Task<IEnumerable<TouristRoutePicture>> GetPicturesByTouristRouteIdAsync(Guid touristRouteId)
{
return await _context.TouristRoutePictures
.Where(p => p.TouristRouteId == touristRouteId).ToListAsync();
}
public async Task<TouristRoutePicture> GetPictureAsync(int pictureId)
{
return await _context.TouristRoutePictures.Where(p => p.Id == pictureId).FirstOrDefaultAsync();
}
public async Task<IEnumerable<TouristRoute>> GetTouristRoutesByIDListAsync(IEnumerable<Guid> ids)
{
return await _context.TouristRoutes.Where(t => ids.Contains(t.Id)).ToListAsync();
}
public void AddTouristRoute(TouristRoute touristRoute)
{
if (touristRoute == null)
{
throw new ArgumentNullException(nameof(touristRoute));
}
_context.TouristRoutes.Add(touristRoute);
//_context.SaveChanges();
}
public void AddTouristRoutePicture(Guid touristRouteId, TouristRoutePicture touristRoutePicture)
{
if (touristRouteId == Guid.Empty)
{
throw new ArgumentNullException(nameof(touristRouteId));
}
if (touristRoutePicture == null)
{
throw new ArgumentNullException(nameof(touristRoutePicture));
}
touristRoutePicture.TouristRouteId = touristRouteId;
_context.TouristRoutePictures.Add(touristRoutePicture);
}
public void DeleteTouristRoute(TouristRoute touristRoute)
{
_context.TouristRoutes.Remove(touristRoute);
}
public void DeleteTouristRoutePicture(TouristRoutePicture picture)
{
_context.TouristRoutePictures.Remove(picture);
}
public void DeleteTouristRoutes(IEnumerable<TouristRoute> touristRoutes)
{
_context.TouristRoutes.RemoveRange(touristRoutes);
}
public async Task<bool> SaveAsync()
{
return (await _context.SaveChangesAsync() >= 0);
}
}
}
TouristRoutePicturesController.cs
using _02NET___CJ_ASP_Travel.Dtos;
using _02NET___CJ_ASP_Travel.Models;
using _02NET___CJ_ASP_Travel.Services;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace _02NET___CJ_ASP_Travel.Controllers
{
[Route("api/touristRoutes/{touristRouteId}/pictures")]
[ApiController]
public class TouristRoutePicturesController : ControllerBase
{
private ITouristRouteRepository _touristRouteRepository;
private IMapper _mapper;
public TouristRoutePicturesController(
ITouristRouteRepository touristRouteRepository,
IMapper mapper
)
{
_touristRouteRepository = touristRouteRepository ??
throw new ArgumentNullException(nameof(touristRouteRepository));
_mapper = mapper ??
throw new ArgumentNullException(nameof(mapper));
}
[HttpGet]
public async Task<IActionResult> GetPictureListForTouristRoute(Guid touristRouteId)
{
if (!(await _touristRouteRepository.TouristRouteExistsAsync(touristRouteId)))
{
return NotFound("旅游线路不存在");
}
var picturesFromRepo = await _touristRouteRepository.GetPicturesByTouristRouteIdAsync(touristRouteId);
if (picturesFromRepo == null || picturesFromRepo.Count() <= 0)
{
return NotFound("照片不存在");
}
return Ok(_mapper.Map<IEnumerable<TouristRoutePictureDto>>(picturesFromRepo));
}
[HttpGet("{pictureId}", Name = "GetPicture")]
public async Task<IActionResult> GetPicture(Guid touristRouteId, int pictureId)
{
if (!(await _touristRouteRepository.TouristRouteExistsAsync(touristRouteId)))
{
return NotFound("旅游线路不存在");
}
var pictureFromRepo = await _touristRouteRepository.GetPictureAsync(pictureId);
if (pictureFromRepo == null)
{
return NotFound("相片不存在");
}
return Ok(_mapper.Map<TouristRoutePictureDto>(pictureFromRepo));
}
[HttpPost]
public async Task<IActionResult> CreateTouristRoutePicture(
[FromRoute] Guid touristRouteId,
[FromBody] TouristRoutePictureForCreationDto touristRoutePictureForCreationDto
)
{
if (!(await _touristRouteRepository.TouristRouteExistsAsync(touristRouteId)))
{
return NotFound("旅游线路不存在");
}
var pictureModel = _mapper.Map<TouristRoutePicture>(touristRoutePictureForCreationDto);
_touristRouteRepository.AddTouristRoutePicture(touristRouteId, pictureModel);
await _touristRouteRepository.SaveAsync();
var pictureToReturn = _mapper.Map<TouristRoutePictureDto>(pictureModel);
return CreatedAtRoute(
"GetPicture",
new
{
touristRouteId = pictureModel.TouristRouteId,
pictureId = pictureModel.Id
},
pictureToReturn
);
}
[HttpDelete("{pictureId}")]
public async Task<IActionResult> DeletePicture(
[FromRoute] Guid touristRouteId,
[FromRoute] int pictureId
)
{
if (!(await _touristRouteRepository.TouristRouteExistsAsync(touristRouteId)))
{
return NotFound("旅游线路不存在");
}
var picture = await _touristRouteRepository.GetPictureAsync(pictureId);
_touristRouteRepository.DeleteTouristRoutePicture(picture);
await _touristRouteRepository.SaveAsync();
return NoContent();
}
}
}
TouristRouteController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using AutoMapper;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.JsonPatch;
using _02NET___CJ_ASP_Travel.Dtos;
using _02NET___CJ_ASP_Travel.Models;
using _02NET___CJ_ASP_Travel.ResourceParameters;
using _02NET___CJ_ASP_Travel.Services;
using _02NET___CJ_ASP_Travel.Helper;
namespace _02NET___CJ_ASP_Travel.Controllers
{
[Route("api/[controller]")] // api/touristroute
[ApiController]
public class TouristRoutesController : ControllerBase
{
private ITouristRouteRepository _touristRouteRepository;
private readonly IMapper _mapper;
public TouristRoutesController(
ITouristRouteRepository touristRouteRepository,
IMapper mapper
)
{
_touristRouteRepository = touristRouteRepository;
_mapper = mapper;
}
// api/touristRoutes?keyword=传入的参数
[HttpGet]
[HttpHead]
public async Task<IActionResult> GerTouristRoutes(
[FromQuery] TouristRouteResourceParamaters paramaters
//[FromQuery] string keyword,
//string rating // 小于lessThan, 大于largerThan, 等于equalTo lessThan3, largerThan2, equalTo5
)// FromQuery vs FromBody
{
var touristRoutesFromRepo = await _touristRouteRepository.GetTouristRoutesAsync(paramaters.Keyword, paramaters.RatingOperator, paramaters.RatingValue);
if (touristRoutesFromRepo == null || touristRoutesFromRepo.Count() <= 0)
{
return NotFound("没有旅游路线");
}
var touristRoutesDto = _mapper.Map<IEnumerable<TouristRouteDto>>(touristRoutesFromRepo);
return Ok(touristRoutesDto);
}
// api/touristroutes/{touristRouteId}
[HttpGet("{touristRouteId}", Name = "GetTouristRouteById")]
public async Task<IActionResult> GetTouristRouteById(Guid touristRouteId)
{
var touristRouteFromRepo = await _touristRouteRepository.GetTouristRouteAsync(touristRouteId);
if (touristRouteFromRepo == null)
{
return NotFound($"旅游路线{touristRouteId}找不到");
}
var touristRouteDto = _mapper.Map<TouristRouteDto>(touristRouteFromRepo);
return Ok(touristRouteDto);
}
[HttpPost]
public async Task<IActionResult> CreateTouristRoute([FromBody] TouristRouteForCreationDto touristRouteForCreationDto)
{
var touristRouteModel = _mapper.Map<TouristRoute>(touristRouteForCreationDto);
_touristRouteRepository.AddTouristRoute(touristRouteModel);
await _touristRouteRepository.SaveAsync();
var touristRouteToReture = _mapper.Map<TouristRouteDto>(touristRouteModel);
return CreatedAtRoute(
"GetTouristRouteById",
new { touristRouteId = touristRouteToReture.Id },
touristRouteToReture
);
}
[HttpPut("{touristRouteId}")]
public async Task<IActionResult> UpdateTouristRoute(
[FromRoute] Guid touristRouteId,
[FromBody] TouristRouteForUpdateDto touristRouteForUpdateDto
)
{
if (!(await _touristRouteRepository.TouristRouteExistsAsync(touristRouteId)))
{
return NotFound("旅游路线找不到");
}
var touristRouteFromRepo = await _touristRouteRepository.GetTouristRouteAsync(touristRouteId);
// 1. 映射dto
// 2. 更新dto
// 3. 映射model
_mapper.Map(touristRouteForUpdateDto, touristRouteFromRepo);
await _touristRouteRepository.SaveAsync();
return NoContent();
}
[HttpPatch("{touristRouteId}")]
public async Task<IActionResult> PartiallyUpdateTouristRoute(
[FromRoute] Guid touristRouteId,
[FromBody] JsonPatchDocument<TouristRouteForUpdateDto> patchDocument
)
{
if (!(await _touristRouteRepository.TouristRouteExistsAsync(touristRouteId)))
{
return NotFound("旅游路线找不到");
}
var touristRouteFromRepo = await _touristRouteRepository.GetTouristRouteAsync(touristRouteId);
var touristRouteToPatch = _mapper.Map<TouristRouteForUpdateDto>(touristRouteFromRepo);
patchDocument.ApplyTo(touristRouteToPatch, ModelState);
if (!TryValidateModel(touristRouteToPatch))
{
return ValidationProblem(ModelState);
}
_mapper.Map(touristRouteToPatch, touristRouteFromRepo);
await _touristRouteRepository.SaveAsync();
return NoContent();
}
[HttpDelete("{touristRouteId}")]
public async Task<IActionResult> DeleteTouristRoute([FromRoute] Guid touristRouteId)
{
if (!(await _touristRouteRepository.TouristRouteExistsAsync(touristRouteId)))
{
return NotFound("旅游路线找不到");
}
var touristRoute = await _touristRouteRepository.GetTouristRouteAsync(touristRouteId);
_touristRouteRepository.DeleteTouristRoute(touristRoute);
await _touristRouteRepository.SaveAsync();
return NoContent();
}
[HttpDelete("({touristIDs})")]
public async Task<IActionResult> DeleteByIDs(
[ModelBinder(BinderType = typeof(ArrayModelBinder))][FromRoute] IEnumerable<Guid> touristIDs)
{
if (touristIDs == null)
{
return BadRequest();
}
var touristRoutesFromRepo = await _touristRouteRepository.GetTouristRoutesByIDListAsync(touristIDs);
_touristRouteRepository.DeleteTouristRoutes(touristRoutesFromRepo);
await _touristRouteRepository.SaveAsync();
return NoContent();
}
}
}
Comments | NOTHING