十、使用 ASP.NET Web API 构建基于 HTTP 的 Web 服务

到目前为止,我们已经学习了如何使用 ASP 创建 web 应用。 净的核心。 但有时仅仅创建一个 web 应用是不够的。 让我们假设你正在使用 ASP.NET Core 创建一个 web 应用,为世界上所有城市提供天气信息。 人们访问您的 web 应用来查找天气信息,他们对服务很满意。 但许多其他网站或网络应用可能需要这些天气信息,如旅游网站、新闻网站和许多其他移动应用。

您可以创建并发布 web 服务,而不是为他们的网站重新编写代码,网站可以在需要的时候使用所需的 web 服务。

在本章中,你将学习以下主题:

  • 什么是基于 http 的服务,它是多么有用
  • 提琴手是什么
  • 如何使用 Fiddler 组成一个 HTTP 请求并触发它以获得 HTTP 响应
  • 如何使用 Web API 设计和实现 HTTP 服务

微软提供了 ASP.NET Web API 用于程序员构建基于 http 的服务。 但是 HTTP 不仅仅用于服务网页。 您可以使用 HTTP 作为平台。 这带来了几个好处:

  • 作为使用 ASP 构建的 web 服务.NET Web API 使用 HTTP 进行通信,这些 Web 服务可以从各种应用中使用,从控制台应用到 Web 应用,从 WCF 服务到移动应用
  • 每当 web 服务的逻辑/代码发生任何更改时,客户机(使用服务的网站)不需要更改任何内容。 他们可以像以前一样使用 web 服务

HTTP 基础知识

HTTP 是构建服务的强大平台。 您可以使用现有的 HTTP 谓词构建服务。 例如,您可以使用现有 HTTP 谓词 GET 来获取产品列表,或者使用 POST 来更新关于产品的信息。 让我们快速了解一下 HTTP 如何构建服务。

在 ASP 中提供 HTML 页面的底层机制没有区别.NET MVC,并在 HTTP 服务上下文中提供数据服务。 两者都遵循请求-响应模式和相同的路由机制。

HTTP 请求可以从任何客户机(台式机、笔记本电脑、平板电脑、移动设备等)发送到服务器,服务器将用 HTTP 响应进行响应。 HTTP 响应可以以任何格式(如 JSON 或 XML)发送到客户机。 如下图所示:

HTTP basics

在上面的图表中,一个请求是从桌面计算机发送的(它可以从移动设备或平板电脑发送; 这没有什么区别),服务器返回请求的 HTTP 响应。 由于大多数设备都支持 HTTP,所以它是无处不在的。

HTTP 动词

HTTP 动词描述如何发送请求。 这些是在 HTTP 中定义的方法,它们规定了如何将 HTTP 请求从客户机发送到服务器

GET 方法

当我们使用 HTTP GET 请求时,信息通过 URL 本身传递:

GET api/employees/{id} 

GET请求根据传递的 ID 获取员工信息。 使用GET请求的优点是它是轻量级的,所有需要的信息都将在 URL 或 header 本身中传递,如下图所示:

GET method

PUT 方法

PUT方法用于创建或更新资源。 PUT是幂等操作,即即使执行多次,预期的行为也不会改变:

PUT method

POST 方法

您可以使用 POST 来创建或更新资源。 通常,POST 用于创建资源,而不是更新它。 根据 HTTP 标准,当你创建一个新资源时,你应该返回一个201 HTTP状态码:

POST method

DELETE 方法

DELETE 方法用于删除资源。 通常,当你删除一个资源时,你会传递 ID 作为一个参数,你不会在请求体中传递任何东西:

DELETE method

通常,HTTP 服务将由其他应用和服务使用。 使用服务的应用称为客户机。 测试 HTTP 服务的一个选项是构建客户机。 但这将耗费时间,而且在测试 HTTP 服务后,我们可能会丢弃客户机代码。

另一种广泛使用的选择是使用应用,使我们能够触发 HTTP 请求并监视响应。 有许多可用的应用,Fiddler 就是其中一个广泛使用的应用。

