Pro Entity Framework Core 2 for ASP.NET Core MVC 翻译

第 4 章 运动商店:一个真实的(数据)应用程序

作者:Adam Freeman
翻译:陈广
日期:2018-12-6


本章,我开始了创建一个更为现实的项目的进程,以演示 ASP.NET Core MVC 及 Entity Framework Core 是如何在一起工作的。项目将是比较简单但现实的,聚焦于最常用的实体框架核心功能。该应用程序将是我在许多书籍中使用的运动商店应用程序的一个变体,它将更多地关注数据和数据存储。

本章,我创建一个简单的自包含的 ASP.NET Core MVC 应用程序。在下一章,我增加 Entity Framework Core 并在数据库中存储应用程序数据。在接下来的章节中,我将添加更多的数据操作,扩展数据模型,并支持客户功能,以及向您演示如何扩展应用程序。在所有的运动商店章节中,我将在不同章节中分别描述关键的特性,并提供了更多的细节。从第5章开始,当我将 Entity Framework Core 添加到项目中时,我还描述了您可能遇到的最常见的问题,并解释了如何解决这些问题。

注意:本书的焦点在于 Entity Framework Core 以及它在 MVC 应用程序中是如何使用的。我省略了一些运动商店的功能,如管理认证功能,并扩展了其它功能,如数据模型。

创建项目

要创建运动商店项目,启动 Visual Studio 并在【文件】菜单中选择【新建】➤【项目】。选择【ASP.NET Core Web 应用程序】项目模板,将名称设置为 SportsStore,单击【浏览】按钮选择一个方便的位置存储项目,如图4-1所示。

提示:您可以在本书的 GitHub 仓库中下载此项目,https://github.com/apress/pro-ef-core-2-for-asp.net-core-mvc

图4-1 创建示例项目

单击【确定】按钮继续设置项目,确保选中窗体顶部的【.NET Core】和【ASP.NET Core 2.1】,并单击【空】模板,如图4-2所示。Visual Studio 包含在项目中设置 ASP.NET Core MVC 和 Entity Framework Core 的模板,但结果隐藏了一些有用的细节。我从最基本的 ASP.NET Core 项目开始,然后在接下来的章节中逐步构建它,这样您就可以看到不同的组件是如何一起工作的。

图4-2 配置 ASP.NET Core 项目

单击【确定】按钮,Visual Studio 将创建一个具有基本配置 SportsStore 项目,它设置了 ASP.NET Core 但没有配置 MVC 框架或 Entity Framework Core。

配置 MVC 框架

下一步是为 MVC 框架添加基础配置,以便我可以添加控制器和视图来处理 HTTP 请求。我将 MVC 中间件添加至 ASP.NET Core 请求管道,如清单4-1所示。

清单 4-1:SportsStore 文件夹下的 Startup.cs 文件,配置 MVC

using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace SportsStore
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseDeveloperExceptionPage();
            app.UseStatusCodePages();
            app.UseStaticFiles();
            app.UseMvcWithDefaultRoute();
        }
    }
}

这些代码使用默认路由架构配置 MVC 框架,添加对静态文件的支持,并配置了错误页以便对开发者提供有用的细节。

添加模型

此应用程序的模型将以产品列表为基础。我创建了 Models 文件夹并向其中添加了名为 Product.cs 的类文件,代码如清单4-2所示。

清单 4-2:Models 文件夹下的 Product.cs 文件的内容

namespace SportsStore.Models
{
    public class Product
    {
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal PurchasePrice { get; set; }
        public decimal RetailPrice { get; set; }
    }
}

这是我在书中通常使用的 Product 类的一个变体,以便我能够更好地演示有用的 Entity Framework Core 特性。

添加存储库

为了提供对应用程序中数据的一致访问,我喜欢使用存储库模式,其中的接口定义用于访问数据的属性和方法,并使用实现类来处理数据存储机制。使用存储库模式的优点是,更易于对应用程序的 MVC 部分进行单元测试,对应用程序的其余部分将隐藏如何存储数据的详细信息。

提示:对于大多数项目来说,使用存储库是一个好主意,但它并不是使用 Entity Framework Core 的要求。例如,在第3部分中,大多数示例没有存储库,因为有许多复杂的代码更改,而且我不希望通过多个类和接口重复进行更改。如果您不相信存储库模式的好处,请不要担心,因为以后总是可以添加存储库模式,尽管需要进行一些重构。

要创建存储库接口,我向 Models 文件夹添加了一个名为 IRepository.cs 的文件,并添加了如清单4-3所示的代码。

清单 4-3:Models 文件夹下的 IRepository.cs 文件的内容

using System.Collections.Generic;

namespace SportsStore.Models
{
    public interface IRepository
    {
        IEnumerable<Product> Products { get; }
        void AddProduct(Product product);
    }
}

Products属性将提供对应用程序所知的所有产品的只读访问。AddProduct方法将用于添加新产品。

对于本章,我将把模型对象存储在内存中,然后在第 5 章中用 Entity Framework Core 替换模型对象。我在 Models 文件夹中添加了一个名为 DataRepository.cs 的类,并添加了清单4-4所示的代码。

清单 4-4:Models 文件夹下的 DataRepository.cs 文件的内容

using System.Collections.Generic;

namespace SportsStore.Models
{
    public class DataRepository : IRepository
    {
        private List<Product> data = new List<Product>();

        public IEnumerable<Product> Products => data;

        public void AddProduct(Product product)
        {
            this.data.Add(product);
        }
    }
}

DataRepository类实现了IRepository接口,并使用一个List来跟踪Product对象,这意味着一旦应用程序停止或重新启动,数据就会丢失。我在第3章中介绍了一个持久存储库,但在介绍 Entity Framework Core 之前,这足以让项目的 ASP.NET Core MVC 部分工作。

我将清单4-5中所示的语句添加到Startup类中,以便将DataRepository类注册为用于依赖于IRepository接口的实现。

清单 4-5:SportsStore 文件夹下的 Startup.cs 文件,配置依赖注入

using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using SportsStore.Models;

namespace SportsStore
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddSingleton<IRepository, DataRepository>();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseDeveloperExceptionPage();
            app.UseStatusCodePages();
            app.UseStaticFiles();
            app.UseMvcWithDefaultRoute();
        }
    }
}

清单4-5中的语句使用AddSingleton方法注册了DataRepository类,这意味着在第一次解析IRepository接口上的依赖时将创建单个对象,然后将其用于所有后续依赖项。

添加控制器和视图

示例应用程序的重点将是产品对象的管理,因为这最大限度地演示了不同的数据特性。我需要一个控制器来接收 HTTP 请求并将它们转换为对Product对象的操作,所以我创建了 Controllers 文件夹,向它添加了一个名为HomeController.cs的代码文件,并使用它来定义如清单4-6所示的控制器。

清单 4-6:Controllers 文件夹下的 HomeController.cs 文件的内容

using Microsoft.AspNetCore.Mvc;
using SportsStore.Models;

namespace SportsStore.Controllers
{
    public class HomeController : Controller
    {
        private IRepository repository;

        public HomeController(IRepository repo) => repository = repo;

        public IActionResult Index() => View(repository.Products);

        [HttpPost]
        public IActionResult AddProduct(Product product)
        {
            repository.AddProduct(product);
            return RedirectToAction(nameof(Index));
        }
    }
}

Index action 将Product对象集合从存储库传递到视图,然后向用户显示数据列表。AddProduct方法基于 HTTP POST 请求所接收的数据存储新的Product对象。

接下来,我创建了 Views/Home 文件夹并向其添加了一个名为 Index.cshtml 的文件,内容如清单4-7所示。此视图将显示应用程序的Product数据,并允许用户创建新对象。

清单 4-7:Views/Home 文件夹下的 Index.cshtml 文件的内容

@model IEnumerable<Product>

<h3 class="p-2 bg-primary text-white text-center">Products</h3>

<div class="container-fluid mt-3">
    <div class="row">
        <div class="col font-weight-bold">Name</div>
        <div class="col font-weight-bold">Category</div>
        <div class="col font-weight-bold text-right">Purchase Price</div>
        <div class="col font-weight-bold text-right">Retail Price</div>
        <div class="col"></div>
    </div>
    <form asp-action="AddProduct" method="post">
        <div class="row">
            <div class="col"><input name="Name" class="form-control" /></div>
            <div class="col"><input name="Category" class="form-control" /></div>
            <div class="col">
                <input name="PurchasePrice" class="form-control" />
            </div>
            <div class="col">
                <input name="RetailPrice" class="form-control" />
            </div>
            <div class="col">
                <button type="submit" class="btn btn-primary">Add</button>
            </div>
        </div>
    </form>
    @if (Model.Count() == 0)
    {
        <div class="row">
            <div class="col text-center p-2">No Data</div>
        </div>
    }
    else
    {
        @foreach (Product p in Model)
        {
            <div class="row p-2">
                <div class="col">@p.Name</div>
                <div class="col">@p.Category</div>
                <div class="col text-right">@p.PurchasePrice</div>
                <div class="col text-right">@p.RetailPrice</div>
                <div class="col"></div>
            </div>
        }
    }
