.netCore webApi相关

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
关于博主:评论和私信会在第一时间回复。或者[直接私信]我。
版权声明:转载请注明出处!
声援博主:如果您觉得文章对您有帮助,关注点赞, 您的鼓励是博主的最大动力!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354