小提琴工具

Fiddler 是用于监视 HTTP 和 HTTPS 流量的代理服务器应用。 您可以监视从客户机发送到服务器的请求、发送到客户机的响应以及从服务器接收到的响应。 这就像看到服务器和客户机之间的管道中的流量一样。 您甚至可以编写请求、触发它并分析接收到的响应,而不需要为服务编写客户机。

您可以在http://www.telerik.com/fiddler下载小提琴手。 你会看到以下窗口:

Fiddler tool

足够的理论。 让我们使用 ASP 创建一个简单的 web 服务。 净 Web API。

启动 Visual Studio 2015:

Fiddler tool

当您单击OK时,将创建一个 Web API 解决方案。 就像 ASP.NET Core 应用控制器继承自 controller 类。

Fiddler tool

Web API 类也将继承相同的 Controller 类。 这是 ASP 的区别.NET Core 和早期版本的 asp.net。 净 MVC。 在早期版本中,所有 Web API 控制器类都继承自ApiController类。 在 ASP.NET 5,它已经被统一了,同样的基本控制器类被用于构建 web 应用和服务。

以下是创建项目时选择Web API模板选项时默认创建的ValuesController类:

Fiddler tool

在我们创建自己的自定义控制器之前,让我们分析一下默认的 API 控制器。 在ValuesController文件中,已经定义了几个 API 方法。

有两个重载的GET方法—一个带有参数,另一个没有参数。 不带参数的GET方法返回该类型的所有资源。 在本例中,我们只返回两个字符串。 在现实世界中,我们将返回资源的元数据。 例如,如果我们在电影 API 控制器上触发GET请求,它将返回关于所有电影的信息。 带有id参数的GET方法返回其 ID 与传递的 ID 匹配的资源。 例如,如果您传递一个电影 ID,它将返回关于该电影的信息。 其他方法(如PUTPOSTDELETE)的主体在这个控制器中是空的,我们将在后面讨论这些方法。

当你运行应用时,你会得到以下输出:

Fiddler tool

默认情况下,它触发一个到api/values的请求,这些值显示在浏览器中。

让我们学习如何从 Fiddler 应用启动 HTTP 请求。 打开提琴手应用。 在左下角,选择红色框中的Web Browsers选项。 选择此选项将使我们能够查看来自Web 浏览器的流量:

Fiddler tool

选择Composer选项卡,输入 URLhttp://localhost:49933/api/values,如下图所示,然后单击右上角的Execute按钮:

Fiddler tool

单击Execute按钮后,将创建一个 HTTP 会话,在左侧窗格中可见(在蓝色框中突出显示)。 单击会话,并选择右上角窗格上的inspector选项卡。 在右下角窗格中选择 JSON 选项卡(在下面的截图中由紫色边框框突出显示)。

您可以看到 HTTP 请求返回的 JSON 数据——value1value2如下截图:

Fiddler tool

现在轮到我们编写自定义 API 了。

在这个自定义 API 中,我们将提供 API 方法来创建员工对象、列出所有员工对象和删除员工对象。

首先,让我们为员工创建一个模型。 我们需要创建一个文件夹来保存这些模型。 右键单击项目,选择添加|新建文件夹,将文件夹命名为Models:

Fiddler tool

右键单击Models文件夹,选择Add|New Item…来创建一个员工模型类。 这个员工模型类只是一个 POCO 类。 参见以下代码:

public class Employee
{
  public int Id {get; set;}
  public string FirstName {get; set;}
  public string LastName {get; set;}
  public string Department {get; set;}
}

然后,我们定义存储库接口来处理模型:

public interface IEmployeeRepository
{
  void AddEmployee(Employee e);
  IEnumerable<Employee> GetAllEmployees();
  Employee GetEmployee(int id);
  Employee RemoveEmployee(int id);
  void UpdateEmployee(Employee employee);
}

然后我们为这个模型实现接口:

public class EmployeeRepository : IEmployeeRepository
{
  private static List<Employee> employees = new List<Employee>();

