旅游网项目笔记

数据模型: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框架

原文地址:https://www.cnblogs.com/liflower/p/14698096.html