</div>

网格布局显示用于创建新对象的内联表单,以及应用程序已知的所有现有Product对象的详细信息,如果没有对象,则显示占位符。

添加最后的修饰

在【解决方案资源管理器】中右键单击 SportsStore 项目,选择【添加】➤【添加客户端库】,并将 twitter-bootstrap 添加至项目中。最终生成的 libman.json 配置文件代码清单4-8所示。

清单 4-8:SportStore 文件夹下的 libman.json 文件的内容

{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": [
    {
      "library": "twitter-bootstrap@4.1.3",
      "destination": "wwwroot/lib/twitter-bootstrap/"
    }
  ]
}

接下来,我创建了 Views/Shared 文件夹并添加了一个名为 _Layout.cshtml 的文件,用于定义清单4-10所示的共享布局。

清单 4-10:Views/Shared 文件夹下的 _Layout.cshtml 文件的内容

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>SportsStore</title>
    <link rel="stylesheet" href="~/lib/twitter-bootstrap/css/bootstrap.min.css" />
</head>
<body>
    <div class="p-2">
        @RenderBody()
    </div>
</body>
</html>

这个简单的布局提供了浏览器所需的 HTML 文档的结构,所以我不必在每个视图中都包含它。它还包含了一个link元素,告诉浏览器需要包含 Bootstrap 样式所需的 CSS 文件,我用它来修饰整本书的内容。

为默认使用清单4-10中的布局,我在 Views 文件夹中添加了一个名为 _ViewStart.cshtml 的文件,其内容如清单4-11所示。(如果您在开始页中使用 MVC 视图模板来创建此文件,Visual Studio 将自动添加清单所示的内容。)

清单 4-11:Views 文件夹下的 _ViewStart.cshtml 文件的内容

@{
    Layout = "_Layout";
}

为启用 ASP.NET Core MVC 标签助手,并让模型类更易于引用,我在 Views 文件夹下添加了一个名为 _ViewImports.cshtml 的文件,并添加了如清单4-12所示的内容。

清单 4-12:Views 文件夹下的 _ViewImport.cshtml 文件的内容

@using SportsStore.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

最后一步是配置应用程序,以便它在 5000 端口监听 HTTP 请求。我编辑了 Properties 文件夹下的 launchSettings.json 文件以取代随机分配的端口,如清单4-13所示。端口 5000 没有特殊意义,只是我在本书中的示例中使用了它。

清单 4-13:Properties 文件夹下的 launchSettings.json 文件,更改服务端口

{
    "iisSettings": {
        "windowsAuthentication": false,
        "anonymousAuthentication": true,
        "iisExpress": {
            "applicationUrl": "http://localhost:5000/",
            "sslPort": 0
        }
    },
    "profiles": {
        "IIS Express": {
            "commandName": "IISExpress",
            "launchBrowser": true,
            "environmentVariables": {
                "ASPNETCORE_ENVIRONMENT": "Development"
            }
        },
        "SportsStore": {
            "commandName": "Project",
            "launchBrowser": true,
            "environmentVariables": {
                "ASPNETCORE_ENVIRONMENT": "Development"
            },
            "applicationUrl": "http://localhost:5000/"
        }
    }
}

运行示例应用程序

要生成并启动示例应用程序,打开一个新的命令提示符或 PowerShell 窗口,导航至 SportStore 项目文件夹(包含 libman.json 文件的那个文件夹),并行动如清单4-14所示的命令。

清单 4-14:启动示例应用程序

dotnet run

应用程序将在 5000 端口监听 HTTP 请求。打开一个浏览器窗口,并导航至 http://localhost:5000。你将看到初始占位符,一旦填写表单字段并单击【Add】按钮,它将被替换,如图4-3所示。

图4-3 运行示例应用程序

总结

本章,我创建了一个在之后章节使用的简单的 ASP.NET Core MVC 应用程序。此时,应用程序的数据是存储在内存中的,这意味着当应用程序停止或重启时,所有产品将丢失。在下一章,我向项目中添加 Entity Framework Core 并在数据库中存储永久数据。

;

© 2018 - IOT小分队文章发布系统 v0.3