美文网首页
.netCore webApi相关

.netCore webApi相关

作者: wwmin_ | 来源:发表于2021-02-12 20:39 被阅读0次

    controller:

    • 输入参数
      模型绑定
      接口的输入参数就是通过模型绑定将 HTTP 请求中的值映射到参数中,模型绑定有以下六种:

    [FromRoute]:通过路由的 URL 中取值,可以自动推断;

    [FromQuery]:获取 URL 地址中的参数,可以自动推断;

    [FromBody]:从HTTP Body取值,通常用于取JSON, XML,可以自动推断;

    [FromHeader]:获取 Request Header 中的参数信息,需要指定

    [FromForm]:获取 Content-Type 为 multipart/form-data 或 application/x-www-form-urlencoded 类型的参数,需要指定

    [FromServices]:获取依赖注入的参数,依赖注入默认是使用构造函数注入,但Controller 可能会因为每个Action用到不一样的 Service 导致很多参数,所以也可以在 Action 注入Service,需要指定。

    • 参数验证
      参数验证是非常重要的,否则本来是 4XX 的问题就会变成 5XX 的问题,参数验证有这么几种:

    Data Annotations

    自定义 Attribute

    实现 IValitableObject 接口

    使用第三方的验证库,比如 FluentValidation

    • Data Annotations
      1、在 User 的实体类上添加相关特性
    public class User
    {
        [Required(ErrorMessage = "姓名不能为空")]
        public string  Name { get; set; }
    
        [EmailAddress(ErrorMessage = "邮件格式不正确")]
        public string  Email { get; set; }
    }
    

    有关更多的 Data Annotations 特性的使用,可以参考官方文档:https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations?view=netcore-3.1

    • IValitableObject 接口
      1、将 User 类继承 IValitableObject 接口,并实现 Validate 方法,代码如下:
    public class User: IValidatableObject
    {
        [Required(ErrorMessage = "姓名不能为空")]
        public string  Name { get; set; }
    
        [EmailAddress(ErrorMessage = "邮件格式不正确")]
        public string  Email { get; set; }
    
        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (Name == Email)
            {
                yield return new ValidationResult("名称不能和邮箱相等",
                    new []{nameof(Name),nameof(Email)});
            }
        }
    }
    

    自定义 Attribute
    自定义 Attribute 功能和 IValitableObject 接口类似,但可以作用于类级别也能用于属性级别,更加灵活。

    1、创建 NameNotEqualEmailAttribute 类,用来实现判断 User 类中的名称和邮箱不能相等

    public class NameNotEqualEmailAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, 
            ValidationContext validationContext)
        {
            var user = validationContext.ObjectInstance as User;
            if (user.Name == user.Email)
            {
                return new ValidationResult("名称不能和邮箱相等",
                    new []{nameof(User)});
            }
            return ValidationResult.Success;
        }
    }
    

    2、在 User 类上添加此特性

    [NameNotEqualEmail]
    public class User
    {
        [Required(ErrorMessage = "姓名不能为空")]
        public string  Name { get; set; }
    
        [EmailAddress(ErrorMessage = "邮件格式不正确")]
        public string  Email { get; set; }
    }
    

    FluentValidation
    FluentValidation 就不多做介绍了,可以参见官方文档:https://fluentvalidation.net/

    ModelBinder
    ModelBinder 是自定义模型绑定器,可以对入参的类型进行一些转换,比如,参数中传递 001,002 这样的字符串,在接口中使用 IEnumerable来进行接收。
    1、创建 StringToListModelBinder 类,如下:

    public class StringToListModelBinder: IModelBinder
    {
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (!bindingContext.ModelMetadata.IsEnumerableType)
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return Task.CompletedTask;
        }
    
        var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString();
        if (string.IsNullOrWhiteSpace(value))
        {
            bindingContext.Result = ModelBindingResult.Success(null);
            return Task.CompletedTask;
        }
    
        var elementType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];
        var converter = TypeDescriptor.GetConverter(elementType);
    
        var values = value.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)
            .Select(x => converter.ConvertFromString(x.Trim())).ToArray();
    
        var typedValues = Array.CreateInstance(elementType, values.Length);
    
        values.CopyTo(typedValues,0);
    
        bindingContext.Model = typedValues;
    
        bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
        return Task.CompletedTask;
    }
    

    2、在 UserController 类中创建 GetUsersByIds 方法

    [HttpGet("ids")]
    public ActionResult<List<User>> GetUsersByIds(
        [ModelBinder(BinderType = typeof(StringToListModelBinder))]IEnumerable<string> ids)
    {
        if (ids == null)
        {
            return BadRequest();
        }
        return Ok();
    }
    

    返回 XML 格式
    尽管使用 Web API 通常都是使用 JSON 格式,但有些时候需要返回 XML 格式,默认情况下,即使请求头中添加了 Accept=application/xml,接口依然会返回 JSON 格式的结果,想要返回 XML 格式,修改 Startup 类的 ConfigureServices 方法即可。

    services.AddControllers().AddXmlDataContractSerializerFormatters();

    错误信息统一返回
    之前的文章中有讲过使用过滤器的方式来做到结果的统一返回。这里介绍另一种方式,使用 ConfigureApiBehaviorOptions ,可以让我们自定义错误信息的返回内容和格式。修改 Startup 类中的 ConfigureServices 方法

    services.AddControllers()
                .AddXmlDataContractSerializerFormatters()
                .ConfigureApiBehaviorOptions(setup =>
                {
                    setup.InvalidModelStateResponseFactory = context =>
                    {
                        var details = new ValidationProblemDetails(context.ModelState)
                        {
                            Type = "http://api.oec2003.com/help",
                            Title = "实体验证错误",
                            Status = StatusCodes.Status422UnprocessableEntity,
                            Detail = "看详细",
                            Instance = context.HttpContext.Request.Path,
                        };
                        details.Extensions.Add("trachid",context.HttpContext.TraceIdentifier);
    
                        return new UnprocessableEntityObjectResult(details)
                        {
                            ContentTypes = { "application/problem+json" }
                        };
                    };
                });
    

    更多详细信息可以看文档:https://docs.microsoft.com/zh-cn/aspnet/core/web-api/handle-errors?view=aspnetcore-3.1

    数据塑形
    在 API 中返回结果到前端时,一般不会直接将底层的 Entity 返回,会创建相对应的 Dto,比如,用户的 Entity 是这样的

    public class User
    {
        public string  Name { get; set; }
    
        public string  Email { get; set; }
    
        public string  Password { get; set; }
    }
    

    创建 User 的 Dto 类 UserDto,如下

    public class UserDto
    {
        public string  Name { get; set; }
        public string  Email { get; set; }
    }
    

    在接口的 Action 方法中使用 AutoMapper 做下转换

    [HttpGet("{userId}")]
    public ActionResult<UserDto> GetUserById(string userId)
    {
        User user = new User()
        {
            Name = "oec2003",
            Email = "oec2003@qq.com",
            Password = "123456"
        };
        return Ok(base.Mapper.Map<UserDto>(user));
    }
    

    同样的接口在前端不同的场景下需要返回不一样的字段数据,一种方式是创建很多不同的接口,返回不同的 Dto 的结果,但这样做非常繁琐,可以通过 ExpandoObject 来实现按客户端的需要进行返回结果,具体步骤如下:

    1、因为获取用户列表的接口方法的是 List,所以先创建一个 IEnumerable 的扩展方法,该扩展方法用于根据传进的字段参数来组装返回的结果,代码如下:

    public static class IEnumerableExtension
    {
        public static IEnumerable<ExpandoObject> GetData<T>
            (this IEnumerable<T> source, string fields)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }
    
            var objectList = new List<ExpandoObject>(source.Count());
            var propertyInfoList = new List<PropertyInfo>();
    
            if (string.IsNullOrWhiteSpace(fields))
            {
                var propertyInfos = typeof(T).GetProperties(BindingFlags.Public |
                                                            BindingFlags.Instance);
                propertyInfoList.AddRange(propertyInfos);
            }
            else
            {
                var fieldSplit = fields.Split(',');
                foreach (var field in fieldSplit)
                {
                    var propertyName = field.Trim();
                    var propertyInfo = typeof(T).GetProperty(propertyName,
                        BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
                    if (propertyInfo == null)
                    {
                        throw  new Exception($"属性名:{propertyName} 没有找到");
                    }
    
                    propertyInfoList.Add(propertyInfo);
                }
            }
    
            foreach (T t in source)
            {
                var obj=new ExpandoObject();
                foreach (var propertyInfo in propertyInfoList)
                {
                    var value = propertyInfo.GetValue(t);
                    ((IDictionary<string, object>) obj).Add(propertyInfo.Name, value);
                }
                objectList.Add(obj);
            }
            return objectList;
        }
    }
    

    2、创建获取用户列表的 Action 方法

    [HttpGet]
    public ActionResult GetUsers([FromBody]string fields)
    {
        var userList =new List<User>() 
        {
            new User(){ Name = "oec2003",Email = "oec2003@qq.com",Password = "123456"},
            new User(){ Name = "oec2004",Email = "oec2004@qq.com",Password = "123456"},
            new User(){ Name = "oec2004",Email = "oec2004@qq.com",Password = "123456"}
        };
        var returnResult = base.Mapper.Map<List<UserDto>>(userList);
        //使用扩展方法按需获取
        return Ok(returnResult.GetData(fields));
    }
    

    本文作者:wwmin
    微信公众号: DotNet技术说
    本文链接:https://www.jianshu.com/p/bf3000755caf
    关于博主:评论和私信会在第一时间回复。或者[直接私信]我。
    版权声明:转载请注明出处!
    声援博主:如果您觉得文章对您有帮助,关注点赞, 您的鼓励是博主的最大动力!

    相关文章

      网友评论

          本文标题:.netCore webApi相关

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