  public EmployeeRepository()
  {

    Employee employee1 = new Employee
    {
      FirstName = "Mugil",
      LastName = "Ragu",
      Department = "Finance",
      Id = 1
    };

   Employee employee2 = new Employee
   {
      FirstName = "John",
      LastName = "Skeet",
      Department = "IT",
      Id = 2
   };

  employees.Add(employee1);
  employees.Add(employee2);
  }

  public IEnumerable<Employee> GetAllEmployees()
  {
    return employees;
  }

  public void AddEmployee(Employee e)
  {
    e.Id = GetNextRandomId();
    employees.Add(e);
  }

  public Employee GetEmployee(int id)
  {
    return employees.Where(emp => emp.Id == id).FirstOrDefault();
  }

  public Employee RemoveEmployee(int id)
  {
    Employee employee = employees.Where(emp => emp.Id ==   id).FirstOrDefault();
    if (employee !=null )
    {
      employees.Remove(employee);
    }
    return employee;
  }

  public void UpdateEmployee(Employee emp)
  {
    Employee employee = employees.Where(e => e.Id ==  emp.Id).FirstOrDefault();
    if(employee != null)
    {
    employee.Department = emp.Department;
    employee.FirstName = emp.FirstName;
    employee.LastName = emp.LastName;
    }
  }

  private int GetNextRandomId()
  {
    int id = -1;
    bool isIdExists;
    Random random = new Random();
    do
    {
      id = random.Next();
      isIdExists = employees.Any(emp => emp.Id == id);
    } while (isIdExists);
    return id;
  }
}

在实现类中有一些事情需要注意:

  • 我们决定不使用数据库,因为我们的目标是使用 Web API 创建 HTTP 服务,而不是编写数据访问代码。
  • 我们使用内存中的列表来保存数据。 所有的操作都将在这个列表中执行。 事实上,数据可以是任何形式的,从关系数据库到简单的内存列表。
  • 在构造函数方法中,我们将一个对象添加到列表中。 这个列表将作为我们的 HTTP 服务的数据库。
  • GetAllEmployeesAPI 方法将作为IEnumerable接口返回所有员工。
  • AddEmployee方法将员工(作为参数传递)添加到列表中。
  • GetEmployee方法将返回 ID 与参数匹配的员工。
  • 方法将从列表中删除该员工。
  • UpdateEmployee方法将更新员工信息。
  • 方法将返回下一个可用的随机整数。 这个整数值用于生成员工 ID。

依赖注入

在大多数实际项目中,我们不会在任何控制器中使用new实例实例化任何对象,原因是我们不希望依赖组件(控制器和存储库之间)之间有紧密耦合。 相反,我们将一个接口传递给控制器,依赖注入容器(如Unity)将在控制器需要它时为我们创建一个对象。 这种设计模式通常称为Control 的反转

假设一个名为class a的类使用另一个类class b。 在本例中,ClassA只要知道ClassB的行为、方法和属性就足够了,而且不需要ClassB的内部实现细节。 因此,我们可以抽象ClassB并从类中创建一个接口,然后将该接口作为参数而不是具体类。 这种方法的优点是,我们可以在运行时传递任何类,只要它实现了一个公认的契约(接口)。

在 ASP.NET 5(包括 asp.net.NET Core 和 Web API),我们内置了对依赖注入的支持。 在ConfigureServices方法中,我们添加了执行依赖注入的行(用粗体突出显示)。 我们指示内置的依赖注入容器在引用IEmployeeRepository接口的地方创建EmployeeRepository类,并且指示它为单例; 这意味着相同的对象(将由依赖注入容器创建)将在应用的整个生命周期中共享:

public void ConfigureServices(IServiceCollection services)
{
  // Add framework services.
  services.AddApplicationInsightsTelemetry(Configuration);
  services.AddMvc();

 services.AddSingleton<IEmployeeRepository, EmployeeRepository>(); 

}

在前面的代码中,我们为依赖注入使用了单例模式,该模式仅在第一次请求服务时才创建服务。 还有其他类型的生命周期服务,如TransientScoped。 瞬态生命周期服务在每次请求时创建,范围生命周期服务在每个请求中创建一次。 以下是使用生命周期时创建的代码片段:

