寸金游( 后端开发 )


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的基本特点

  • 无状态
  • 面向“资源”
  • 使用HTTP的动词
  • HATOAS超媒体即应用状态引擎

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的请求

  • 显示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();
        }
    }
}

测试 - 运行成功

声明:三二一的一的二|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 寸金游( 后端开发 )


三二一的一的二