第四章 SportsStore一个实际的(数据)应用

  在这一章中,我开始构建一个更实际的项目,该项目演示了如何将ASP.NET Core MVC和EF Core一起使用。该项目将是简单但实际的,并将重点放在最常用的EF Core功能上。这个应用程序将是我在许多书中使用的SportsStore应用程序的变体,它将更加关注数据和数据存储。

  在本章中,我构建了一个简单的ASP.NET Core MVC应用程序。在下一章中,我将添加EF Core,并将应用程序数据存储在数据库中。在下面的章节中,我将添加更多的数据操作、扩展数据模型、添加对客户功能的支持,并向您展示如何扩展应用程序。贯穿SportsStore的所有章节(4-10章),我都会指出将在后面哪些章节(如11-18章)更详细地描述这些关键的功能。从第5章开始,当我向项目中添加EF Core时,我还将描述您可能遇到的最常见问题,并解释如何解决它们。

注意:本书的重点是EF Core以及如何在MVC应用程序中使用它。我省略了一些SportsStore功能,如Administration管理员功能中的身份验证,却扩展了其他功能,如数据模型。

4.1 创建项目  

  下面创建SportsStore工程,启动Visual Studio创建新项目。选择ASP.NET Core Web Application project,将Name设置为SportsStore,然后单击Browse按钮,选择一个适合的位置来存储项目,如图4-1所示。

小贴士:您可以从GitHub资源库为本书下载这个项目,https://github.com/apress/pro-ef-core-2-for-asp.net- coremvc。

  单击OK按钮继续,在弹出窗口顶部确保选中了.NET Core和ASP.NET Core 2.0,然后单击Empty模板,如图4-2所示。当然也可以使用能直接将ASP.NET Core MVC和Entity Framework Core引用添加到一个工程中的工程模板,但这么做会掩盖一些有用的细节。所以我们选择的是Empty空白模板从一个空白的ASP.NET Core project开始,并在接下来的章节中逐步构建它,这样您就可以看到不同组件是如何协同工作的。

单击OK按钮,Visual Studio将使用ASP.NET Core的基本配置创建SportsStore项目。但不配置MVC框架或EF Core。

 

4.1.1 配置MVC框架

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

 1 //Listing 4-1. Configuring MVC in the Startup.cs File in the SportsStore Folder
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Threading.Tasks;
 6 using Microsoft.AspNetCore.Builder;
 7 using Microsoft.AspNetCore.Hosting;
 8 using Microsoft.AspNetCore.Http;
 9 using Microsoft.Extensions.DependencyInjection;
10 
11 namespace SportsStore
12 {
13     public class Startup
14     {
15         public void ConfigureServices(IServiceCollection services)
16         {
17             services.AddMvc();
18         }
19         public void Configure(IApplicationBuilder app, IHostingEnvironment env)
20         {
21             app.UseDeveloperExceptionPage();
22             app.UseStatusCodePages();
23             app.UseStaticFiles();
24             app.UseMvcWithDefaultRoute();
25         }
26     }
27 }

这些更改使用默认路由配置MVC框架,添加对静态文件的支持,并配置错误页面,以便为开发人员提供有用的详细信息。

 

4.1.2 添加Model

此应用程序的Model模型将基于产品列表。我创建了Models文件夹,往里面添加了一个Product.cs文件。代码如清单4-2所示。

1 //Listing 4-2. The Contents of the Product.cs File in the Models Folder
2 namespace SportsStore.Models {
3     public class Product {
4         public string Name { get; set; }
5         public string Category { get; set; }
6         public decimal PurchasePrice { get; set; }
7         public decimal RetailPrice { get; set; }
8     }
9 }    

这是我在书中经常使用的Product类,稍作修改以更好地演示EF Core功能。

 

4.1.3 添加一个仓储类

  为了在应用程序中对数据提供一致性的访问,我喜欢使用repository仓储模式,其中interface接口用于定义访问数据的属性和方法,实现类用于处理数据存储机制。使用仓储模式的优点是:对应用程序的MVC部分进行单元测试更容易,并且对应用程序的其余部分隐藏了数据存储的细节(封装)。

小贴士:对于大多数项目来说,使用仓储是一个好主意,但它不是使用EF Core的必要条件。例如在第3部分中,大多数示例没有仓储,因为有许多复杂的代码要更改,而且我不想对多个类和接口重复进行更改。如果您还怀疑仓储模式的好处请不要担心,因为您总是可以稍后添加一个仓储模式,尽管需要进行一点重构。

 

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

1 //Listing 4-3. The Contents of the IRepository.cs File in the Models Folder
2 using System.Collections.Generic;
3 namespace SportsStore.Models {
4     public interface IRepository {
5         IEnumerable<Product> Products { get; }
6         void AddProduct(Product product);
7     }
8 }

