65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET Web API - 保持简单

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2016年11月25日

CPOL

14分钟阅读

viewsIcon

75380

本文旨在尝试分离控制器层,以降低其复杂性并提高最终代码的质量和可维护性。

引言

ASP.NET Web API 是一个框架,可以轻松构建 HTTP 服务,这些服务可以覆盖广泛的客户端,包括浏览器和移动设备上的原生应用。它借鉴了 HTTP 协议的简单性,为应用程序之间通过网络进行通信建立了简单的规则。

为什么选择 Web API?

ASP.NET MVC 框架于 2009 年发布,实现了模型-视图-控制器 (MVC) 模式。MVC 允许将应用程序分为三个层级:

  • 模型(Model),由领域内的业务对象组成

  • 视图(View),由用于构建领域内应用程序用户界面的组件组成

  • 控制器(Controller),由系统的其余部分组成。其功能包括控制模型层和视图层之间的交互。在这个三层框架中,它默认也是领域业务逻辑的容器

虽然模型层和视图层有明确的角色来定义其职责和功能,但控制器层往往被赋予了多种职责,包括作为应用程序的入口点、管理其他两个层的交互、包含领域的业务逻辑、处理持久化、安全、审计以及所有其他责任。

ASP.NET Web API 是一项演进式发展,它通过与 HTTP 协议的相应规则保持一致,简化了应用程序和应用程序组件的通信规则。这一发展自然而然地提供了一个机会,可以将控制器活动更细致地划分为可重用且独立的业务逻辑层和仓库层,这些层可以被多个应用程序共享。

架构方面的考虑

尽管引入了 MVC 及其对职责分离的关注,控制器层仍然相当笨重,因为它最终要负责所有超出模型层和视图层范围的功能。本文旨在尝试分离控制器层,以便有意义地降低其复杂性,从而提高最终代码的质量和可维护性。

设计目标

领域或业务逻辑是应用程序中的主要组件之一。将此组件置于一个单独的层中的主要理由是,该组件可能被各种应用程序共享。如果它与处理安全、审计和数据持久化等补充功能的代码交织在一起,那么它被共享的可能性就会降低。这样一来,一个领域内的每个应用程序很可能会实现自己的业务规则层。代码重复、实现冲突以及开发和维护所需资源激增是这种情况的常见副作用。我们在本文中的第一个目标是设计一个独立于控制器代码其余部分的独立业务(服务)层。

仓库层或持久化层处理数据的永久存储和检索。这通常默认与控制器代码结合在一起。这可能导致大量的仓库代码重复,因为各种控制器都实现了持久化代码。这种紧密耦合也可能使持久化层的更改(例如更换数据库软件甚至升级到新版本)成为一个资源密集且容易出错的操作。本文的另一个目标是将仓库层从控制器和业务层中独立出来。

入门要求

您将需要 Visual Studio 2013 或更高版本(支持 Web 开发)、SQL Server 2008 或更高版本以及 Northwind 数据库(https://northwinddatabase.codeplex.com/)。关于测试我们 Web API 的最后一节将需要使用 Fiddler(https://www.telerik.com/download/fiddler)。

我假设您已经下载并安装了这些或等效的组件,所以不再赘述,让我们开始吧。

第 1 步:创建一个 ASP.NET Web API 项目

打开 Visual Studio,在“开始页”上点击“新建项目”。在“新建项目”对话框中,选择“ASP.NET Web 应用程序”,输入“NorthwindWebAPI”作为我们应用程序的名称,然后点击“确定”。

Visual Studio - New Project dialog

这将打开“新建 ASP.NET 项目”对话框。选择 Web API 模板。这应该会自动勾选 MVC 和 Web API 复选框。将身份验证更改为“无身份验证”,然后点击“确定”继续。

New Project - Select Template dialog

几秒钟后,您的应用程序模板生成应该会完成,您应该会在“解决方案资源管理器”窗口中看到以下设置。注意,带有删除线的组件表示(对于本用例而言)是多余的元素,可以删除。

Default Elements of Web API project

切换回 Visual Studio 并按 F5 键,看看我们从这个默认的应用程序外壳中得到了什么。我将跳过对该模板生成的组件的解释。如果您想在继续之前了解更多细节,我推荐观看 Jon Galloway 的这个 4 分钟短视频:“ASP.NET Web API,第 1 部分:您的第一个 Web API”

在继续下一步之前,请停止正在运行的应用程序。

2) 设置模型、仓库和服务层

如前文“设计目标”部分所述,我们的目标是构建独立的服务层和仓库层,以便它们可以被多个应用程序使用。我们通过创建独立的项目来容纳每一层来实现这一点。

Layered Architecture for Web API

如上图所示,每一层都引用其紧邻的下一层。模型层是所有其他层的共同依赖项。它是各层之间通信的媒介。仓库层负责所有数据库操作,其他任何层都不应包含任何数据库组件代码。服务层封装了领域的业务逻辑。控制器(在 MVC 项目中)对应用程序 UI 发起的事件做出反应。在确保请求的有效性并验证任何必需的参数后,它们将请求传递给相应的服务进行处理。
因此,当 OrderController 收到一个 Get 请求时,它会调用 OrderService 的相关方法。OrderService 通过分别调用 OrderRepository 和 OrderDetailRepository 来组装 Order。如有必要,它还可能联系 CustomerRepository 来检索订单的客户信息,以及 EmployeeRepository 来检索处理该订单的员工信息。它将这个 Order 对象返回给 OrderController,OrderController 可以用它来填充视图或响应 Web 服务请求。

第 2a 步:为每一层创建独立的项目

在“解决方案资源管理器”中右键单击解决方案名称,然后再次选择“添加 - 新建项目”。输入“NorthwindWebAPI.Models”作为项目名称,然后点击“确定”。从“新建 ASP.NET 项目”对话框中选择“空”模板,然后点击“确定”。

New Project - Empty Template

使用“空”模板再添加两个项目。将它们命名为“NorthwindWebAPI.Repositories”和“NorthwindWebAPI.Services”。您的解决方案结构应如下图所示。

注意:您可以删除这三个项目中的“package.config”和“web.config”文件。从现在开始,我将它们称为 Models、Repositories 和 Services 项目。

Solution Explorer - showing all projects

第 2b 步:设置启动项目并添加引用

右键单击 NorthwindWebAPI 项目,然后选择“设为启动项目”。Visual Studio 可能会将最后添加的项目设置为您的启动项目,这样做可以纠正这个假设。

Solution Explorer - Set as StartUp Project

右键单击 Repositories 项目中的“引用”节点,然后选择“添加引用”。从“解决方案”中选择“NorthwindWebAPI.Models”,如下图所示,然后点击“确定”。

Add Reference to Models project

同样,右键单击 Services 项目中的“引用”节点,然后选择“添加引用”。从“解决方案”中选择“NorthwindWebAPI.Models”以及“NorthwindWebAPI.Repositories”,然后点击“确定”。

最后,右键单击 NorthwindWebAPI 项目中的“引用”节点,然后选择“添加引用”。从“解决方案”中选择“NorthwindWebAPI.Models”以及“NorthwindWebAPI.Services”,然后点击“确定”。

3) 创建模型层的组件

我们现在添加代码,为每个模型组件定义成员和构造函数。这些组件是 Customer、Employee、Order、Order_Detail 和 Product。

第 3a 步:创建数据模型

右键单击 Models 项目,选择“添加 - 类”,将类命名为“Customer”。用以下代码替换文件中的代码:

using System;
using System.Data;


