• 旅游网项目笔记


    数据模型:DDD领域驱动模型 -->标记 后续进行学习

    比较流行的数据持久化模式:repository仓储模式
    Models文件夹存放与数据有关的代码

    注:
    1.无论是put patch get post ,都不能直接接触到模型数据,必须创建响应的Dto模型;
    2.当我们进行Put更新的时候,要记得使用AutoMapper,它可以直接将Dto墨香映射到Entity实例对象,而我们只需要Save那么一小下就ok啦
    3.Dto类的做好了AutoMapper映射,Dto中的子数据也要进行AutoMapper否则会报错

    前期开发流程:

    1.在Models文件夹下首先创建业务数据模型(旅游网站:基本都是围绕着旅游路线的,所以首先创建旅游路线)

    2.在Database文件夹内创建数据库映射配置文件=>执行数据库迁移(在数据库中添加Json种子数据)

    3.在Services文件夹内创建ITouristRouteRepository仓库接口服务,并创建实现该接口的类

    4.在Controllers文件夹下创建控制器

    5.正式的开始

    (1)Status Code 返回码存在的问题

    当我们根据touristRouteId进行查询指定的路线图的时候,如果输入了错误的touristRouteId,服务端没有查询到数据,返回的代码为 204 not content(执行成功没有内容需要显示)
    这里是不对的,服务器应该返回的Status Code 为 404 not found ;
    下面的代码解决了这个问题:(我们判断从Repository中拿到的对象是否为空,来确定返回的Status Code)

            [HttpGet]//http://localhost:5000/api/TouristRoutes
            public IActionResult GetTouristRoutes()
            {
                var touristRoutesFromRepo = _touristRouteRepository.GetTouristRoutes();
                if (touristRoutesFromRepo == null || touristRoutesFromRepo.Count() <= 0)
                {
                    return NotFound("没有旅游路线图");
                }
                return Ok(touristRoutesFromRepo);
            }
            [HttpGet]//http://localhost:5000/api/TouristRoutes/99BA5433-DF5F-A898-C8E0-78B8BA55F251
            [Route("{touristRouteId:Guid}")]//(:Guid确保传进来的数据为Guid)
            public IActionResult GetTouristRouteById(Guid touristRouteId)
            {
                var touristRouteFromRepo = _touristRouteRepository.GetTouristRoute(touristRouteId);
                if (touristRouteFromRepo == null)
                {
                    return NotFound($"没有查询到该路由路线图,请检查该Id:{touristRouteId}的路线图是否存在,或者联系管理员");
                }
                return Ok(touristRouteFromRepo);
            }
    

    (2)内容协商(Content Negotiation)与数据格式

    前后端分离的开发模式,需要Api对应不同的前端,返回不同的数据格式(json,xml)
    在这里需要实现的是:Api可以根据请求的数据类型,动态的响应不同类型的数据格式
    请求头部的媒体类型定义"accept"与"Content-type" ,遇见无法识别的格式返回错误代码 406 unacceptable
    ASP.NET Core 支持内容协商,自动化处理

    在这里我们发现在我们请求application/xml格式的数据的时候,服务端返回的依然是json的格式,这个时候正确的响应应该是 406 unacceptable ,下面我们处理这个问题
    并且添加了对Xml格式数据的支持

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddControllers(setupAction =>
                {
                    setupAction.ReturnHttpNotAcceptable = true;//这里配置了对不支持的数据格式的请求返回406 unacceptable
                }
                ).AddXmlDataContractSerializerFormatters();//添加了对Xml格式数据的支持
            }
    

    (3)Model数据模型与Dto(Data Transfer Object)数据传输对象

    直接向前端发挥数据模型,会暴露系统的业务核心(使用Dto可以屏蔽我们不希望暴露的核心业务)
    颗粒度太粗,无法对输出的数据做精细的调整(eg:数据模型有一个,而要展示给不同客户不同的数据,这个时候就需要Dto了)
    Model面向业务
    Dto面向界面ui

    下面我们实现一下:Model与Dto分离
    首先我们创建Dtos文件夹,在文件夹内创建TouristRouteDto
    然后使用Automapper进行映射 nuget:AutoMapper.Extensions.Microsoft.DependencyInjection

    services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
    

    然后创建Profile文件夹,在文件夹内创建继承Profile的类

    AutoMapper在完成依赖注入以后,回去寻找项目文件夹中命名为Profile的文件夹,扫描文件夹中的所有文件,在Profile的构造函数中完成对对象映射的配置

        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.TravelDays,
                    opt => opt.MapFrom(src => src.TripType.ToString())
                    )
                    .ForMember(dest => dest.DepartureCity,
                    opt => opt.MapFrom(src => src.DepartureCity.ToString())
                    );
            }
        }
    

    然后我们在控制器中注入服务
    执行映射

    var touristRouteDto = _mapper.Map<TouristRouteDto>(touristRouteFromRepo);
    

    调用-完成!

    (4)获取嵌套对象关系型数据
    eg:通过路由路线获取路由路线图片
    这里需要在IRepository中添加新的功能

    需求:当我们点击一个旅游路线的时候,接下来要显示这个旅游路线所有的路线图片,而路线和路线图就是嵌套的关系

    下面是代码时间,我们仅仅添加了一个include方法,这个方法使得两个表连接到了一起
    我们在仓库中将拿到的子数据一同返回到controller中,因为我们使用AutoMapper,它会自动将TouristRoute和TourstRouteDto中属性名称相同的对象进行映射,当然也包括集合类
    所以在后面返回的数据中就包含了,子集合中的数据

       public IEnumerable<TouristRoute> GetTouristRoutes()
            {
                //Include就是efcore中连接两张表的方法
                return _context.TouristRoutes.Include(t => t.TouristRoutePictures);
            }
    

    (4)HttpHead请求
    只需要在Action前添加[httphead],它返回的信息没有主体
    This method is often used for testing hypertext links for validity, accessibility, and recent modification.

    (4)向Api传递参数
    这里有多种方法:
    1.使用Attribute
    [FromQuery] 从Url的参数字符串中获取(地址栏)
    [FromBody] 主题数据中获取
    [FromForm] 表单数据中获取
    [FromRoute] Mvc架构下的Route路由URL的参数
    [FromService] 数据来源于已注入的服务依赖

    重点区分FromQuery和FromRoute
    FromQuery:http://localhost:5000/api/TouristRoutes/?Id=99ba5433-df5f-a898-c8e0-78b8ba55f251
    FromRoute:http://localhost:5000/api/TouristRoutes/99ba5433-df5f-a898-c8e0-78b8ba55f251

    -----------------------------华丽的分割线---------------------------------------------------------------

    从这里开始,会有记录风格一些新的变化(面向业务需求开发,而不是面向开发而开发)

    下面我们对代码进行了业务上的优化
    客户在网页上需要根据一些条件进行检索过滤,快速的得到用户想要的信息
    下面我们异步的实现了过滤,为了进一步提高性能,我们还使用了IQueryable接口类型(延迟查询)
    只有在使用了ToList()、FirstOrDefault(),这种聚合查询的时候,EfCore才会执行数据库查询
    在这之前,我们可以默认IQueryable只是存储了查询的语句

    代码简介:
    首先我们从Url中的QueryString参数拿到过滤的条件和值,
    因为评分过滤条件包含了(大于/小于/等于)+评分值,所以我们需要使用到正则表达式将其从一个字符串中分离为2个,在字符串未拆分前,我们需要声明存储分离后的值的变量,在这里有一个为int值类型的ratingValue评分值,不能为空,且在后面的代码中我们判定ratingValue值有效的条件为>=0,所以我们设置其默认值为-1即可;

     [HttpGet]//http://localhost:5000/api/TouristRoutes?keyword=埃及&ratingValue=largerThan4
            [HttpHead]
            public async Task<IActionResult> GetTouristRoutes(
                [FromQuery]string keyword,
                [FromQuery(Name = "ratingValue")] string rating //lessThan,largerThan,equalTo ====>lessThan3,largerThan4,equalTo1
                )
            {
                Regex regex = new Regex(@"([A-Za-z0-9-]+)(d+)");
                string operatorType = string.Empty;
                int ratingValue = -1;
    
                ////确保rating不为空,否则会报错
                if (!string.IsNullOrEmpty(rating))
                {
                    Match match = regex.Match(rating.Trim());
                    if (match.Success)
                    {
                        operatorType = match.Groups[1].Value;
                        ratingValue = int.Parse(match.Groups[2].Value);
                    }
                }
    
                var touristRoutesFromRepo = await _touristRouteRepository.GetTouristRoutes(keyword, operatorType, ratingValue);
                if (touristRoutesFromRepo == null || touristRoutesFromRepo.Count() <= 0)
                {
                    return NotFound("您所查找的旅游路线图不存在,如有问题请拨打客服电话:110");
                }
                var touristRoutesDto = _mapper.Map<IEnumerable<TouristRouteDto>>(touristRoutesFromRepo);
                return  Ok(touristRoutesDto);
            }
    

    仓库代码:

     public async Task<IEnumerable<TouristRoute>> GetTouristRoutes(string keyword, string operatorType, int ratingValue)
            {
                IQueryable<TouristRoute> Queryable = _context.TouristRoutes.Include(x => x.TouristRoutePictures);
                //Include就是efcore中连接两张表的方法
                if (!String.IsNullOrWhiteSpace(keyword))
                {
                   Queryable = Queryable.Where(x => x.Tittle.Contains(keyword));
                }
                //ratingVlaue>=0的时候才需要过滤
                if (ratingValue >= 0 && operatorType != null)
                {
                    switch (operatorType)
                    {
                        case "lessThan": Queryable = Queryable.Where(x => x.Rating <= ratingValue);break;
                        case "largerThan": Queryable = Queryable.Where(x => x.Rating >= ratingValue);break;
                        case "equalThan": Queryable = Queryable.Where(x => x.Rating == ratingValue);break;
                        default:
                            break;
                    }
                    return await Queryable.ToListAsync();
                }
                return await Queryable.ToListAsync();
            }
    

    下面我们再对上面的程序进行优化:封装资源过滤器

    通过创建封装资源过滤参数的[TouristRouteResourceParamaters.cs]类(实现过滤参数与控制器的解耦合)
    同时将正则表达式一部分代码,转移到了这个类下的_rating变量的Rating的属性中

    
            [HttpGet]//http://localhost:5000/api/TouristRoutes?keyword=埃及&ratingValue=largerThan4
            [HttpHead]
            public async Task<IActionResult> GetTouristRoutes(
                [FromQuery]TouristRouteResourceParamaters paramaters //lessThan,largerThan,equalTo ====>lessThan3,largerThan4,equalTo1
                )
            {
    
                var touristRoutesFromRepo = await _touristRouteRepository.GetTouristRoutes(paramaters.Keyword, paramaters.RatingOperatorType, paramaters.RatingValue);
                if (touristRoutesFromRepo == null || touristRoutesFromRepo.Count() <= 0)
                {
                    return NotFound("您所查找的旅游路线图不存在,如有问题请拨打客服电话:110");
                }
                var touristRoutesDto = _mapper.Map<IEnumerable<TouristRouteDto>>(touristRoutesFromRepo);
                return  Ok(touristRoutesDto);
            }
    
        public class TouristRouteResourceParamaters
        {
            public string Keyword { get; set; }
            public string RatingOperatorType { get; set; }
            public int RatingValue { get; set; } = -1;
            private string _rating;
            public string Rating
            {
                get
                {
                    return _rating;
                }
                set
                {
                    if (value != null)
                    {
                        Regex regex = new Regex(@"([A-Za-z0-9-]+)(d+)");
                        Match match = regex.Match(value);
                        if (match.Success)
                        {
                            RatingOperatorType = match.Groups[1].Value;
                            RatingValue = int.Parse(match.Groups[2].Value);
                        }
                    }
                    _rating = value;
    
                }
            }//lessThan,largerThan,equalTo ====>lessThan3,largerThan4,equalTo1
        }
    

    我们Post资源偶,还需创建子资源

            [HttpPost]
            public async Task<IActionResult> CreateTouristRoutePictures(
                [FromRoute] Guid touristRouteId,
                [FromBody] TouristRouteForCreationPicDto touristRouteForCreationPicDto
                )
            {
                if (!_touristRouteRepository.TouristRouteExists(touristRouteId))
                {
                    return NotFound(); 
                }
    
                var pictureModel = _mapper.Map<TouristRoutePicture>(touristRouteForCreationPicDto);
                _touristRouteRepository.AddTouristRoutePicture(touristRouteId, pictureModel);
                await _touristRouteRepository.Save();
    
                var pictureDto = _mapper.Map<TouristRoutePictureDto>(pictureModel);
                return CreatedAtAction(nameof(GetPicture), new { touristRouteId = touristRouteId, picId = pictureModel.Id },pictureDto);
            }
    

    仓库代码

     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);
            }
    

    当然我们页可以在Post提交TouristRoute路由路线的时候,在json中添加子数据直接提交
    其他的映射工作AutoMapper已经替我们完成了,所以映射框架在工作中是能大幅度提高工作效率的

    数据验证:
    目前为止,我们提交的数据中如果Tittle=null,那么就会返回数据库错误,无法提交。因为过多的错误可能导致数据库崩溃
    所以我们选择在Dto层面上进行数据验证,这样就避免了这一个问题
    这个验证和模型中的验证属性标签是一样的

    [Required(ErrorMessage ="Tittle不可以为空值")]
    

    当然我们还可以添加自定义验证
    就是使Dto类实现IValidatableObject接口,在Validate方法中实现数据的验证

    自定义错误信息和错误报告.ConfigureApiBehaviorOptions

     services.AddControllers(
                    configure: setup =>
                    {
                        setup.ReturnHttpNotAcceptable = true;
                        // setup.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
                    }
                    )
                    .AddXmlDataContractSerializerFormatters()
                    .ConfigureApiBehaviorOptions(
                    setup => setup.InvalidModelStateResponseFactory = context =>
                        {
                            var problemDetails = new ValidationProblemDetails(context.ModelState)
                            {
                                Type = "旅游网",
                                Title = "error",
                                Status = StatusCodes.Status422UnprocessableEntity,
                                Detail = "请看详细信息",
                                Instance = context.HttpContext.Request.Path
                            };
                            problemDetails.Extensions.Add("traceId", context.HttpContext.TraceIdentifier);
                            return new UnprocessableEntityObjectResult(problemDetails)
                            {
                                ContentTypes = { "application/problem+json" }
                            };
                        }
                    );
    

    添加类级别的验证;在使用的类上面添加[TouristRouteTitleMustBeDifferentFromDescriptionAttribute]标签即可,后面我们会实现解耦

        /// <summary>
        /// 类级别的数据验证
        /// </summary>
        public class TouristRouteTitleMustBeDifferentFromDescriptionAttribute:ValidationAttribute
        {
            protected override ValidationResult IsValid(object value, ValidationContext validationContext)
            {
                var touristRouteDto =(TouristRouteForCreationgDto) validationContext.ObjectInstance;
                if (touristRouteDto.Tittle == touristRouteDto.Description)
                {
                    return new ValidationResult("路线名称必须与路线描述不同", new[] { "TouristRouteForCreationDto" });
                }
                return ValidationResult.Success;
            }
        }
    

    将Json中的值映射到实体类中的属性:
    Services.Configure<映射到的类>(_configuration.GetSectiong(key:"键值"));

    PUT方法
    这里有一点特别,可能是因为太过于简洁,因为AutoMapper已经为我们完成了

            [HttpPut]
            [Route("{touristRouteId:Guid}")]
            public async Task<IActionResult> UpdateTouristRouet(
                [FromRoute] Guid TouristRouteId,
                [FromBody] TouristRouteForUpdateDto touristRouteForUpdateDto)
            {
                if(!_touristRouteRepository.TouristRouteExists(TouristRouteId))
                {
                    return NotFound("没有找到旅游路线");
                }
                var touristRoute = _touristRouteRepository.GetTouristRoute(TouristRouteId);
                //在这里Map()会直接将传进来的对象映射到TouristRoute的Entity instance上,我们只需要.save()提交一下即可;
                var touristRoutePut = _mapper.Map(touristRouteForUpdateDto, touristRoute);
                await _touristRouteRepository.Save();
                return NoContent();
            }
    

    JSON Patch 6个操作
    add
    move
    remove
    copy
    replace
    test

    [
        {"op":"replace","path":"/title","value":"福岛3日游" }
    ]
    

    这里需要使用到jsonPatch框架和NewtonsoftJson框架

  • 相关阅读:
    用C++做微信公众平台开发的后台开发时,用sha1加密验证的方法
    UART Receive FIFO and Receive Timeout
    Compile cpp File Manually without IDE under Mingw Environment
    html5 返回当前地理位置的坐标点(经纬度)
    逆袭!花两个月吃透这份“MySQL宝典”拿到字节offer
    MySQL约束的概述
    2020-11-28
    人工智能能力提升指导总结
    年轻就该多尝试,教你20小时Get一项新技能
    MySQL~存储过程基本操作
  • 原文地址:https://www.cnblogs.com/liflower/p/14698096.html
Copyright © 2020-2023  润新知