• ASP NET Core ---POST, PUT, PATCH, DELETE,Model 验证


    参照 草根专栏- ASP.NET Core + Ng6 实战:https://v.qq.com/x/page/u0765jbwc6f.html

    一、POST

    安全性和幂等性

    1. 安全性是指方法执行后并不会改变资源的表述
    2. 幂等性是指方法无论执行多少次都会得到同样的结果

    POST添加资源:

    • 不安全, 不幂等
    • 参数 [FromBody]
    • 返回 201 Create :    CreatedAtRoute(): 它允许响应里带着Location Header,在这个Location Header里包含着一个uri,通过这个uri就可以GET到我们刚刚创建好的资源
    • HATEOAS

     (1)添加 PostAddResource.cs 类

    namespace BlogDemo.Infrastructure.Resources
    {
        public class PostAddResource  
        {
            public string Title { get; set; }
            public string Body { get; set; }
    
            public string Remark { get; set; }
        }
    }

    (2)添加映射

    namespace BlogDemo.Api.Extensions
    {
        public class MappingProfile:Profile
        {
            public MappingProfile()
            {
                CreateMap<Post, PostDTO>().ForMember(dest=>dest.Updatetime,opt=>opt.MapFrom(src=>src.LastModified));
                CreateMap<PostDTO, Post>();
                CreateMap<PostAddResource, Post>();
            }
        }
    }

    (3)添加post方法:

            [HttpPost(Name = "CreatePost")]
             public async Task<IActionResult> Post([FromBody] PostAddResource postAddResource)
            {
                if (postAddResource == null)
                {
                    return BadRequest();
                }
    
             
    
                var newPost = _mapper.Map<PostAddResource, Post>(postAddResource);
    
                newPost.Author = "admin";
                newPost.LastModified = DateTime.Now;
    
                _postRepository.AddPost(newPost);
    
                if (!await _unitOfWork.SaveAsync())
                {
                    throw new Exception("Save Failed!");
                }
    
                var resultResource = _mapper.Map<Post, PostDTO>(newPost);
    
                var links = CreateLinksForPost(newPost.Id);
                var linkedPostResource = resultResource.ToDynamic() as IDictionary<string, object>;
                linkedPostResource.Add("links", links);
    
                return CreatedAtRoute("GetPost", new { id = linkedPostResource["Id"] }, linkedPostResource);
            }
    View Code

      (4) 测试:

     二、Model验证:

              定义验证规则
              检查验证规则
              把验证错误信息发送给API的消费者

    1、验证方式:

              内置验证:
                   DataAnnotation
                   ValidationAttribute
                  IValidatebleObject
              第三方: FluentValidation

    2、使用FluentValidation组件(关注点分离)

            (1) 安装:                  

                      FluentValidation.AspNetCore
                      FluentValidation

             (2) 为每一个Resource建立验证器:

                       继承AbstractValidator<T>

    namespace BlogDemo.Infrastructure.Resources
    {
       public class PostAddResourceValidator:AbstractValidator<PostAddResource>
        {
            public PostAddResourceValidator()
            {
                RuleFor(x => x.Title).NotEmpty()
                    .WithName("标题").WithMessage("{PropertyName}是必须填写的")
                    .MaximumLength(50).WithMessage("{PropertyName}的最大长度是{MaxLength}");
    
                RuleFor(x => x.Body).NotEmpty()
                    .WithName("正文").WithMessage("{PropertyName}是必须填写的")
                    .MinimumLength(50).WithMessage("{PropertyName}的最大长度是{MinLength}");
            }
        }
    }

                (3)配置:

    services.AddMvc(……).AddFluentValidation();
    services.AddTransient<IValidator<PostAddResource>, PostAddResourceValidator>();

                 (4)Action添加验证:

                         ModelState.IsValid
                         ModelState
                               它是一个字典,包含了Model的状态以及Model所绑定的验证
                               对于提交的每个属性,它都包含了一个错误信息的集合
                               返回: 422 Unprocessable Entity
                          验证错误信息在响应的body里面带回去

                if (!ModelState.IsValid)
                {
                    return  UnprocessableEntity(ModelState);
                }

                   (5)测试

                (6)Action添加Accpet和Content-Type 的自定义hateoas

            [RequestHeaderMatchingMediaType("Content-Type", new[] { "application/vnd.cgzl.post.create+json" })]
            [RequestHeaderMatchingMediaType("Accept", new[] { "application/vnd.cgzl.hateoas+json" })]

                  (7)  staupDevelopment 注册hateoas

                services.AddMvc(option => {
                    option.ReturnHttpNotAcceptable = true;
                  //  option.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
                    var outputFormatter = option.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault();
                    if (outputFormatter != null)
                    {
                        outputFormatter.SupportedMediaTypes.Add("application/vnd.cgzl.hateoas+json");
                    }
    
                    var intputFormatter = option.InputFormatters.OfType<JsonInputFormatter>().FirstOrDefault();
                    if (intputFormatter != null)
                    {
                        intputFormatter.SupportedMediaTypes.Add("application/vnd.cgzl.post.create+json");
                        intputFormatter.SupportedMediaTypes.Add("application/vnd.cgzl.post.update+json");
                    }
    
                })

                  (8)测试:

    3、自定义错误验证返回结果:

          (1) 添加MyUnprocessableEntityObjectResult.cs   ResourceValidationError.cs   ResourceValidationResult.cs 类

    namespace BlogDemo.Api.Helpers
    {
        public class MyUnprocessableEntityObjectResult : UnprocessableEntityObjectResult
        {
            public MyUnprocessableEntityObjectResult(ModelStateDictionary modelState) : base(new ResourceValidationResult(modelState))
            {
                if (modelState == null)
                {
                    throw new ArgumentNullException(nameof(modelState));
                }
                StatusCode = 422;
            }
        }
    }
    View Code
    namespace BlogDemo.Api.Helpers
    {
        public class ResourceValidationError
        {
            public string ValidatorKey { get; private set; }
            public string Message { get; private set; }
    
            public ResourceValidationError(string message, string validatorKey = "")
            {
                ValidatorKey = validatorKey;
                Message = message;
            }
        }
    }
    View Code
    namespace BlogDemo.Api.Helpers
    {
        public class ResourceValidationResult : Dictionary<string, IEnumerable<ResourceValidationError>>
        {
            public ResourceValidationResult() : base(StringComparer.OrdinalIgnoreCase)
            {
    
            }
    
            public ResourceValidationResult(ModelStateDictionary modelState)
                : this()
            {
                if (modelState == null)
                {
                    throw new ArgumentNullException(nameof(modelState));
                }
    
                foreach (var keyModelStatePair in modelState)
                {
                    var key = keyModelStatePair.Key;
                    var errors = keyModelStatePair.Value.Errors;
                    if (errors != null && errors.Count > 0)
                    {
                        var errorsToAdd = new List<ResourceValidationError>();
                        foreach (var error in errors)
                        {
                            var keyAndMessage = error.ErrorMessage.Split('|');
    
                            if (keyAndMessage.Length > 1)
                            {
                                errorsToAdd.Add(new ResourceValidationError(keyAndMessage[1], keyAndMessage[0]));
                            }
                            else
                            {
                                errorsToAdd.Add(new ResourceValidationError(keyAndMessage[0]));
                            }
                        }
                        Add(key, errorsToAdd);
                    }
                }
            }
        }
    }
    View Code

           (2)Action中添加自定义验证:

            public async Task<IActionResult> Post([FromBody] PostAddResource postAddResource)
            {
          
                if (!ModelState.IsValid)
                {
                    return new  MyUnprocessableEntityObjectResult(ModelState);
                }
             }

                (3)测试

    三、Delete

    1、 在PostRepository 添加Delete方法

            public void Delete(Post post)
            {
                _myContext.Posts.Remove(post);
            }

     2、Action添加Delete方法:

            [HttpDelete("{id}", Name = "DeletePost")]
            public async Task<IActionResult> DeletePost(int id)
            {
                var post = await _postRepository.GetPostId(id);
                if (post == null)
                {
                    return NotFound();
                }
    
                _postRepository.Delete(post);
    
                if (!await _unitOfWork.SaveAsync())
                {
                    throw new Exception($"Deleting post {id} failed when saving.");
                }
    
                return NoContent();
            }

    四、PUT(整体更新)

    1、添加PostUpdateResource.cs 类;

        public class PostUpdateResource : PostAddOrUpdateResource
        {
            
        }

    2、重构  PostAddOrUpdateResourceValidator.cs ,改成泛型

    namespace BlogDemo.Infrastructure.Resources
    {
       public class PostAddOrUpdateResourceValidator<T> : AbstractValidator<T> where T: PostAddOrUpdateResource
        {
            public PostAddOrUpdateResourceValidator()
            {
                RuleFor(x => x.Title).NotEmpty()
                    .WithName("标题").WithMessage("required|{PropertyName}是必须填写的")
                    .MaximumLength(50).WithMessage("maxLength|{PropertyName}的最大长度是{MaxLength}");
    
                RuleFor(x => x.Body).NotEmpty()
                    .WithName("正文").WithMessage("required|{PropertyName}是必须填写的")
                    .MinimumLength(10).WithMessage("minLength|{PropertyName}的最小长度是{MinLength}");
            }
        }
    }

    3、资源验证注册:

                services.AddTransient<IValidator<PostUpdateResource>, PostAddOrUpdateResourceValidator<PostUpdateResource>>();

    4、添加Map

    namespace BlogDemo.Api.Extensions
    {
        public class MappingProfile:Profile
        {
            public MappingProfile()
            {
                CreateMap<Post, PostDTO>().ForMember(dest=>dest.Updatetime,opt=>opt.MapFrom(src=>src.LastModified));
                CreateMap<PostDTO, Post>();
                CreateMap<PostAddResource, Post>();
                CreateMap<PostUpdateResource, Post>();
            }
        }
    }

    5、添加自定义hateoas

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddMvc(option => {
                    option.ReturnHttpNotAcceptable = true;
                  //  option.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
                    var outputFormatter = option.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault();
                    if (outputFormatter != null)
                    {
                        outputFormatter.SupportedMediaTypes.Add("application/vnd.cgzl.hateoas+json");
                    }
    
                    var intputFormatter = option.InputFormatters.OfType<JsonInputFormatter>().FirstOrDefault();
                    if (intputFormatter != null)
                    {
                        intputFormatter.SupportedMediaTypes.Add("application/vnd.cgzl.post.create+json");
                        intputFormatter.SupportedMediaTypes.Add("application/vnd.cgzl.post.update+json");
                    }
    
                })
    }

    6、Action添加PUT方法:

            [HttpPut("{id}", Name = "UpdatePost")]
            [RequestHeaderMatchingMediaType("Content-Type", new[] { "application/vnd.cgzl.post.update+json" })]
            public async Task<IActionResult> UpdatePost(int id, [FromBody] PostUpdateResource postUpdate)
            {
                if (postUpdate == null)
                {
                    return BadRequest();
                }
    
                if (!ModelState.IsValid)
                {
                    return new MyUnprocessableEntityObjectResult(ModelState);
                }
    
                var post = await _postRepository.GetPostId(id);
                if (post == null)
                {
                    return NotFound();
                }
    
                post.LastModified = DateTime.Now;
                _mapper.Map(postUpdate, post);
    
                if (!await _unitOfWork.SaveAsync())
                {
                    throw new Exception($"Updating post {id} failed when saving.");
                }
                return NoContent();
            }
    View Code

    7、测试

    五、Patch(局部更新)

     

     

    1、 PostRepository 添加Update方法:

            public void Update(Post post)
            {
                _myContext.Entry(post).State = EntityState.Modified;
            }

    2、Action添加Patch方法:

            [HttpPatch("{id}", Name = "PartiallyUpdatePost")]
            public async Task<IActionResult> PartiallyUpdateCityForCountry(int id,
                [FromBody] JsonPatchDocument<PostUpdateResource> patchDoc)
            {
                if (patchDoc == null)
                {
                    return BadRequest();
                }
    
                var post = await _postRepository.GetPostByIdAsync(id);
                if (post == null)
                {
                    return NotFound();
                }
    
                var postToPatch = _mapper.Map<PostUpdateResource>(post);
    
                patchDoc.ApplyTo(postToPatch, ModelState);
    
                TryValidateModel(postToPatch);
    
                if (!ModelState.IsValid)
                {
                    return new MyUnprocessableEntityObjectResult(ModelState);
                }
    
                _mapper.Map(postToPatch, post);
                post.LastModified = DateTime.Now;
                _postRepository.Update(post);
    
                if (!await _unitOfWork.SaveAsync())
                {
                    throw new Exception($"Patching city {id} failed when saving.");
                }
    
                return NoContent();
            }
    View Code

    3、测试

     

    六、HTTP常用方法总结

      

  • 相关阅读:
    Guava的学习2
    Guava的学习1
    数据结构
    二叉搜索树的第k个结点
    滑动窗口的最大值
    僵尸进程和孤儿进程
    fork和vfork,exec
    扑克牌顺子
    字符流中第一个不重复的字符
    表示数值的字符串
  • 原文地址:https://www.cnblogs.com/fuyouchen/p/9596956.html
Copyright © 2020-2023  润新知