namespace NorthwindWebAPI.Models
{
    public class Customer
    {
        public string CustomerID { get; set; }
        public string CompanyName { get; set; }
        public string ContactName { get; set; }
        public string ContactTitle { get; set; }
        public string Address { get; set; }
        public string City { get; set; }
        public string Region { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
        public string Phone { get; set; }
        public string Fax { get; set; }


        public Customer() { }  //the empty constructor aids serialization


        public Customer(IDataReader reader)
    	{
        this.CustomerID = reader["CustomerID"].ToString();
        this.CompanyName = reader["CompanyName"].ToString();
        this.ContactName = reader["ContactName"].ToString();
        this.ContactTitle = reader["ContactTitle"].ToString();
        this.Address = reader["Address"].ToString();
        this.City = reader["City"].ToString();
        this.Region = reader["Region"] == DBNull.Value ? string.Empty : reader["Region"].ToString();
        this.PostalCode = reader["PostalCode"].ToString();
        this.Country = reader["Country"].ToString();
        this.Phone = reader["Phone"].ToString();
        this.Fax = reader["Fax"] == DBNull.Value ? string.Empty : reader["Fax"].ToString();
    	}
    }
}

使用相同的步骤创建“Employee”类,并将以下代码粘贴到默认类代码之上。

using System;
using System.Data;


namespace NorthwindWebAPI.Models
{
    public class Employee
    {
        public int EmployeeID { get; set; }
        public string LastName { get; set; }
        public string FirstName { get; set; }
        public string Title { get; set; }
        public string TitleOfCourtesy { get; set; }
        public DateTime? BirthDate { get; set; }
        public DateTime? HireDate { get; set; }
        public string Address { get; set; }
        public string City { get; set; }
        public string Region { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
        public string HomePhone { get; set; }
        public string Extension { get; set; }
        public byte[] Photo { get; set; }
        public string Notes { get; set; }
        public string PhotoPath { get; set; }


        public Employee() { }  //the empty constructor aids serialization


        public Employee(IDataReader reader)
        {
            this.EmployeeID = Convert.ToInt32(reader["EmployeeID"]);
            this.TitleOfCourtesy = reader["TitleOfCourtesy"] as string;
            this.FirstName = reader["FirstName"].ToString();
            this.LastName = reader["LastName"].ToString();
            this.Title = reader["Title"] as string;
            this.BirthDate = reader["BirthDate"] as DateTime?;
            this.HireDate = reader["HireDate"] as DateTime?;
            this.Address = reader["Address"] as string;
            this.City = reader["City"] as string;
            this.Region = reader["Region"] as string;
            this.PostalCode = reader["PostalCode"] as string;
            this.Country = reader["Country"] as string;
            this.HomePhone = reader["HomePhone"] as string;
            this.Extension = reader["Extension"] as string;
            this.Photo = reader["Photo"] as byte[];
            this.Notes = reader["Notes"] as string;
            this.PhotoPath = reader["PhotoPath"] as string;
        }
    }
}

使用相同的步骤创建“Order”类,并将以下代码粘贴到默认类代码之上。

using System;
using System.Collections.Generic;
using System.Data;

namespace NorthwindWebAPI.Models
{
    public class Order
    {
        public int OrderID { get; set; }
        public DateTime? OrderDate { get; set; }
        public DateTime? RequiredDate { get; set; }
        public DateTime? ShippedDate { get; set; }
        public int? ShipVia { get; set; }
        public decimal? Freight { get; set; }
        public string ShipName { get; set; }
        public string ShipAddress { get; set; }
        public string ShipCity { get; set; }
        public string ShipRegion { get; set; }
        public string ShipPostalCode { get; set; }
        public string ShipCountry { get; set; }

        public Customer Customer { get; set; }
        public Employee Employee { get; set; }

        public ICollection<Order_Detail> Order_Details { get; set; }

        public Order() { } //the empty constructor aids serialization

        public Order(IDataReader reader)
        {
            this.OrderID = Convert.ToInt32(reader["OrderID"]);
            this.OrderDate = reader["OrderDate"] as DateTime?;
            this.RequiredDate = reader["RequiredDate"] as DateTime?;
            this.ShippedDate = reader["ShippedDate"] as DateTime?;
            this.ShipVia = reader["ShipVia"] as int?;
            this.Freight = reader["Freight"] as decimal?;
            this.ShipName = reader["ShipName"] as string;
            this.ShipAddress = reader["ShipAddress"] as string;
            this.ShipCity = reader["ShipCity"] as string;
            this.ShipRegion = reader["ShipRegion"] as string;
            this.ShipPostalCode = reader["ShipPostalCode"] as string;
            this.ShipCountry = reader["ShipCountry"] as string;


            this.Customer = new Customer();
            this.Customer.CustomerID = reader["CustomerID"] as string;
            this.Customer.CustomerID = this.Customer.CustomerID;
            this.Customer.CompanyName = reader["CompanyName"] as string;
            this.Customer.ContactName = reader["ContactName"] as string;


            this.Employee = new Employee();
            this.Employee.EmployeeID = Convert.ToInt32(reader["EmployeeID"]);
            this.Employee.EmployeeID = this.Employee.EmployeeID;
            this.Employee.FirstName = reader["FirstName"] as string;
            this.Employee.LastName = reader["LastName"] as string;


            Order_Details = new HashSet<Order_Detail>();
        }
    }
}

使用相同的步骤创建“Order_Detail”类,并将以下代码粘贴到默认类代码之上。

using System;
using System.Data;

namespace NorthwindWebAPI.Models
{
    public class Order_Detail
    {
        public int OrderID { get; set; }
        public int ProductID { get; set; }
        public decimal UnitPrice { get; set; }
        public short Quantity { get; set; }
        public float Discount { get; set; }

        public Order Order { get; set; }
        public Product Product { get; set; }

        public Order_Detail() { } //the empty constructor aids serialization

        public Order_Detail(IDataReader reader)
        {
            this.OrderID = Convert.ToInt32(reader["OrderID"]);
            this.ProductID = Convert.ToInt32(reader["ProductID"]);
            this.UnitPrice = Convert.ToDecimal(reader["UnitPrice"]);
            this.Quantity = Convert.ToInt16(reader["Quantity"]);
            this.Discount = Convert.ToInt32(reader["Discount"]);


            this.Product = new Product();
            this.Product.ProductID = this.ProductID;
            this.Product.ProductName = reader["ProductName"].ToString();
        }
    }
}

使用相同的步骤创建“Product”类,并将以下代码粘贴到默认类代码之上。

namespace NorthwindWebAPI.Models
{
    public class Product
    {
        public int ProductID { get; set; }
        public string ProductName { get; set; }
    }
}

第 3b 步:为数据模型创建帮助类

使用相同的步骤创建“ExtensionMethods”类,并将以下代码粘贴到默认类代码之上。

using System;
using System.Data.SqlClient;

namespace NorthwindWebAPI.Models
{
    public static class ExtensionMethods
    {
        public static SqlParameter[] ToParameterArray(this Customer customer)
        {
            return new SqlParameter[] { new SqlParameter("@CustomerID", customer.CustomerID)
                                                , new SqlParameter("@CompanyName", customer.CompanyName)
                                                , new SqlParameter("@ContactName", customer.ContactName)
                                                , new SqlParameter("@ContactTitle", customer.ContactTitle)
                                                , new SqlParameter("@Address", customer.Address)
                                                , new SqlParameter("@City", customer.City)
                                                , new SqlParameter("@Region", customer.Region ?? (object)DBNull.Value)
                                                , new SqlParameter("@PostalCode", customer.PostalCode)
                                                , new SqlParameter("@Country", customer.Country)
                                                , new SqlParameter("@Phone", customer.Phone)
                                                , new SqlParameter("@Fax", customer.Fax ?? (object)DBNull.Value)
                                                };
        }

