十一、Web API
在本章中,我们将涵盖以下主题:
- 使用 ActionResults
- 配置内容协商
- 配置跨域起始请求
- 使用轻便
- 测试 Web api
- 管理异常
使用 ActionResult
在本食谱中,您将学习如何使用ActionResult
返回 Web API。 ActionResult
是一种核心类型的 MVC,用于从服务器返回结果到客户端。 ActionResult
是一个基类和它的抽象类,所以我们可以使用它的派生类之一,例如JsonResult
、ViewResult
、RedirectResult
或FileResult
。
准备
我们将创建一个带有CRUD
方法的 Web API 控制器,以理解每个 HTTP 谓词必须返回的ActionResult
内容。
怎么做……
ASP.NET Core MVC 和 Web api 合并,基类现在是相同的。 ActionResults
现在返回 HTTP 状态码的结果,就像ApiController
基类在 ASP. xml 之前返回.NET 的核心。
我们将使用ActionResults
来返回带有CRUD
Web API 控制器的 HTTP 状态代码:
-
首先,让我们通过创建一个空 Web 应用来创建 Web API 应用。
-
接下来,我们将添加 ASP.NET Core MVC 依赖于项目:
"Microsoft.AspNetCore.MVC": "2.0.0",
- 接下来,我们将以下代码添加到
Startup.cs
中。 这段代码将允许我们使用 Web API 的控制器:
public void ConfigureServices(IServiceCollection services)
{
services.AddMVC();
}
public void Configure(IApplicationBuilder app)
{
app.UseMVC();
}
- 接下来,我们将创建一个在这个 Web API 控制器中使用的存储库:
public interface IProductRepository
{
IEnumerable<Product> GetAllProducts();
void Add(Product product);
Product Find(int id);
void Update(Product product);
void Remove(int id);
}
- 接下来,让我们在
Startup.cs
中注册和配置存储库生命周期:
services.AddScoped<IProductRepository, ProductRepository>();
- 接下来,让我们创建 Web API 控制器:
[Route("api/[controller]")]
public class ProductApiController : Controller
{
private readonly IProductRepository _productRepository;
public ProductApiController(IProductRepository repository)
{
_productRepository = repository;
}
}
- 现在我们将添加
GET
方法。 其路线为api
/productapi
:
[HttpGet]
public IActionResult Get()
{
var productsFromRepo = _productRepository.GetAllProducts();
if (productsFromRepo == null)
return NotFound();
// return HTTP response status code 404
return Ok(productsFromRepo);
// return HTTP status code 200
}
- 现在我们将添加不同路径的
GET
方法api
/productapi
/{id}
:
[HttpGet("{id:int}")]
public IActionResult Get(int id)
{
var productFromRepo = _productRepository.Find(id);
if (productFromRepo == null)
return NotFound();
// return HTTP response status code 404
return Ok(productFromRepo);
// return HTTP response status code 200
}
- 接下来,我们将添加
POST
方法。 它的路由为api
/productapi
,在请求体中有一个Product
对象:
[HttpPost]
public IActionResult Post([FromBody]Product product)
{
if (product == null)
return BadRequest();
// return HTTP response status code 400
_productRepository.Add(product);
return CreatedAtRoute("GetProduct",
new { id = product.Id }, product);
// return HTTP response status code 201
}
- 接下来,我们将添加
PUT
方法。 它的路由为api
/productapi
/{id}
,在请求体中有一个Product
对象:
[HttpPut("{id}")]
public IActionResult Put(int id, [FromBody]Product product)
{
if (product == null || product.Id != id)
return BadRequest();
// return HTTP response status code 401
var productFromRepo = _productRepository.Find(id);
if (productFromRepo == null)
return NotFound();
// return HTTP response status code 404
_productRepository.Update(product);
return new NoContentResult();
// return HTTP response status code 204
}
- 接下来,我们将添加
PATCH
方法。 它的路由为api
/productapi
/{id}
,在请求体中有一个Product
对象。 当我们进行部分更新时,使用PATCH
动词:
[HttpPut("{id}")]
public IActionResult Patch(int id, [FromBody]Product product)
{
if (product == null)
return BadRequest();
// return HTTP response status code 400
var productFromRepo = _productRepository.Find(id);
if (productFromRepo == null)
return NotFound();
// return HTTP response status code 404
productFromRepo.Id = product.Id;
_productRepository.Update(product);
return new NoContentResult();
// return HTTP response status code 204
}
- 接下来,我们将添加
DELETE
方法。 其路线为api
/productapi
/{id}
:
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
var productFromRepo = _productRepository.Find(id);
if (productFromRepo == null)
return NotFound();
// return HTTP response status code 404
_productRepository.Remove(id);
return new NoContentResult();
// return HTTP response status code 204
}
它是如何工作的…
IActionResult
由ActionResult
抽象类实现。 ActionResult
抽象类用于创建多个类,向调用方客户端返回预定义的 HTTP 状态码,如下所示:
OkResult
:返回200
HTTP 状态码CreatedResult
:返回201
HTTP 状态码CreatedAtActionResult
:返回201
HTTP 状态码CreatedAtRouteResult
:返回201
HTTP 状态码NoContentResult
:返回204
HTTP 状态码BadRequestResult
:返回400
HTTP 状态码UnauthorizedResult
:返回401
HTTP 状态码NotFoundResult
:返回404
HTTP 状态码
ControllerBase
类具有针对每种结果类型的几个方法。 我们可以从 Web API 中调用其中一个来返回所需的状态代码,比如下面的代码:
return Ok(); // returns OkResult
return Created(); // returns CreatedResult
return NotFound(); // returns NotFoundResult
配置内容协商
在本食谱中,您将学习如何在 ASP 中管理内容协商.NET 的核心。 ASP。 由于ContentFormatters
,NET Core 可以以任何格式(如 JSON、XML、PLIST 和 SOAP)返回数据。 更多信息请参见https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-formatters。
任何客户机都可以发出一个带有头的请求,以告诉服务器它希望响应的格式。 服务器应用可以读取报头值,并在创建对请求的响应时使用它。 内容协商是整个流程的名称。
准备
默认情况下,ASP.NET Core 将从动作方法返回 JSON。
让我们从前面的配方中获得代码,并让我们看看从GetAllProducts
得到的结果:
我们得到 JSON 值,因为在默认情况下,ASP.NET Core 不考虑浏览器发送的Accept
头。
怎么做……
- 首先,让我们在项目中添加以下依赖项:
"Microsoft.AspNetCore.MVC.Formatters.Xml": "2.0.0"
- 接下来,我们将以下代码添加到
Startup.cs
的ConfigureServices
方法中:
services.AddMVC(options =>
{
options.RespectBrowserAcceptHeader = true;
options.InputFormatters.Add(
new XmlDataContractSerializerInputFormatter());
options.OutputFormatters.Add(
new XmlDataContractSerializerOutputFormatter());
});
- 最后,我们可以看到 XML 格式的产品列表:
配置跨域起始请求
在本教程中,您将学习如何在 ASP 中配置和使用跨源资源共享(CORS).NET Core 应用。
准备
为了配置和使用 CORS,我们将创建两个应用:一个 Web API 应用公开配置了 CORS 约束的服务,另一个客户机应用试图通过 jQuery AJAX 调用使用 Web API 服务。
怎么做……
我们将创建两个独立的应用项目,并从其中一个向other
发出请求。 other
项目是一个 ASP.NET Core 项目,我们将在其中启用/配置 CORS:
- 首先,让我们创建 Web API 应用,通过创建一个空的 Web 应用:
dotnet new mvc -n Chapter11.R3.Server
- 接下来,我们将添加 ASP.NET Core MVC 依赖于项目:
"Microsoft.AspNetCore.MVC": "2.0.0"
- 接下来,我们将以下代码添加到
Startup.cs
中。 这段代码允许我们使用 Web API 的控制器:
public void ConfigureServices(IServiceCollection services)
{
services.AddMVC();
}
public void Configure(IApplicationBuilder app)
{
app.UseMVC();
}
- 接下来,让我们将 CORS 中间件添加到 ASP.NET Core 管道,让我们通过将以下代码添加到
Startup.cs
来配置它:
public void ConfigureServices(IServiceCollection services)
{
services.AddMVC();
services.AddCors(options => { options.AddPolicy("AllowMyClientOrigin", builder =>
builder.WithOrigins("http://localhost:63125")
.WithMethods("GET", "HEAD")
.WithHeaders("accept", "content-type", "origin"));
}); }
public void Configure(IApplicationBuilder app)
{
app.UseMVC();
app.UseCors("AllowMyClientOrigin"); }
要使用在ConfigureServices
方法中创建的新的 CORS 策略,我们在Configure
方法中调用该策略。 当前的 CORS 策略有三种方法:
如果我们想要允许所有客户端域名的 url 到 Web API 中,我们应该用ConfigureServices
方法中的这段代码改变 CORS 策略:
services.AddCors(options =>options.AddPolicy("AllowMyClientOrigin",
p => p.AllowAnyOrigin()));
- 接下来,我们将用
GET
方法创建一个 API 控制器:
[Route("api/[controller]")]
public class TestAPIController : Controller
{
[HttpGet]
public IActionResult Get()
{
var result = new { Success = "True", Message = "API Message" };
return Ok(result);
}
}
- 最后,为了允许来自其他域的应用使用
GET
方法,我们还将在我们希望公开给域的 Web API 方法之上添加以下属性。EnableCors
属性接受一个字符串参数,这是之前在Startup.cs
中创建的 CORS 策略:
[HttpGet]
[EnableCors("AllowMyClientOrigin")] public IActionResult Get()
{
var result = new { Success = "True", Message = "API Message" };
return Ok(result);
}
- 为了允许控制器级的 CORS 配置对该控制器中的所有方法可用,我们可以在
controller
之上添加EnableCors
属性,而不是Action
方法:
[Route("api/[controller]")]
[EnableCors("AllowMyClientOrigin")] public class TestAPIController : Controller
{
[HttpGet]
public IActionResult Get()
{
var result = new { Success = "True", Message = "API Message" };
return Ok(result);
}
}
- 为了允许应用级别的 CORS 配置可用于此应用中的所有控制器,我们可以将
CorsAuthorizationFilterFactory
过滤器添加到全局过滤器集合中。 代码最终应该如下:
public void ConfigureServices(IServiceCollection services)
{
services.AddMVC();
services.AddCors(options =>
{
options.AddPolicy("AllowMyClientOrigin", builder =>
builder.WithOrigins("http://localhost:63125")
.WithMethods("GET", "HEAD")
.WithHeaders("accept", "content-type", "origin"));
});
services.Configure<MVCOptions>(options =>
{ options.Filters.Add(new
CorsAuthorizationFilterFactory("AllowMyClientOrigin"));
}); }
[Route("api/[controller]")]
public class TestAPIController : Controller
{
[HttpGet]
public IActionResult Get()
{
var result = new { Success = "True", Message = "API Message" };
return Ok(result);
}
}
- 接下来,让我们创建一个新的空 web 应用来测试 web API,并通过右键单击项目的根,添加一个
bower.js
文件来添加前端依赖项。 然后,去添加|新项目| .NET Core|客户端| Bower 配置文件:
- 接下来,让我们通过右键单击项目的根目录,将 jQuery 添加到使用 Bower 包管理器的客户机应用中。 然后,选择 Manage Bower Packages 并安装 jQuery:
- 接下来,让我们在
wwwroot
文件夹中添加一个 HTML 页面,以添加 JavaScript 代码,它将调用 API 服务。wwwroot
文件夹是我们添加所有静态文件(.html
、.css
、.js
、.font
、图像等等)的文件夹。 我们也可以将这个.js
代码添加到视图中:
- 在 ASP 中允许静态文件.NET Core 应用,我们必须添加以下代码到
Configure
方法:
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();
}
- 接下来,我们将在刚刚创建的 HTML 页面中添加以下代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<button id="bt1">Call API</button>
<script src="lib/jquery/dist/jquery.js"></script>
<script type="text/javascript">
$(document).ready(function () {
jQuery.support.cors = true;
$('#bt1').click(function () {
console.log('Clicked');
$.ajax({
url: 'http://localhost:64112/api/TestAPI',
type: 'GET',
dataType: 'json',
success: function (data) {
console.log(data.success);
console.log(data.message);
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
}
});
});
});
</script>
</body>
</html>
- 最后,通过单击 call API 按钮,我们将启动 HTML 页面并使用
GET
HTTP 方法调用TestAPI
控制器。 为了确保没有发生错误,我们将通过测试应用的浏览器中的F12按钮启动。 让我们打开控制台,看看结果:
- 如果发生任何错误,控制台消息将是如下截图所示:
使用轻便
在本教程中,您将学习如何使用Swagger为我们的 REST api 创建帮助页面和文档。
准备
让我们用 VS 2017 创建一个空项目。
怎么做……
- 首先,我们将
Swashbuckle
引用添加到项目中:
"Swashbuckle": "5.6.0"
- 接下来,让我们在
Startup.cs
中添加Swagger作为中间件:
public void ConfigureServices(IServiceCollection services)
{
services.AddMVC();
services.AddSwaggerGen();
}
public void Configure(IApplicationBuilder app)
{
app.UseMVC();
app.UseSwagger();
app.UseSwaggerUi();
}
- 现在,让我们通过访问http://{UrlAPI}/swagger/ui来启动 API 文档。 我们现在可以看到生成的 API 文档:
- 当我们点击每个 HTTP 方法时,我们可以看到 Swagger 提供给我们的所有选项,比如轻松地测试 API:
- 接下来,让我们使用另一个特性:添加关于 API 的信息。 为此,我们将以下代码添加到
Startup.cs
:
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath);
Configuration = builder.Build();
}
public IConfigurationRoot Configuration
{
get;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMVC();
services.AddSwaggerGen();
services.ConfigureSwaggerGen(options => { options.SingleApiVersion(new Info
{ Version = "v1",
Title = "ECommerce API",
Description = "The Api to get all data from the SB Store",
TermsOfService = "None"
}); options.IncludeXmlComments( Path.ChangeExtension(Assembly.GetEntryAssembly().Location, "xml")
); options.DescribeAllEnumsAsStrings(); }); }
public void Configure(IApplicationBuilder app)
{
app.UseMVC();
app.UseSwagger();
app.UseSwaggerUi();
}
}
options.IncludeXmlComments()
方法意味着我们可以考虑 API 方法上面的 XML 注释来生成 Swagger 文档:
- 让我们在 c#代码中添加一些注释,看看所有这些变化:
/// <summary>
/// This method insert a product in the database
/// </summary>
/// <param name="product"></param>
/// <returns>CreatedAtRouteResult</returns>
[HttpPost]
public IActionResult Post([FromBody]Product product)
- 让我们看看现在在 Swagger UI 中的变化:
- 最后,让我们看看 Swagger 更有趣的功能:Try it Out! 按钮测试 HTTP 方法,并允许我们查看响应和请求 URL 的更多相关信息:响应体、响应代码和响应头:
它是如何工作的…
Swagger 为我们提供了一种很好的、简单的方法来生成完整的 UI 文档。 该文档将由现有的 API 源代码生成,并且可以通过应用中的 XML 注释来完成。 它还允许我们测试每个 HTTP 方法(如Postman),通过生成的 UI 发送值,并选择不同的内容类型报头,如application/json
或application/xml
。
它还提供了一些客户端 api,允许 Swagger 生成的端点与 JavaScript、AngularJS 或 Xamarin 等客户端技术之间进行通信。
测试 Web API
在本教程中,您将学习如何使用 Moq 和 xUnit 测试 Web API 控制器。
准备
让我们在 VS 2017 中创建一个类库项目,并使用本章Using ActionResultsrecipe 中创建的ProductAPIController
方法。 我们将为这个 API 创建一些测试用例。
怎么做……
- 首先,让我们在解决方案中创建一个类库项目:
-
接下来,我们将更改项目中生成的代码,以导入
xunit
、dotnet-test-xunit
和moq
。 我们还必须在 Web API 项目中添加引用。 -
下面是一些测试方法:
public class ProductApiControllerTests
{
#region Tests for GET : api/productapi
[Fact]
public void GET
_Returns404NotFoundResultIfProductListHaveNoItemsInRepo()
{
// Arrange
var mockRepo = new Mock<IProductRepository>();
var emptyProductList = GetEmptyProductsList();
mockRepo.Setup(repo => repo.GetAllProducts())
.Returns(emptyProductList);
var controller = new ProductApiController(mockRepo.Object);
// Act
var result = controller.Get();
// Assert
Assert.IsType<NotFoundObjectResult>(result);
}
[Fact]
public void GET
_Returns200OkResultIfProductListHaveAtLeastOneItemInRepo()
{
// Arrange
var mockRepo = new Mock<IProductRepository>();
var productList = GetProductsList();
mockRepo.Setup(repo => repo.GetAllProducts())
.Returns(productList);
var controller = new ProductApiController(mockRepo.Object);
// Act
var result = controller.Get();
// Assert
Assert.IsType<OkObjectResult>(result);
}
#endregion
#region Tests for POST : api/productapi
public void POST
_Returns400BadRequestResultIfProductFromRequestParameterIsNull()
{
// Arrange
var mockRepo = new Mock<IProductRepository>();
var product = GetProduct();
var nullProduct = GetNullProduct();
mockRepo.Setup(repo => repo.Add(product));
var controller = new ProductApiController(mockRepo.Object);
// Act
var result = controller.Post(nullProduct);
// Assert
Assert.IsType<BadRequestObjectResult>(result);
}
public void POST
_Returns201CreatedAtRouteResultIfProductIsInsertedSuccesfully
InRepo()
{
// Arrange
var mockRepo = new Mock<IProductRepository>();
var product = GetProduct();
mockRepo.Setup(repo => repo.Add(product));
var controller = new ProductApiController(mockRepo.Object);
// Act
var result = controller.Post(product);
// Assert}
Assert.IsType<CreatedAtRouteResult>(result);
}
#endregion
#region private methods
private IEnumerable<Product> GetEmptyProductsList()
{
return new List<Product>();
}
private IEnumerable<Product> GetProductsList()
{
return new List<Product>
{
new Product { Id = 1, Name = "Phone" },
new Product { Id = 2, Name = "Laptop" },
new Product { Id = 3, Name = "Computer" },
new Product { Id = 4, Name = "Screen" },
new Product { Id = 5, Name = "Mouse" }
};
}
private Product GetEmptyProduct()
{
return new Product();
}
private Product GetNullProduct()
{
return null;
}
private Product GetProduct()
{
return new Product
{
Id = 6, Name = "Tablet"
};
}
private Product GetProductById(int id)
{
return GetProductsList().Where(p => p.Id == id).SingleOrDefault();
}
private Product GetProductAleadyExist()
{
return GetProductsList()
.Where(p => p.Id == 1).SingleOrDefault();
}
#endregion
}
Most of the test methods have been removed for brevity. The test project can be found on the GitHub repository, at https://github.com/polatengin/B05277.
有更多的…
我们可以使用一些工具来测试我们的 api,例如:
- WireShark(https://www.wireshark.org/)是一款很好的网络监控工具,可以监听来自网卡的所有收发报文。
- 邮差(https://www.getpostman.com/)和【T5 提琴手】(【https://www.telerik.com/fiddler T6】)也很有用的工具为测试 api 来创建与 HTTP 调用。 他们把自己安装成计算机的某种代理。 它们可以向特定的 URL 发送和显示请求和响应对。
管理异常
在本菜谱中,您将学习如何使用 Web API 管理异常。
通常,我们不想失去异常的原因。 因此,我们应该持久化并维护所有异常的日志。 就持久化异常日志所需的存储容量而言,异常日志可能是巨大的。
我们可以记录所有异常文件或数据库表(如该软件,甲骨文,或 MySql),或文档存储(如 MongoDb (https://www.mongodb.com/),或 Azure CosmosDb(https://azure.microsoft.com/en-us/services/cosmos-db/)。
记录的异常至少包含:
- 一个描述性的信息
- 异常消息
- 异常。net 类型
- 堆栈跟踪
我们通常只向客户端发送描述性消息,并记录关于异常的其他信息。
使用 Web API 2,在 ASP 之前。 在 asp.net Core 中,HttpError
类向客户端发送了一个结构化错误。
HttpError
传统上由 Web API 使用,以(某种)标准化的方式向客户端提供错误信息。 它有一些有趣的特性:
ExceptionMessage
ExceptionType
InnerException
Message
ModelState
MessageDetail
StackTrace
准备
让我们打开 ASP.NET Core Web API 项目,我们在Using ActionResultsrecipe 中创建。
怎么做……
- 首先,让我们将
WebApiCompatShim
库添加到project.json
:
"Microsoft.AspNetCore.MVC.WebApiCompatShim": "1.0.0"
- 为了使用
WebApiCompatShim
,我们需要修改ConfigureServices
中的一些代码来添加一个服务:
services.AddMVC().AddWebApiConventions();
- 接下来,我们将更改
api
/productapi
/{id}
的现有代码:
[HttpGet("{id:int}")]
public IActionResult Get(int id)
{
var productFromRepo = _productRepository.Find(id);
if (productFromRepo == null)
return NotFound();
return Ok(productFromRepo);
}
我们可以增加一个不同参数的Get()
方法,如下:
[Route("{id:int}")]
public HttpResponseMessage Get(int id, HttpRequestMessage request)
{
var product = _productRepository.Find(id);
if (product == null)
{
Return request.CreateErrorResponse(HttpStatusCode.NotFound,
new HttpError
{
Message = "This product does not exist",
MessageDetail = string.Format("The product requested with
ID {0} does not exist in the repository", id)
});
}
return request.CreateResponse(product);
}
- 最后,如果我们发送一个产品 ID(在存储库中不存在)作为参数,我们将收到以下消息:
有更多的…
我们可以通过创建一个Exception
中间件来全局管理异常。
从 Web API 2.1 开始,我们使用IExceptionHandler
来全局处理异常。 它帮助捕获控制器、操作、过滤器、路由,有时还捕获MessageHandlers
和MediaTypeFormatters
中的异常。
我们也可以考虑创建一个自定义全局Exception
过滤器来记录异常,但是我们将在第 12 章、Filters中讨论这个问题。
版权属于:月萌API www.moonapi.com,转载请注明出处