Fork me on GitHub

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常用方法总结

 

  

 

posted @ 2018-09-06 14:07  精进的小陈  阅读(2536)  评论(0编辑  收藏  举报