Products属性将提供对所有产品的只读访问(Products属性只读),AddProduct方法将用于添加新产品。

在本章中,我先把模型对象存储在内存中,后面再用EF Core存到数据库。我在Models文件夹添加了一个DataRepository.cs,并添加了如清单4-4所示的代码。

 1 //Listing 4-4. The Contents of the DataRepository.cs File in the Models Folder
 2 using System.Collections.Generic;
 3 namespace SportsStore.Models
 4 {
 5     public class DataRepository : IRepository
 6     {
 7         private List<Product> data = new List<Product>();
 8         public IEnumerable<Product> Products => data;
 9         public void AddProduct(Product product)
10         {
11             this.data.Add(product);
12         }
13     }
14 }

DataRepository类实现IRepository接口,并使用一个List来存产品对象,这意味着一旦应用程序停止或重启数据就会丢失。我在第4章中介绍了一个持久仓储,但是这在我引入EF Core之前,已经足够让项目的ASP.NET Core MVC方面正常工作了。我将清单4-5所示的语句添加到Startup类中,以便在DI依赖注入将DataRepository类注册为IRepository接口的实现。

 1 //Listing 4-5. Configuring Dependency Injection in the Startup.cs File in the SportsStore Folder
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Threading.Tasks;
 6 using Microsoft.AspNetCore.Builder;
 7 using Microsoft.AspNetCore.Hosting;
 8 using Microsoft.AspNetCore.Http;
 9 using Microsoft.Extensions.DependencyInjection;
10 using SportsStore.Models;
11 namespace SportsStore
12 {
13     public class Startup
14     {
15         public void ConfigureServices(IServiceCollection services)
16         {
17             services.AddMvc();
18             services.AddSingleton<IRepository, DataRepository>();
19         }
20         public void Configure(IApplicationBuilder app, IHostingEnvironment env)
21         {
22             app.UseDeveloperExceptionPage();
23             app.UseStatusCodePages();
24             app.UseStaticFiles();
25             app.UseMvcWithDefaultRoute();
26         }
27     }
28 }

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

 

4.1.4 添加控制器和视图

  示例应用程序的重点是产品对象的管理,因为这能更好地演示不同的数据操作功能。我需要一个Controller控制器来接收HTTP请求并将它们转换为对产品对象的操作,因此我创建了Controllers文件夹,并向其添加了一个HomeController.cs文件,控制器代码如清单4-6所示。

 1 //Listing 4-6. The Contents of the HomeController.cs File in the Controllers Folder
 2 using Microsoft.AspNetCore.Mvc;
 3 using SportsStore.Models;
 4 namespace SportsStore.Controllers
 5 {
 6     public class HomeController : Controller
 7     {
 8         private IRepository repository;
 9         public HomeController(IRepository repo) => repository = repo;
10         public IActionResult Index() => View(repository.Products);
11         [HttpPost]
12         public IActionResult AddProduct(Product product)
13         {
14             repository.AddProduct(product);
15             return RedirectToAction(nameof(Index));
16         }
17     }
18 }

  Index() Action操作方法将产品对象的集合从仓储传递到其视图,视图将向用户显示数据表格。AddProduct方法根据从HTTP POST请求中接收的数据,创建并存储一个新产品对象。

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

 1 //Listing 4-7. The Contents of the Index.cshtml File in the Views/Home Folder
 2 @model IEnumerable<Product>
 3 <h3 class="p-2 bg-primary text-white text-center">Products</h3>
 4 <div class="container-fluid mt-3">
 5     <div class="row">
 6         <div class="col font-weight-bold">Name</div>
 7         <div class="col font-weight-bold">Category</div>
 8         <div class="col font-weight-bold text-right">Purchase Price</div>
 9         <div class="col font-weight-bold text-right">Retail Price</div>
10         <div class="col"></div>
11     </div>
12     <form asp-action="AddProduct" method="post">
13         <div class="row">
14             <div class="col"><input name="Name" class="form-control" /></div>
15             <div class="col"><input name="Category" class="form-control" /></div>
16             <div class="col">
17                 <input name="PurchasePrice" class="form-control" />
18             </div>
19             <div class="col">
20                 <input name="RetailPrice" class="form-control" />
21             </div>
22             <div class="col">
23                 <button type="submit" class="btn btn-primary">Add</button>
24             </div>
25         </div>
26     </form>
27     @if (Model.Count() == 0)
28     {
29         <div class="row">
30             <div class="col text-center p-2">No Data</div>
31         </div>
32     }
33     else
34     {
35         @foreach (Product p in Model)
36         {
37             <div class="row p-2">
38                 <div class="col">@p.Name</div>
39                 <div class="col">@p.Category</div>
40                 <div class="col text-right">@p.PurchasePrice</div>
41                 <div class="col text-right">@p.RetailPrice</div>
42                 <div class="col"></div>
43             </div>
44         }
45     }
46 </div>

