使用 ASP.NET 5 MVC 和 Entity Framework 7 构建 Web 应用程序






4.57/5 (14投票s)
使用 ASP.NET 5 MVC 和 Entity Framework 7 构建技术活动管理应用程序
ASP.NET 5 入门
每个人都喜欢带有“开源”标签的技术。是的!新的 ASP.NET 5 是一个开源项目和跨平台框架。我想,您现在已经迫不及待地想学习一些 ASP.NET 5 了!嗯,我是 Node.js 的忠实粉丝,所以当我听说 ASP.NET 5 及其像 Node.js 一样的模块化时,我想为什么不试试呢。相信我,伙计们!我非常满意。ASP.NET 5 确实能与许多现代 Web 应用程序构建框架并驾齐驱,拥有中间件、服务等所有广为人知的概念。要了解更多关于 ASP.NET 5 及其架构的信息,您可以阅读他们的官方文档:http://docs.asp.net/。该文档也是开源的。您可以贡献您的专业知识来处理带有扳手图标的任何主题。
我们要构建什么?
简而言之,我们将构建一个简单的 CMS 应用程序来管理一个虚构的技术讲座活动的内容。我称之为“TechTalkers”。该应用程序有一个管理员面板,管理员可以在其中管理应用程序用户的可见内容。我为我们的“TechTalkers”应用程序及其将要使用的技术堆栈制定了一个小用户故事:
1. 用户故事
1.1 管理员
- 管理员登出/登录系统
- 管理员面板
- 管理演讲者
- 管理与演讲者相关的演讲主题
1.2 用户
- 一个可访问的网站
- 演讲者列表
- 演讲者详情
- 主题列表
- 主题详情
- 演讲者列表
2. 技术堆栈
2.1 ASP.NET 5
- MVC
- Razor 视图引擎
- 身份验证与授权
- 身份
2.2 Entity Framework
- ORM(对象关系映射),用于使用本地数据库管理应用程序数据
- Entity Framework Code First
文件 > 新建项目
我在 Windows 上,所以将使用 Visual Studio 2015。配置 Mac 和 Linux 以支持 ASP.NET 5 非常简单。请参考此链接。
http://aspnet.readthedocs.org/en/latest/getting-started/index.html
在那里,您将找到如何在 Mac 或 Linux 上设置 ASP.NET 5 并开始使用 Visual Studio Code 或您想要的任何编辑器进行开发。
打开 Visual Studio 2015。转到“文件”>“新建项目”,选择“ASP.NET Web 应用程序”。给项目命名为“TechTalkers”,然后单击“确定”。在模板屏幕中,在“ASP.NET 5 预览模板”下,选择“Web 应用程序”并单击“确定”。该模板遵循 ASP.NET MVC 模式来构建您的 Web 应用程序。它还内置了单独的用户帐户登录/登出功能。
项目结构
项目结构非常简单。您将在 `Views` 文件夹中找到 `.cshtml` 文件(`.cshtml` 扩展名用于 Razor 视图。Razor 允许您在 HTML 文件中编写 C# 代码)。所有数据传输对象/POCO(纯粹的类对象)都位于 `Models` 文件夹中。控制器位于 `Controllers` 文件夹中。控制器用于将任何传入的请求路由到您应用程序中的特定业务逻辑块。除了这些,我们还有 `Services` 和 `Migrations` 文件夹。`Services` 文件夹中包含用于配置第三方服务(如电子邮件和 SMS)的文件。`Migrations` 文件夹中包含数据库迁移历史记录的快照。这些由 Entity Framework 生成。(稍后详细介绍 EF)
现在,让我们运行项目。单击播放图标(从“使用…浏览”选项中选择您喜欢的浏览器)。
您将看到一个 `注册` 和一个 `登录` 页面,以及另外三个页面,即 `主页`、`关于` 和 `联系` 页面。用户可以使用 `注册` 页面注册到系统中,并开始使用该应用程序。一旦您注册,您的所有凭据信息都将保存到本地数据库中。本地数据库的连接字符串可以在 `config.json` 文件中找到。
{
"AppSettings": {
"SiteTitle": "TechTalkers.Web"
},
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-TechTalkers.Web-6bfb60b0-3080-4310-b46c-9dc1331c9bfa;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
}
您可以从 `SQL Server 对象资源管理器` 窗口访问数据库。
我们的系统与此体系结构略有不同。在我们的系统中,只有一个用户可以登录/登出系统。他/她当然是管理员。我们将显式创建一个管理员用户并将其直接放入我们的数据库。因此,不需要 `注册` 页面。同样,也不需要 `关于` 和 `联系` 页面。
创建对象模型
好的,让我们动手吧。我们需要创建两个模型类,它们将使用 Entity Framework 映射到数据库中的两个不同表。一个表用于存储演讲者信息,下一个表用于存储与演讲者相关的演讲主题信息。
现在,右键单击 `Models` 文件夹,然后添加两个类,分别命名为 `Speaker` 和 `Topic`。模型类如下所示:
Speaker.cs
public class Speaker
{
public Guid SpeakerId { get; set; }
public string Name { get; set; }
public string Bio { get; set; }
}
Topic.cs
public class Topic
{
public Guid TopicId { get; set; }
public string Title { get; set; }
public Guid SpeakerId { get; set; }
public Speaker Speaker { get; set; }
}
`TopicId` 和 `SpeakerId` 将映射到其各自表中的唯一标识符列/主键。类中定义的每个属性都将生成一个具有适当列类型的数据库列,除了 `public Speaker Speaker { get; set; }`。此属性用于导航到 Topic 的关联 Speaker 对象。因此,它被称为导航属性。`public Guid SpeakerId { get; set; }` 用作 Speaker 表中的外键。
使用 Entity Framework 进行对象关系映射
我们已经有了模型,现在必须将它们映射到本地数据库表。Entity Framework 将帮助我们实现这一点。在 `IdentityModels.cs` 类中,添加两个 `DbSet` 类型的属性,分别命名为 `Speakers` 和 `Topics`。
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Speaker> Speakers { get; set; }
public DbSet<Topic> Topics { get; set; }
}
`IdentityDbContext` 用于在应用程序上下文和数据库之间创建桥梁,以便您可以进行双向通信。泛型类型的 `DbSet` 允许您对相应的数据库表运行 `Linq` 或 `Lamda` 查询。
如果我们只想生成 `Topic` 和 `Speaker` 表,我们将使用 `DbContext` 类而不是 `IdentityDbContext`。但是 `IdentityDbContext` 除了其他数据库表之外,还可以帮助我们生成存储用户凭据、用户角色等信息的表。好了,现在重新编译项目。要使这两个新表出现在我们的数据库中,我们必须运行一个迁移命令。让我们开始吧。打开命令提示符,将目录更改为您的项目文件夹,然后运行下面的命令:
dnvm use 1.0.0-beta5
dnx . ef migration add AddedSpeakerTopicModels
dnx . ef migration apply
运行这些命令将在 `Migrations` 文件夹中生成一个新的迁移文件,并在其前面加上时间戳。这是当前数据库结构的快照。
接下来,我们将添加控制器来与 `Speaker` 和数据库表进行交互。
1.1 添加 Speaker 控制器和视图
右键单击 `Controllers` 文件夹,然后选择“添加新项”,然后添加一个 MVC 控制器类;将其命名为 `SpeakerController`。
控制器类仅包含一个 `Index()` 方法,该方法返回一个操作结果。
public IActionResult Index()
{
return View();
}
当用户访问 `[application-url]/Speaker/Index` 或仅 `[application-url]/Speaker` URL 时,将执行此操作并返回一个视图。但是,由于此控制器操作没有关联的视图,我们将看到以下错误:
现在是时候为 speaker 控制器的 `Index()` 操作添加 index 视图了。`Index` 视图将显示一个表格,其中列出了系统中添加的所有 Speaker,并且还将包含一个添加到系统的新 speaker 的链接。在 Views 文件夹下添加一个名为 `Speaker` 的新文件夹。右键单击 `Speaker` 文件夹,选择“添加新项”,然后选择 `MVC 视图页`。将其命名为 `Index`,因为它是一个针对 `Index()` 操作的视图。
复制并粘贴下面的标记
@model IEnumerable<TechTalkers.Web.Models.Speaker>
@{
ViewBag.Title = "Speakers";
}
<h2>@ViewBag.Title</h2>
<p>
<a asp-action="Add">Add a new speaker</a>
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Bio)
</th>
<th></th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Bio)
</td>
<td>
<a asp-action="Detail" asp-route-speakerId="@item.SpeakerId">Detail</a> |
<a asp-action="Edit" asp-route-speakerId="@item.SpeakerId">Edit</a> |
<a asp-action="Delete" asp-route-speakerId="@item.SpeakerId">Delete</a>
</td>
</tr>
}
</table>
如您所见,视图包含一个表格,其中显示了 Speaker 列表中的所有 Speaker。它还提供了查看详细信息、编辑和删除 Speaker 的链接。但由于我们还没有任何 Speaker,我们暂时看不到它们。要将 Speaker 列表传递给此视图,我们需要对控制器进行一些修改。修改后的控制器如下所示:
public class SpeakerController : Controller
{
ApplicationDbContext _db;
public SpeakerController(ApplicationDbContext db)
{
_db = db;
}
// GET: /<controller>/
public IActionResult Index()
{
var speakers = _db.Speakers;
return View(speakers);
}
}
因此,我们在控制器构造函数中实例化了一个 `ApplicationDbContext` 类型的对象。现在,我们可以使用上下文对象 `_db` 通过 Entity Framework 查询数据库。在 `Index()` 操作中,我们查询并将数据库中的所有 Speaker 获取到 `speakers` 列表中,然后将列表传递给我们的 `Index` 视图。现在,如果您运行该应用程序并转到与之前相同的 URL,您将看到下面的视图:
我们已将 `Add` 操作附加到 `add a speaker` 链接。让我们为此添加两个控制器操作:
[HttpGet]
public IActionResult Add()
{
return View();
}
[HttpPost]
public IActionResult Add(Speaker speaker)
{
_db.Speakers.Add(speaker);
_db.SaveChanges();
return RedirectToAction("Index");
}
我们有两个用于添加的控制器。这纯粹是因为当管理员请求 `[application-url]/Add` URL 时,他们正在向我们的服务器发出“GET”请求,该请求返回 `Speaker` 添加表单。然后,如果他们填写所有信息并单击“添加”,他们将向我们的服务器发出“POST”请求,该请求包含从提供的信息构建的对象。在获取传入的 speaker 对象后,我们将该 speaker 保存到我们的数据库,然后再次重定向到 `Index` 视图。speaker 添加视图如下所示:
@model TechTalkers.Web.Models.Speaker
@{
ViewBag.Title = "Add a Speaker";
}
<h2>@ViewBag.Title</h2>
<div>
<form asp-controller="Speaker" asp-action="Add" method="post">
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" placeholder="name" />
</div>
<div class="form-group">
<label asp-for="Bio"></label>
<textarea asp-for="Bio" class="form-control" rows=5 placeholder="bio"></textarea>
</div>
<input type="submit" class="btn btn-default" value="Add" />
</form>
</div>
运行项目并添加一个用户后,将在 `Index` 视图中显示添加的 speaker,如下所示。之前我添加了三个 speaker,所以列表显示了四个 speaker。
创建 `Detail` 视图及其控制器也很简单。从 `Index` 视图中,我们将 `speakerId` 传递给我们的 detail 视图:
<a asp-action="Detail" asp-route-speakerId="@item.SpeakerId">Detail</a>
这就是为什么我们需要一个接受 `speakerId` 作为输入参数的 HTTP 'GET' 控制器。我们所要做的就是搜索与 `speakerId` 关联的 speaker,然后将该 speaker 传递给我们的 `Detail` 视图。控制器如下所示:
[HttpGet]
public IActionResult Detail(Guid speakerId)
{
var id = RouteData.Values["speakerId"];
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
return View(speaker);
}
这是视图
@model TechTalkers.Web.Models.Speaker
@{
ViewBag.Title = "Details";
}
<h2>@ViewBag.Title</h2>
<div>
<dl class="dl-horizontal">
<dt>@Html.DisplayNameFor(model => model.Name)</dt>
<dd>@Html.DisplayFor(model => model.Name)</dd>
<dt>@Html.DisplayNameFor(model => model.Bio)</dt>
<dd>@Html.DisplayFor(model => model.Bio)</dd>
</dl>
</div>
以下是它的样子:
与 add 视图一样,我们有两个用于 `Edit` 视图的控制器。其中一个与 `Detail` 控制器完全相同,因为我们必须将选定的 speaker 传递给 `Edit` 视图,以便为输入字段提供先前的值。第二个控制器与第二个 `Add` 控制器非常相似,除了我们更新数据库中的 speaker 而不是添加它。这是两个控制器:
[HttpGet]
public IActionResult Edit(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
return View(speaker);
}
[HttpPost]
public IActionResult Edit(Speaker speaker)
{
_db.Speakers.Update(speaker);
_db.SaveChanges();
return RedirectToAction("Index");
}
`Edit` 视图如下所示:
@model TechTalkers.Web.Models.Speaker
@{
ViewBag.Title = "Edit Speaker";
}
<h2>@ViewBag.Title</h2>
<div>
<form asp-controller="Speaker" asp-action="Edit" method="post" asp-route-speakerId="@Model.SpeakerId">
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Bio"></label>
<textarea asp-for="Bio" rows=3 class="form-control"></textarea>
</div>
<input type="submit" class="btn btn-default" value="Edit" />
</form>
</div>
最后是 `Delete` 控制器。首先,我们必须选择带有传入 `speakerId` 的 speaker,并在 delete 视图中显示其详细信息。在 delete 视图中,我们必须有一个 delete 按钮,该按钮将最终删除 speaker。控制器如下所示:
[HttpGet]
public IActionResult Delete(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
return View(speaker);
}
public IActionResult DeleteConfirmed(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
_db.Speakers.Remove(speaker);
_db.SaveChanges();
return RedirectToAction("Index");
}
视图如下所示:
@model TechTalkers.Web.Models.Speaker
@{
ViewBag.Title = "Delete";
}
<h2>@ViewBag.Title</h2>
<div>
<dl class="dl-horizontal">
<dt>@Html.DisplayNameFor(model => model.Name)</dt>
<dd>@Html.DisplayFor(model => model.Name)</dd>
<dt>@Html.DisplayNameFor(model => model.Bio)</dt>
<dd>@Html.DisplayFor(model => model.Bio)</dd>
</dl>
<form asp-controller="Speaker" asp-action="DeleteConfirmed" asp-route-speakerId="@Model.SpeakerId">
<input type="submit" class="btn btn-default" value="Delete" />
</form>
</div>
让我们尝试一下。
1.2 添加 Topic 控制器和视图
大部分工作与创建 `Speaker` 控制器和视图几乎相同。额外的要求是需要一个可用 Speaker 的下拉列表来选择一个与主题关联的 Speaker。我们来做一下。
和以前一样,创建一个控制器并命名为 `TopicController`。在构造函数中创建一个 `ApplicationDbContext` 实例。像以前一样创建 `Index()` 控制器,只是现在将主题列表传递给视图。别忘了包含我们讨论过的导航属性:
public IActionResult Index()
{
var topics = _db.Topics.Include(s=>s.Speaker);
return View(topics);
}
视图中没有新内容。只需复制并粘贴下面的标记:
@model IEnumerable<TechTalkers.Web.Models.Topic>
@{
ViewBag.Title = "Topics";
}
<h2>@ViewBag.Title</h2>
<p>
<a asp-action="Add">Add a new topic</a>
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Speaker)
</th>
<th></th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Speaker.Name)
</td>
<td>
<a asp-action="Detail" asp-route-topicId="@item.TopicId">Detail</a> |
<a asp-action="Edit" asp-route-topicId="@item.TopicId">Edit</a> |
<a asp-action="Delete" asp-route-topicId="@item.TopicId">Delete</a>
</td>
</tr>
}
</table>
在 `Add` 控制器中做了一些小的改动,我们应该将 Speaker 列表发送到视图,以便管理员在创建主题时可以从中进行选择。因此,我们将 Speaker 列表放入一个 `ViewBag` 对象中。
[HttpGet]
public IActionResult Add()
{
Speakers = GetAllSpeakers();
ViewBag.Speakers = Speakers;
return View();
}
private IEnumerable<SelectListItem> GetAllSpeakers()
{
return _db.Speakers.ToList().Select(speaker => new SelectListItem
{
Text = speaker.Name,
Value = speaker.SpeakerId.ToString(),
});
}
添加主题视图如下所示。我们将 `ViewBag` 对象中可用的 Speaker 列表传递给 HTML `select` 控件:
@model TechTalkers.Web.Models.Topic
@{
ViewBag.Title = "Add a Topic";
}
<h2>@ViewBag.Title</h2>
<div>
<form asp-controller="Topic" asp-action="Add" method="post">
<div class="form-group">
<label asp-for="Title"></label>
<input asp-for="Title" class="form-control" placeholder="title" />
</div>
<div class="form-group">
<select asp-for="SpeakerId" asp-items="@ViewBag.Speakers" class="form-control"></select>
</div>
<input type="submit" class="btn btn-default" value="Add"/>
</form>
</div>
最后,用于回发的 Add 控制器是这样的:
[HttpPost]
public IActionResult Add(Topic topic)
{
_db.Topics.Add(topic);
_db.SaveChanges();
return RedirectToAction("Index");
}
这里是 detail 视图及其关联的控制器:
@model TechTalkers.Web.Models.Topic
@{
ViewBag.Title = "Details";
}
<h2>@ViewBag.Title</h2>
<div>
<dl class="dl-horizontal">
<dt>@Html.DisplayNameFor(model => model.Title)</dt>
<dd>@Html.DisplayFor(model => model.Title)</dd>
<dt>@Html.DisplayNameFor(model => model.Speaker)</dt>
<dd>@Html.DisplayFor(model => model.Speaker.Name)</dd>
</dl>
</div>
Topic Detail 控制器
[HttpGet]
public IActionResult Detail(Guid topicId)
{
Topic topic = _db.Topics.Include(s=>s.Speaker).FirstOrDefault(t => t.TopicId == topicId);
return View(topic);
}
Topic Edit 视图及其控制器如下所示:
@model TechTalkers.Web.Models.Topic
@{
ViewBag.Title = "Edit Topic";
}
<h2>@ViewBag.Title</h2>
<div>
<form asp-controller="Topic" asp-action="Edit" method="post" asp-route-topicId="@Model.TopicId">
<div class="form-group">
<label asp-for="Title"></label>
<input asp-for="Title" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Speaker"></label>
<select asp-for="SpeakerId" asp-items="@ViewBag.Speakers" class="form-control"></select>
</div>
<input type="submit" class="btn btn-default" value="Edit" />
</form>
</div>
再次在 Topic Edit 控制器中,我们将 Speaker 列表放入一个 `ViewBag` 中,以便 select 控件可以填充。第二个控制器与 `Speaker` 的第二个 `Edit` 视图相同:
[HttpGet]
public IActionResult Edit(Guid topicId)
{
Speakers = GetAllSpeakers();
ViewBag.Speakers = Speakers;
Topic topic = _db.Topics.Include(s => s.Speaker).FirstOrDefault(t => t.TopicId == topicId);
return View(topic);
}
[HttpPost]
public IActionResult Edit(Topic topic)
{
_db.Topics.Update(topic);
_db.SaveChanges();
return RedirectToAction("Index");
}
Delete 视图和控制器如下所示:
@model TechTalkers.Web.Models.Topic
@{
ViewBag.Title = "Delete";
}
<h2>@ViewBag.Title</h2>
<div>
<dl class="dl-horizontal">
<dt>@Html.DisplayNameFor(model => model.Title)</dt>
<dd>@Html.DisplayFor(model => model.Title)</dd>
<dt>@Html.DisplayNameFor(model => model.Speaker)</dt>
<dd>@Html.DisplayFor(model => model.Speaker.Name)</dd>
</dl>
<form asp-controller="Topic" asp-action="DeleteConfirmed" asp-route-topicId="@Model.TopicId">
<input type="submit" class="btn btn-default" value="Delete" />
</form>
</div>
最后是 `Delete` 控制器。一个用于“GET”请求,一个用于“POST”请求。
[HttpGet]
public IActionResult Delete(Guid topicId)
{
Topic topic = _db.Topics.Include(s => s.Speaker).FirstOrDefault(t => t.TopicId == topicId);
return View(topic);
}
public IActionResult DeleteConfirmed(Guid topicId)
{
Topic topic = _db.Topics.FirstOrDefault(t => t.TopicId == topicId);
_db.Topics.Remove(topic);
_db.SaveChanges();
return RedirectToAction("Index");
}
让我们看看我们是否犯了任何错误:
一切看起来都不错。
添加管理员身份验证
正如我之前所说,我们的系统将只有一个用户,他最终是管理员。只有管理员才能添加/删除/编辑 `Topics` 和 `Speakers`。因此,不需要为外部用户提供注册视图。您可以删除 `AccountController` 的大部分代码,但现在我将其注释掉了。注释掉代码比删除更好。您将来可能需要该块。
public IActionResult Register() { ... }
public async Task<IActionResult> Register(RegisterViewModel model) { ... }
我们有一个负责初始化数据库并包含一个用户的控制器。不要担心它们,因为如果您没有将您的应用程序注册到那些服务,您将无法访问外部注册/登录服务。这意味着注释掉那些控制器或保留它们无关紧要。
我创建了一个负责使用管理员角色的用户初始化数据库的类。它看起来像这样:
public class DatabaseInitializer : IDatabaseInitializer
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public DatabaseInitializer(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}
public async Task CreateUserAndRoleAsync()
{
var user = await _userManager.FindByEmailAsync("fiyazhasan@gmail.com");
if (user == null)
{
user = new ApplicationUser {
UserName = "fiyazhasan@gmail.com",
Email = "fiyazhasan@gmail.com"
};
await _userManager.CreateAsync(user, "@Fizz1234");
}
var role = await _roleManager.FindByNameAsync("admin");
if (role == null) {
role = new IdentityRole {
Name = "admin"
};
await _roleManager.CreateAsync(role);
}
await _userManager.AddToRoleAsync(user, "admin");
}
}
public interface IDatabaseInitializer
{
Task CreateUserAndRoleAsync();
}
实现接口只是为了依赖注入。如果您不想这样做,则不必这样做。在 `DatabaseInitializer` 中,我们有两个属性,可以帮助我们与 `Identity` 模块进行通信。要创建新用户,我们使用 `UserManager`,要管理他们的角色,我们使用 `RoleManager` 类。`UserManager` 能够处理 `ApplicationUser` 类型,该类型只是 `IdentityUser` 的一个实现。此类可以在 `IdentityModels` 类中找到。
public class ApplicationUser : IdentityUser
{
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Speaker> Speakers { get; set; }
public DbSet<Topic> Topics { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
如您所见,`ApplicationUser` 类是空的。您可以在注册时添加其他属性作为必填字段,否则只需将其删除,然后直接使用 `IdentityUser` 和 UserManager,我在这里使用了 `RoleManager`(在 `RoleManager` 中使用了 `IdentityRole`)。其余代码不言而喻。我所做的就是创建一个用户并为其分配一个管理员角色。
现在,我们必须将这个初始化程序类注入到启动类中的 `ConfigureServices` 方法中。现在,要在应用程序运行时运行初始化程序,我们调用 `CreateUserAndRoleAsync()` 方法,该方法位于 `ConfigureServices()` 所在的同一类中。这是启动类的最终外观:
public void ConfigureServices(IServiceCollection services)
{
// Add Entity Framework services to the services container.
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
// Add Identity services to the services container.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Configure the options for the authentication middleware.
// You can add options for Google, Twitter and other middleware as shown below.
// For more information see http://go.microsoft.com/fwlink/?LinkID=532715
services.Configure<FacebookAuthenticationOptions>(options =>
{
options.AppId = Configuration["Authentication:Facebook:AppId"];
options.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
});
services.Configure<MicrosoftAccountAuthenticationOptions>(options =>
{
options.ClientId = Configuration["Authentication:MicrosoftAccount:ClientId"];
options.ClientSecret = Configuration["Authentication:MicrosoftAccount:ClientSecret"];
});
// Add MVC services to the services container.
services.AddMvc();
// Uncomment the following line to add Web API services which makes it easier to port Web API 2 controllers.
// You will also need to add the Microsoft.AspNet.Mvc.WebApiCompatShim package to the 'dependencies' section of project.json.
// services.AddWebApiConventions();
// Register application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddTransient<IDatabaseInitializer,DatabaseInitializer>();
}
// Configure is called after ConfigureServices is called.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IDatabaseInitializer databaseInitializer)
{
loggerFactory.MinimumLevel = LogLevel.Information;
loggerFactory.AddConsole();
// Configure the HTTP request pipeline.
// Add the following to the request pipeline only in development environment.
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseErrorPage(ErrorPageOptions.ShowAll);
app.UseDatabaseErrorPage(DatabaseErrorPageOptions.ShowAll);
}
else
{
// Add Error handling middleware which catches all application specific errors and
// sends the request to the following path or controller action.
app.UseErrorHandler("/Home/Error");
}
// Add static files to the request pipeline.
app.UseStaticFiles();
// Add cookie-based authentication to the request pipeline.
app.UseIdentity();
// Add authentication middleware to the request pipeline. You can configure options such as Id and Secret in the ConfigureServices method.
// For more information see http://go.microsoft.com/fwlink/?LinkID=532715
// app.UseFacebookAuthentication();
// app.UseGoogleAuthentication();
// app.UseMicrosoftAccountAuthentication();
// app.UseTwitterAuthentication();
// Add MVC to the request pipeline.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
// Uncomment the following line to add a route for porting Web API 2 controllers.
// routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}");
});
databaseInitializer.CreateUserAndRoleAsync();
}
1.1 限制外部用户添加、编辑和删除 Speaker 和 Topic
这一步非常简单,我们只需要用 `[Authorize(Roles = "admin")]` 装饰我们的 `Add`、`Edit` 和 `Delete` 控制器。这意味着我们只想授予管理员访问控制器的权限。以下是为 speaker 的 `Add`、`Edit` 和 `Delete` 控制器添加该属性后的样子。请对 `Topic` 控制器也这样做。
public class SpeakerController : Controller
{
readonly ApplicationDbContext _db;
public SpeakerController(ApplicationDbContext db)
{
_db = db;
}
// GET: /<controller>/
public IActionResult Index()
{
var speakers = _db.Speakers;
return View(speakers);
}
[HttpGet]
[Authorize(Roles = "admin")]
public IActionResult Add()
{
return View();
}
[HttpPost]
[Authorize(Roles = "admin")]
public IActionResult Add(Speaker speaker)
{
_db.Speakers.Add(speaker);
_db.SaveChanges();
return RedirectToAction("Index");
}
[HttpGet]
[Authorize(Roles = "admin")]
public IActionResult Detail(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
return View(speaker);
}
[HttpGet]
[Authorize(Roles = "admin")]
public IActionResult Edit(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
return View(speaker);
}
[HttpPost]
[Authorize(Roles = "admin")]
public IActionResult Edit(Speaker speaker)
{
_db.Speakers.Update(speaker);
_db.SaveChanges();
return RedirectToAction("Index");
}
[HttpGet]
[Authorize(Roles = "admin")]
public IActionResult Delete(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
return View(speaker);
}
[Authorize(Roles = "admin")]
public IActionResult DeleteConfirmed(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
_db.Speakers.Remove(speaker);
_db.SaveChanges();
return RedirectToAction("Index");
}
}
现在,如果您尝试以外部用户的身份添加/编辑/删除 speaker 或 topic,您将被带到登录屏幕。如果您使用管理员凭据登录,则可以添加/编辑/删除 speaker 或 topic。
是时候从 Layout 页面中删除 `注册` 和 `登录` 链接了。转到 Views > Shared >_LoginPartial 并注释掉这些行。
else
{
<ul class="nav navbar-nav navbar-right">
<li><a asp-action="Register" asp-controller="Account">Register</a></li>
<li><a asp-action="Login" asp-controller="Account">Log in</a></li>
</ul>
}
该行负责向外部用户显示注册和登录链接。当然,我们不需要它。管理员可以通过转到此链接来登录 - `[application-url]/Account/Login`。
在离开之前,只需将 `Speaker` 和 `Topic` 视图链接添加到 `_Layout` 视图中。用这两个链接替换 About 和 Contact 链接:
<li><a asp-controller="Topic" asp-action="Index">Topic</a></li>
<li><a asp-controller="Speaker" asp-action="Index">Speaker</a></li>
完整的应用程序概览
<iframe allowfullscreen="" frameborder="0" src="https://www.youtube.com/embed/iyflwFgX5l0"></iframe>
如果 iframe 不起作用,请参阅此视频链接 - 使用 ASP.NET 5 MVC 和 Entity Framework 7 构建 Web 应用程序
我没做什么
我没有在模型的属性上应用验证属性。因此,如果您尝试对它们执行一些奇怪的操作(例如,使 speaker 的名称值长度为 500 个字符),将会出现异常。同样,我也没有对控制器的代码进行 null 安全检查,也没有将它们包含在 try catch 中。
结论
应用程序应该运行正常。如果您发现任何错误,请在下方留言。希望您喜欢这篇帖子。下次见。再见,技术爱好者们。 :)