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
发送以下请求修改:
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");
});
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)
本文来自博客园,作者:一纸年华,转载请注明原文链接:https://www.cnblogs.com/nullcodeworld/p/18154358