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

使用 CodeFirst 入门 ASP.Net Web API 2

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (24投票s)

2014年9月29日

CPOL

10分钟阅读

viewsIcon

203892

downloadIcon

4618

一个基础教程,使用 ASP.Net Web API 2 和 Entity Framework Code First 方法构建和调用 RESTful 服务。

引言

在本文中,我将向您展示使用 asp.net WebAPI 2 和 Entity Framework Code first 方法构建 RESTful 服务的完整演练。

背景

Asp.Net WebAPI 是一个用于构建和调用 Http 服务的框架,支持包括浏览器、平板电脑、手机等广泛的设备。它提供内容协商功能,通过该功能,API 可以根据请求的内容类型提供响应。最常用的内容类型是 JSON 和 XML。

它构建在 ASP.NET MVC 之上,包含 MVC 的一些特性,例如路由、控制器、动作、过滤器、模型绑定器、IOC 容器或依赖注入。

关键概念

  • API 服务实际上就是控制器。所有处理都发生在控制器类中。
  • 利用 MVC 特性。
  • 路由
  • 模型绑定
  • 操作过滤器
  • 约定优于配置
  • HttpResponseMessage 带有正确的接受标头和状态码。
  • 内置的 XML 和 JSON 响应媒体格式化器

使用代码

在本文中,我将展示使用 Asp.Net WebAPI 2 和 Entity Framework Code first 方法创建 RESTful 服务以处理所有数据库相关操作的完整演练。

我们将开发一个简单的 ProductReview 服务,包含 Product 和 Reviews 两个表,作为示例应用程序,以帮助您了解 Web API。

我们将使用 Visual Studio 2013 和 Entity Framework“Code First 方法”,其中我们将使用“简单旧的 CLR 对象”(POCO) 来定义我们的模型对象。

让我们开始在 Visual Studio 2013 中选择并添加一个新项目。转到 **文件 --> 新建 --> 项目**,在左侧选择 **Web** 和 **ASP.NET Web 应用程序**。

从显示的对话框中选择 **WebAPI** 模板。

完成此步骤后,Visual Studio 会创建一个新的示例 WebAPI 项目模板,其中包含所有必要的文件。

为数据库添加模型类

在本节中,我们将使用 EntityFramework Code First 方法设置数据库。我们将使用两个表:

  1. Products 和
  2. 评估

通过 Code First,我们将添加两个模型类:一个用于 Products,另一个用于 Reviews。在项目中,转到 Models 文件夹并添加以下两个模型类:

public class Product
    {
        public int ProductId { get; set; }

        [Required]
        public string Name { get; set; }

        public string Category { get; set; }

        public int Price { get; set; }

        //Navigation Property
        public ICollection Reviews { get; set; }
    }
public class Review
    {
        public int ReviewId { get; set; }

        public int ProductId { get; set; }

        [Required]
        public string Title { get; set; }

        public string Description { get; set; }

        //Navigation Property
        public Product Product { get; set; }
    }

在 EntityFramework Code First 方法中,它使用这些模型来创建相应的表。根据约定,对于两个模型,Id 属性将成为该表的主键。

这里我们考虑 Products 表和 Reviews 表之间的一对多关系(假设一个产品可以有多个评论)。

因此,Products 模型有一个导航属性,它是一个集合,指向 Review。同样,在 Review 表中,我们有一个指向 Product 的导航属性(不是集合,因为一个评论只能与一个产品关联)。Review 表包含一个 ProductId 属性,它是该表的外键。

添加 Web API 控制器类

在这里,我们将添加一个 Web API 控制器类,以促进与数据库实体进行所有 CRUD(创建、读取、更新和删除)操作。

WebApi 控制器类是所有处理发生的地方。

此时,在项目内的 Controllers 文件夹中有一个 **ValuesController**。我们不需要这个控制器,因为它只包含一个 Web API 控制器的示例。

删除 ValuesController 类并 **生成** 项目。

注意:请确保您已生成项目。下一步,我们将通过脚手架添加一个 Web API 控制器,VS 需要一个已编译的程序集才能做到这一点。

接下来,在解决方案资源管理器中右键单击 controllers 文件夹,然后转到 **添加**,然后 **控制器**

之后,会显示一个新的对话框,询问 **添加脚手架**。选择 **带操作的 Web API2 控制器,使用 Entity Framework**。

点击添加按钮后,会显示 **添加控制器** 对话框。

选择我们将要为其添加控制器的模型类。在我们的例子中是 Product。选择 DataContext 类的默认名称。

这会在项目中创建两个类:

  1. Controllers/ProductsController.cs:这代表了使用 RESTAPI 管理所有基本 CRUD 操作的 Web API 控制器。
  2. Models/ProductReviewsContext.cs:这代表了在运行时管理实体所有数据操作的派生上下文。它继承自 System.Data.Entity.DbContext,并为 Models 中的每个类公开一个泛型类型 DbSet<TEntity>。

重复相同的步骤,通过脚手架化 Review 模型类来添加 ReviewsController。

设置数据库

现在转到 **工具 -> Nuget 包管理器** 并选择 **程序包管理器控制台**。在程序包管理器控制台对话框中执行以下命令。

Enable-Migrations

运行此命令后,它会将一个 **Migrations** 文件夹添加到我们的项目中。这个 Migrations 文件夹包含一个文件:

Configuration.cs 类文件:这个类包含配置数据库迁移的方法。使用数据库迁移,Entity Framework 可以轻松使用项目中定义的模型类来创建数据库。

填充数据库

Configuration.cs 类包含一个 Seed 方法,该方法用于在数据库初始化过程中插入/更新数据。在 Configuration 类的 Seed 方法中已经包含了一个注释掉的示例代码片段,用于添加数据。

我们将修改 Seed 方法,为 Product 和 Review 实体添加一些测试数据。

将以下代码添加到 Configuration.Seed 方法中:

context.Products.AddOrUpdate(p => p.ProductId,
                new Product { Name = "Product 1", Category = "Category 1", Price = 200 },
                new Product { Name = "Product 2", Category = "Category 2", Price = 500 },
                new Product { Name = "Product 3", Category = "Category 3", Price = 700 }
                );

context.Reviews.AddOrUpdate(r => r.ReviewId,
                new Review {Title = "Review 1", Description = "Test review 1", ProductId = 1},
                new Review {Title = "Review 2", Description = "Test review 2", ProductId = 1}
                );

这使用了 AddOrUpdate(),如果记录不存在,则添加新记录;如果记录已存在,则更新现有记录。

接下来,在程序包管理器控制台中执行以下命令:

首先,

Add-Migration Initial

上面的命令生成了创建模型数据库的代码。

Update-Database

此命令执行上面“Add-Migration Initial”命令生成的代码并创建数据库。

注意:默认情况下,数据库是使用 localDb 创建的。

此时,Web.Config ConnectionStrings 部分中的连接字符串指向 LocalDb。

<add name="ProductReviewsContext" connectionString="Data Source=(localdb)\v11.0; Initial Catalog=ProductReviewsContext-20140929010853; Integrated Security=True; MultipleActiveResultSets=True; AttachDbFilename=|DataDirectory|ProductReviewsContext-20140929010853.mdf"
      providerName="System.Data.SqlClient" />

在执行上述命令之前,可以更改此连接字符串以指向 Sql Server Express 实例,从而在 Microsoft Sql Server 中创建数据库表。

现在数据库已创建,可以在 Visual Studio 的服务器资源管理器窗口中看到。

要查看数据,请右键单击任何表并选择显示数据。

运行并测试应用程序以进行基本 CRUD 操作

ASP.NET Web API 中的路由

Web API 使用路由来将统一资源标识符(URI)匹配到各种操作。位于解决方案资源管理器 App_Start 节点内的 **WebApiConfig** 文件定义了 Web API 使用的默认路由机制。该机制基于 HTTP 方法、操作和属性的组合。但是,我们可以定义自己的路由机制来支持有意义的 URI。

指定的默认路由如下:

config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

默认路由模板以关键字 *“api”* 开头,以避免与 MVC 路由发生任何冲突。

理解 Asp.Net Web API 中路由的工作原理

当 Web API 收到一个 Http 请求时,它会首先尝试匹配默认路由模板,即 *“api/{controller}/{id}”*。如果没有匹配项,则返回 404 未找到错误。

一旦请求匹配成功

  • 请求将被转发到请求中定义的控制器。假设我们有一个请求 *“api/products”*,那么将调用 “ProductsController”。
  • 为了找到操作,WebAPI 会查找请求的方法,然后查找名称以 HttpMethod 名称开头的方法。
    例如:对于 GET 请求 *“api/products”*,Web API 会查找一个以 Get Http 动词开头的方法,如 GetProducts()。对于所有动词 POST、PUT 和 DELETE,情况也是如此。
  • 对于其他占位符 {id},它们被映射到操作参数。
    例如,对于请求的 URL *“api/products/1”*,它被映射到操作方法 GetProductById(int id)。

我将使用一个名为 Postman 的 Google Chrome 扩展来检查 API 返回的响应。Postman 是一个非常好的扩展,可以创建 Http 请求并调用 API 服务,它提供了多种选项。请从 此处 下载该扩展。

检索所有产品详细信息(GET) 

从 Visual studio 运行应用程序,选择 Google Chrome 浏览器。在 Chrome 中启动 Postman 应用程序,然后输入一个 URL,例如:

GET - *https://:53811/api/products* : 这将列出数据库中的所有产品。当请求此 URL 时,路径被映射到 ProductsController 的 GetProducts() 操作。

       // GET: api/Products
        public IQueryable<Product> GetProducts()
        {
            return db.Products;
        }

Postman 窗口中的响应是我们以 JSON 格式获得的所有产品列表。

如果您注意到,所有 Products 的评论都显示为 null,因为 EF 无法加载相关的条目。

让我们尝试更新 GetProducts() 操作方法以加载 Review 数据。

       // GET: api/Products
        public IQueryable<Product> GetProducts()
        {
            return db.Products.Include(p => p.Reviews);
        }

再次执行相同的请求 *https://:53811/api/products* 会生成以下错误:

这里的问题是,当我们定义 Product 类时,我们包含了 Reviews 的导航属性

       //Navigation Property
        public ICollection<Review> Reviews { get; set; }

并且我们在 Review 模型类中也包含了 Product 的导航属性。

       //Navigation Property
        public Product Product { get; set; }

当我们尝试加载相关实体的 {id} 数据时,会创建一个循环对象图。

因此,当 JSON 或 XML 格式化器尝试加载和序列化数据时,都会引发不同的异常。

为了解决这个错误,我们将使用 **DTO**(数据传输对象)类来获取所有数据。在 Models 文件夹内添加以下新的 DTO 类:

    public class ProductDetailsDto
    {
        public string Name { get; set; }

        public string Category { get; set; }

        public int Price { get; set; }

        public List<Review> Reviews { get; set; }
    }

转到 **Controllers -> ProductsController.cs -> GetProducts()** 操作并添加以下代码:

       // GET: api/Products
        public IList<ProductDetailsDto> GetProducts()
        {
            return db.Products.Select(p => new ProductDetailsDto
            {
                Name = p.Name,
                Category = p.Category,
                Price = p.Price,
                Reviews = p.Reviews.ToList()
            }).ToList();
        }

启动 Postman 主页并输入相同的 URL *https://:53811/api/products*。以下是我们获得的响应,它包含了 Product 和相关的产品评论数据。

获取单个产品的详细信息

在 Postman 应用程序中输入 URL *https://:53811/api/products/1*。这会调用 Products 控制器的 GetProduct(int id) 操作,其中请求中的占位符 id 值 1 被映射到 GetProduct(int id) 操作的参数。

       // GET: api/Products/5
        [ResponseType(typeof(Product))]
        public IHttpActionResult GetProduct(int id)
        {
            Product product = db.Products.Find(id);
            if (product == null)
            {
                return NotFound();
            }

            return Ok(product);
        }

我们获得的响应是单个产品的详细信息。

创建一个新产品(CREATE)

我们将创建一个 POST 请求来向数据库添加新产品。

在 Postman 页面,从下拉列表中选择 POST 动词 -> 点击顶部的 Headers 按钮 -> 设置 Content-Type: application/json 标头。

选择 raw 选项卡,并以 JSON 格式输入产品详细信息。例如:

{
        "Name": "Product 4",
        "Category": "Category 4",
        "Price": 200,
        "Reviews": [
            {
                "ReviewId": 1,
                "ProductId": 1,
                "Title": "Review 1",
                "Description": "Test review 1",
                "Product": null
            },
            {
                "ReviewId": 2,
                "ProductId": 1,
                "Title": "Review 2",
                "Description": "Test review 2",
                "Product": null
            }
        ]
    }

输入 URL *https://:53811/api/products* 并点击 Send 按钮。

由于请求的方法是 Post 类型,因此请求被映射到 ProductsController 中的 PostProduct() 操作。

        // POST: api/Products
        [ResponseType(typeof(Product))]
        public IHttpActionResult PostProduct(Product product)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            db.Products.Add(product);
            db.SaveChanges();

            return CreatedAtRoute("DefaultApi", new { id = product.ProductId }, product);
        }

要验证数据是否已添加到数据库,请向 *https://:53811/api/products* 发送一个 Get 请求。

更新产品详细信息(PUT)

这与 POST 方法类似。在这里,我们将发送一个更新的产品 JSON 对象来更新现有产品详细信息。唯一的区别在于 URL。请求的 URL 将是以下形式:

https://:53811/api/products/1 ,

其中 id 参数用于匹配要更新的产品。ProductsController 中的操作方法如下:

        // PUT: api/Products/5
        [ResponseType(typeof(void))]
        public IHttpActionResult PutProduct(int id, Product product)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            if (id != product.ProductId)
            {
                return BadRequest();
            }

            db.Entry(product).State = EntityState.Modified;

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!ProductExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return StatusCode(HttpStatusCode.NoContent);
        }

让我们使用相同的 Postman 扩展来测试这一点。撰写一个新请求,URL 为 *https://:53811/api/products/1*,用于更新第一个产品的详细信息。

将标头设置为 Content-Type: application/json。

在请求正文的 raw 选项卡中添加以下更新的测试 JSON 对象。

{
    "ProductId": 1,
    "Name": "Updated Product",
    "Category": "Updated Category",
    "Price": 1000,
    "Reviews": null
}

发送请求。在此请求成功完成后,通过发送一个简单的 GET (*https://:53811/api/products/1*) 请求到 API 来验证同一产品的详细信息。

删除产品详细信息(DELETE)

对于删除操作,请求的 URL 是 DELETE(*https://:53811/api/products/3*),其中最后一个参数是产品的 ID。ProductsController.cs 文件中的 Delete(int id) 操作会将 URL 中的 ID 映射到检索产品详细信息,然后从数据库中删除它。

        // DELETE: api/Products/5
        [ResponseType(typeof(Product))]
        public IHttpActionResult DeleteProduct(int id)
        {
            Product product = db.Products.Find(id);
            if (product == null)
            {
                return NotFound();
            }

            db.Products.Remove(product);
            db.SaveChanges();

            return Ok(product);
        }

让我们使用 Postman 编写相同的代码来从数据库中删除 ID 为 3 的产品。

在 URL 字段中输入要删除的产品的请求 URL *https://:53811/api/products/3*。从下拉列表中选择 DELETE Http 动词并发送请求。

现在让我们尝试获取 ID = 3 的产品。

因此,我们在该 URL 处没有找到产品,因此 ID 为 3 的产品现已从数据库中删除。

 

至此,我的文章结束了,我们学习了如何使用 ASP.Net Web API 2 和 Entity Framework Code First 构建和调用 RESTful 服务。

© . All rights reserved.