        public static SqlParameter[] ToParameterArray(this Employee employee)
        {
            return new SqlParameter[]
            {
                new SqlParameter("@EmployeeID", employee.EmployeeID)
                , new SqlParameter("@LastName", employee.LastName)
                , new SqlParameter("@FirstName", employee.FirstName)
                , new SqlParameter("@Title", employee.Title ?? (object)DBNull.Value)
                , new SqlParameter("@TitleOfCourtesy", employee.TitleOfCourtesy ?? (object)DBNull.Value)
                , new SqlParameter("@BirthDate", employee.BirthDate ?? (object)DBNull.Value)
                , new SqlParameter("@HireDate", employee.HireDate ?? (object)DBNull.Value)
                , new SqlParameter("@Address", employee.Address ?? (object)DBNull.Value)
                , new SqlParameter("@City", employee.City ?? (object)DBNull.Value)
                , new SqlParameter("@Region", employee.Region ?? (object)DBNull.Value)
                , new SqlParameter("@PostalCode", employee.PostalCode ?? (object)DBNull.Value)
                , new SqlParameter("@Country", employee.Country ?? (object)DBNull.Value)
                , new SqlParameter("@HomePhone", employee.HomePhone ?? (object)DBNull.Value)
                , new SqlParameter("@Extension", employee.Extension ?? (object)DBNull.Value)
                , new SqlParameter("@Photo", employee.Photo ?? (object)DBNull.Value)
                , new SqlParameter("@Notes", employee.Notes ?? (object)DBNull.Value)
                , new SqlParameter("@PhotoPath", employee.PhotoPath ?? (object)DBNull.Value)
            };
        }

        public static SqlParameter[] ToParameterArray(this Order order)
        {
            return new SqlParameter[]
            {
                new SqlParameter("@OrderID", order.OrderID)
                , new SqlParameter("@CustomerID", order.Customer.CustomerID)
                , new SqlParameter("@EmployeeID", order.Employee.EmployeeID)
                , new SqlParameter("@OrderDate", order.OrderDate)
                , new SqlParameter("@RequiredDate", order.RequiredDate ?? (object)DBNull.Value)
                , new SqlParameter("@ShippedDate", order.ShippedDate ?? (object)DBNull.Value)
                , new SqlParameter("@ShipVia", order.ShipVia ?? (object)DBNull.Value)
                , new SqlParameter("@Freight", order.Freight ?? (object)DBNull.Value)
                , new SqlParameter("@ShipName", order.ShipName ?? (object)DBNull.Value)
                , new SqlParameter("@ShipAddress", order.ShipAddress ?? (object)DBNull.Value)
                , new SqlParameter("@ShipCity", order.ShipCity ?? (object)DBNull.Value)
                , new SqlParameter("@ShipRegion", order.ShipRegion ?? (object)DBNull.Value)
                , new SqlParameter("@ShipPostalCode", order.ShipPostalCode ?? (object)DBNull.Value)
                , new SqlParameter("@ShipCountry", order.ShipCountry ?? (object)DBNull.Value)
            };
        }

        public static SqlParameter[] ToParameterArray(this Order_Detail orderDetail)
        {
            return new SqlParameter[]
            {
                new SqlParameter("@OrderID", orderDetail.OrderID)
                , new SqlParameter("@ProductID", orderDetail.ProductID)
                , new SqlParameter("@UnitPrice", orderDetail.UnitPrice)
                , new SqlParameter("@Quantity", orderDetail.Quantity)
                , new SqlParameter("@Discount", orderDetail.Discount)
            };
        }
    }
}    

第 3c 步:添加连接字符串

从 NorthwindWebAPI 项目中打开 web.config 文件,并在已有的 <configuration> 和 <appSettings> 标签之间插入 connectionStrings 节点。注意,不要粘贴 <configuration> 和 <appSettings> 标签,它们在下面仅作为指示 connectionStrings 节点插入位置的参考

在该节点中,将字符串“SQLSERVERINSTANCE”替换为您的 SQL Server 实例的实际名称。确保“initial catalog”的值与您的 Northwind 数据库的名称匹配。

<configuration>
  <connectionStrings>
    <add name="NorthwindDbConnection" providerName="System.Data.SqlClient"
         connectionString="Data Source=SQLSERVERINSTANCE;initial catalog=Northwind;integrated security=true;"/>
  </connectionStrings>
<appSettings>    

这样就完成了我们的模型层。让我们通过按“Ctrl-Shift-B”来编译和构建解决方案,进行一次健全性检查。您应该不会有任何错误或警告。

4) 创建仓库层组件

我们将使用 SQL Server 作为数据库,并使用简单的 TSQL 语句来存储和检索数据。一个流行的替代方案是使用实体框架(Entity Framework)来简化仓库层。我的选择纯粹是出于个人偏好。然而,如果有人希望切换,通过重写这个仓库层来实现切换,会比将代码嵌入到每个控制器类中更简单。

第 4a 步:创建 Repository 基类

右键单击 Repositories 项目,选择“添加 - 类”,将类命名为“Repository”。用以下代码替换文件中的代码:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;

namespace NorthwindWebAPI.Repositories
{
    public abstract class Repository<T> where T : class
    {
        public string ConnectionString { get; set; }


        public abstract T PopulateRecord(IDataReader reader);


        public Repository()
        {
            ConnectionString = ConfigurationManager.ConnectionStrings["NorthwindDbConnection"].ConnectionString;
        }

        #region Generic Get methods
        public virtual IEnumerable<T> Get(string sqlCommand)
        {
            List<T> tList = new List<T>();
            using (IDataReader reader = ExecuteReader(sqlCommand, null, CommandType.Text))
            {
                while (reader.Read())
                {
                    var entity = PopulateRecord(reader);
                    tList.Add(entity);
                }
            }

            return tList;
        }

        public virtual IEnumerable<T> Get(string sqlCommand, SqlParameter[] parameters
                                        , CommandType commandType = CommandType.Text)
        {
            List<T> entityList = null;
            using (IDataReader reader = ExecuteReader(sqlCommand, parameters, commandType))
            {
                entityList = new List<T>();
                while (reader.Read())
                {
                    entityList.Add(PopulateRecord(reader));
                }
            }

            return entityList.Count > 0 ? entityList : null;
        }
        #endregion

        #region Database command execution methods
        public virtual SqlConnection CreateConnection()
        {
            SqlConnection sqlConnection = new SqlConnection(ConnectionString);
            sqlConnection.Open();
            return sqlConnection;
        }

        protected virtual IDataReader ExecuteReader(string commandText, SqlParameter[] parameters = null
                                            , CommandType commandType = CommandType.Text
                                            , SqlConnection connection = null)
        {
            bool isNewConnection = false;
            if (connection == null || connection.State == ConnectionState.Closed)
            {
                connection = CreateConnection();
                isNewConnection = true;
            }

            try
            {
                SqlCommand command = new SqlCommand(commandText, connection);
                command.CommandType = commandType;
                if (parameters != null)
                {
                    command.Parameters.AddRange(parameters);
                }

                var commandBehavior = isNewConnection ? CommandBehavior.CloseConnection : CommandBehavior.Default;
                return command.ExecuteReader(commandBehavior);
            }
            catch (Exception)
            {
                if (isNewConnection && connection.State == ConnectionState.Open)
                {
                    connection.Close();
                }
                throw;
            }
        }

        protected virtual int ExecuteNonQuery(string commandText, SqlParameter[] parameters = null
                                            , CommandType commandType = CommandType.Text
                                            , SqlConnection connection = null
                                            , SqlTransaction transaction = null)
        {
            bool isLocalConnection = false;
            try
            {
                SqlCommand sqlCommand = BuildSqlCommand(commandText, parameters, commandType, ref connection, transaction, ref isLocalConnection);

                return sqlCommand.ExecuteNonQuery();
            }
            finally
            {
                if (isLocalConnection && connection.State != ConnectionState.Closed)
                    connection.Close();
            }
        }

        protected virtual object ExecuteScalar(string commandText, SqlParameter[] parameters = null
                                            , CommandType commandType = CommandType.Text
                                            , SqlConnection connection = null
                                            , SqlTransaction transaction = null)
        {
            bool isLocalConnection = false;
            try
            {
                SqlCommand sqlCommand = BuildSqlCommand(commandText, parameters, commandType, ref connection, transaction
                                                        , ref isLocalConnection);
                return sqlCommand.ExecuteScalar();
            }
            finally
            {
                if (isLocalConnection && connection.State != ConnectionState.Closed)
                    connection.Close();
            }
        }

        private SqlCommand BuildSqlCommand(string commandText, SqlParameter[] parameters, CommandType commandType
                                            , ref SqlConnection connection
                                            , SqlTransaction transaction
                                            , ref bool isLocalConnection)
        {
            SqlCommand sqlCommand = new SqlCommand(commandText);
            sqlCommand.CommandType = commandType;
            if (parameters != null)
            {
                sqlCommand.Parameters.AddRange(parameters);
            }

            if (connection == null || connection.State == ConnectionState.Closed)
            {
                connection = CreateConnection();
                isLocalConnection = true;
            }

            sqlCommand.Connection = connection;

            if (transaction != null)
                sqlCommand.Transaction = transaction;
            return sqlCommand;
        }
        #endregion
    }
}    

第 4b 步:创建 CustomerRepository 类

右键单击 Repositories 项目,选择“添加 - 类”,将类命名为“CustomerRepository”。用以下代码替换文件中的代码:

using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

using NorthwindWebAPI.Models;

namespace NorthwindWebAPI.Repositories
{
    public class CustomerRepository : Repository<Customer>
    {
        public override Customer PopulateRecord(IDataReader reader)
        {
            return new Customer(reader);
        }

        public IEnumerable<Customer> Get()
        {
            var sqlCommand = "SELECT CustomerID, CompanyName, ContactName, ContactTitle "
                            + ", [Address], City, Region, PostalCode, Country, Phone, Fax "
                            + " FROM dbo.Customers";

            return base.Get(sqlCommand);
        }

        public IEnumerable<Customer> Get(string customerId)
        {
            var sqlCommand = "SELECT CustomerID, CompanyName, ContactName, ContactTitle "
                            + ", [Address], City, Region, PostalCode, Country, Phone, Fax "
                            + " FROM dbo.Customers WHERE CustomerID = @CustomerID";
            var parameters = new SqlParameter[] { new SqlParameter("@CustomerID", customerId) };

            return base.Get(sqlCommand, parameters);
        }

        public void Add(Customer customer)
        {
            var sqlCommand = "IF NOT EXISTS(SELECT 1 FROM dbo.Customers WHERE CustomerID = @CustomerID)"
                            + "INSERT INTO dbo.Customers "
                            + " (CustomerID, CompanyName, ContactName, ContactTitle "
                            + ", [Address], City, Region, PostalCode, Country, Phone, Fax) "
                            + "VALUES(@CustomerID, @CompanyName, @ContactName, @ContactTitle "
                            + ", @Address, @City, @Region, @PostalCode, @Country, @Phone, @Fax) ";

            base.ExecuteNonQuery(sqlCommand, customer.ToParameterArray());
        }

        public void Update(Customer customer)
        {
            var sqlCommand = "UPDATE dbo.Customers "
                            + " SET CompanyName = @CompanyName, ContactName = @ContactName "
                            + "     , ContactTitle = @ContactTitle, [Address] = @Address "
                            + "     , City = @City, Region = @Region, PostalCode = @PostalCode "
                            + "     , Country = @Country, Phone = @Phone, Fax = @Fax "
                            + " WHERE CustomerID = @CustomerID ";

            base.ExecuteNonQuery(sqlCommand, customer.ToParameterArray());
        }

        public void Delete(string customerId)
        {
            var sqlCommand = "DELETE FROM dbo.Customers WHERE CustomerID = @CustomerID ";
            var parameters = new SqlParameter[] { new SqlParameter("@CustomerID", customerId) };

            base.ExecuteNonQuery(sqlCommand, parameters);
        }
    }
}

第 4c 步:创建 EmployeeRepository 类

右键单击 Repositories 项目,选择“添加 - 类”,将类命名为“EmployeeRepository”。用以下代码替换文件中的代码:

using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

using NorthwindWebAPI.Models;

namespace NorthwindWebAPI.Repositories
{
    public class EmployeeRepository : Repository<Employee>
    {
        public override Employee PopulateRecord(IDataReader reader)
        {
            return new Employee(reader);
        }

        public IEnumerable<Employee> Get()
        {
            var sqlCommand = "SELECT EMPLOYEE.EmployeeID, EMPLOYEE.LastName, EMPLOYEE.FirstName, EMPLOYEE.Title, EMPLOYEE.TitleOfCourtesy "
                            + ", EMPLOYEE.BirthDate, EMPLOYEE.HireDate, EMPLOYEE.HomePhone, EMPLOYEE.Extension "
                            + ", EMPLOYEE.[Address], EMPLOYEE.City, EMPLOYEE.Region, EMPLOYEE.PostalCode, EMPLOYEE.Country "
                            + ", EMPLOYEE.Photo, EMPLOYEE.Notes, EMPLOYEE.PhotoPath "
                            + ", EMPLOYEE.ReportsTo, MANAGER.FirstName + ' ' + MANAGER.LastName as Manager "
                            + "FROM dbo.Employees EMPLOYEE "
                            + "   INNER JOIN dbo.Employees MANAGER ON EMPLOYEE.ReportsTo = MANAGER.EmployeeID";

            return base.Get(sqlCommand);
        }

        public IEnumerable<Employee> Get(int employeeId)
        {
            var sqlCommand = "SELECT EMPLOYEE.EmployeeID, EMPLOYEE.LastName, EMPLOYEE.FirstName, EMPLOYEE.Title, EMPLOYEE.TitleOfCourtesy "
                            + ", EMPLOYEE.BirthDate, EMPLOYEE.HireDate, EMPLOYEE.HomePhone, EMPLOYEE.Extension "
                            + ", EMPLOYEE.[Address], EMPLOYEE.City, EMPLOYEE.Region, EMPLOYEE.PostalCode, EMPLOYEE.Country "
                            + ", EMPLOYEE.Photo, EMPLOYEE.Notes, EMPLOYEE.PhotoPath "
                            + ", EMPLOYEE.ReportsTo, MANAGER.FirstName + ' ' + MANAGER.LastName as Manager "
                            + "FROM dbo.Employees EMPLOYEE "
                            + "   INNER JOIN dbo.Employees MANAGER ON EMPLOYEE.ReportsTo = MANAGER.EmployeeID "
                            + "WHERE EMPLOYEE.EmployeeID = @EmployeeId ";
            var parameters = new SqlParameter[] { new SqlParameter("@EmployeeId", employeeId) };

            return base.Get(sqlCommand, parameters);
        }

        public void Add(Employee employee)
        {
            var sqlCommand =
                "IF NOT EXISTS(SELECT 1 FROM dbo.Employees WHERE FirstName = @FirstName AND LastName = @LastName "
                + "                           AND Title = @Title AND ReportsTo = @ReportsTo) "
                + " INSERT INTO dbo.Employees "
                + "    (LastName, FirstName, Title, TitleOfCourtesy, BirthDate, HireDate, [Address], City "
                + "    , Region, PostalCode, Country, HomePhone, Extension, Photo, Notes, ReportsTo, PhotoPath) "
                + " VALUES(@LastName, @FirstName, @Title, @TitleOfCourtesy, @BirthDate, @HireDate, @Address, @City "
                + "        , @Region, @PostalCode, @Country, @HomePhone, @Extension, @Photo, @Notes, @ReportsTo, @PhotoPath) ";

            base.ExecuteNonQuery(sqlCommand, employee.ToParameterArray());
        }

        public void Update(Employee employee)
        {
            var sqlCommand =
            "UPDATE dbo.Employees "
            + "SET LastName = @LastName, FirstName = @FirstName, Title = @Title, TitleOfCourtesy = @TitleOfCourtesy "
            + "    , BirthDate = @BirthDate, HireDate = @HireDate, [Address]=@Address,City=@City, Region=@Region "
            + "    , PostalCode=@PostalCode, Country=@Country, HomePhone=@HomePhone, Extension=@Extension "
            + "    , Photo=@Photo, Notes=@Notes, ReportsTo=@ReportsTo, PhotoPath=@PhotoPath "
            + "WHERE EmployeeID = @EmployeeID ";

            base.ExecuteNonQuery(sqlCommand, employee.ToParameterArray());
        }

        public void Delete(int employeeId)
        {
            var sqlCommand = "DELETE FROM dbo.Employees WHERE EmployeeID = @EmployeeId";
            var parameters = new SqlParameter[] { new SqlParameter("employeeId", employeeId) };

            base.ExecuteNonQuery(sqlCommand, parameters);
        }
    }
}    

第 4d 步:创建 OrderDetailRepository 类

右键单击 Repositories 项目,选择“添加 - 类”,将类命名为“OrderDetailRepository”。用以下代码替换文件中的代码:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

using NorthwindWebAPI.Models;

namespace NorthwindWebAPI.Repositories
{
    public class OrderDetailRepository : Repository<Order_Detail>
    {
        public override Order_Detail PopulateRecord(IDataReader reader)
        {
            return new Order_Detail(reader);
        }

        #region GET methods
        public IEnumerable<Order_Detail> GET()
        {
            throw new NotImplementedException("This web service does not allow retrieving items outside of an order");
        }

        /// <summary>
        /// Get order details for a given order
        /// </summary>
        /// <param name="orderId">the order for which details have been requested</param>
        /// <returns></returns>
        public IEnumerable<Order_Detail> Get(int orderId)
        {
            var sqlCommand =
                "SELECT OrderID, DETAILS.ProductID, DETAILS.UnitPrice, Quantity, Discount, PRODUCT.ProductName "
                + "FROM dbo.[Order Details] DETAILS "
                + "    INNER JOIN dbo.Products PRODUCT ON DETAILS.ProductID = PRODUCT.ProductID "
                + "WHERE OrderID = @OrderID ";
            var parameters = new SqlParameter[] { new SqlParameter("@OrderID", orderId) };

            return base.Get(sqlCommand, parameters);
        }
        #endregion

        #region ADD methods
        public void Add(IEnumerable<Order_Detail> orderItems, SqlConnection sqlConnection, SqlTransaction sqlTransaction)
        {
            foreach (Order_Detail item in orderItems)
                Add(item, sqlConnection, sqlTransaction);
        }

        public void Add(Order_Detail orderItem, SqlConnection sqlConnection = null, SqlTransaction sqlTransaction = null)
        {
            var sqlCommand =
                "IF NOT EXISTS(SELECT 1 FROM dbo.[Order Details] WHERE OrderID = @OrderID AND ProductID = @ProductID) "
                + "INSERT INTO dbo.[Order Details] "
                + "    (OrderID, ProductID, UnitPrice, Quantity, Discount) "
                + "VALUES(@OrderID, @ProductID, @UnitPrice, @Quantity, @Discount) ";

            base.ExecuteNonQuery(sqlCommand, orderItem.ToParameterArray(), CommandType.Text, sqlConnection, sqlTransaction);
        }
        #endregion

        #region UPDATE methods
        public void Update(IEnumerable<Order_Detail> orderItems, SqlConnection sqlConnection, SqlTransaction sqlTransaction)
        {
            foreach (var item in orderItems)
                Update(item, sqlConnection, sqlTransaction);
        }

        public void Update(Order_Detail orderDetail, SqlConnection sqlConnection = null, SqlTransaction sqlTransaction = null)
        {
            var sqlCommand =
                "UPDATE dbo.[Order Details] "
                + "   SET UnitPrice = @UnitPrice, Quantity = @Quantity, Discount = @Discount "
                + "WHERE OrderID = @OrderID AND ProductID = @ProductID ";

            base.ExecuteNonQuery(sqlCommand, orderDetail.ToParameterArray(), CommandType.Text, sqlConnection, sqlTransaction);
        }
        #endregion

        #region DELETE methods
        public void Delete(int orderId, SqlConnection sqlConnection = null, SqlTransaction sqlTransaction = null)
        {
            var sqlCommand = "DELETE FROM dbo.[Order Details] WHERE OrderID = @OrderID ";
            var parameters = new SqlParameter[] { new SqlParameter("@OrderID", orderId) };

            base.ExecuteNonQuery(sqlCommand, parameters, CommandType.Text, sqlConnection, sqlTransaction);
        }

        public void Delete(int orderId, int productId, SqlConnection sqlConnection = null, SqlTransaction sqlTransaction = null)
        {
            var sqlCommand = "DELETE FROM dbo.[Order Details] "
                            + "WHERE OrderID = @OrderID AND ProductID = @ProductID ";
            var parameters = new SqlParameter[] { new SqlParameter("@OrderID", orderId)
                                                , new SqlParameter("@ProductID", productId) };

            base.ExecuteNonQuery(sqlCommand, parameters, CommandType.Text, sqlConnection, sqlTransaction);
        }
        #endregion
    }
}    

第 4e 步:创建 OrderRepository 类

右键单击 Repositories 项目,选择“添加 - 类”,将类命名为“OrderRepository”。用以下代码替换文件中的代码:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

using NorthwindWebAPI.Models;

namespace NorthwindWebAPI.Repositories
{
    public class OrderRepository : Repository<Order>
    {
        private OrderDetailRepository _orderDetailRepository;
        public OrderRepository()
        {
            _orderDetailRepository = new OrderDetailRepository();
        }

        public override Order PopulateRecord(IDataReader reader)
        {
            return new Order(reader);
        }

        public IEnumerable<Order> Get()
        {
            var sqlCommand =
                "SELECT OrderID, ORDERS.CustomerID, ORDERS.EmployeeID, OrderDate, RequiredDate, ShippedDate, ShipVia "
                + "    , Freight, ShipName, ShipAddress, ShipCity, ShipRegion, ShipPostalCode, ShipCountry "
                + "    , CUSTOMERS.CompanyName, CUSTOMERS.ContactName, EMPLOYEES.FirstName, EMPLOYEES.LastName "
                + "FROM dbo.Orders ORDERS "
                + " INNER JOIN dbo.Customers CUSTOMERS ON ORDERS.CustomerID = CUSTOMERS.CustomerID "
                + " INNER JOIN dbo.Employees EMPLOYEES ON ORDERS.EmployeeID = EMPLOYEES.EmployeeID ";

            return base.Get(sqlCommand);
        }

        public IEnumerable<Order> Get(int OrderId)
        {
            var sqlCommand =
                "SELECT OrderID, ORDERS.CustomerID, ORDERS.EmployeeID, OrderDate, RequiredDate, ShippedDate, ShipVia "
                + "    , Freight, ShipName, ShipAddress, ShipCity, ShipRegion, ShipPostalCode, ShipCountry "
                + "    , CUSTOMERS.CompanyName, CUSTOMERS.ContactName, EMPLOYEES.FirstName, EMPLOYEES.LastName "
                + "FROM dbo.Orders ORDERS "
                + " INNER JOIN dbo.Customers CUSTOMERS ON ORDERS.CustomerID = CUSTOMERS.CustomerID "
                + " INNER JOIN dbo.Employees EMPLOYEES ON ORDERS.EmployeeID = EMPLOYEES.EmployeeID "
                + "WHERE OrderID = @OrderID ";
            var parameters = new SqlParameter[] { new SqlParameter("@OrderID", OrderId) };

            return base.Get(sqlCommand, parameters);
        }

        public void Add(Order order)
        {
            var sqlCommand =
                    "INSERT INTO dbo.Orders "
                    + "    (CustomerID, EmployeeID, OrderDate, RequiredDate, ShippedDate, ShipVia, Freight "
                    + "    , ShipName, ShipAddress, ShipCity, ShipRegion, ShipPostalCode, ShipCountry) "
                    + " VALUES "
                    + "   (@CustomerID, @EmployeeID, @OrderDate, @RequiredDate, @ShippedDate, @ShipVia, @Freight "
                    + "    , @ShipName, @ShipAddress, @ShipCity, @ShipRegion, @ShipPostalCode, @ShipCountry); "

                    + "SELECT SCOPE_IDENTITY() ";
            SqlConnection sqlConnection = new SqlConnection(ConnectionString);
            sqlConnection.Open();
            SqlTransaction sqlTransaction = sqlConnection.BeginTransaction();
            try
            {
                var orderId = base.ExecuteScalar(sqlCommand, order.ToParameterArray()
                                            , CommandType.Text, sqlConnection, sqlTransaction);
                if (orderId == DBNull.Value)
                    throw new NullReferenceException("Order creation failed");

                order.OrderID = Convert.ToInt32(orderId);
                foreach (var item in order.Order_Details)
                    item.OrderID = order.OrderID;

                _orderDetailRepository.Add(order.Order_Details, sqlConnection, sqlTransaction);
            }
            catch (Exception)
            {
                sqlTransaction.Rollback();
                sqlConnection.Close();
                throw;
            }

            sqlTransaction.Commit();
            sqlConnection.Close();
        }

        public void Update(Order order)
        {
            var sqlCommand =
                "UPDATE dbo.Orders "
                + "   SET CustomerID = @CustomerID, EmployeeID = @EmployeeID, OrderDate = @OrderDate "
                + "      ,RequiredDate = @RequiredDate, ShippedDate = @ShippedDate, ShipVia = @ShipVia "
                + "      ,Freight = @Freight, ShipName = @ShipName, ShipAddress = @ShipAddress, ShipCity = @ShipCity "
                + "      , ShipRegion = @ShipRegion, ShipPostalCode = @ShipPostalCode, ShipCountry = @ShipCountry "
                + "WHERE OrderID = @OrderID ";

            SqlConnection sqlConnection = new SqlConnection(ConnectionString);
            sqlConnection.Open();
            SqlTransaction sqlTransaction = sqlConnection.BeginTransaction();
            try
            {
                base.ExecuteScalar(sqlCommand, order.ToParameterArray()
                                            , CommandType.Text, sqlConnection, sqlTransaction);

                _orderDetailRepository.Update(order.Order_Details, sqlConnection, sqlTransaction);
            }
            catch (Exception)
            {
                sqlTransaction.Rollback();
                sqlConnection.Close();
                throw;
            }

            sqlTransaction.Commit();
            sqlConnection.Close();
        }

        public void Delete(int orderId)
        {
            SqlConnection sqlConnection = new SqlConnection(ConnectionString);
            sqlConnection.Open();
            SqlTransaction sqlTransaction = sqlConnection.BeginTransaction();
            try
            {
                _orderDetailRepository.Delete(orderId, sqlConnection, sqlTransaction);

                var sqlCommand = "DELETE FROM dbo.Orders WHERE OrderID = @OrderID  ";
                var parameters = new SqlParameter[] { new SqlParameter("@OrderID", orderId) };
                ExecuteNonQuery(sqlCommand, parameters, CommandType.Text, sqlConnection, sqlTransaction);
            }
            catch (Exception)
            {
                sqlTransaction.Rollback();
                sqlConnection.Close();
                throw;
            }

            sqlTransaction.Commit();
            sqlConnection.Close();
        }
    }
}

这样就完成了我们的仓库层。让我们通过按“Ctrl-Shift-B”来编译和构建解决方案,进行一次健全性检查。您应该不会有任何错误。

5) 创建服务层组件

服务层由 CustomerService、EmployeeService 和 OrderService 组成。OrderService 将协调对仓库的调用以检索订单详情,因此我们不需要一个单独的 OrderDetailService。

第 5a 步:创建 CustomerService 类

右键单击 Services 项目,选择“添加 - 类”,将类命名为“CustomerService”。用以下代码替换文件中的代码:

using System.Collections.Generic;
using System.Linq;

using NorthwindWebAPI.Models;
using NorthwindWebAPI.Repositories;

namespace NorthwindWebAPI.Services
{
    public class CustomerService
    {
        private CustomerRepository _customerRepository;

        /// <summary>
        /// customerlist stores in-memory copy of repository (cached).
        /// </summary>
        private static List<Customer> _customerList;

        public CustomerService()
        {
            _customerRepository = new CustomerRepository();
        }

        public IEnumerable<Customer> Get()
        {
            if (_customerList == null)
            {
                _customerList = _customerRepository.Get().ToList();
            }
            return _customerList;
        }

        public IEnumerable<Customer> Get(string customerId)
        {
            if (_customerList != null)
                return _customerList.Where(c => c.CustomerID == customerId);
            else return _customerRepository.Get(customerId);
        }

        public void Add(Customer customer)
        {
            _customerRepository.Add(customer);
            if (_customerList != null)
                _customerList.Add(customer);
        }

        public void Update(Customer customer)
        {
            _customerRepository.Update(customer);
            if (_customerList != null)
            {
                _customerList.Remove(_customerList.FirstOrDefault(c => c.CustomerID == customer.CustomerID));
                _customerList.Add(customer);
            }
        }

        public void Delete(string customerId)
        {
            _customerRepository.Delete(customerId);
            if (_customerList != null)
            {
                _customerList.Remove(_customerList.Where(c => c.CustomerID == customerId).FirstOrDefault());
            }
        }
    }
}    

第 5b 步:创建 EmployeeService 类

右键单击 Services 项目,选择“添加 - 类”,将类命名为“EmployeeService”。用以下代码替换文件中的代码:

using System.Collections.Generic;
using System.Linq;

using NorthwindWebAPI.Models;
using NorthwindWebAPI.Repositories;

namespace NorthwindWebAPI.Services
{
    public class EmployeeService
    {
        private EmployeeRepository _employeeRepository;
        private static List<Employee> _employeeList;

        //this boolean determines if cached employee list is up to date
        private static bool _employeeListStale = true;

        public EmployeeService()
        { _employeeRepository = new EmployeeRepository(); }

        public IEnumerable<Employee> Get()
        {
            if (_employeeList == null || _employeeListStale)
            {
                _employeeList = _employeeRepository.Get().ToList();
            }

            return _employeeList;
        }

        public IEnumerable<Employee> Get(int employeeId)
        {
            if (_employeeList != null && _employeeListStale == false)
                return _employeeList.Where(emp => emp.EmployeeID == employeeId);
            else return _employeeRepository.Get(employeeId);
        }

        public void Add(Employee employee)
        {
            _employeeRepository.Add(employee);
            _employeeListStale = true;
        }

        public void Update(Employee employee)
        {
            _employeeRepository.Update(employee);
            _employeeListStale = true;
        }

        public void Delete(int employeeId)
        {
            _employeeRepository.Delete(employeeId);
            _employeeListStale = true;
        }
    }
}

第 5c 步:创建 OrderService 类

右键单击 Services 项目,选择“添加 - 类”,将类命名为“OrderService”。用以下代码替换文件中的代码:

using System.Collections.Generic;
using System.Linq;

using NorthwindWebAPI.Models;
using NorthwindWebAPI.Repositories;

namespace NorthwindWebAPI.Services
{
    public class OrderService
    {
        private OrderRepository _orderRepository;
        private OrderDetailRepository _orderDetailRepository;
        private CustomerRepository _customerRepository;
        private EmployeeRepository _employeeRepository;

        private static List<Order> _orderList;  //complete list of orders
        private static bool _orderListStale = true;

        public OrderService()
        {
            _orderRepository = new OrderRepository();
            _orderDetailRepository = new OrderDetailRepository();
            _customerRepository = new CustomerRepository();
            _employeeRepository = new EmployeeRepository();
        }

        /// <summary>
        /// Gets all orders with names of customer, customer contact and employee.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Order> Get()
        {
            if (_orderList == null || _orderListStale)
            {
                _orderList = _orderRepository.Get().ToList();
                _orderListStale = false;
            }

            return _orderList;
        }

        /// <summary>
        /// Get an order with related customer, employee & order details
        /// </summary>
        /// <param name="orderId"></param>
        /// <returns></returns>
        public IEnumerable<Order> Get(int orderId)
        {
            List<Order> orderList = null;
            if (_orderList != null && !_orderListStale)
            {
                ///look for order within cached order list
                orderList = _orderList.Where(ord => ord.OrderID == orderId).ToList();
            }

            if (orderList == null)
            {
                orderList = _orderRepository.Get(orderId).ToList();
                if (orderList == null)
                    return orderList;
            }

            ///get details for this order
            foreach (var order in orderList)
                order.Order_Details = _orderDetailRepository.Get(order.OrderID).ToList();

            return orderList;
        }

        /// <summary>
        /// Adds a new order including order details.
        /// New products, customers & employees are NOT added. It is assumed all references to
        ///     producte, customers & employees refer to existing instances.
        /// </summary>
        /// <param name="order"></param>
        public void Add(Order order)
        {
            _orderRepository.Add(order);
        }

        public void Update(Order order)
        {
            _orderRepository.Update(order);
        }

        public void Delete(int orderId)
        {
            _orderRepository.Delete(orderId);
        }
    }
}

这样就完成了我们的服务层。让我们通过按“Ctrl-Shift-B”来编译和构建解决方案,进行一次健全性检查。您应该不会有任何错误。

 

6) 为 Web API 应用程序创建控制器

现在我们已经按照设计目标完成了三个层的代码,剩下的就是为我们的 Web API 应用程序编写控制器了。我们到目前为止的努力应该能使这个阶段的代码比将所有逻辑都放在控制器中更清晰、更易于管理。

第 6a 步:创建 CustomerController 类

右键单击 NorthwindWebAPI 项目的 Controller 文件夹,然后选择“添加 - 控制器”……

Add New Controller

选择“Web API 2 控制器 - 空”,然后点击“添加”。在下一个对话框中,为“控制器名称”输入“CustomerController”,然后再次点击“添加”。

Add Empty Web API 2 Controller

用以下代码替换文件中的代码:

using System;
using System.Net;
using System.Net.Http;
using System.Web.Http;

using NorthwindWebAPI.Models;
using NorthwindWebAPI.Services;

namespace NorthwindWebAPI.Controllers
{
    public class CustomerController : ApiController
    {
        private CustomerService _customerService;

        public CustomerController()
        {
            _customerService = new CustomerService();
        }

        // GET: api/Customer
        public HttpResponseMessage Get()
        {
            var customers = _customerService.Get();
            if (customers != null)
                return Request.CreateResponse(HttpStatusCode.OK, customers);
            else return Request.CreateErrorResponse(HttpStatusCode.NotFound
                                    , "No customers found");
        }

        // GET: api/Customer/5
        public HttpResponseMessage Get(string id)
        {
            var customer = _customerService.Get(id);
            if (customer != null)
                return Request.CreateResponse(HttpStatusCode.OK, customer);
            else return Request.CreateErrorResponse(HttpStatusCode.NotFound
                                    , "Customer with Id " + id + " does not exist");
        }

        // POST: api/Customer
        public HttpResponseMessage Post([FromBody]Customer customer)
        {
            _customerService.Add(customer);

            var message = Request.CreateResponse(HttpStatusCode.Created);
            message.Headers.Location = new Uri(Request.RequestUri + customer.CustomerID);
            return message;
        }

        // PUT: api/Customer/5
        public HttpResponseMessage Put([FromBody]Customer customer)
        {
            _customerService.Update(customer);
            return Request.CreateResponse(HttpStatusCode.OK, string.Empty);
        }

        // DELETE: api/Customer/5
        public HttpResponseMessage Delete(string id)
        {
            _customerService.Delete(id);
            return Request.CreateResponse(HttpStatusCode.OK, string.Empty);
        }
    }
}    

第 6b 步:创建 EmployeeController 类

按照上面第 6a 步中概述的步骤添加另一个控制器,将其命名为“EmployeeController”。用以下代码替换文件中的代码:

using System.Net;
using System.Net.Http;
using System.Web.Http;

using NorthwindWebAPI.Models;
using NorthwindWebAPI.Services;

namespace NorthwindWebAPI.Controllers
{
    public class EmployeeController : ApiController
    {
        private EmployeeService _employeeService;

        public EmployeeController()
        {
            _employeeService = new EmployeeService();
        }

        // GET: api/Employee
        public HttpResponseMessage Get()
        {
            var employees = _employeeService.Get();
            if (employees != null)
                return Request.CreateResponse(HttpStatusCode.OK, employees);
            else return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No employees found");
        }

        // GET: api/Employee/5
        public HttpResponseMessage Get(int id)
        {
            var employee = _employeeService.Get(id);
            if (employee != null)
                return Request.CreateResponse(HttpStatusCode.OK, employee);
            else return Request.CreateErrorResponse(HttpStatusCode.NotFound
                                , "Employee with Id " + id.ToString() + " does not exist");
        }

