使用 ASP.NET Core 为本机移动应用创建后端服务Create backend services for native mobile apps with ASP.NET Core

本文内容

作者:Steve Smith

移动应用可与 ASP.NET Core 后端服务通信。有关从 iOS 模拟器和 Android 仿真程序连接本地 Web 服务的说明,请参阅从 iOS 模拟器和 Android 仿真程序连接到本地 Web 服务

查看或下载后端服务代码示例

本机移动应用示例The Sample Native Mobile App

本教程演示如何创建使用 ASP.NET Core MVC 支持本机移动应用的后端服务。它使用 Xamarin Forms ToDoRest 应用 作为其本机客户端,其中包括 Android、 iOS、 Windows Universal 和 Window Phone 设备的单独本机客户端。你可以遵循链接中的教程来创建本机应用程序(并安装需要的免费 Xamarin 工具),以及下载 Xamarin 示例解决方案。Xamarin 示例包含一个 ASP.NET Web API 2 服务项目,使用本文中的 ASP.NET Core 应用替换(客户端无需进行任何更改)。

在 Android 智能手机上运行的 ToDoRest 应用程序

功能Features

ToDoRest 应用支持列出、 添加、删除和更新待办事项。每个项都有一个 ID、 Name(名称)、Notes(说明)以及一个指示该项是否已完成的属性 Done。

待办事项的主视图如上所示,列出每个项的名称,并使用复选标记指示它是否已完成。

点击 + 图标打开“添加项”对话框:

“添加项”对话框

点击主列表屏幕上的项将打开一个编辑对话框,在其中可以修改项的名称、 说明以及是否完成,或删除项目:

“编辑项”对话框

此示例默认配置为使用托管在 developer.xamarin.com上的后端服务,允许只读操作。若要使用在你计算机上运行的下一节创建的 ASP.NET Core 应用对其进行测试,你需要更新应用程序的 RestUrl 常量。导航到 ToDoREST 项目,然后打开 Constants.cs 文件。使用包含计算机 IP 的 URL 地址替换 RestUrl(不是 localhost 或 127.0.0.1,因为此地址用于从设备模拟器中,而不是从你的计算机中访问)。请包括端口号 (5000)。为了测试你的服务能否在设备上正常运行,请确保没有活动的防火墙阻止访问此端口。

  1. // URL of REST service (Xamarin ReadOnly Service)
  2. //public static string RestUrl = "http://developer.xamarin.com:8081/api/todoitems{0}";
  3. // use your machine's IP address
  4. public static string RestUrl = "http://192.168.1.207:5000/api/todoitems/{0}";

创建 ASP.NET Core 项目Creating the ASP.NET Core Project

在 Visual Studio 中创建一个新的 ASP.NET Core Web 应用程序。选择 Web API 模板和 No Authentication(无身份验证)。将项目命名为 ToDoApi

“新建 ASP.NET Web 应用程序”对话框,其中已选中 Web API 项目模板

对于向端口 5000 进行的请求,应用程序均需作出响应。更新 Program.cs,使其包含 ,以便实现以下操作.UseUrls("http://*:5000")

  1. var host = new WebHostBuilder()
  2. .UseKestrel()
  3. .UseUrls("http://*:5000")
  4. .UseContentRoot(Directory.GetCurrentDirectory())
  5. .UseIISIntegration()
  6. .UseStartup<Startup>()
  7. .Build();

备注

请确保直接运行应用程序,而不是在 IIS Express 后运行,因为在默认情况下,后者会忽略非本地请求。从命令提示符处运行 dotnet run,或从 Visual Studio 工具栏中的“调试目标”下拉列表中选择应用程序名称配置文件。

添加一个模型类来表示待办事项。使用 [Required] 属性标记必需字段:

  1. using System.ComponentModel.DataAnnotations;
  2. namespace ToDoApi.Models
  3. {
  4. public class ToDoItem
  5. {
  6. [Required]
  7. public string ID { get; set; }
  8. [Required]
  9. public string Name { get; set; }
  10. [Required]
  11. public string Notes { get; set; }
  12. public bool Done { get; set; }
  13. }
  14. }

API 方法需要通过某种方式处理数据。使用原始 Xamarin 示例所用的 IToDoRepository 接口:

  1. using System.Collections.Generic;
  2. using ToDoApi.Models;
  3. namespace ToDoApi.Interfaces
  4. {
  5. public interface IToDoRepository
  6. {
  7. bool DoesItemExist(string id);
  8. IEnumerable<ToDoItem> All { get; }
  9. ToDoItem Find(string id);
  10. void Insert(ToDoItem item);
  11. void Update(ToDoItem item);
  12. void Delete(string id);
  13. }
  14. }

