ASP.NET Core 创建 API(第二日):在 ASP.NET Core 中创建 API






4.98/5 (28投票s)
如何使用 ASP.NET Core 创建 API
目录
- 引言
- 中间件
- 路线图
- MVC 模式
- ASP.NET Core MVC 中间件
- 创建 API 控制器以返回资源
- ASP.NET MVC Core 中的路由
- 通过路由返回资源
- 处理模型类
- 结论
- Github 上的源代码
- 参考文献
- 其他系列
引言
本系列“ASP.NET Core Web API”的文章将重点介绍如何使用 ASP.NET Core 创建 Web API。在系列文章的上一篇中,我们学习了 ASP.NET Core 的基础知识,以及如何设置一个空白解决方案并使用请求管道和中间件。在本文中,我们将减少理论,并着手创建 API。我们将利用 ASP.NET Core MVC 中间件来构建 API。本文将详细介绍如何返回资源、数据以及如何通过 HTTP 请求与 API 进行通信。我们可以使用系列文章上一篇结束时获得的相同源代码。
中间件
为了构建 API,我们将向 HTTP 请求管道添加一个中间件,这可以通过在 Startup
类的 Configure()
方法中添加中间件来实现。为此,我们需要将服务添加到容器中。在 .NET 的早期版本中,我们通过 ASP.NET Web API 构建服务,而对于创建带有用户界面并由最终用户使用的应用程序,我们使用 ASP.NET MVC。在 ASP.NET Core 中,我们现在没有单独的 Web API 框架了。
MVC 和 Web API 的功能现已合并到一个框架中,即 ASP.NET Core MVC。其目的是构建使用 API 和 MVC 方法的 Web 应用程序。要创建 API,我们需要配置中间件并将其添加到请求管道中。
路线图
我们将遵循一个路线图来详细学习和涵盖 ASP.NET Core 的所有方面。以下是涵盖整个系列的路线图或文章列表
- ASP.NET Core 创建 API(第一日):入门和 ASP.NET Core 请求管道
- ASP.NET Core 创建 API(第二日):在 ASP.NET Core 中创建 API 并返回资源
- ASP.NET Core 创建 API(第三日):在 ASP.NET Core API 中处理 HTTP 状态码、序列化器设置和内容协商
- ASP.NET Core 创建 API(第四日):理解 ASP.NET Core API 中的资源
- ASP.NET Core 创建 API(第五日):ASP.NET Core API 中的控制反转和依赖注入
- ASP.NET Core 创建 API(第六日):Entity Framework Core 入门
- ASP.NET Core 创建 API(第七日):ASP.NET Core API 中的 Entity Framework Core
MVC 模式
MVC 代表 Model View Controller(模型-视图-控制器)。MVC 基本上是一种用于定义用户界面的架构设计模式。它鼓励松耦合和关注点分离,并提高可测试性。它由三个部分组成。
模型负责业务逻辑。在某些情况下,当 MVC 模式仅用于应用程序架构的顶层时,模型不包含逻辑,而是由应用程序层(如业务层)的组件处理。在某些实现中,会使用视图模型来检索和存储数据。视图是 UI 层,它负责以结构良好、用户界面丰富的方式显示数据,例如以 HTML 的形式。控制器负责视图和模型之间的通信,并根据用户输入做出决策。在这些组件的依赖关系方面,控制器和视图都依赖于模型,控制器在一定程度上也依赖于视图。因此,控制器根据用户输入决定显示给用户的视图,并在需要时将模型中的数据提供给视图。但是,在讨论构建 API 时,我们应该看看这种结构如何满足我们的需求。因此,在 MVC 的术语中,视图基本上是我们作为响应获得的数据,即它以 JSON 的形式表示我们的资源或数据。
在我们的例子中,我们期望来自任何具有用户界面的其他用户应用程序的请求,这些应用程序只是想访问我们的服务。当请求到达时,控制器上的一个操作将被调用,控制器将发送输入数据,然后将模型返回给视图。在这种情况下,视图不是 aspx、razor 或 html 页面,而是以 JSON 格式表示结果数据。
ASP.NET Core MVC 中间件
现在,我们将尝试向我们的应用程序添加 ASP.NET Core MVC 中间件。在 Startup 类中,我们可以添加中间件,但要继续实现,首先需要将 AspNetCore.MVC
nugget 包添加到应用程序中,因为该服务定义在名为 Microsoft.AspNetCore.Mvc
的 Nuget 包的外部依赖项中。右键单击项目并选择“管理 Nuget 包”,如下图所示。
现在安装 Microsoft.AspNetCore.Mvc
包的最新稳定版本。在我们的例子中,它是 1.1.2。
安装完成后,所有必要的与 ASP.NET MVC 相关的包都将被添加。现在,在 Startup
类的 ConfigureServices
方法中,我们将把中间件添加到请求管道。只需编写 services.AddMVC()
方法来添加服务。
// This method gets called by the runtime.
// Use this method to add services to the container.
// For more information on how to configure your application,
// visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
接下来是 configure
方法,现在我们将把 MVC 中间件添加到请求管道。在这种情况下,我们可以调用 app.UseMvc()
方法;我们将此添加到我们在系列文章第一篇中添加异常处理程序之后。
这样,我们的异常处理程序可以在将请求委托给 MVC 中间件之前捕获异常,并且还可以处理与 MVC 相关的异常并发送正确的响应。我们现在还可以注释掉我们为引发异常而引入的代码。
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler();
}
app.UseMvc();
//app.Run(async (context) =>
//{
// throw new Exception("Test Dev Exception Page");
//});
//app.Run(async (context) =>
//{
// await context.Response.WriteAsync("Hello World!");
//});
}
导航到项目属性和调试选项卡,并将环境变量更改为 Development(还记得吗?我们在系列文章上一篇中将其更改为生产模式)。
现在运行应用程序,我们会看到一个空白页面。
现在通过按 F12 打开浏览器的开发者工具。我们仍然在那里看到两个异常。
我们得到了 404 异常,即“未找到”状态。这是显而易见的,因为我们刚刚添加了 MVC 中间件,但没有做其他任何事情。没有代码要执行来返回数据,也没有定义路由。所以,让我们创建一个实际返回数据的控制器,比如 Employee
信息。
创建 API 控制器以返回资源
现在,我们将向我们的应用程序添加一个新的控制器。由于我们正在构建一个 Employee
API,我们将把控制器命名为 EmployeesController
。我们可以遵循基于模板的方法来添加带有默认操作方法的控制器,但从学习的角度来看,我们将从头开始实现,以便我们真正了解我们正在创建的内容,并且应该对我们的代码拥有完全的控制权。因此,在项目中添加一个新文件夹并将其命名为 Controllers,然后向该 Controllers 文件夹添加一个新类并将其命名为 EmployeesController
。
EmployeesController
应派生自 Microsoft.AspNetCore.Mvc.Controller
类。
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace EmployeeInfo.API.Controllers
{
public class EmployeesInfoController : Controller
{
}
}
现在,我们将添加负责返回数据的操作。由于我们需要以 JSON 格式返回数据,因此在这种情况下,我们可以利用 JsonResult
类。这将把传递给 JsonResult
类构造函数的对象转换为 JSON 格式。
public JsonResult GetEmployees()
{
return new JsonResult();
}
由于我们需要返回 employees
列表,理想情况下我们应该有一个员工实体类。但现在我们还没有,让我们创建三个匿名对象并返回一个 employees
列表。
public JsonResult GetEmployees()
{
return new JsonResult(new List<object>()
{
new {employeeid = 01989, Name = "John Patrick"},
new {employeeid = 01987, Name= "Michael"},
new {employeeid = 01988, Name= "Akhil Mittal"}
});
}
当需要通过 HTTP 服务获取数据时,我们需要向一个 URI 发送 HTTP 请求,该 URI 将请求委托给 API 的“Get
”方法。我们可以直接从浏览器或任何工具发送请求,以方便地检查服务响应。我们将使用 Postman 来完成所有这些请求和响应跟踪操作。
将 Postman 安装到 Chrome 浏览器应用并打开它。
所以,让我们打开 Postman。在 Postman 中,我们可以创建我们的请求。首先运行应用程序,然后创建一个 URI 请求来获取员工列表,例如 https://:2542/api/employees,选择 GET
方法,然后点击 Send 按钮发送请求。作为响应,我们没有收到 employees
列表,而是收到了一个 **404 Not Found** 的状态,如下图所示。
这意味着 MVC 框架目前不知道如何将请求的 URI(即 api/employees)映射到 Get
方法或 GetEmployees
方法。这时就出现了路由的概念。让我们详细探讨一下。
ASP.NET MVC Core 中的路由
路由的目的是将 URI 请求匹配到控制器的特定方法。MVC 框架负责解析接收到的请求,并将其映射到控制器和相应的操作。路由可以有两种方式:第一种是约定路由,第二种是特性路由。
约定路由
对于约定路由,我们需要遵循如下约定:
app.UseMvc(config => {
config.MapRoute(
name: "Default",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "Home", action = "Index" }
);
});
这与我们在 .NET 早期版本中与 MVC 或 Web API 使用的类似。在这种情况下,我们需要在 app.UseMvc
方法中传递配置以遵循约定路由。这将把 URI employees/index 映射到名为 Employees
控制器上的 index
方法。这些是传统的路由方式,更适合典型的 MVC 应用程序,其中需要返回视图。对于基于 API 的应用程序,不推荐使用这种约定路由,而是推荐使用特性路由。
特性路由
特性路由允许我们使用我们想要的属性来装饰我们的控制器和操作,从而提供对路由操作的更多控制。这些属性带有模板,然后通过该模板将 URI 匹配到控制器的特定操作/方法。
API 最常见的功能是创建、读取、更新和删除操作。
对于读取资源,合适的 HTTP 方法是“GET
”,因此在代码的操作级别,我们在负责读取和返回资源的代码上使用 HttpGet
属性。例如,api/employees
将返回所有 employees
,而 api/employees/01989
将返回 employee
ID 为 01989 的 employee
。
POST
由操作级别的 HttpPost
属性指定。它负责创建资源或持久化资源。例如,api/employees
URI 可用于创建 employee
资源。
PUT
和 PATCH
用于更新 URI,两者都用在操作级别。这两种 Http
方法之间有一个细微的差别。当使用 PUT
时,它表示用于完整更新,即请求中的所有字段都将覆盖现有字段,PUT
的 HTTP 属性是 HttpPut
。对于 PATCH
,它是 HttpPatch
,PATCH
表示部分更新。与 PUT
不同,它不进行完整更新,而是部分更新,即如果使用 PATCH
,也可以更新几个字段。
最后一个是 DELETE
,HTTP 属性是 HttpDelete
。顾名思义,它用于删除 URI,例如,api/employees/01989
将删除 ID 为 01989 的 employee
。
还有一个属性不映射到任何特定的 HTTP 属性,称为“Route
”属性。此属性在控制器级别使用,为所有操作级别的属性提供一个公共模板。例如,如果我们知道我们的 API URI 以“api/employees
”开头,那么 Route
属性可以用于控制器级别,并将“api/employees
”作为所有操作的模板值,这样我们就可以跳过为所有操作提供此特定值。
通过路由返回资源
现在,在前面的章节中,我们试图通过 URI 获取资源。现在我们知道了路由是如何工作的,所以只需将路由属性添加到我们的操作中,看看它是如何工作的。由于我们需要获取 employees
,我们将使用 HttpGet
属性来操作。将 [HttpGet("api/employees")]
放在操作上方。
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace EmployeeInfo.API.Controllers
{
public class EmployeesInfoController : Controller
{
[HttpGet("api/employees")]
public JsonResult GetEmployees()
{
return new JsonResult(new List<object>()
{
new {employeeid = 01989, Name = "John Patrick"},
new {employeeid = 01987, Name= "Michael"},
new {employeeid = 01988, Name= "Akhil Mittal"}
});
}
}
}
编译应用程序,运行它,然后再次从 postman 请求资源。
这次,我们得到了期望的结果,即来自我们操作的员工列表。因此,证明了路由和操作都工作正常。所以我们现在有了一个工作的 API 了吗?
控制器通常包含映射到 CRUD 操作的其他方法,我们希望我们的每个操作都有一个一致的 URI。如果我们知道我们所有的操作都将具有“api/employees
”作为属性,我们可以利用控制器级别的 Route
属性,并将此 URI 部分放在那里,如下所示。
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace EmployeeInfo.API.Controllers
{
[Route("api/employees")]
public class EmployeesInfoController : Controller
{
[HttpGet()]
public JsonResult GetEmployees()
{
return new JsonResult(new List<object>()
{
new {employeeid = 01989, Name = "John Patrick"},
new {employeeid = 01987, Name= "Michael"},
new {employeeid = 01988, Name= "Akhil Mittal"}
});
}
}
}
在这种情况下,默认情况下,通过请求中的 Get
Http Verb,我们的 get
方法将被调用并返回结果。运行应用程序并再次从 postman 发送请求,以检查它是否正常工作。
在大多数情况下,人们会使用模型类、POCO 或 DTO,然后将它们序列化为 JSON。现在我们将看到如何改进此实现。
处理模型类
在本节中,我们将尝试避免直接返回 JSON 和匿名对象。因此,我们将为 Employee
创建一个模型/实体类。向项目添加一个名为 Models 的新文件夹,并添加一个名为 EmployeeDto
的类。向该 EmployeeDto
类添加 ID
、Name
、Designation
和 Salary
等属性。还可以为 DTO 类添加计算字段属性,例如,他过去工作过的公司的详细信息。
EmployeeDto Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace EmployeeInfo.API.Models
{
public class EmployeeDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Designation { get; set; }
public string Salary { get; set; }
public int NumberOfCompaniesWorkedWith { get; set; }
}
}
这里有一个重要的点,即 API 返回或接受的内容与底层数据存储使用的模型不同。目前,我们将使用内存中的数据,因为焦点仅在 API 上。因此,内存中的数据存储将仅处理我们正在创建的这些 DTO 类。在系列文章的后续文章中,我们将看到如何使用 Entity Framework Core 处理数据库和对象。由于现在我们需要返回数据,我们将创建一个包含员工列表的内存数据存储。稍后,我们可以随时使用数据库来获取数据并使用 Entity Framework 进行通信。
因此,添加一个名为 EmployeesDataStore
的新类,并创建一个名为 Employees
的属性,即 Employee
DTO 的列表。并在其中添加一些虚拟数据。
在我们的 EmployeesDataStore
上创建一个 static
属性,并将其命名为 Current
,它将返回 EmployeesDataStore
的实例。由于它是一个 static
属性,实例会保留在内存中,我们可以继续处理相似的数据,直到我们重新启动应用程序。请注意,这不是一种推荐的方法,但我们暂时遵循它来理解主题。
EmployeesDataStore.cs:
using EmployeeInfo.API.Models;
using System.Collections.Generic;
namespace EmployeeInfo.API
{
public class EmployeesDataStore
{
public static EmployeesDataStore Current { get; } = new EmployeesDataStore();
public List<employeedto> Employees { get; set; }
public EmployeesDataStore()
{
//Dummy data
Employees = new List<employeedto>()
{
new EmployeeDto()
{
Id = 1,
Name = "Akhil Mittal",
Designation = "Technical Manager",
Salary="$50000"
},
new EmployeeDto()
{
Id = 2,
Name = "Keanu Reaves",
Designation = "Developer",
Salary="$20000"
},
new EmployeeDto()
{
Id = 3,
Name = "John Travolta",
Designation = "Senior Architect",
Salary="$70000"
},
new EmployeeDto()
{
Id = 4,
Name = "Brad Pitt",
Designation = "Program Manager",
Salary="$80000"
},
new EmployeeDto()
{
Id = 5,
Name = "Jason Statham",
Designation = "Delivery Head",
Salary="$90000"
}
};
}
}
}
现在我们可以转到控制器并修改实现。不再使用 JSON 列表中的匿名属性,我们现在直接调用 EmployeesDataStore Current
属性和其中的 Employees
。
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace EmployeeInfo.API.Controllers
{
[Route("api/employees")]
public class EmployeesInfoController : Controller
{
[HttpGet()]
public JsonResult GetEmployees()
{
return new JsonResult(EmployeesDataStore.Current.Employees);
}
}
}
现在运行应用程序,然后再次从 postman 请求响应。
所以,我们得到了我们在 DataStore
类中指定的所有 employees
详细信息。
现在我们还可以创建新的操作,例如返回单个实体。添加一个名为 GetEmployee
的新操作。由于这也是一个 Get
,我们用 HttpGet
属性对其进行装饰。URL 应该是“api/employees
”,即默认 URL 加上要获取的员工的 ID,[HttpGet("api/employees/{id}")]
,但由于“api/employees
”是默认路由的一部分,我们可以省略它,因为它会自动追加到操作路由。ID 是通过 URI 传递的参数,它也必须是操作的参数,以便获取 ID 匹配的所需 employee
。
[HttpGet("{id}")]
public JsonResult GetEmployee(int id)
{
return new JsonResult
(EmployeesDataStore.Current.Employees.FirstOrDefault(emp => emp.Id == id));
}
在上面的操作方法中,我们在员工数据存储列表中找到 ID 与传入的 id
参数匹配的特定 employee
。现在编译项目。运行应用程序,然后从 postman 发送一个 GET
请求,但使用不同的 URI,即 https://:2542/api/employees/1。
使用 https://:2542/api/employees/2
因此,我们只从 employees
列表中得到了一个 employee
,该 employee
的 id
是 1
,通过 URI 传递。因此,路由模板将正确的路由匹配到操作,然后调用该操作。
现在想象一个场景,你发送一个带有不存在的路由或员工的请求,例如 https://:2542/api/companies,我们会得到以下 404 响应,即**未找到**,这完全没问题。
如果我们还记得之前的实现,我们得到了 500 内部服务器错误。所以,这些代码基本上是 HTTP 状态码,在这种情况下,它们是由框架自动返回的。
如果我们尝试获取一个不存在的员工,例如 https://:2542/api/employees/8,我们会得到**200 OK** 的结果,其中结果数据为 null。
框架本身不会仅仅因为我们的 URI 可以路由就返回 404。但是返回 null 是不正确的结果。所以,如果员工不存在,我们理想情况下也应该从代码或 API 获得 404 响应。我们应该在所有情况下都返回正确的状态码。我们将在下一篇文章中介绍状态码以及更多内容。敬请关注?
结论
在本文中,我们学习了 MVC 模式以及传统的 Web API 和 MVC 与 ASP.NET Core MVC 有何不同。我们首先了解了 MVC 模式,即 Model-View-Controller。模型负责应用程序数据的业务逻辑。视图表示显示数据的区域,在 API 的情况下,通常是以 API 返回的 JSON 格式,而控制器负责视图和模型之间的通信。此模式使我们能够编写可重用且可测试的代码。我们学会了如何添加 MVC 中间件以及如何使用 HttpGet
从我们的 API 获取数据。我们了解了路由如何处理请求 URI 并将其映射到我们控制器的操作。我们学习了如何从头开始创建 API 以及如何处理实体对象并返回资源。在下一篇文章中,我们将介绍 HTTP 状态码、返回子资源、序列化字符串和内容协商等主题。
Github 上的源代码
参考文献
其他系列
我的系列文章列表如下: