ASP.NET Core 基础(十二)—— Web API概述

此文是在官方文档的基础上做的个人笔记,一些简单的内容就没用再列出来了,参考官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/web-api/?view=aspnetcore-5.0

1.web api返回值类型

框架提供以下三种返回类型:

  1. 特定类型(如string、自定义对象、List等)
  2. IActionResult
  3. ActionResult<T>

1.1特定类型

1.1.1返回一个List:

[HttpGet]
public List<Product> Get() =>_repository.GetProducts();

也可以返回string等基础类型。

1.1.2返回IEnumerable<T>IAsyncEnumerable<T>:

在ASP.NET Core 2.2及更低版本中,如果返回值是IEnumerable<T>,则会以同步的方式枚举结果并返回。所以应当使用ToListAsync()

public async Task<IEnumerable<Product>> GetOnSaleProducts() =>await _context.Products.Where(p => p.IsOnSale).ToListAsync();

在Core 3.0及更高版本中,会异步缓冲Where的结果,然后再返回,所以不存在上述性能问题。当然也可以使用IAsyncEnumerable<T>来保证异步迭代。

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
    var products = _repository.GetProductsAsync();
    await foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

1.2 IActionResult

与返回特定类型相比,可以返回400,404, BadRequest(), BadRequestResult对象等更多类型。因为返回的类型不特定,所以要配合[ProducesResponseType]使用,来告诉Swagger都有哪些返回类型。

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Product))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById(int id)
{
    if (!_repository.TryGetProduct(id, out var product))
    {
        return NotFound();
    }
    return Ok(product);
}

1.3 ActionResult<T>

相对于IActionResult来说有以下几个区别:

  1. 可以不用给[ProducesResponseType]特性,指定Type了。因为可以从泛型T推断出来。
  2. ActionResult(就是诸如NotFound(), Ok(product)之类的方法)和返回类型T都转换为ActionResult<T>。所以可以直接return product不必非得写成return Ok(product)
[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }
    await _repository.AddProductAsync(product);
    return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}

2.返回值格式

2.1固定格式

一些类将会返回固定格式的数据,忽略请求头中的accept字段,如JsonResult将返回application/jsonContentResult将返回text/plain。内置的Ok(product)方法返回json格式数据。

2.2格式协商

框架使用的默认格式是json。当请求包含accept请求头时,就会发生格式协商。格式协商特点如下:

  • ObjectResult实现
  • 操作结果帮助程序,如Ok(product)CreateAtAction等也是基于ObjectResult

如果返回的是单个的POCO对象(如product),则会自动封装为ObjectResult。若返回null,则返回204 No Content响应。

框架默认支持三种格式:

  1. application/json
  2. text/json
  3. text/plain

2.2.1 Accept请求头

如果包含了这个请求头,框架会按顺序枚举请求头中的媒体类型,然后找到可以生成这种格式要求的格式化程序。

如果没有找到格式化程序且MvcOptions.ReturnHttpNotAcceptable设置为true,就返回406 Not Acceptable,如果没设置,则使用第一个可以生成响应的格式化程序(默认json)。

与浏览器的协商
与一般的客户端请求不同,浏览器都会提供accept请求头。框架如果检测到请求来自浏览器,则会忽略accept请求头而且如果没有其它项进行了配置则会返回json格式。这样的好处是浏览器的体验将更加一致(这是微软说的┗( ▔, ▔ )┛)。

如果要采用浏览器传过来的accept头,则设置RespectBrowserAcceptHeadertrue

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        options.RespectBrowserAcceptHeader = true; // false by default
    });
}

2.2.2 支持XML格式的返回

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
        .AddXmlSerializerFormatters();
}

使用XmlSerializer来序列化结果,控制器会基于accept头来决定返回结果的格式。

2.2.3 配置基于System.Text.Json的格式化程序

System.Text.Json序列化时默认为camelCase, 如果希望使用PascalCase则配置JsonSerializerOptions

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
            .AddJsonOptions(options => 
            {
                //使用PacalCase
                options.JsonSerializerOptions.PropertyNamingPolicy = null;
                //自定义json转换器
                options.JsonSerializerOptions.Converters.Add(new MyCustomJsonConverter());
            });
}

但是如果你返回的是框架自带的Problem0方法,即使你配置了使用PascalCase, 也会返回camelCase:

[HttpGet("error")]
public IActionResult GetError()
{
    return Problem("Something went wrong!");//会返回一个错误码为500的响应
}

2.2.4 配置基于Newtonsoft.Json的格式化程序

core 3.0之前的格式化器都是用的Newtonsoft.Json, 后续处于性能原因改为了Syste.Text.Json。所以如果你想在新版本的.net中继续使用Newtonsoft.Json可以添加nuget包Microsoft.AspNetCore.Mvc.NewtonsoftJson. 然后进行如下的配置:

services.AddControllers().AddNewtonsoftJson();

这段代码表示web api, mvc, razor的以下功能使用Newtonsoft.Json

  • 用于读取和写入json的格式化程序
  • JsonResult
  • Json Patch
  • IJsonHelper
  • TempData

以下情况适合使用Newtonsoft.Json

  1. 需要使用Newtonsoft.Json的特有功能,如[JsonProperty][JsonIgnore]
  2. 需要自定义序列化设置
  3. 生成OpenApi文档