在此示例中,该实现仅使用一个专用项集合:

  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using ToDoApi.Interfaces;
  4. using ToDoApi.Models;
  5. namespace ToDoApi.Services
  6. {
  7. public class ToDoRepository : IToDoRepository
  8. {
  9. private List<ToDoItem> _toDoList;
  10. public ToDoRepository()
  11. {
  12. InitializeData();
  13. }
  14. public IEnumerable<ToDoItem> All
  15. {
  16. get { return _toDoList; }
  17. }
  18. public bool DoesItemExist(string id)
  19. {
  20. return _toDoList.Any(item => item.ID == id);
  21. }
  22. public ToDoItem Find(string id)
  23. {
  24. return _toDoList.FirstOrDefault(item => item.ID == id);
  25. }
  26. public void Insert(ToDoItem item)
  27. {
  28. _toDoList.Add(item);
  29. }
  30. public void Update(ToDoItem item)
  31. {
  32. var todoItem = this.Find(item.ID);
  33. var index = _toDoList.IndexOf(todoItem);
  34. _toDoList.RemoveAt(index);
  35. _toDoList.Insert(index, item);
  36. }
  37. public void Delete(string id)
  38. {
  39. _toDoList.Remove(this.Find(id));
  40. }
  41. private void InitializeData()
  42. {
  43. _toDoList = new List<ToDoItem>();
  44. var todoItem1 = new ToDoItem
  45. {
  46. ID = "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
  47. Name = "Learn app development",
  48. Notes = "Attend Xamarin University",
  49. Done = true
  50. };
  51. var todoItem2 = new ToDoItem
  52. {
  53. ID = "b94afb54-a1cb-4313-8af3-b7511551b33b",
  54. Name = "Develop apps",
  55. Notes = "Use Xamarin Studio/Visual Studio",
  56. Done = false
  57. };
  58. var todoItem3 = new ToDoItem
  59. {
  60. ID = "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
  61. Name = "Publish apps",
  62. Notes = "All app stores",
  63. Done = false,
  64. };
  65. _toDoList.Add(todoItem1);
  66. _toDoList.Add(todoItem2);
  67. _toDoList.Add(todoItem3);
  68. }
  69. }
  70. }

Startup.cs 中配置该实现:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. // Add framework services.
  4. services.AddMvc();
  5. services.AddSingleton<IToDoRepository,ToDoRepository>();
  6. }

现可创建 ToDoItemsController。

提示

有关创建 Web API 的详细信息,请参阅使用 ASP.NET Core MVC 和 Visual Studio 生成首个 Web API

创建控制器Creating the Controller

在项目中添加新控制器 ToDoItemsController它应继承 Microsoft.AspNetCore.Mvc.Controller。添加 Route 属性以指示控制器将处理路径以 api/todoitems 开始的请求。路由中的 [controller] 标记会被控制器的名称代替(省略 Controller 后缀),这对全局路由特别有用。详细了解 路由