services.AddTransient

 <IEmployeeRepository, EmployeeRepository>(); 

services.AddScoped  <

IEmployeeRepository, EmployeeRepository>();

现在是时候进入创建 API 控制器的操作的核心部分了。 右键单击Controllers文件夹,选择Add |New Item。 然后从列表中选择Web API Controller Class,如下图所示。 命名你的控制器,然后点击添加按钮:

Dependency Injection

移除 Controller 中生成的代码,并添加以下构造函数:

public EmployeeController(IEmployeeRepository employeesRepo)
{
  employeeRepository = employeesRepo;
}
private IEmployeeRepository employeeRepository {get; set;}

在前面的构造函数中,我们注入了依赖项。 在调用这个构造函数时,将创建EmployeeRepository对象。

让我们实现两个GET方法——第一个方法将返回所有员工的详细信息,第二个GET方法将根据传递的员工 ID 返回员工:

public IEnumerable<Employee> GetAll()
{
  return employeeRepository.GetAllEmployees();
}

[HttpGet("{id}",Name ="GetEmployee")]
public IActionResult GetById(int id)
{
  var employee = employeeRepository.GetEmployee(id);
  if(employee == null)
  {
  return NotFound();
  }
  return new ObjectResult(employee);
}

让我们从 Fiddler 调用这些 HTTP 方法。

运行解决方案,打开 Fiddler 应用,并单击Composer选项卡。

选择 HTTP 方法(我们选择了GET方法,因为我们有一个 GET API 方法)并输入 URLhttp://localhost:49933/api/employee

请注意,当我运行应用时,它运行在端口49933上; 在您的情况下,端口号将不同,因此相应地构造您的 URL。

输入 URL 并选择方法后,点击Execute按钮,如下截图所示:

Dependency Injection

单击Execute按钮后,将创建一个 HTTP 会话,并触发请求。

单击左侧窗格上的会话(如下截图所示),并在右侧窗格中选择inspector选项卡。 您可以在右下角窗格的JSON选项卡中查看结果:

Dependency Injection

让我们触发另一个 HTTP 请求以获取特定员工的信息,比如 ID 为 2 的员工。 我们将通过附加 IDhttp://localhost:49933/api/employee/2来构建 URL,如下所示:

Dependency Injection

选择最近创建的 HTTP 会话,并单击它:

您可以在右侧窗格中看到 JSON 格式的结果。

现在,我们将向我们的服务中添加CreateUpdateDelete操作。 首先,我们将提供 Create 功能,将员工的添加到我们的服务:

[HttpPost]
public IActionResult Add([FromBody] Employee emp)
{
  if (emp == null)
  {
    return BadRequest();
  }
  employeeRepository.AddEmployee(emp);
  return CreatedAtRoute("GetEmployee", new { id = emp.Id }, emp);
}

采用上述Add方法时,应考虑以下几点:

  1. 我们将Employee对象作为参数传递。 我们通过指定[FromBody]属性来指示Add方法从请求体中获取该对象:
    • 如果没有传递 employee 对象,我们将把错误的请求返回给调用的客户端
    • 如果它不为空,我们将调用存储库方法来将员工添加到列表中(在现实世界中,我们将把它添加到数据库中)
  2. 一旦我们添加了雇员,我们将在创建新资源时返回201 状态码(根据 HTTP 标准)。

打开 Fiddler 应用,按照以下步骤添加员工:

  1. 选择 HTTP 方式为POST,输入 URLhttp://localhost:54504/api/employee/
  2. 您需要在请求头中将内容类型指定为application/json。 请看下面的截图,我们在其中添加了Content-Type: application/json到请求头。
  3. As mentioned in the code, we have to pass the employee object in the form of JSON in the body of the request. In the following request, we have formed a JSON that contains the properties of the Employee object with the values in the brackets { "FirstName" : "James", "LastName" : "Anderson","Department" : "IT"}:

    Dependency Injection

