ASP.NET Core应用程序3:高级Web服务特性

1 准备工作

添加SuppliersController控制器。

    [ApiController]
    [Route("api/[controller]")]
    public class SuppliersController:ControllerBase
    {
        private DataContext _context;
        public SuppliersController(DataContext dataContext)
        {
            _context = dataContext;
        }
        [HttpGet("{id}")]
        public async Task<Supplier> GetSupplier(long id)
        {
            Supplier supplier = await _context.Suppliers.FindAsync(id);
            return supplier;
        }
    }

2 处理相关数据

当使用Include方法时,EFCore可通过数据库中的关系填充导航属性。

        [HttpGet("{id}")]
        public async Task<Supplier> GetSupplier(long id)
        {
            Supplier supplier = await _context.Suppliers
                .Include(s => s.Products)
                .FirstAsync(s => s.SupplierId == id);
            return supplier;
        }

但是这样会导致报错:对象循环。因为Suppliers和Product对象的导航属性之前创建了一个循环引用。

打破循环引用最简单的办法如下。

            foreach (Product p in supplier.Products)
            {
                p.Supplier = null;
            };
            return supplier;

3 支持HTTP Patch方法

使用Patch请求只向Web服务发送更改,而不是完整地替换对象。

3.1 理解JSON Patch

客户端会在HTTP Patch请求中发送Web服务Json数据,如下:

[
    {"op":"replace","path":"Name","value":"abc"},
]

3.2 安装和配置JSON Patch

安装JSON Patch包

dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson --version 3.1.1

在Startup方法中启用序列化器。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<DataContext>(opts =>
            {
                opts.UseSqlServer(Configuration["ConnectionStrings:ProductConnection"]);
                opts.EnableSensitiveDataLogging(true);
            });
            services.AddControllers().AddNewtonsoftJson(); 
            services.Configure<MvcNewtonsoftJsonOptions>(opts =>
            {
                opts.SerializerSettings.NullValueHandling
                    = Newtonsoft.Json.NullValueHandling.Ignore;
            });
        }

AddNewtonsoftJson方法启用JSON.NET序列化器,替代标准的ASP.NET Core序列化器。它有自己的配置类MvcNewtonsoftJsonOptions,设置了NullValueHandlinge值丢弃空值。

3.4 定义操作方法

        [HttpPatch("{id}")]
        public async Task<Supplier> PatchSupplier(long id,
            JsonPatchDocument<Supplier> supplierDocument)
        {
            Supplier supplier = await _context.Suppliers.FindAsync(id);
            if (supplier != null)
            {
                supplierDocument.ApplyTo(supplier);
                await _context.SaveChangesAsync();
            }
            return supplier;
        }

用HttpPatch修饰方法,通过JsonPatchDocument参数来处理JSON Patch文档。它有个ApplyTo方法,将每个操作应用于一个对象。
发送以下请求修改:
Invoke-RestMethod http://localhost:5000/api/suppliers/1 -Method Patch -ContentType "application/json" -Body '[{"op":"replace","path":"Name","value":"abc"}]'

4 理解内容的格式化

4.1 理解默认的内容策略

  • 如果操作方法返回一个字符串,则吧未修改的字符串返回客户端,响应的Content-Type头设置为text/plain。
  • 对于所有其他类型,包括int等其他简单类型,数据格式化为json,响应的Content-Type头设置为application/json。

添加ContentController类。

    [ApiController]
    [Route("api/[controller]")]
    public class ContentController : ControllerBase
    {
        private DataContext _dataContext;
        public ContentController(DataContext dataContext)
        {
            _dataContext = dataContext;
        }
        [HttpGet("string")]
        public string GetString()
            => "addffdsfdfdfd";

        [HttpGet("object")]
        public async Task<Product> GetObject()
        {
            return await _dataContext.Products.FirstAsync();
        }
    }

Invoke-WebRequest http://localhost:5000/api/content/string | select @{n='Content-Type';e={$_.Headers."Content-Type"}},Content
Invoke-WebRequest http://localhost:5000/api/content/object | select @{n='Content-Type';e={$_.Headers."Content-Type"}},Content

4.2 理解内容协商