控制器需要 IToDoRepository 才能正常运行;通过控制器的构造函数请求该类型的实例。在运行时,此实例将使用框架对 依赖关系注入 的支持来提供。

  1. using System;
  2. using Microsoft.AspNetCore.Http;
  3. using Microsoft.AspNetCore.Mvc;
  4. using ToDoApi.Interfaces;
  5. using ToDoApi.Models;
  6. namespace ToDoApi.Controllers
  7. {
  8. [Route("api/[controller]")]
  9. public class ToDoItemsController : Controller
  10. {
  11. private readonly IToDoRepository _toDoRepository;
  12. public ToDoItemsController(IToDoRepository toDoRepository)
  13. {
  14. _toDoRepository = toDoRepository;
  15. }

此 API 支持四个不同的 HTTP 谓词来执行对数据源的 CRUD(创建、读取、更新、删除)操作。最简单的是读取操作,它对应于 HTTP GET 请求。

读取项目Reading Items

要请求项列表,可对 List 方法使用 GET 请求。[HttpGet] 方法的 List 属性指示此操作应仅处理 GET 请求。此操作的路由是在控制器上指定的路由。你不一定必须将操作名称用作路由的一部分。你只需确保每个操作都有唯一的和明确的路由。路由属性可以分别应用在控制器和方法级别,以此生成特定的路由。

  1. [HttpGet]
  2. public IActionResult List()
  3. {
  4. return Ok(_toDoRepository.All);
  5. }

List 方法返回 200 OK 响应代码和所有 ToDo 项,并序列化为 JSON 。

你可以使用多种工具测试新的 API 方法,如 Postman,如此处所示:

Postman 控制台,其中显示一个 todoitem 的 GET 请求,以及显示所返回的三个项目的 JSON 的响应正文

创建项目Creating Items

按照约定,创建新数据项映射到 HTTP POST 谓词。Create 方法具有应用于它的 [HttpPost] 属性,并接受 ToDoItem 实例。由于 item 参数在 POST 的正文中传递,因此该参数会指定 [FromBody] 属性。

在该方法中,会检查项的有效性和之前是否存在于数据存储,并且如果没有任何问题,则使用存储库添加。检查 ModelState.IsValid 将执行 模型验证,应该在每个接受用户输入的 API 方法中执行此步骤。

  1. [HttpPost]
  2. public IActionResult Create([FromBody] ToDoItem item)
  3. {
  4. try
  5. {
  6. if (item == null || !ModelState.IsValid)
  7. {
  8. return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
  9. }
  10. bool itemExists = _toDoRepository.DoesItemExist(item.ID);
  11. if (itemExists)
  12. {
  13. return StatusCode(StatusCodes.Status409Conflict, ErrorCode.TodoItemIDInUse.ToString());
  14. }
  15. _toDoRepository.Insert(item);
  16. }
  17. catch (Exception)
  18. {
  19. return BadRequest(ErrorCode.CouldNotCreateItem.ToString());
  20. }
  21. return Ok(item);
  22. }

示例中使用一个枚举,后者包含传递到移动客户端的错误代码:

  1. public enum ErrorCode
  2. {
  3. TodoItemNameAndNotesRequired,
  4. TodoItemIDInUse,
  5. RecordNotFound,
  6. CouldNotCreateItem,
  7. CouldNotUpdateItem,
  8. CouldNotDeleteItem
  9. }

使用 Postman 测试添加新项,选择 POST 谓词并在请求正文中以 JSON 格式提供新对象。你还应添加一个请求标头指定 Content-Typeapplication/json

显示 POST 和响应的 Postman 控制台

该方法返回在响应中新建的项。

更新项目Updating Items

通过使用 HTTP PUT 请求来修改记录。除了此更改之外,Edit 方法几乎与 Create 完全相同。请注意,如果未找到记录,则 Edit 操作将返回 NotFound(404) 响应。

  1. [HttpPut]
  2. public IActionResult Edit([FromBody] ToDoItem item)
  3. {
  4. try
  5. {
  6. if (item == null || !ModelState.IsValid)
  7. {
  8. return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
  9. }
  10. var existingItem = _toDoRepository.Find(item.ID);
  11. if (existingItem == null)
  12. {
  13. return NotFound(ErrorCode.RecordNotFound.ToString());
  14. }
  15. _toDoRepository.Update(item);
  16. }
  17. catch (Exception)
  18. {
  19. return BadRequest(ErrorCode.CouldNotUpdateItem.ToString());
  20. }
  21. return NoContent();
  22. }

若要使用 Postman 进行测试,将谓词更改为 PUT。在请求正文中指定要更新的对象数据。

显示 PUT 和响应的 Postman 控制台

为了与预先存在的 API 保持一致,此方法在成功时返回 NoContent (204) 响应。

删除项Deleting Items

删除记录可以通过向服务发出 DELETE 请求并传递要删除项的 ID 来完成。与更新一样,请求的项不存在时会收到 NotFound 响应。请求成功会得到 NoContent (204) 响应。

  1. [HttpDelete("{id}")]
  2. public IActionResult Delete(string id)
  3. {
  4. try
  5. {
  6. var item = _toDoRepository.Find(id);
  7. if (item == null)
  8. {
  9. return NotFound(ErrorCode.RecordNotFound.ToString());
  10. }
  11. _toDoRepository.Delete(id);
  12. }
  13. catch (Exception)
  14. {
  15. return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
  16. }
  17. return NoContent();
  18. }

请注意,在测试删除功能时,请求正文中不需要任何内容。

显示 DELETE 和响应的 Postman 控制台

常见的 Web API 约定Common Web API Conventions

开发应用程序的后端服务时,你将想要使用一组一致的约定或策略来处理横切关注点。例如,在上面所示服务中,针对不存在的特定记录的请求会收到 NotFound 响应,而不是BadRequest 响应。同样,对于此服务,传递模型绑定类型的命令始终检查 ModelState.IsValid 并为无效的模型类型返回 BadRequest

一旦为 Api 指定通用策略,一般可以将其封装在 Filter(筛选器)详细了解 如何封装 ASP.NET Core MVC 应用程序中的通用 API 策略

其他资源Additional resources