WebAPI 基础

1. ApiController

  • WebAPI中Controller直接即继承自ControllerBase。在ASP.NET Core 2.1之后引入[ApiController]用于批注 Web API 控制器类。[ApiController]特性通常结合ControllerBase来为控制器启用特定 REST 行为。

    [Route("api/[controller]")]
    [ApiController]
    public class ProductsController : ControllerBase
    
  • 在 ASP.NET Core 2.2 或更高版本中,可将[ApiController]特性应用于程序集。以这种方式进行注释,会将 web API 行为应用到程序集中的所有控制器。 建议将程序集级别的特性应用于 Startup 类。

    [assembly: ApiController]
    namespace WebApiSample.Api._22
    {
        public class Startup
        {
        }
    

2. 路由匹配

2.1 RouteAttributeHttpMethodAttribute

WebAPI中必须为每个Controller使用[Route]特性进行路由设定,而不能通过UseMvc中定义的传统路由或通过Startup.Configure中的UseMvcWithDefaultRoute配置路由。
Controller设定路由方式一样,我们也可以在Action方法上使用[Route]单独设定路由,除了[Route],我们也可以使用HttpMethodAttribute设定路由,用法相同,HttpMethodAttribute包括[HttpGet][HttpPost][HttpPut][HttpDelete]等。Action路由建立在Controller路由之上。
使用HttpMethodAttribute定义路由时会同时限制Action方法的HTTP访问方式,如果单纯想为Action方法设定路由同时允许多种HTTP访问方式,可以是使用[Route]配置路由。
路由不区分大小写。

[Route("api/test")]
public class TestController : ControllerBase
{
    // GET api/test
    [HttpGet]
    public ActionResult<string> Get()
    {
        return nameof(Get);
    }
    //GET api/test/1
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return nameof(Get) + id;
    }
    //GET api/test/getbyname/colin
    [HttpGet("GetByName/{name?}")]
    public ActionResult<string> Get(string name)
    {
        return "GetByName" + name;
    }
    //GET api/test/colin/18
    [HttpGet("{name}/{age}")]
    public ActionResult<string> Get(string name,int age)
    {
        return nameof(Get) + name + age;
    }
}

2.2 Restful 路由

WebAPI默认路由使用Restful风格,按照请求方式进行路由,不作标记的情况下,Action方法名会按照请求方式进行StartWith匹配。所以的Get()GetById()GetXXX()没有任何区别。如果使用[HttpGet]标记了Action方法,则方法名任意取,不必以GET开头。同理,POSTPUTDELETE亦是如此。

2.3 自定义路由

2.3.1 Restful 之殇

完全符合Restful风格的API在很多业务常见下并不能满足需求。如之前所说,把所有业务抽象为CRUD操作并不现实,简单通过HTTP状态码也不容易区分处理结果。除此之外,仅通过简单几种谓词语意进行路由在难以满足复杂业务需求。如,根据ID查询用户、根据用户名查询用户、根据手机号查询用户。

// 错误方式,调用报错
[Route("api/test")]
public class TestController : ControllerBase
{
    [HttpGet("{id}")]
    public ActionResult<User> GetById(int id)
    {
        return Users.FirstOrDefault(u=>u.Id==id);
    }
    [HttpGet("{userName}")]
    public ActionResult<User> GetByUserName(string userName)
    {
        return Users.FirstOrDefault(u=>u.UserName==userName);
    }
    [HttpGet("{phoneNumber}")]
    public ActionResult<User> GetByPhoneNumber(string phoneNumber)
    {
        return Users.FirstOrDefault(u=>u.PhoneNumber==phoneNumber);
    }
}

以上代码可以编译通过,但由于三个Action匹配相同路由规则,所以GET请求~/api/test/xxx 时会出现歧义而抛出AmbiguousMatchException

2.3.2 自定义Action路由

此时我们可以通过前面提到的RouteAttributeHttpMethodAttribute来为每个Action设置特定路由。

// 自定义Action路由
[Route("api/test")]
public class TestController : ControllerBase
{
    //GET api/test/getbyid/1
    [HttpGet("GetById/{id}")]
    public ActionResult<User> GetById(int id)
    {
        return Users.FirstOrDefault(u=>u.Id==id);
    }
    
    //GET api/test/getbyusername/colin
    [HttpGet("GetByUserName/{userName}")]
    public ActionResult<User> GetByUserName(string userName)
    {
        return Users.FirstOrDefault(u=>u.UserName==userName);
    }
    //GET api/test/getbyphonenumber/110
    [HttpGet("GetByPhoneNumber/{phoneNumber}")]
    public ActionResult<User> GetByPhoneNumber(string phoneNumber)
    {
        return Users.FirstOrDefault(u=>u.PhoneNumber==phoneNumber);
    }
}
2.3.3 回归MVC路由

以上为每个Action单独配置路由后解决了Restful遇到的问题。不难发现当每个Action方法路由名称恰好是自身方法名时,我们便可以通过Action名称来访问对应接口,这与MVC路由方式效果一致。
单独为每个Action方法都配置路由较为繁琐,我们可以仿照MVC路由方式直接配置Controller路由,路由效果一致,但使用跟简单。

// 自定义Controller路由
[Route("api/test/{Action}")]
public class TestController : ControllerBase
{
    //GET api/test/getbyid/1
    [HttpGet("{id?}")]
    public ActionResult<User> GetById(int id)
    {
        return Users.FirstOrDefault(u=>u.Id==id);
    }
    //GET/POST/PUT/DELETE api/test/getbyusername/colin
    [Route("{userName}")]
    public ActionResult<User> GetByUserName(string userName)
    {
        return Users.FirstOrDefault(u=>u.UserName==userName);
    }
    //GET api/test/getbyphonenumber?phoneNumber=110
    [HttpGet]
    public ActionResult<User> GetByPhoneNumber(string phoneNumber)
    {
        return Users.FirstOrDefault(u=>u.PhoneNumber==phoneNumber);
    }
}

Restful风格路由与MVC路由只是匹配Action方法方式不同,MVC路由通过Action方法名定位要比Restful通过谓词语意定位更加多变,更容易应付复杂的业务场景。

3. API参数

GETPOSTPUTDELETE等所有请求方式均可使用 URL参数 和 对象参数 进行参数传递。
GETDELETE请求通常传递数据量较少,多使用URL参数。POSTPUT请求通常传递数量较大,多使用对象参数。

3.1 URL参数

简单参数有两种,QueryString参数和路由参数,这两种都参数以不同形式体现在URL中,所以我们统称为URL参数。
在参数少且简单对安全性要求不高的情况下,可以使用URL参数。

[Route("api/test")]
public class TestController : ControllerBase
{
    //GET api/test?name=colin&age=18
    [HttpGet]
    public ActionResult<string> Get(string name, int age)
    {
        return name + age;
    }
    //DELETE api/test/1
    [HttpDelete("{id}")]
    public ActionResult Delete(int id)
    {
        return NoContent();
    }
}

3.2 对象参数

参数内容多且复杂或安全性较高的情况下,在API中接收参数时我们常把参数字段封装到一个参数模型类中。使用非URL参数而不在服务端封装对象会遇到很多麻烦,不建议使用。
客户端传递对象参数的方式有很多中,一般需要约定Content-Type报文头。服务端接收对象参数常使用[FromXXX]特性。

特性ContentType传参方式
[FromQuery]-?name=colin&age=18
[FromHeader]- 或 application/x-www-form-urlencodedmultipart/form-data?name=colin&age=18key-value
[FromForm]multipart/form-dataapplication/x-www-form-urlencodedname-value
[FromBody] 或 无标记application/json{name:'colin',age:18}
[Route("api/test")]
public class TestController : ControllerBase
{
    [HttpPost]
    public ActionResult Post([FromForm] Person p)
    {
        return CreatedAtAction(nameof(Post), new {id = p.Id}, p);
    }
    [HttpPut("{id}")]
    public ActionResult Put(int id, [FromBody] Person p)
    {
        return NoContent();
    }

在这里插入图片描述
在这里插入图片描述

JSON

ContentTypeapplciation/json时,传递参数必须是JSON格式

按照JSON官网的规范(“A value can be a string in double quotes, or a number, or true or false or null, or an object or an array. These structures can be nested.”),JSON可以直接传递字符串、数字和布尔三种简单类型。需要特别注意的是,字符串需要包裹在双引号直接(双引号作为字符串的一部分)。

[HttpPost]
public void Post([FromBody] string value)
{
}

在这里插入图片描述
在这里插入图片描述

4. 返回值

ASP.NET Core 提供以下 Web API 控制器操作返回类型选项:

  • 特定类型
  • IActionResult
  • ActionResult<T>
    多数情况下返回数据时统一使用ActionResult<T>类型。T是实际属数据类型,在Action方法中编码时直接返回T类型数据即可。ASP.NET Core 自动将对象序列化为JSON,并将 JSON 写入响应消息的正文中。
    三种返回类型具体区别和使用参见官方文档

5. 异常处理

5.1 业务性错误

简单的错误可以直接使用HttpStatusCode返回,如请求资源不能存在直接返回NotFound(404)即可。
较为复杂的业务错误,如,“用户年龄不合法”、“Id不存在等”,这种情况HttpStatusCode不足以满足业务需要,
一般我们可以自定义一个统一的返回对象来做详细说明。

public interface IApiResult{}
public class ApiResult<T>:IApiResult
{
    /// <summary>
    /// 业务码。可自定义一套业务码标准
    /// </summary>
    public int Code { get; set; } = 200;
    /// <summary>
    /// 消息。一般可用于传输错误消息
    /// </summary>
    public string Message { get; set; }
    /// <summary>
    /// 数据内容。一般为实际请求数据,如Json
    /// </summary>
    public T Content { get; set; }
    public ApiResult(int code, string message, T content)
    {
        Code = code;
        Message = message;
        Content = content;
    }
}

使用方式如下:

[HttpGet("{age}")]
public ActionResult<IApiResult> Get(int age)
{
    if (age < 18||age>60)
    {
        return new ApiResult<string>(0,"年龄超限",null);
    }
    else
    {
        return new ApiResult<string>(1,"OK","123");    
    } 
}

5.2 常规异常处理

在API代码中做好必要的异常捕捉和处理,如用户请求参数合法性校验等。一般API中只做简单的数据采集校验,响应和格式化返回数据等工作,复杂的业务逻辑处理是业务逻辑层的工作,一般在BLL中做异常捕获和处理。

5.3 全局异常过滤器

全局未处理异常可以通过异常过滤器来进行捕捉处理。
自定义异常过滤器。

public class MyAsyncExceptionFilter : IAsyncExceptionFilter
{
    private ILogger _logger;
    public MyAsyncExceptionFilter(ILogger<MyAsyncExceptionFilter> logger)
    {
        _logger = logger;
    }
    public async Task OnExceptionAsync(ExceptionContext context)
    {
        context.ExceptionHandled = true;
        var msg = context.Exception.Message;
        _logger.LogError(msg);
        context.Result = new ObjectResult(new ApiResult<string>(500, msg, null)) {StatusCode = 500};
        await Task.CompletedTask;
    }
}

Startup中注册过滤器。

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add<MyAsyncExceptionFilter>();
    });
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值