在 Program 类中,启用 NLog,记得添加 using NLog.Web:
Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) .UseNLog();最后,在 appsettings.json 中进行以下微调来配置日志记录:
"Logging": { "LogLevel": { "Default": "Information", "Microsoft": "None", "Microsoft.AspNetCore": "Error", "Microsoft.Hosting.Lifetime": "Information" } }这里的基本思想是减少与此 API 无关的日志条目的数量。您可以随意调整这些设置,以便恰当地记录 API 所需要的日志内容。
是时候言归正传了,在 Controller 类中,添加 using Microsoft.Extensions.Logging 并注入一个普通的旧式 ASP.NET logger:
private readonly ILogger<ProductsController> _logger; public ProductsController(ProductContext context, ILogger<ProductsController> logger) { _logger = logger; // ... }假设,现在您的团队决定要抓取客户端请求获取 100 条或更多条记录的频率相关的遥测数据。
将下面的代码放入 GetProducts 中:
if (request.Limit >= 100) _logger.LogInformation("Requesting more than 100 products.");请确保有一个已存在的临时文件夹来核查日志,例如:C:\temp\BuildRestApiNetCore\。
一条日志记录看起来可能是这样的:
{ "Timestamp": "2020-07-12 10:30:30.8960", "Level": "INFO", "Logger": "BuildRestApiNetCore.Controllers.ProductsController", "Action": "GetProducts", "Message": "Requesting more than 100 products." } 带动词的 REST Endpoints深吸一口气,然后畅快地呼出。该 API 差不多可以投入生产环境了,而且只用了很少的代码。现在,我将快速转向 POST、PUT、PATCH 和 DELETE 等 REST 特性的介绍。
POST Endpoint 接收带有新产品的 body,并将其添加到列表当中。此方法是非幂等的,因为它在调用时会创建新的资源。
将下面的代码放入 ProductsController 中:
[HttpPost] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public ActionResult<Product> PostProduct([FromBody] Product product) { try { _context.Products.Add(product); _context.SaveChanges(); return new CreatedResult($"/products/{product.ProductNumber.ToLower()}", product); } catch (Exception e) { _logger.LogWarning(e, "Unable to POST product."); return ValidationProblem(e.Message); } }ASP.NET 通过 ValidationProblem 自动处理异常。该验证将返回一条符合 RFC 7807 规范的响应,并带有一条消息。在实际的系统中,我建议确保不要暴露任何关于 API 的内部信息。将异常信息放在此处有助于客户端对代码进行故障排除,但安全性也很重要。我在这里选择包含错误信息主要是为了演示目的。此处还会将异常记录为警告,以避免记录大量的错误。当异常太多时,监控工具可能会呼叫值班人员。最佳实践是仅在可能需要人工干预的灾难性故障期间记录错误。
使用 swagger 工具,curl 命令为:
curl -i -X POST :5000/v1/products -H "accept: application/json" -H "Content-Type: application/json" -d "{\"productNumber\":\"string\",\"name\":\"string\",\"price\":10,\"department\":\"string\"}"当请求有问题时,API 会如下响应:
{ "errors": {}, "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title":"One or more validation errors occurred.", "status": 400, "detail": "An item with the same key has already been added. Key: string", "traceId":"|c445a403-43564e0626f9af50." }400 (Bad Request) 响应表示请求中的用户错误。因为无法信任用户发送有效数据,所以 API 会记录一个警告。
请注意,如果成功,POST 将返回带有 Location 的 201:
HTTP/1.1 201 Created Date: Mon, 13 Jul 2020 22:52:46 GMT Content-Type: application/json; charset=utf-8 Server: Kestrel Content-Length: 76 Location: /products/bc916 api-supported-versions: 1.0这将引导客户端转向新资源。此处,转向 GET Endpoint 是个好主意:
[HttpGet] [Route("{productNumber}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult<Product> GetProductByProductNumber([FromRoute] string productNumber) { var productDb = _context.Products .FirstOrDefault(p => p.ProductNumber.Equals(productNumber, StringComparison.InvariantCultureIgnoreCase)); if (productDb == null) return NotFound(); return Ok(productDb); }404 响应表示该资源在 API 中尚不存在,但可能会在将来的某个时候变得可用。
PUT 是类似的:
[HttpPut] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public ActionResult<Product> PutProduct([FromBody] Product product) { try { var productDb = _context.Products .FirstOrDefault(p => p.ProductNumber.Equals(product.ProductNumber, StringComparison.InvariantCultureIgnoreCase)); if (productDb == null) return NotFound(); productDb.Name = product.Name; productDb.Price = product.Price; productDb.Department = product.Department; _context.SaveChanges(); return Ok(product); } catch (Exception e) { _logger.LogWarning(e, "Unable to PUT product."); return ValidationProblem(e.Message); } }在 REST 设计中,PUT 允许对整个资源进行更新。它是幂等的,因为多次相同的请求不会改变资源的数量。