美文网首页ASP .NET Core Web Api + Angular
ASP .NET Core Web API_12_ POST P

ASP .NET Core Web API_12_ POST P

作者: xtddw | 来源:发表于2018-10-23 14:49 被阅读54次

    安全性&幂等性

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


    POST 添加资源

    不安全,不幂等

    • 参数[FromBody]
    • 返回201 Created
      * CreatedAtRoute(): 它允许响应里带着LocationHeader,其中包含着一个URI,通过这个URI就可以GET到我们刚刚创建好的资源。
    • HATEOAS
    1. PostAddResource
      public class PostAddResource
        {
            public string Title { get; set; }
            public string Body { get; set; }
         }
    
    1. MappingProfile
     public MappingProfile()
            {
                CreateMap<Post, PostResource>()
                    .ForMember(dest => dest.UpdateTime, opt => opt.MapFrom(src => src.LastModified));
                CreateMap<PostResource, Post>();
    
                CreateMap<PostAddResource,Post>();
    
            }
    
    1. Action中Post方法
          [HttpPost(Name ="CreatePost")]
            public async Task<IActionResult> Post([FromBody] PostAddResource postAddResource)
            {
                if (postAddResource == null)
                {
                    return BadRequest("not data!");
                }
    
                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 post data Failed!");
                }
    
                var resultResource = _mapper.Map<Post, PostResource>(newPost);
                
                //HATEOAS
                var links = CreateLinksForPost(newPost.Id);
                var linkedPostResource = resultResource.ToDynamic() as IDictionary<string, object>;
                linkedPostResource.Add("links", links);
    
                //return Ok(resultResource);//200
                return CreatedAtRoute("GetPost",new { id = linkedPostResource["Id"] },linkedPostResource); //201
            }
    

    Model 验证

    • 定义验证规则
    • 检查验证规则
    • 把验证错误信息发送给API消费者
    1. 内置验证:
      • DataAnnotation


      • ValidationAttribute
      • IValidatebleObject
    2. 第三方FluentValidation
      • 关注点分离(SOC,Seperation of Concerns)
      • 安装包
        * FluentValidation
        * FluentValidation.AspNetCore
      • 为每一个Resource建立验证器
        • 继承AbstractValidator<T>


      public class PostAddResourceValidator:AbstractValidator<PostAddResource>
        {
            public PostAddResourceValidator()
            {
                RuleFor(x => x.Title)
                    .NotNull()
                    .WithName("标题")
                    .WithMessage("{PropertyName}是必填的")
                    .MaximumLength(50)
                    .WithMessage("{PropertyName}的最大长度是{MaxLength}");
                RuleFor(x => x.Body)
                   .NotNull()
                   .WithName("正文")
                   .WithMessage("{PropertyName}是必填的")
                   .MinimumLength(50)
                   .WithMessage("{PropertyName}的最小长度是{MaxLength}");
            }
        }
    
    • 配置
    //注册FluentValidator
    services.AddTransient<IValidator<PostAddResource>, PostAddResourceValidator>();
    
     services.AddMvc(
                    options =>
                    {
                        options.ReturnHttpNotAcceptable = true; //开启406
                        //options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
    
                        //自定义mediaType
                        var outputFormatter = options.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault();
                        if (outputFormatter != null)
                        {
                            outputFormatter.SupportedMediaTypes.Add("application/vnd.enfi.hateoas+json");
                        }
                    })
                    .AddJsonOptions(options =>
                    {
                        options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                    })
                    .AddFluentValidation();
    
    • 验证
      • ModelStatus.IsValid
      • ModelState
        • 字典,包含Model的状态以及Model所绑定的验证
        • 对于提交的每个属性,它都包含了一个错误信息的集合
    • 返回:422 UnprocessableEntity
      • 验证错误信息在响应的body里面带回去
     if (!ModelState.IsValid)
                {
                    return UnprocessableEntity(ModelState);
                }
    
    • MediaType
      var inputFormatter = options.InputFormatters.OfType<JsonInputFormatter>().FirstOrDefault();
      if (inputFormatter!=null)
       {
                inputFormatter.SupportedMediaTypes.Add("application/vnd.enfi.post.create+json");
        }
    
    [HttpPost(Name ="CreatePost")]
    [RequestHeaderMatchingMediaType("Content-Type", new[] { "application/vnd.enfi.post.create+json" })]
    [RequestHeaderMatchingMediaType("Accept", new[] { "application/vnd.enfi.hateoas+json" })]
    public async Task<IActionResult> Post([FromBody] PostAddResource postAddResource)
    {
    ...
    }
    
    Headers
    Body
    Repson Body

    POST 一次性添加集合资源

    • 把整个集合看作一种资源
    • 参数[FromBody]IEnumerable<T>
    • 返回201,CreatedAtRoute(),带着ID的集合
    • GET方法参数为ID的集合,用于查询创建的集合资源
      • ArrayModelBinder:IModelBinder

    自定义验证错误返回结果

    • 满足Angular客户端表单验证要求:
      • 错误的类型:required,maxLength ...
    • MyUnprocessableEntityObjectResult
      • 继承:ObjectResult
      • ResourceValidationResult:Dictionary<string,IEnumerable<ResourceValidationError>>
    1. PostAddResourceValidator
    public class PostAddResourceValidator:AbstractValidator<PostAddResource>
     {
     public PostAddOrUpdateResourceValidator()
            {
            RuleFor(x => x.Title)
                    .NotNull()
                    .WithName("标题")
                    .WithMessage("required|{propertyName}是必填的")
                    
                    .MaximumLength(50)
                    .WithMessage("maxlength|{PropertyName}的最大长度是{MaxLength}");
                RuleFor(x => x.Body)
                   .NotNull()
                   .WithName("正文")
                   .WithMessage("required|{PropertyName}是必填的")
                   .MinimumLength(50)
                   .WithMessage("minlength|{PropertyName}的最小长度是{MinLength}");
        }
    }
    
    1. ResourceValidationError
     public class ResourceValidationError
        {
            public ResourceValidationError(string message,string validatorKey ="")
            {
               
                Message = message;
                ValidatorKey = validatorKey;
            }
    
            public string Message { get; private set; }
            public string ValidatorKey { get; private set; }
        }
    
    1. ResourceValidationResult
     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);
                    }
                }
            }
        }
    
    1. MyUnprocessableEntityObjectResult
     public class MyUnprocessableEntityObjectResult : UnprocessableEntityObjectResult
        {
            public MyUnprocessableEntityObjectResult(ModelStateDictionary modelState) : base(new ResourceValidationResult(modelState))
            {
                if (modelState == null)
                {
                    throw new ArgumentNullException(nameof(modelState));
                }
                StatusCode = 422;
            }
        }
    
    1. 使用
    if (!ModelState.IsValid)
    {
           return new MyUnprocessableEntityObjectResult(ModelState);
          //return UnprocessableEntity(ModelState);
    }
    
    满足Angular响应要求

    DELETE

    • 参数: ID
    • 返回: 204 No Content
    • 不安全
    • 幂等:多次请求的副作用和单次请求的副作用是一样的,每次发送DELETE请求后,服务器的状态是一样的
      [HttpDelete("{id}",Name ="DeletePost")]
            public async Task<IActionResult> DeletePost(int id)
            {
                var post = await _postRepository.GetPostByIdAsync(id);
                if (post ==null)
                {
                    return NotFound();
                }
                _postRepository.DeletePost(post);
                if (!await _unitOfWork.SaveAsync())
                {
                    throw new Exception($"Deleting post {id} failed  when saving.");
                }
                return NoContent();
            }
    

    PUT 整体更新

    • 参数: ID [FromBody]不需要ID属性
      • 单独的Resource Model.
    • 返回: 204 No Content 202 OK
    • 不安全
    • 幂等
    • 整体更新 容易引起问题
    • 集合资源整体更新
    1. 抽象父类
    public class PostAddOrUpdateResource
    {
            public string Title { get; set; }
            public string Body { get; set; }
    }
    
    1. 继承
    public class PostUpdateResource:PostAddOrUpdateResource
    {
    }
    
    1. 修改FluentValidator
     public class PostAddOrUpdateResourceValidator<T>:AbstractValidator<T> where  T:PostAddOrUpdateResource
    {
      ......
    }
    
    1. 修改注册
     //注册FluentValidator
     services.AddTransient<IValidator<PostAddResource>, PostAddOrUpdateResourceValidator<PostAddResource>>();
     services.AddTransient<IValidator<PostUpdateResource>, PostAddOrUpdateResourceValidator<PostUpdateResource>>();
    
    1. 添加mappingProfile
      CreateMap<PostUpdateResource,Post>();
    

    6.Action>>>Post

     [HttpPut("{id}",Name ="UpdatePost")]
      //注意要在mvc中注册 Content-Type
     [RequestHeaderMatchingMediaType("Content-Type", new[] { "application/vnd.enfi.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.GetPostByIdAsync(id);
                if (post == null)
                {
                    return NotFound("Cannot found the data for update.");
                }
    
                post.LastModified = DateTime.Now;
                _mapper.Map(postUpdate, post);
                if (!await _unitOfWork.SaveAsync())
                {
                    throw new Exception($"Deleting post {id} failed  when updating.");
                }
                return NoContent();
            }
    

    PATCH 局部更新

    • application/json-patch+json
      PATCH
    • 参数: ID [FromBody] JsonPatchDocument<T>
    • patchDoc.ApplyTo()
    • 返回: 204 No Content 202 OK
    • 不安全
    • 不幂等
    1. Repository中添加Update方法
     public void UpdatePost(Post post)
            {
                _applicationContext.Entry(post).State = EntityState.Modified;
            }
    
    1. Action 中添加Update方法
    [HttpPatch("{id}",Name ="PartiallyUpdatePost")]
            public async Task<IActionResult> PartiallyUpdatePost(int id,[FromBody] JsonPatchDocument<PostUpdateResource> pathDoc)
            {
                if (pathDoc ==null)
                {
                    return BadRequest();
                }
                var post = await _postRepository.GetPostByIdAsync(id);
                if (post ==null)
                {
                    return NotFound("Cannot found the data for update.");
                }
                var postToPatch = _mapper.Map<PostUpdateResource>(post);
                pathDoc.ApplyTo(postToPatch, ModelState);
                TryValidateModel(postToPatch);
                if (!ModelState.IsValid)
                {
                    return new MyUnprocessableEntityObjectResult(ModelState);
                }
                _mapper.Map(postToPatch, post);
                post.LastModified = DateTime.Now;
                _postRepository.UpdatePost(post);
                if (!await _unitOfWork.SaveAsync())
                {
                    throw new Exception($"post {id} failed  when partially updating patch.");
                }
                return NoContent();
            }
    
    Headers
    Body

    总结

    Http常用方法

    相关文章

      网友评论

        本文标题:ASP .NET Core Web API_12_ POST P

        本文链接:https://www.haomeiwen.com/subject/knzozftx.html