创建.NET Core API
虽然本文在技术上是我正在开发的一个有趣的gamedev lite项目系列的一部分,但我今晚在项目上的活动为分享如何创建新的ASP .NET Core Web API提供了一个很好的机会。
本文将向您介绍创建,运行和测试新的ASP .NET Core Web API的一些简单步骤。
先决条件
我将使用.NET Core 2.1,因为它是我在我的机器上安装的,尽管今天.NET Core 3的候选版本1已经出来了。
开始:
-
下载并安装Visual Studio 2019 Community Edition。您需要安装.NET Core跨平台开发工具。
-
下载并安装.NET Core 2.1 SDK
项目设置
打开Visual Studio 2019并创建一个新项目。
出现提示时,选择ASP .NET Core Web Application并单击Next。
为您的项目提供有意义的名称。解决方案名称将自动生成。
下一个Visual Studio将询问您要选择的起始模板。这些选择不会将您排除在以后的其他路径中。现在,我们将选择API并取消选中右侧的所有框以用于简单的演示应用程序。
单击“创建”,应创建并打开项目。
运行API
要验证一切正常,请转到屏幕顶部的“调试”菜单,然后单击“启动而不调试”。这将启动一个Web浏览器,并为您提供一个带有文本(“value1”,“value2”)的空白网页。
信不信由你,这意味着一切正常。您的浏览器导航到了 ValuesController
class并点击其HTTP GET路由,该路由返回该内容。
这是一个片段 ValuesController.cs
位于 Controllers
夹:
(Route("api/(controller)")) (ApiController) public class ValuesController : ControllerBase { // GET api/values (HttpGet) public ActionResult<IEnumerable<string>> Get() { return new string() { "value1", "value2" }; } // Other code omitted... }
浏览器导航到这里 /api/values
与…匹配 ValuesController
按名称前缀(见 Route
属性 ValuesController
类)。在这个控制器里面,我们映射到了 Get
上面列出的方法,因为使用的方法是GET(浏览器导航执行GET请求),我们没有进一步查看 api/values
。
因此,ASP .NET Core运行了 Get()
方法并返回200 OK结果,其中包含在上面列表中的字符串数组中定义的内容。
非常酷我们的代码有效。现在是时候进行一些更深层次的开发了。
将项目添加到解决方案中
添加新项目时我想做的第一件事是创建两个新的库项目并将它们添加到解决方案中。第一个是用于保存所有应用程序逻辑的库,第二个是单元测试库。
在解决方案浏览器中,右键单击您的解决方案(包含项目的最顶层项目),然后选择“添加”,然后选择“新建项目”。
选择类库(.NET标准),单击下一步,给它一个有意义的名称(我命名为我的MattEland.Starship.Logic),然后单击创建。
现在已经创建了库,我们将右键单击Solution Explorer中的主Web API项目,选择Add,然后选择Reference。从这里,我们将检查我们添加的库的名称,然后单击“确定”。
这允许主API项目使用库中定义的代码,这有助于将API特定逻辑与域逻辑分离,并且如果需要,可以更轻松地将应用程序逻辑移植到控制台,桌面或移动应用程序。
现在,单击解决方案浏览器并添加另一个新项目。这次我们将选择一个新的XUnit测试项目或一个NUnit测试项目。出于本教程的目的,我将演示使用NUnit测试项目(.NET Core)模板。
根据您的意愿命名项目(我的是MattEland.Starship.Tests)并单击Create。
接下来,我们将右键单击测试项目并像上面一样添加依赖项。这次我们将为库和Web应用程序添加依赖项。这样我们的测试就可以直接在控制器上调用方法进行集成测试。
将类添加到库中
接下来,让我们创建一些示例域类并将它们放在我们的逻辑库中。选择项目后,右键单击并单击添加,然后单击类…
从这里开始,将选择保留为类,但给它一个有意义的名称。我的将是 GameState.cs
代表回合制游戏的状态。
在这个类中加入一些简单的代码 – 足以测试一个简单的对象结构。
我的数据将如下:
namespace MattEland.Starship.Logic { public class GameState { public GameState(int id) { Id = id; } public int Id { get; } public int ClosedCount { get; set; } } }
我也打算创建一个 GameRepository
存储GameState实例。这个类是我们的控制器将与之交互的。
下面列出了一个非常简单的面向演示的存储库:
using System.Collections.Generic; using System.Linq; namespace MattEland.Starship.Logic { public class GameRepository { private readonly IList<GameState> _games = new List<GameState>(); public GameRepository() { // Start with some sample data CreateNewGame(); } public IEnumerable<GameState> Games => _games; public GameState GetGame(int id) => _games.FirstOrDefault(g => g.Id == id); public GameState CreateNewGame() { int id = _games.Count + 1; var game = new GameState(id); _games.Add(game); return game; } public bool DeleteGame(int id) { var game = _games.FirstOrDefault(g => g.Id == id); return game != null && _games.Remove(game); } } }
现在我们有一些基本逻辑和一个存储库类来管理操作,让我们看看它如何插入控制器。
创建第一个控制器
接下来,让我们删除 ValuesController.cs
文件(如果要将其作为参考,则将其保留)并将新控制器添加到Web API项目中。在我的情况下,这被称为 GamesController
管理各种可用的游戏状态。
该类将保留我们之前创建的存储库类的新实例,并将操作转发给它。
我的控制器如下:
using System.Collections.Generic; using MattEland.Starship.Logic; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace MattEland.Starship.ProcessingService.Controllers { (Route("api/(controller)")) (ApiController) public class GamesController : ControllerBase { private readonly GameRepository _repository = new GameRepository(); // GET api/games (HttpGet) public ActionResult<IEnumerable<GameState>> LoadGame() { return Ok(_repository.Games); } // GET api/games/42 (HttpGet("{id}")) public ActionResult<GameState> LoadGame(int id) { var game = _repository.GetGame(id); if (game != null) { return Ok(game); } return new NotFoundResult(); } // POST api/games (HttpPost) public CreatedResult NewGame() { var game = _repository.CreateNewGame(); return new CreatedResult($"/api/games/{game.Id}", game); } // DELETE api/games/42 (HttpDelete("{id}")) public StatusCodeResult Delete(int id) { bool deleted = _repository.DeleteGame(id); if (deleted) { return new StatusCodeResult(StatusCodes.Status204NoContent); } return new NotFoundResult(); } } }
请注意,我定义了一个标准的GET方法来获取所有游戏以及一个特定的GET方法来获取一个游戏的ID。这些方法通过输入的参数来区分 HttpGet
属性与获取特定游戏一个变量 {id}
被映射为 int id
参数。
另请注意我定义 HttpPost
和 HttpDelete
用于创建新游戏和删除现有游戏的方法的动词。
再次注意,这是非常小的。在实际应用程序中,我会将请求验证和错误处理等内容放入API层(如果不是由中间件处理)。
在浏览器中测试它
现在逻辑已经准备好了,你认为我们可以在没有调试的情况下运行并看到我们的新响应,但请记住Visual Studio导航到 /api/values
上次启动的路径。这是因为项目的调试设置配置为查看该路径。
我们可以通过转到Web API项目的属性节点并双击它,然后选择“调试”选项卡,然后在“开始浏览器”文本框中更改URL以匹配新控制器的名称来更改默认路径。
配置好所有内容并保存项目(文件>全部保存)后,无需调试即可运行,并验证您是否看到了预期的数据。
就我而言,我看到: ({"id":1,"closedCount":0})
根据我的简单对象定义看起来是正确的。
此时,您可以针对本地实例发出HTTP请求,它将以适当的响应进行响应。
通过代码测试它
我喜欢测试我的应用程序比安装每个API调用更安全,所以我喜欢至少有一个或两个集成级别测试模拟直接调用 Controller
类。我的大多数测试都是针对像这样的事情的单元测试 GameState
要么 GameRepository
,但测试API逻辑是否正常运行也很有帮助。
在里面 UnitTest1.cs
(您可以根据需要重命名),我将修改现有的测试,如下所示:
using MattEland.Starship.Logic; using MattEland.Starship.ProcessingService.Controllers; using NUnit.Framework; namespace Tests { public class Tests { (Test) public void Test1() { // Arrange var controller = new GamesController(); // Act var result = controller.NewGame(); // Assert Assert.IsNotNull(result); GameState state = (GameState) result.Value; Assert.AreEqual(2, state.Id); } } }
这会在我的控制器上调用NewGame方法(在我的情况下是一个HTTP POST动词)并检查结果以查看是否创建并返回了新游戏,并且它的ID与我期望的一致。
请注意,为了支持直接引用控制器,我必须遵循Visual Studio操作建议并添加引用 Microsoft.AspNetCore.Mvc.Core
。
我还发现我的测试最初失败,直到我添加了一个NuGet引用 Microsoft.AspNetCore.MVC.Abstractions
。通过右键单击解决方案浏览器中的测试项目并选择“管理NuGet包…”然后搜索程序集并单击“选择安装”来执行此操作。
从这里,您可以通过单击上方菜单中的“测试”,然后单击Windows,然后单击“文件浏览器”来运行测试。这将使测试窗格可用,您可以单击测试用例以运行并运行所选测试。
注意:您的UI可能与我的不同。我使用ReSharper,它在用户界面中增加了额外的测试工具
围绕着一个简单的单元测试 GameRepository
从内部状态开始可能如下所示:
using System.Linq; using MattEland.Starship.Logic; using NUnit.Framework; namespace Tests { public class RepositoryTests { (Test) public void RepositoryShouldStartWithGameState() { // Arrange var repository = new GameRepository(); // Act var games = repository.Games; // Assert Assert.Greater(games.Count(), 0); } } }
闭幕
在本文中,我们创建了一个新的API项目,一个共享逻辑库和一个测试项目,并验证了所有功能都正常运行。
虽然在这个例子中仍然有很多非常基本的并且可以大大改进,但这应该可以帮助你开始。请继续关注优化此应用程序和处理常见方案的后续文章。在ASP .NET Core中有很多东西需要学习,但它是API开发的绝佳平台。