(1)启用XML格式

要使内容协商生效,必须对应用程序进行配置,以便可以选择使用的格式。Json已经成为默认格式,但是MVC框架也可以支持将数据编码为Xml。
启用xml格式。

services.AddControllers().AddNewtonsoftJson().AddXmlSerializerFormatters(); 

xml序列化不能处理EFCore导航属性,因为它是通过接口定义的。修改GetObject使用可序列化的ProductBindingTarget对象

        [HttpGet("object")]
        public async Task<ProductBindingTarget> GetObject()
        {
            Product p = await _dataContext.Products.FirstAsync();
            return new ProductBindingTarget()
            {
                Name = p.Name,
                Price = p.Price,
                CategoryId = p.CategoryId,
                SupplierId = p.SupplierId
            };
        }

只有可用Json格式时别无选择,现在有了选择可以内容协商用application/xml输出。
Invoke-WebRequest http://localhost:5000/api/content/object -Headers @{Accept="application/xml"} | select @{n='Content-Type';e={$_.Headers."Content-Type"}},Content

(2)完全尊重Accept表头

如果Accept表头包含任何格式的/,mvc框架始终使用json格式。我们需要两个配置告诉mvc框架尊重客户端设置,默认情况下不发送json数据。

            services.Configure<MvcOptions>(opts => {
                opts.RespectBrowserAcceptHeader = true;
                opts.ReturnHttpNotAcceptable = true;
            });

设置MvcOptions对象属性,RespectBrowserAcceptHeader为true表示禁止在Accept包含/时回退到json,ReturnHttpNotAcceptable为true表示禁止在客户端请求不支持数据格式时回退到json。

4.3 指定操作结果格式

        [HttpGet("object")]
        [Produces("application/json")]
        public async Task<ProductBindingTarget> GetObject()

4.4 在URL中请求格式

通过使用FormatFilter属性修饰操作方法并确保操作方法的路由中有一个format段变量来启用该特性。

        [HttpGet("object/{format?}")]
        [FormatFilter]
        [Produces("application/json", "application/xml")]
        public async Task<ProductBindingTarget> GetObject()

可以在URl中设置请求格式,如:http://localhost:5000/api/content/object/xml、http://localhost:5000/api/content/object/json。

4.5 限制操作方法接收的格式

可以将Consumes属性应用于操作方法,以限制要处理的数据类型。

        [HttpPost]
        [Consumes("application/json")]
        public string SaveProductJson(ProductBindingTarget product)
        {
            return $"JSON: {product.Name}";
        }

        [HttpPost]
        [Consumes("application/xml")]
        public string SaveProductXml(ProductBindingTarget product)
        {
            return $"XML: {product.Name}";
        }

5 记录和探索Web服务

OpenAPI规范也称为Swagger,它提供描述Web服务。

5.1 解决操作冲突

OpenAPI发现过程要求对每个操作方法使用唯一HTTP方法和URL模式组合。该进程不支持Consumes属性,需要删除这些方法。

5.2 安装和配置Swashbuckle包

dotnet add package Swashbuckle.AspNetCore --version 5.5.0-rc2
在Startup中配置。

            services.AddSwaggerGen(options => {
                options.SwaggerDoc("v1",
                    new OpenApiInfo { Title = "WebApp", Version = "v1" });
            });
            app.UseSwagger();
            app.UseSwaggerUI(options => {
                options.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApp");
            });

在浏览器中访问:http://localhost:5000/swagger/v1/swagger.json,可以看到控制器的期望接收的数据详细和生成的响应范围。访问:http://localhost:5000/swagger/index.html,可以看到Web服务的OpenAPI描述。

5.3 微调API描述

(1)运行API分析器

在项目文件夹.csproj文件中启用分析器。

  <PropertyGroup>
    <IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
  </PropertyGroup>

分析器会检测到return NotFound();只返回一个状态码响应。

(2)声明操作方法结果类型

要修复检测到的问题,可以使用ProducesResponseType属性声明操作方法结果类型。

        [HttpGet("{id}")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<IActionResult> GetProduct(long id)
posted @ 2024-04-24 09:24  一纸年华  阅读(20)  评论(0编辑  收藏  举报