2.2.5 限定响应格式

考虑使用[Produces]过滤器,与大多数过滤器一样,可以用在Controller、Action和全局范围上。

[ApiController]
[Route("[controller]")]
[Produces("application/json")]
public class WeatherForecastController : ControllerBase
{}

上述代码将这个controller下所有的action强制返回json格式。

2.2.6 一些特殊情况

string类型的返回默认为text/plain, 如果是通过accept请求头请求话,也有可能返回text/html。可以通过删除StringOutputFormatter来删除此默认行为。当返回结果是null时,框架会返回204 No Content,也可以通过删除HttpNoContentOutputFormatter删除此默认行为:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        // requires using Microsoft.AspNetCore.Mvc.Formatters;
        options.OutputFormatters.RemoveType<StringOutputFormatter>();
        options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
    });
}

当删除了StringOutputFormatter之后,框架会将string格式化为json返回,如果删除了json格式化器,框架就返回xml,如果xml格式化器也没有,则返回406 Not Acceptable.

当删除了HttpNoContentOutputFormatter之后,json格式化器会返回正文为null的数据。xml格式化器返回空的xml元素。

2.3 在URL中指定响应格式

客户端可以在请求的URL中设置服务端要响应的格式。服务端可以通过[FormatFilter]过滤器解析这种格式的请求:

[Route("api/[controller]")]
[ApiController]
[FormatFilter]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}.{format?}")]
    public Product Get(int id)
    {}
}
路由格式化器
/api/products/5默认输出格式化器
/api/products/5.jsonJSON 格式化器(如配置)
/api/products/5.xmlXML格式化器(如配置)

3.自定义格式化器

框架提供了对json和xml格式的输入和输出格式化器,为纯文本提供了输入格式化器但是没有提供输入格式化器,但是可以自定义。

参考 TextPlainInputFormatter :https://github.com/aspnet/Entropy/blob/master/samples/Mvc.Formatters/TextPlainInputFormatter.cs

自定义格式化器的一般步骤:

  1. 创建输入格式化类
  2. 创建输入格式化类
  3. 将上面两个类的实例添加到MvcOptionsInputFormattersOutputFormatters集合中。

3.1创建输入、输出格式化类

  1. 首先,从相应的基类中派生。一般文本格式化器从TextOutputFormatterTextInputFormatter派生,二进制格式化器从OutputFormatterInputFormatter派生。
  2. 在构造函数中指定媒体类型和编码
  3. 重写CanReadTypeCanWriteTypeReadRequestBodyAsyncWriteReponseBodyAsync方法
public class VcardOutputFormatter : TextOutputFormatter
{
    public VcardOutputFormatter()
    {
        //指定媒体类型和编码
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
        SupportedEncodings.Add(Encoding.UTF8);
        SupportedEncodings.Add(Encoding.Unicode);
    }

    protected override bool CanWriteType(Type type)
    {
        return typeof(Contact).IsAssignableFrom(type) ||
            typeof(IEnumerable<Contact>).IsAssignableFrom(type);
    }

    public override async Task WriteResponseBodyAsync(
        OutputFormatterWriteContext context, Encoding selectedEncoding)
    {
        var httpContext = context.HttpContext;
        var serviceProvider = httpContext.RequestServices;

        var logger = serviceProvider.GetRequiredService<ILogger<VcardOutputFormatter>>();
        var buffer = new StringBuilder();

        if (context.Object is IEnumerable<Contact> contacts)
        {
            foreach (var contact in contacts)
            {
                FormatVcard(buffer, contact, logger);
            }
        }
        else
        {
            FormatVcard(buffer, (Contact)context.Object, logger);
        }

        await httpContext.Response.WriteAsync(buffer.ToString(), selectedEncoding);
    }

    private static void FormatVcard(
        StringBuilder buffer, Contact contact, ILogger logger)
    {
        buffer.AppendLine("BEGIN:VCARD");
        buffer.AppendLine("VERSION:2.1");
        buffer.AppendLine($"N:{contact.LastName};{contact.FirstName}");
        buffer.AppendLine($"FN:{contact.FirstName} {contact.LastName}");
        buffer.AppendLine($"UID:{contact.Id}");
        buffer.AppendLine("END:VCARD");

        logger.LogInformation("Writing {FirstName} {LastName}",
            contact.FirstName, contact.LastName);
    }
}

如果action的返回结果不是简单的字符串而是一个类的子类,而且你的格式化器只能格式化其中的某一个子类,则考虑重写CanWriteResult方法。方法的入参是一个object,你可以检查这个对象的类型。如果action返回的是IActionResult则不需要重写CanWriteResult

3.2 配置使用自定义的格式化器

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        options.InputFormatters.Insert(0, new VcardInputFormatter());
        options.OutputFormatters.Insert(0, new VcardOutputFormatter());
    });
}

4.使用web api分析器

作用:当你的action返回了未在[ProducesResponseType]中声明的状态码或者未声明的返回结果或者使用了一个永远不会返回的状态码或者包含显示的模型检查时,会给出warning

从core 3.0开始分析器内置在SDK中,如果要使用,则在csproj文件中添加:

<PropertyGroup>
 <IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
</PropertyGroup>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JimCarter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值