grid网格布局用于显示创建新对象的内联表单,以及应用程序所有现有产品对象的详细信息,或占位符(如果没有对象的话)。

4.1.5 收尾工作

右键单击SportsStore工程,选择添加➤新项目,并选择JSON文件模板(在ASP.NET Core➤Web➤General),来创建一个.bowerrc的文件,添加清单4 - 8所示的内容。(注意这个文件名很重要:它以句点开始,包含字母r两次,没有文件扩展名)。

1 Listing 4-8. The Contents of the .bowerrc File in the SportsStore Folder
2 {
3     "directory": "wwwroot/lib"
4 }

我再次使用JSON文件模板创建了一个bower.json文件,内容显示在清单4 - 9。

1 Listing 4-9. The Contents of the bower.json File in the SportsStore Folder
2 {
3     "name": "asp.net",
4     "private": true,
5     "dependencies": {
6     "bootstrap": "4.0.0"
7     }
8 }

将更改保存到文件后,Visual Studio将下载Bootstrap包的新版本,并将其安装到wwwroot/lib文件夹中。

注意:在本章中我用来安装Bootstrap的客户端包管理工具Bower已经被废弃,最终会被替换。Bower仍然处于积极的支持下,Visual Studio中对它提供了集成支持,这也是我在本书中使用它的原因。

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

 1 Listing 4-10. The Contents of the _Layout.cshtml File in the Views/Shared Folder
 2 <!DOCTYPE html>
 3 <html>
 4 <head>
 5     <meta name="viewport" content="width=device-width" />
 6     <title>SportsStore</title>
 7     <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
 8 </head>
 9 <body>
10     <div class="p-2">
11         @RenderBody()
12     </div>
13 </body>
14 </html>

  这个简单的布局提供了HTML文档框架,所以我不必在每个视图中都再写一遍。它还包含一个link元素,告诉浏览器要请求Bootstrap风格的CSS文件,我在整本书中使用它来样式化内容。

  为了默认使用清单4-10中的布局,我在Views文件夹内添加了一个_ViewStart.cshtml的文件。内容如清单4-11所示。(如果您使用MVC View Start Page项的模板来创建这个文件,那么Visual Studio将自动添加列表中显示的内容。)

1 Listing 4-11. The Contents of the _ViewStart.cshtml File in the Views Folder
2 @{
3      Layout = "_Layout";
4 }

为启用ASP.NET Core MVC tag helpers,而且让model类更容易被引用, 我在Views文件夹内添加了一个_ViewImports.cshtml文件。如清单4-12所示的内容。

1 Listing 4-12. The Contents of the _ViewImports.cshtml File in the Views Folder
2 @using SportsStore.Models
3 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

最后一步是配置应用程序,使其能够侦听端口5000上的HTTP请求。我编辑了Properties文件夹内的launchSettings.json来替换随机分配的端口,如下所示清单4-13。端口5000没有什么特别的意义,除了我在本书的例子中使用它。

 1 //Listing 4-13. Changing the Service Ports in the launchSettings.json File in the Properties Folder
 2 {
 3   "iisSettings": {
 4     "windowsAuthentication": false,
 5     "anonymousAuthentication": true,
 6     "iisExpress": {
 7       "applicationUrl": "http://localhost:5000/",
 8       "sslPort": 0
 9     }
10   },
11   "profiles": {
12     "IIS Express": {
13       "commandName": "IISExpress",
14       "launchBrowser": true,
15       "environmentVariables": {
16         "ASPNETCORE_ENVIRONMENT": "Development"
17       }
18     },
19     "SportsStore": {
20       "commandName": "Project",
21       "launchBrowser": true,
22       "environmentVariables": {
23         "ASPNETCORE_ENVIRONMENT": "Development"
24       },
25       "applicationUrl": "http://localhost:5000/"
26     }
27   }
28 }

 

4.2 运行小例子

下面编译和运行小例子,新打开一个命令行或PowerShell窗口,导航到SportStore项目文件夹(包含了bower.json文件),执行下面清单4-14中的命令

Listing 4-14. Starting the Example Application
dotnet run

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

4.3 本章小结

本章我创建了一个简单的ASP.NET Core MVC 应用程序,我后面的章节还要接着用。目前应用程序的数据还是存储在内存中的,这意味着应用程序停止或重启后所有的数据将会丢失。下一章,我将EF Core添加到项目中,以此将数据持久化存储到数据库中。

 

posted on 2018-12-28 16:54  困兽斗  阅读(276)  评论(0)    收藏  举报

导航