创建好请求后,可以单击Execute按钮来触发请求。 这会返回201 HTTP 状态码,这是创建新资源的标准 HTTP 响应:

Dependency Injection

一旦我们在服务器中创建了资源,我们就会重定向响应以获得新创建的资源。 当我们调用CreatedAtRoute方法并将新创建的员工 ID 作为参数传递时,就会发生这种情况。

单击左侧的会话,并在右侧窗格中选择Inspector选项卡。 现在您可以看到请求的响应。 响应包含在服务器中新创建的Employee对象。 我们必须注意,Employee对象的 ID 是在服务器上生成的,并且在下面的响应中可用。 本例中为该员工生成的 ID 为1771082655:

Dependency Injection

在前面的 Fiddler 窗口的右下角面板中,我们可以看到新创建资源的完整 JSON 响应。

现在我们将添加一个 Web API 方法来更新资源。 更新资源的方法与创建资源的方法非常相似,只有一些区别。 当我们创建资源时,我们使用HTTP POST方法,而当我们更新资源时,我们使用HTTP PUT方法。

如果在存储库中找不到传递的员工 ID,我们将返回一个404 错误响应,这是一个未找到资源的 HTTP 标准错误响应。

下面是更新资源的 Web API 控制器方法代码:

[HttpPut]
public IActionResult Update([FromBody] Employee emp)
{
  if( emp == null)
  {
    return BadRequest();
  }
  Employee employee = employeeRepository.GetEmployee(emp.Id);
  if(employee == null)
  {
    return NotFound();
  }
  employeeRepository.UpdateEmployee(emp);
  return new NoContentResult();
}

以下是用于更新员工的存储库层代码:

public void UpdateEmployee(Employee emp)
{
  Employee employee = employees.Where(e => e.Id == emp.Id).FirstOrDefault();
  if (employee != null)
  {
    employee.Department = emp.Department;
    employee.FirstName = emp.FirstName;
    employee.LastName = emp.LastName;
  }
}

打开 Fiddler 应用,并编写一个请求HTTP PUT。 因为我们要将Employee对象传递到请求的主体中,所以我们需要将内容类型提到为application/json。 在请求体中,我们需要以 JSON 格式提供Employee对象,如下截图所示:

Dependency Injection

当您单击Execute按钮时,HTTP PUT请求将被触发,我们的 Web API 方法将被调用。 一旦成功,将返回HTTP 204响应:

Dependency Injection

删除方法

删除资源时应使用HTTP DELETE方法。 不需要在请求体中传递任何内容。

删除资源的 Web API 方法

DeleteWeb API 方法有一个void返回类型,它将返回一个HTTP 200响应:

[HttpDelete("{id}")]
public void Delete(int id)
{
  employeeRepository.RemoveEmployee(id);
}

用于删除员工数据的 Web 存储库层代码

在下面的存储库层方法中,我们从内部员工列表中删除员工(其 ID 与传递的参数的 ID 匹配)。 但在现实世界中,我们将与数据库交互以删除特定的员工。 考虑以下代码:

public Employee RemoveEmployee(int id)
{
  Employee employee = employees.Where(emp => emp.Id ==   id).FirstOrDefault();
  if(employee != null)
  {
    employees.Remove(employee);
  }
  return employee;
}

打开 Fiddler 应用,选择DELETEHTTP 方法,传递带有参数的 URL,然后单击Execute按钮。 请注意,我们没有在请求头中传递内容类型,因为我们没有在请求体中传递任何 employee 对象:

Web Repository layer code for deleting the employee data

当我们返回 void 时,Web APIDELETE方法返回一个HTTP 200状态,正如你在 Fiddler 应用的左侧窗格中所看到的:

Web Repository layer code for deleting the employee data

总结

在本章中,您了解了 HTTP 服务及其用途。 我们讨论了如何使用 Web API 设计和实现 HTTP 服务。 我们使用 Fiddler 工具构造 HTTP 请求并返回响应。 我们还学习了如何编写 Web API 方法来从头到尾执行 CRUD 操作,从编写 Web API 方法到触发请求和返回响应。