        // POST: api/Employee
        public HttpResponseMessage Post([FromBody]Employee employee)
        {
            _employeeService.Add(employee);
            return Request.CreateResponse(HttpStatusCode.Created); ;
        }

        // PUT: api/Employee/5
        public HttpResponseMessage Put(int id, [FromBody]Employee employee)
        {
            _employeeService.Update(employee);
            return Request.CreateResponse(HttpStatusCode.OK);
        }

        // DELETE: api/Employee/5
        public HttpResponseMessage Delete(int id)
        {
            _employeeService.Delete(id);
            return Request.CreateResponse(HttpStatusCode.OK);
        }
    }
}

第 6c 步:创建 OrderController 类

按照上面第 6a 步中概述的步骤添加另一个控制器,将其命名为“OrderController”。用以下代码替换文件中的代码:

using System.Net;
using System.Net.Http;
using System.Web.Http;

using NorthwindWebAPI.Models;
using NorthwindWebAPI.Services;

namespace NorthwindWebAPI.Controllers
{
    public class OrderController : ApiController
    {
        private OrderService _orderService;
        public OrderController()
        {
            _orderService = new OrderService();
        }
        // GET api/order
        public HttpResponseMessage Get()
        {
            var orders = _orderService.Get();
            if (orders != null)
                return Request.CreateResponse(HttpStatusCode.OK, orders);
            else return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No orders found");
        }

        // GET api/order/10248
        public HttpResponseMessage Get(int id)
        {
            var order = _orderService.Get(id);
            if (order != null)
                return Request.CreateResponse(HttpStatusCode.OK, order);
            else return Request.CreateErrorResponse(HttpStatusCode.NotFound,
                                            "Order #" + id.ToString() + " does not exist");
        }

        // POST api/order
        public HttpResponseMessage Post([FromBody]Order order)
        {
            _orderService.Add(order);
            return Request.CreateResponse(HttpStatusCode.Created, order);
        }

        // PUT api/order/10248
        public HttpResponseMessage Put([FromBody]Order order)
        {
            _orderService.Update(order);
            return Request.CreateResponse(HttpStatusCode.OK);
        }

        // DELETE api/order/10248
        public HttpResponseMessage Delete(int id)
        {
            _orderService.Delete(id);
            return Request.CreateResponse(HttpStatusCode.OK);
        }
    }
}

这样就完成了我们应用程序的编码工作。让我们通过按“Ctrl-Shift-B”来编译和构建解决方案,进行一次健全性检查。您应该不会有任何错误。

7) Web API 应用程序已就绪,开始测试

让我们做一些简单的测试来验证我们的应用程序是否正常工作。按“F5”运行应用程序。由默认项目模板生成的应用程序提供了一个占位符页面。浏览器的地址栏应显示我们应用程序的基本 URL,格式为 https://:58656/,其中数字部分在您的机器上可能会有所不同。

第 7a 步:测试 GET Customer 方法

我们通过在浏览器地址栏中显示的基础 URL 后附加一个路径来开始测试,调用的类型和传递的参数决定了将激活哪个特定的控制器方法。例如,以下调用应该获取所有客户的列表:

    /api/customer

我们可以通过在前一个调用后附加 customerID 来获取特定客户的信息

    /api/customer/ALFKI

下面列出了检索员工和订单信息的相应调用:

    /api/employee

    /api/employee/3

    /api/order

    /api/order/10248

对添加、更新或删除方法的 Web API 调用需要使用 Fiddler。打开 Fiddler 并导航到“Composer”选项卡,然后选择“Parsed”子选项卡。让我们首先重复上面的一个调用,即通过 customerID 请求特定客户的那个调用。

要获取特定客户的详细信息,请将第一个输入下拉框中的操作谓词设置为“GET”。在第二个输入字段中输入相关的 URL,然后点击“Execute”按钮。

Fiddler - GET customer

左侧面板(见上图)应该会显示一个新条目,第二列的值为 200。双击此条目应在主面板中显示 Web API 的响应,如下所示。注意,您可以通过激活“Raw”子选项卡以原始格式查看输出。

Fiddler - GET customer - response

第 7b 步:测试 POST (添加) Customer 方法

切换回 Composer 选项卡,将操作谓词更改为 POST,并将 URL 更改为:

    https://:99999/api/customer    ...将 99999 替换为您的端口号

在请求头(URL 正下方,见下方截图)中添加以下字符串:

Content-Type: application/json

将下面的文本复制到请求正文字段中,如下图所示。

{"CustomerID":"MYCST","CompanyName":"Customer King","ContactName":"Maria Von Trap","ContactTitle":"Sales Representative","Address":"Obere Str. 57","City":"Berlin","Region":"","PostalCode":"12209","Country":"Germany","Phone":"030-0074321","Fax":"030-0076545"}

Fiddler - POST customer - request

点击“Execute”向 Northwind 数据库中添加一个新行。一个与 JSON 数据匹配的记录应该被添加到 Northwind 数据库的 Customer 表中。

您可以双击左侧面板中的最新条目(下图中高亮显示)来查看我们 POST 请求的响应。注意,我们的应用程序以状态码“201”响应了 POST 请求,表示记录已成功创建。

Fiddler - POST customer - response

第 7c 步:测试 PUT (更新) Customer 方法

切换回 Composer 选项卡,将操作谓词更改为 PUT,并将 URL 指向 /api/customer。确认请求头中仍有上一步的“Content-Type”声明。

将下面的文本复制到请求正文字段中,如下图所示。

{"CustomerID":"MYCST","CompanyName":"Customer isKing","ContactName":"Anna-marie Cox","ContactTitle":"Sales Associate","Address":"Dungarten Rd.","City":"Bismarck","Region":"","PostalCode":"24098","Country":"Austria","Phone":"030-0074321","Fax":"030-0076545"}

Fiddler - PUT customer - request

点击“Execute”,将请求正文中先前添加的行更新到 Northwind 数据库中。与 CustomerID 匹配的记录应在 Northwind 数据库的 Customer 表中得到更新。注意,我们的应用程序以状态码“200”响应了 PUT 请求,表示记录已成功更新。

Fiddler - PUT customer - response

第 7d 步:测试 DELETE Customer 方法

切换回 Composer 选项卡,将操作谓词更改为 DELETE,并将 URL 更改为:

    https://:99999/api/customer/MYCST     ...将 99999 替换为您的端口号

请求正文应为空,因为 Delete 方法只需要 CustomerID 作为参数,而我们已经在上面的 URL 中提供了它。

Fiddler - DELETE customer - request

点击“Execute”从 Northwind 数据库中删除先前添加的行。与 CustomerID 匹配的记录将被删除。注意,我们的应用程序以状态码“200”响应了 PUT 请求,表示记录已成功删除。

Fiddler - DELETE customer - response

执行一次对 Customer GET 方法的 Web API 调用,并使用已删除的 customerID 作为参数,以确认记录已成功删除。URL 与上面用于 DELETE 操作的 URL 相同,因为我们想使用相同的 customerID 作为参数。操作谓词需要更改为 GET。

Fiddler - GET customer - request - verify delete

返回的响应正文应该是空的,这与我们上次执行针对特定客户的 GET 请求时不同。

Fiddler - GET customer - response - Verify Delete

我们可以类似地测试 Employee 和 Order 控制器,但对于这篇已经很长的文章来说,这里是一个合理的停止点。如果有什么特别感兴趣的增强功能,请随时在评论中提及,我会尝试做一个后续文章来回应常见的请求。感谢您的时间和关注。

历史

  • 2016年11月24日 - 初稿
© . All rights reserved.