ASP .NET Core Web Api + Angular

ASP .NET Core Web API_12_ POST P

2018-10-23  本文已影响54人  xtddw

安全性&幂等性

POST 添加资源

不安全,不幂等

  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 验证

  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();
 if (!ModelState.IsValid)
            {
                return UnprocessableEntity(ModelState);
            }
  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 一次性添加集合资源

自定义验证错误返回结果

  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

  [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 整体更新

  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 局部更新

  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常用方法
上一篇 下一篇

猜你喜欢

热点阅读