本章将创建贯穿本书这一部分的示例项目。
1 创建项目
dotnet new globaljson --sdk-version 3.1.101 --output MyAdvanced
dotnet new web --no-https --output MyAdvanced --framework netcoreapp3.1
dotnet new sln -o MyAdvanced
dotnet sln MyAdvanced add MyAdvanced
向项目中添加 NuGet 包
dotnet add package Microsoft.EntityFrameworkCore.Design --version 3.1.1
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 3.1.1
安装全局工具包
dotnet tool uninstall --global dotnet-ef
dotnet tool install --global dotnet-ef --version 3.1.1
2 添加数据模型
这个应用程序的数据模型由三个类组成,分别代表人员、工作部门和位置。创建一个 Model 文件夹,并向其中添加一个名为 Person.cs 的类文件。
public class Person
{
public long PersonId { get; set; }
public string Firstname { get; set; }
public string Surname { get; set; }
public long DepartmentId { get; set; }
public long LocationId { get; set; }
public Department Department { get; set; }
public Location Location { get; set; }
}
将一个名为 Department.cs 的类文件添加到 Models 文件夹中。
public class Department
{
public long Departmentid { get; set; }
public string Name { get; set; }
public IEnumerable<Person> People { get; set; }
}
在 Models 文件夹中添加一个名为 Location.cs 的类文件。
public class Location
{
public long LocationId { get; set; }
public string City { get; set; }
public string State { get; set; }
public IEnumerable<Person> People { get; set; }
}
要创建提供对数据库的访问的 EF Core 的上下文类, Models 文件夹添加 DataContext.cs。
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> opts)
: base(opts) { }
public DbSet<Person> People { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Location> Locations { get; set; }
}
2.1 准备种子数据
将一个名为 SeedData.cs 的类添加到 Models 文件夹中,以定义用于填充数据库的种子数据。
public static class SeedData
{
public static void SeedDatabase(DataContext context)
{
context.Database.Migrate();
if (context.People.Count() == 0 && context.Departments.Count() == 0 &&
context.Locations.Count() == 0)
{
Department d1 = new Department { Name = "Sales" };
Department d2 = new Department { Name = "Development" };
Department d3 = new Department { Name = "Support" };
Department d4 = new Department { Name = "Facilities" };
context.Departments.AddRange(d1, d2, d3, d4);
context.SaveChanges();
Location l1 = new Location { City = "Oakland", State = "CA" };
Location l2 = new Location { City = "San Jose", State = "CA" };
Location l3 = new Location { City = "New York", State = "NY" };
context.Locations.AddRange(l1, l2, l3);
context.People.AddRange(
new Person
{
Firstname = "Francesca",
Surname = "Jacobs",
Department = d2,
Location = l1
},
new Person
{
Firstname = "Charles",
Surname = "Fuentes",
Department = d2,
Location = l3
},
new Person
{
Firstname = "Bright",
Surname = "Becker",
Department = d4,
Location = l1
},
new Person
{
Firstname = "Murphy",
Surname = "Lara",
Department = d1,
Location = l3
},
new Person
{
Firstname = "Beasley",
Surname = "Hoffman",
Department = d4,
Location = l3
},
new Person
{
Firstname = "Marks",
Surname = "Hays",
Department = d4,
Location = l1
},
new Person
{
Firstname = "Underwood",
Surname = "Trujillo",
Department = d2,
Location = l1
},
new Person
{
Firstname = "Randall",
Surname = "Lloyd",
Department = d3,
Location = l2
},
new Person
{
Firstname = "Guzman",
Surname = "Case",
Department = d2,
Location = l2
});
context.SaveChanges();
}
}
}
2.2 配置 Entity Framework Core 服务和中间件
对 Startup 类进行更改,它配置 Entity Framewonk Core 并设置 DataContext 服务,将使用该服务访问数据库。
public class Startup
{
public Startup(IConfiguration config)
{
Configuration = config;
}
public IConfiguration Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DataContext>(opts =>
{
opts.UseSqlServer(Configuration[
"ConnectionStrings:PeopleConnection"]);
opts.EnableSensitiveDataLogging(true);
});
}
public void Configure(IApplicationBuilder app, DataContext context)
{
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("controllers",
"controllers/{controller=Home}/{action=Index}/{id?}");
endpoints.MapDefaultControllerRoute();
endpoints.MapRazorPages();
});
SeedData.SeedDatabase(context);
}
}
要定义用于应用程序数据的连接字符串,在 appsetingsjson 文件中添加配置。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"ProductConnection": "Server=.;Database=People;MultipleActiveResultSets=true;User ID=sa;Pwd=Admin123;"
}
}
2.3 创建和应用迁移
创建 Entity Framework Core 迁移
dotnet ef migrations add Initial
创建迁移后,将其应用到数据库。
dotnet ef database update
3 添加引导 CSS 框架
用引导 CSS 框架为示例应用程序生成的 HTML 元素设置样式要安装引导包。
libman init -p cdnjs
libman install [email protected] -d wwwroot/lib/twitter-bootstrap
如果使用的是 Visual Studio,可以通过单击项目并从弹出菜单中选择客户端库来安装客户端包。
4 配置服务和中间件
在这个项目中启用运行时 Razor 视图编译,安装提供运行时编译服务的包。
dotnet add package Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation --version 3.1.1
同时会使用 MVC 控制器和 Razor Pages 响应请求。Startup 类中配置应用程序使用的服务和中间件。
services.AddControllersWithViews().AddRazorRuntimeCompilation();
services.AddRazorPages().AddRazorRuntimeCompilation();
public void Configure(IApplicationBuilder app, DataContext context)
{
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("controllers",
"controllers/{controller=Home}/{action=Index}/{id?}");
endpoints.MapDefaultControllerRoute();
endpoints.MapRazorPages();
});
SeedData.SeedDatabase(context);
}
除了默认的控制器路由之外,还添加了一个匹配以控制器开始的 URL 路径的路由,这将使后续章节中的示例在控制器和 Razor Pages 之间切换时更容易理解。这与在前几章中采用的约定相同,把以 /pages 开头的 URL 路径路由到 Razor 页面。
5 创建控制器和视图
添加 Controllers 文件夹,并添加 HomeController.cs。
public class HomeController : Controller
{
private DataContext context;
public HomeController(DataContext dbContext)
{
context = dbContext;
}
public IActionResult Index([FromQuery] string selectedCity)
{
return View(new PeopleListViewModel
{
People = context.People
.Include(p => p.Department).Include(p => p.Location),
Cities = context.Locations.Select(l => l.City).Distinct(),
SelectedCity = selectedCity
});
}
}
public class PeopleListViewModel
{
public IEnumerable<Person> People { get; set; }
public IEnumerable<string> Cities { get; set; }
public string SelectedCity { get; set; }
public string GetClass(string city) =>
SelectedCity == city ? "bg-info text-white" : "";
}
为给控制器提供视图,创建 Views/Home 文件夹,并添加一个名为Index.cshtml 的 Razor 视图。
@model PeopleListViewModel
<h4 class="bg-primary text-white text-center p-2">People</h4>
<table class="table table-sm table-bordered table-striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Dept</th>
<th>Location</th>
</tr>
</thead>
<tbody>
@foreach (Person p in Model.People)
{
<tr class="@Model.GetClass(p.Location.City)">
<td>@p.PersonId</td>
<td>@p.Surname, @p.Firstname</td>
<td>@p.Department.Name</td>
<td>@p.Location.City, @p.Location.State</td>
</tr>
}
</tbody>
</table>
<form asp-action="Index" method="get">
<div class="form-group">
<label for="selectedCity">City</label>
<select name="selectedCity" class="form-control">
<option disabled selected>Select City</option>
@foreach (string city in Model.Cities)
{
<option selected="@(city == Model.SelectedCity)">
@city
</option>
}
</select>
</div>
<button class="btn btn-primary" type="submit">Select</button>
</form>
要启用标签助手并添加视图中默认可用的名称空间,请给 Views 文件夹添加一个名为 _ViewImports.cshtml 的 Razor 视图导入文件。
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using MyAdvanced.Models
@using MyAdvanced.Controllers
Views 文件夹添加 _ViewStart.cshtml。
@{
Layout = "_Layout";
}
要创建布局,请创建 Views/Shared 文件夹,并向其中添加一个名为 _Layout.cshtml。
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<div class="m-2">
@RenderBody()
</div>
</body>
</html>
6 创建 Razor Pages
创建 Pages 文件夹,Index.cshtml 的 Razor Pages。
@page "/pages"
@model IndexModel
<h4 class="bg-primary text-white text-center p-2">People</h4>
<table class="table table-sm table-bordered table-striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Dept</th>
<th>Location</th>
</tr>
</thead>
<tbody>
@foreach (Person p in Model.People)
{
<tr class="@Model.GetClass(p.Location.City)">
<td>@p.PersonId</td>
<td>@p.Surname, @p.Firstname</td>
<td>@p.Department.Name</td>
<td>@p.Location.City, @p.Location.State</td>
</tr>
}
</tbody>
</table>
<form asp-page="Index" method="get">
<div class="form-group">
<label for="selectedCity">City</label>
<select name="selectedCity" class="form-control">
<option disabled selected>Select City</option>
@foreach (string city in Model.Cities)
{
<option selected="@(city == Model.SelectedCity)">
@city
</option>
}
</select>
</div>
<button class="btn btn-primary" type="submit">Select</button>
</form>
@functions
{
public class IndexModel : PageModel
{
private DataContext context;
public IndexModel(DataContext dbContext)
{
context = dbContext;
}
public IEnumerable<Person> People { get; set; }
public IEnumerable<string> Cities { get; set; }
[FromQuery]
public string SelectedCity { get; set; }
public void OnGet()
{
People = context.People.Include(p => p.Department)
.Include(p => p.Location);
Cities = context.Locations.Select(l => l.City).Distinct();
}
public string GetClass(string city) =>
SelectedCity == city ? "bg-info text-white" : "";
}
}
要启用标签助手并在 Razor Pages 的 View 部分添加默认可用的名称空间,给 Pages 文件夹添 _Viewlmports.cshtml。
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using MyAdvanced.Models
@using Microsoft.AspNetCore.Mvc.RazorPages
@using Microsoft.EntityFrameworkCore
要指定 Razor Pages 的默认布局,给 Pages 文件夹添加 _ViewStart.cshiml。
@{
Layout = "_Layout";
}
要创建布局,给 Pagcs 文件夹添加 _Layout.cshtml的 Razor 布局。
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<div class="m-2">
<h5 class="bg-secondary text-white text-center p-2">Razor Page</h5>
@RenderBody()
</div>
</body>
</html>
7 运行
使用浏览器请求 http:/localhost:5000/controllers
和 http:/localhost:5000/pages
。