- ASP.NET Core 教程
- ASP.NET Core - 首页
- ASP.NET Core - 概述
- ASP.NET Core - 环境设置
- ASP.NET Core - 新建项目
- ASP.NET Core - 项目布局
- ASP.NET Core - Project.Json
- ASP.NET Core - 配置
- ASP.NET Core - 中间件
- ASP.NET Core - 异常
- ASP.NET Core - 静态文件
- ASP.NET Core - 设置 MVC
- ASP.NET Core - MVC 设计模式
- ASP.NET Core - 路由
- ASP.NET Core - 属性路由
- ASP.NET Core - 操作结果
- ASP.NET Core - 视图
- 设置 Entity Framework
- ASP.NET Core - DBContext
- ASP.NET Core - Razor 布局视图
- ASP.NET Core - Razor 视图启动
- ASP.NET Core - Razor 视图导入
- ASP.NET Core - Razor 标签助手
- ASP.NET Core - Razor 编辑表单
- ASP.NET Core - 身份概述
- ASP.NET Core - 授权属性
- 身份配置
- ASP.NET Core - 身份迁移
- ASP.NET Core - 用户注册
- ASP.NET Core - 创建用户
- ASP.NET Core - 登录和注销
- ASP.NET Core 有用资源
- ASP.NET Core - 快速指南
- ASP.NET Core - 有用资源
- ASP.NET Core - 讨论
ASP.NET Core - 快速指南
ASP.NET Core - 概述
ASP.NET Core 是微软的新一代 Web 框架。它从头开始进行了重新设计,旨在快速、灵活、现代,并在不同平台上运行。展望未来,ASP.NET Core 是可以用于使用 .NET 进行 Web 开发的框架。如果您在过去几年中对 MVC 或 Web API 有任何经验,您会注意到一些熟悉的功能。在本教程结束时,您将拥有开始使用 ASP.NET Core 并编写应用程序所需的一切,该应用程序可以创建、编辑和查看数据库中的数据。
ASP.NET 简史
多年来,ASP.NET 一直用于开发 Web 应用程序。从那时起,该框架经历了稳定的演变,最终导致了其最新的后代 ASP.NET Core 1.0。
ASP.NET Core 1.0 不是 ASP.NET 4.6 的延续。
它是一个全新的框架,一个并排的项目,可以与我们知道的所有其他内容愉快地共存。
它是当前 ASP.NET 4.6 框架的实际重写,但更小且模块化程度更高。
有些人认为很多东西保持不变,但这并非完全正确。ASP.NET Core 1.0 对 ASP.NET 格局是一个重大的根本性变化。
什么是 ASP.NET Core
ASP.NET Core 是一个开源且针对云进行优化的 Web 框架,用于开发可在 Windows、Linux 和 Mac 上开发和运行的现代 Web 应用程序。它包括 MVC 框架,该框架现在将 MVC 和 Web API 的功能组合到一个 Web 编程框架中。
ASP.NET Core 应用程序可以在 .NET Core 或完整的 .NET Framework 上运行。
它的架构旨在为部署到云或在本地运行的应用程序提供优化的开发框架。
它由具有最少开销的模块化组件组成,因此您在构建解决方案时可以保留灵活性。
您可以在 Windows、Mac 和 Linux 上跨平台开发和运行您的 ASP.NET Core 应用程序。
ASP.NET Core 的优势
ASP.NET Core 具有以下优势 -
ASP.NET Core 进行了许多架构更改,从而产生了更精简且模块化的框架。
ASP.NET Core 不再基于 System.Web.dll。它基于一组粒度细化且经过良好分解的 NuGet 包。
这使您可以优化您的应用程序,使其仅包含您需要的 NuGet 包。
较小的应用程序表面积的好处包括更严格的安全、减少服务、改进的性能和降低的成本
使用 ASP.NET Core,您可以获得以下改进 -
在 Windows、Mac 和 Linux 上构建和运行跨平台 ASP.NET 应用程序。
构建在 .NET Core 上,支持真正的并排应用程序版本控制。
简化现代 Web 开发的新工具。
用于 Web UI 和 Web API 的单个对齐 Web 堆栈。
基于云就绪的环境配置。
内置支持依赖项注入。
标签助手使 Razor 标记与 HTML 更加自然。
能够在 IIS 上托管或在您自己的进程中进行自托管。
ASP.NET Core - 环境设置
ASP.NET Core 是 ASP.NET 的重大重新设计。本主题介绍了 ASP.NET Core 中的新概念,并解释了它们如何帮助您开发现代 Web 应用程序。
要在您的应用程序中使用 ASP.NET Core,必须在您的系统中安装以下内容 -
- Microsoft Visual Studio 2015
- Microsoft .NET Core 1.0.0 - VS 2015 工具预览版 2
Microsoft 提供 Visual Studio 的免费版本,其中还包含 SQL Server,可以从 www.visualstudio.com/en-us/downloads/downloadvisual-studio-vs.aspx 下载,并且 Microsoft .NET Core 1.0.0 - VS 2015 工具预览版 2 可以从 https://go.microsoft.com/fwlink/?LinkId=817245. 下载。
Microsoft Visual Studio 2015 的安装
现在让我们了解安装中涉及的步骤
步骤 1 - 下载完成后,运行安装程序。将显示以下对话框。
步骤 2 - 单击上面的屏幕截图中的“安装”按钮。然后安装过程将开始。
步骤 3 - 安装过程成功完成后,您将看到以下对话框。
步骤 4 - 关闭此对话框,并在需要时重新启动计算机。
步骤 5 - 从“开始”菜单打开 Visual Studio。这将打开以下对话框,并且第一次需要一些时间(仅用于准备)。
步骤 6 - 现在您将看到 Visual Studio 的主窗口。
步骤 7 - 安装 Visual Studio 后,关闭 Visual Studio 并启动 Microsoft .NET Core 1.0.0 - VS 2015 工具预览版 2。
步骤 8 - 选中复选框并单击“安装”。
步骤 9 - 安装完成后,您将看到以下消息。
步骤 10 - 现在您可以使用 ASP.NET Core 开始您的应用程序了。
ASP.NET Core - 新建项目
在本章中,我们将讨论如何在 Visual Studio 中创建新项目。
安装 Visual Studio 2015 工具后,您可以从文件→新建项目菜单选项开始构建新的 ASP.NET Core 应用程序。
在“新建项目”对话框中,您将看到以下三个不同的 Web 项目模板 -
ASP.NET Web 应用程序 - 简单 ASP.NET 应用程序模板。
ASP.NET Core Web 应用程序 (.NET Core) - 这将使用可在 .NET Core 框架上运行的跨平台兼容项目启动您。
ASP.NET Core Web 应用程序 (.NET Framework) - 这将在 Windows 上的标准 .NET Framework 上启动一个新项目。
在左侧窗格中,选择模板→Visual C#→Web,在中间窗格中选择 ASP.NET Core Web 应用程序 (.NET Core) 模板。让我们将此应用程序称为FirstAppDemo,并为您的 ASP.NET Core 项目指定位置,然后单击“确定”。
在上面的对话框中,您可以从可用的 ASP.NET Core 模板中选择 ASP.NET 应用程序的特定模板。
ASP.NET Core 模板当前包含三个不同的模板。其中,Web 应用程序模板将帮助您在文件系统上布置大量文件。这还允许您立即使用 ASP.NET MVC。
在这里,我们将从空模板开始。这将帮助我们从头开始构建它。让我们选择“空”模板,关闭“在云中托管”并单击“确定”。
Visual Studio 现在将在一段时间后启动项目。在“解决方案资源管理器”窗口中,您将看到此项目中的所有文件。
让我们运行此应用程序,您可以通过按 Ctrl+F5 或转到“调试”菜单来执行此操作。转到“调试”菜单后,选择“不调试启动”。
此应用程序只能显示“Hello World!”。它在localhost:57741上运行。在窗口系统托盘中,您还可以看到 IIS Express 正在运行。
并且站点的名称为FirstAppDemo。如果您之前使用过 ASP.NET 框架的早期版本进行编程,那么您与 Visual Studio 的交互方式以及 Visual Studio 使用 IIS Express 托管应用程序的方式,所有这些方面都将很熟悉。
ASP.NET Core - 项目布局
在本章中,我们将讨论 ASP.NET Core 项目在文件系统上的显示方式以及不同的文件和目录如何协同工作。
让我们打开上一章中创建的FirstAppDemo项目。
在“解决方案资源管理器”窗口中,右键单击“解决方案”节点,然后选择“在文件资源管理器中打开文件夹”。
您现在将看到根目录中包含两个文件:FirstAppDemo.sln 和global.json。
FirstAppDemo.sln 是一个解决方案文件。Visual Studio 多年来一直默认使用此扩展名,如果您想在 Studio 中打开应用程序并对其进行操作,可以双击该文件。
还有一个global.json文件。让我们在 Visual Studio 中打开此文件。
在文件中,项目的设置非常重要。此项目设置告诉 ASP.NET 在哪里查找您的源代码以及哪些文件夹包含您的项目。
有两个可能的文件夹“src”用于源代码和“test”文件夹。除非您的项目和源代码位于这两个文件夹之一中,否则代码将不可用于构建。如果需要,您可以更改这些设置。
Windows 资源管理器在磁盘上具有“src”文件夹。您没有测试文件夹。在测试文件夹中,您可以放置单元测试项目。让我们双击“src”文件夹。
您可以看到 FirstAppDemo 项目和 Web 应用程序。现在,双击该文件夹。
这些是应用程序的源代码文件,您也可以在“解决方案资源管理器”窗口中看到此文件夹结构。这是因为在当前版本的 ASP.NET Core 中,文件系统决定了项目中的内容。
如果向磁盘添加新文件,则该文件将添加到项目中。如果删除文件,则该文件将从项目中删除。所有内容都保持同步,这与早期版本的 ASP.NET Core 略有不同,在早期版本中,项目文件(一个 *.cs proj 文件)包含项目中所有内容的清单。
ASP.NET Core 还会在文件更改或出现新文件时编译应用程序。
示例
让我们通过在文本编辑器中打开 **Startup.cs** 文件来看一个简单的示例。
正是这行代码响应了对应用程序的每个 HTTP 请求,并且它只是响应“Hello World!”。
让我们更改上面截图中的字符串,将其改为“**Hello World! This ASP.NET Core Application**”,如下面的程序所示。
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace FirstAppDemo { public class Startup { // This method gets called by the runtime. // Use this method to add services to the container. // For more information on how to configure your application, // visit http://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. // Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()){ app.UseDeveloperExceptionPage(); } app.Run(async (context) => { await context.Response.WriteAsync( "Hello World! This ASP.NET Core Application"); }); } } }
在文本编辑器中按 Ctrl + S 保存此文件,然后返回到 Web 浏览器并刷新应用程序。
您现在可以看到您的更改已反映在浏览器中。
这是因为 ASP.NET 将监视文件系统并在文件更改时自动重新编译应用程序。您无需在 Visual Studio 中显式构建应用程序。
事实上,您可以使用完全不同的编辑器,例如 Visual Studio Code。
您需要使用 Visual Studio 做的就是通过运行(不使用调试器)启动 Web 服务器。您也可以按 Ctrl + F5,并可以编辑文件、保存文件,然后只需刷新浏览器即可查看更改。
这是一种使用像 C# 这样的编译语言构建 Web 应用程序的不错的工作流程。
ASP.NET Core - Project.Json
在本章中,我们将讨论 **project.json** 文件。此文件使用 JavaScript 对象表示法来存储配置信息,它确实是 .NET 应用程序的核心。如果没有此文件,您将没有 ASP.NET Core 项目。在这里,我们将讨论此文件的一些最重要的功能。让我们双击 **project.json** 文件。
目前,project.json 文件中的默认代码实现如下:
{ "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": ["dotnet5.6", "portable-net45+win8"] } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "configProperties": { "System.GC.Server": true } }, "publishOptions": { "include": ["wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] } }
如我们所见,我们在该文件的顶部有版本信息。这是您在构建应用程序时将使用的版本号。
版本为 1.0.0,但此文件最重要的部分是依赖项。
如果您的应用程序要执行任何有用的工作,那么您将需要库和框架来完成这项工作,例如将数据存储到数据库或从数据库检索数据,或呈现复杂的 HTML。
在此版本的 ASP.NET Core 中,所有依赖项都通过 NuGet 包管理器进行管理。
NuGet 已经在 .NET 领域存在了几年,但现在管理所有依赖项的主要方式是使用作为 NuGet 包封装的库和框架。
应用程序需要的所有顶级 NuGet 包都将存储在此 project.json 文件中。
"Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0
您可以看到我们在该文件中有一些依赖项,并且确切的依赖项可能会在 ASP.NET 的最终版本中发生变化。当您想要添加新的依赖项(例如 ASP.NET MVC 框架)时,您可以轻松地在此 project.json 文件中键入,您还将获得一些 **IntelliSense** 帮助,包括不仅包名称而且版本号,如下面的屏幕截图所示。
您也可以使用 UI,方法是右键单击解决方案资源管理器中的“引用”,然后选择“管理 NuGet 包”。您现在可以看到当前安装的包。
这些包与 project.json 文件中的包相同,您也可以转到浏览器并添加其他包,包括预发布包,例如,将 MVC 框架安装到此项目中。
如果您现在使用“安装”按钮安装此包,则此包将存储在 project.json 中。“框架”部分是 project.json 的另一个重要部分,此部分告诉 ASP.NET 您的应用程序可以使用哪些 .NET 框架。
"frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } },
在这种情况下,您将看到“**netcoreapp1.0**”是项目中使用的框架,您还可以包含在安装 Visual Studio 时安装的完整 .NET Framework。
它与安装 Windows 操作系统的许多版本时包含的 .NET Framework 相同。
它是已经存在 15 年的 .NET Framework,它包含从 Web 编程到桌面编程的所有功能的框架。
这是一个庞大的框架,只能在 Windows 上运行。
“**netcoreapp1.0**”是 .NET Core 框架。它是一个跨平台框架,可以在各种平台上运行,不仅包括 Windows,还包括 OS X 和 Linux。
此框架的功能少于完整的 .NET Framework,但它确实具有我们进行 ASP.NET Core Web 开发所需的所有功能。
ASP.NET Core - 配置
在本章中,我们将讨论与 ASP.NET Core 项目相关的配置。在解决方案资源管理器中,您将看到 Startup.cs 文件。如果您使用过早期版本的 ASP.NET Core,您可能希望看到 global.asax 文件,该文件是可以在其中编写代码以在 Web 应用程序启动期间执行的一个位置。
您还希望看到一个 web.config 文件,其中包含应用程序执行所需的所有配置参数。
在 ASP.NET Core 中,这些文件都消失了,取而代之的是从 Startup.cs 加载配置和启动代码。
文件中有一个 Startup 类,在此类中,您可以配置应用程序,甚至可以配置配置源。
以下是 **Startup.cs 文件**中的默认实现。
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace FirstAppDemo { public class Startup { // This method gets called by the runtime. // Use this method to add services to the container. // For more information on how to configure your application, // visit http://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. Use this method to configure // the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } } }
在 Startup 类中,有两个方法是大多数工作将发生的地方。类的 Configure 方法是构建 HTTP 处理管道的所在。
这定义了应用程序如何响应请求。目前,此应用程序只能显示“Hello World!”,如果我们希望应用程序的行为有所不同,则需要通过在此 Configure 方法中添加其他代码来更改管道。
例如,如果我们想要提供静态文件(例如 index.html 文件),则需要在 Configure 方法中添加一些代码。
您还可以拥有错误页面或将请求路由到 ASP.NET MVC 控制器;这两种情况都需要在此 Configure 方法中进行一些工作。
在 Startup 类中,您还将看到 **ConfigureServices()** 方法。这有助于您为应用程序配置组件。
现在,我们为每个响应都使用了硬编码字符串——“**Hello World**!”字符串。我们不希望硬编码字符串,而是希望从知道我们要显示的文本的某个组件加载此字符串。
此其他组件可能会从数据库、Web 服务或 JSON 文件加载该文本,确切位置并不重要。
我们只需要设置一个场景,以便我们没有这个硬编码字符串。
在解决方案资源管理器中,右键单击项目节点,然后选择“添加”→“新建项”。
在左侧窗格中,选择“已安装”→“代码”,然后在中间窗格中,选择 JSON 文件。将此文件命名为 **AppSettings.json**,然后单击“添加”按钮,如上图所示。
我们还可以让程序从文件中读取文本,而不是在 Startup.cs 中使用“Hello World!”字符串。让我们在 **AppSettings.json 文件**中添加以下代码。
{ "message": "Hello, World! this message is from configuration file..." }
现在我们需要从 Startup.cs 文件访问此消息。以下是将从 JSON 文件读取上述消息的 **Startup.cs** 文件的实现。
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; namespace FirstAppDemo { public class Startup { public Startup() { var builder = new ConfigurationBuilder() .AddJsonFile("AppSettings.json"); Configuration = builder.Build(); } public IConfiguration Configuration { get; set; } // This method gets called by the runtime. // Use this method to add services to the container. // For more information on how to configure your application, // visit http://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. // Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.Run(async (context) => { var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); } // Entry point for the application. public static void Main(string[] args) =7gt; WebApplication.Run<Startup>(args); } }
现在让我们运行应用程序。运行应用程序后,它将产生以下输出。
ASP.NET Core - 中间件
在本章中,我们将了解如何设置中间件。ASP.NET Core 中的中间件控制我们的应用程序如何响应 HTTP 请求。它还可以控制应用程序在发生错误时的外观,并且是我们如何对用户进行身份验证和授权以执行特定操作的关键部分。
中间件是软件组件,这些组件组装到应用程序管道中以处理请求和响应。
每个组件都会选择是否将请求传递到管道中的下一个组件,并且可以在调用管道中的下一个组件之前和之后执行某些操作。
请求委托用于构建请求管道。请求委托处理每个 HTTP 请求。
ASP.NET Core 中的每个中间件都是一个对象,并且每个中间件都具有非常具体、集中和有限的作用。
最终,我们需要许多中间件才能使应用程序正常运行。
现在让我们假设我们希望将有关每个请求的信息记录到我们的应用程序中。
在这种情况下,我们可能安装到应用程序中的第一个中间件是一个日志记录组件。
此记录器可以查看有关传入请求的所有信息,但记录器很可能只是记录一些信息,然后将此请求传递给下一个中间件。
中间件是此处理管道中的一系列组件。
我们安装到应用程序中的下一个中间件是授权器。
授权器可能正在 HTTP 标头中查找特定的 Cookie 或访问令牌。
如果授权器找到令牌,则允许请求继续。否则,授权器本身可能会使用 HTTP 错误代码或重定向代码响应请求,以将用户发送到登录页面。
但是,否则,授权器会将请求传递给下一个中间件,即路由器。
路由器查看 URL 并确定您的下一步操作。
路由器查看应用程序以查找要响应的内容,如果路由器找不到要响应的内容,则路由器本身可能会返回 **404 未找到错误**。
示例
现在让我们举一个简单的例子来进一步了解中间件。我们使用 **Startup 类**的 Configure 方法在 ASP.NET 中设置中间件。
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; namespace FirstAppDemo { public class Startup { public Startup() { var builder = new ConfigurationBuilder() .AddJsonFile("AppSettings.json"); Configuration = builder.Build(); } public IConfiguration Configuration { get; set; } // This method gets called by the runtime. // Use this method to add services to the container. // For more information on how to configure your application, // visit http://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. // Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.Run(async (context) => { var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); } // Entry point for the application. public static void Main(string[] args) => WebApplication.Run<Startup>(args); } }
在 **Configure()** 方法内部,我们将对 IApplicationBuilder 接口上的扩展方法进行调用以添加中间件。
默认情况下,一个新的空项目中包含两个中间件:
- IISPlatformHandler
- 使用 app.Run 注册的中间件
IISPlatformHandler
IISPlatformHandler 允许我们使用 Windows 身份验证。它会查看每个传入请求,并检查是否有任何与该请求关联的 Windows 身份信息,然后调用下一个中间件。
使用 app.Run 注册的中间件
在本例中,下一个中间件是使用 app.Run 注册的中间件。Run 方法允许我们传入另一个方法,我们可以用它来处理每个响应。Run 不是我们经常见到的,它被称为终端中间件。
使用 Run 注册的中间件将永远没有机会调用另一个中间件,它只接收请求,然后必须生成某种响应。
您还可以访问 Response 对象,并且可以使用 Response 对象来写入字符串。
如果您想在 app.Run 之后注册另一个中间件,则该中间件将永远不会被调用,因为,同样,Run 是一个终端中间件。它永远不会调用下一个中间件。
如何添加另一个中间件
让我们按照以下步骤添加另一个中间件:
步骤 1 - 要添加另一个中间件,请右键单击项目并选择“管理 NuGet 包”。
步骤 2 - 搜索 Microsoft.aspnet.diagnostics,它实际上是用于异常处理、异常显示页面和诊断信息的 ASP.NET Core 中间件。此特定包包含我们可以使用的许多不同的中间件。
步骤 3 - 如果您的项目中未安装该包,请安装它。
步骤 4 - 现在让我们转到 Configure() 方法并调用 app.UseWelcomePage 中间件。
// This method gets called by the runtime. // Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseWelcomePage(); app.Run(async (context) => { var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); }
步骤 5 - 运行您的应用程序,您将看到以下欢迎屏幕。
此欢迎屏幕可能不是很有用。
步骤 6 - 让我们尝试一些可能更有用的东西。我们将使用 RuntimeInfoPage 而不是使用欢迎页面。
// This method gets called by the runtime. // Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseRuntimeInfoPage(); app.Run(async (context) => { var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); }
步骤 7 - 保存您的 Startup.cs 页面并刷新浏览器,您将看到以下页面。
此 RuntimeInfoPage 是一个中间件,它只会响应针对特定 URL 的请求。如果传入请求与该 URL 不匹配,则此中间件只会让请求传递到下一个中间件。请求将通过 IISPlatformHandler 中间件,然后转到 UseRuntimeInfoPage 中间件。它不会创建响应,因此它将转到我们的 app.Run 并显示字符串。
步骤 8 - 让我们在 URL 的末尾添加“/runtimeinfo”。您现在将看到由该运行时信息页面中间件生成的页面。
您现在将看到一个响应,该响应提供了一些关于您的运行时环境的信息,例如操作系统、运行时版本、体系结构、类型以及您正在使用的所有包等。
ASP.NET Core - 异常
在本章中,我们将讨论异常和错误处理。当您的 ASP.NET Core 应用程序中发生错误时,您可以通过多种方式处理它们。让我们看看诊断包中提供的另一个中间件。此中间件将帮助我们处理错误。
为了模拟错误,让我们转到 app.Run 并查看如果我们每次点击此中间件时都抛出异常,应用程序的行为如何。
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; namespace FirstAppDemo { public class Startup { public Startup() { var builder = new ConfigurationBuilder() .AddJsonFile("AppSettings.json"); Configuration = builder.Build(); } public IConfiguration Configuration { get; set; } // This method gets called by the runtime. // Use this method to add services to the container. // For more information on how to configure your application, // visit http://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. // Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseRuntimeInfoPage(); app.Run(async (context) => { throw new System.Exception("Throw Exception"); var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); } // Entry point for the application. public static void Main(string[] args) => WebApplication.Run<Startup>(args); } }
它只会抛出一个带有非常通用消息的异常。保存 Startup.cs 页面并运行您的应用程序。
您将看到我们无法加载此资源。出现了一个 HTTP 500 错误,一个内部服务器错误,这并没有什么帮助。获取一些异常信息可能会更好。
让我们添加另一个中间件,即 UseDeveloperExceptionPage。
// This method gets called by the runtime. // Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseDeveloperExceptionPage(); app.UseRuntimeInfoPage(); app.Run(async (context) => { throw new System.Exception("Throw Exception"); var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); }
此中间件与其他中间件略有不同,其他中间件通常会查看传入请求并对该请求做出一些决定。
UseDeveloperExceptionPage 不太关心传入请求,而关心管道中稍后发生的事情。
它将调用下一个中间件,但随后它将等待查看管道中稍后是否有任何内容生成异常,如果存在异常,则此中间件将为您提供一个错误页面,其中包含有关该异常的其他信息。
现在让我们再次运行应用程序。它将生成如下屏幕截图所示的输出。
现在,您将看到一些在开发中出现错误时预期的信息。您还将获得堆栈跟踪,并且可以看到在 Startup.cs 的第 37 行抛出了一个未处理的异常。
您还可以看到原始异常详细信息,所有这些信息对开发人员都非常有用。实际上,我们可能只想在开发人员运行应用程序时显示此信息。
ASP.NET Core - 静态文件
在本章中,我们将学习如何处理文件。几乎每个 Web 应用程序都需要的一个重要功能是从文件系统提供文件(静态文件)。
静态文件(如 JavaScript 文件、图像、CSS 文件)是我们文件系统上存储的资产,ASP.NET Core 应用程序可以直接将其提供给客户端。
静态文件通常位于 Web 根目录 (wwwroot) 文件夹中。
默认情况下,这是我们唯一可以直接从文件系统提供文件的位置。
示例
现在让我们举一个简单的例子,在这个例子中,我们将了解如何在我们的应用程序中提供这些文件。
在这里,我们想向我们的 FirstAppDemo 应用程序添加一个简单的 HTML 文件,并且此 HTML 文件必须进入 Web 根目录 (wwwroot) 文件夹。在解决方案资源管理器中右键单击 wwwroot 文件夹,然后选择添加→新建项。
在中间窗格中,选择HTML 页面并将其命名为index.html,然后单击添加按钮。
您将看到一个简单的index.html文件。让我们添加一些简单的文本和标题,如下所示。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Welcome to ASP.NET Core</title> </head> <body> Hello, Wolrd! this message is from our first static HTML file. </body> </html>
当您运行应用程序并在浏览器中转到index.html时,您会看到app.Run中间件抛出一个异常,因为当前我们的应用程序中没有任何内容。
没有中间件会去查找文件系统上的任何文件以提供服务。要解决此问题,请通过右键单击解决方案资源管理器中的项目并选择管理 NuGet 包来转到NuGet 包管理器。
搜索Microsoft.AspNet.StaticFiles,它将找到静态文件中间件。让我们安装此 nuget 包,现在我们应该有其他方法可以在 Configure 方法中使用来注册中间件。
让我们在 Configure 方法中添加UseStaticFiles中间件,如下面的程序所示。
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; namespace FirstAppDemo { public class Startup { public Startup() { var builder = new ConfigurationBuilder() .AddJsonFile("AppSettings.json"); Configuration = builder.Build(); } public IConfiguration Configuration { get; set; } // This method gets called by the runtime. // Use this method to add services to the container. // For more information on how to configure your application, // visit http://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. // Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseDeveloperExceptionPage(); app.UseRuntimeInfoPage(); app.UseStaticFiles(); app.Run(async (context) => { throw new System.Exception("Throw Exception"); var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); } // Entry point for the application. public static void Main(string[] args) => WebApplication.Run<Startup>(args); } }
除非您覆盖选项并传入一些不同的配置参数,否则静态文件对于给定的请求将执行的操作是查看请求路径。然后将此请求路径与文件系统和文件系统上的内容进行比较。
如果静态文件看到它可以使用的文件,它将提供该文件,并且不会调用下一个中间件。
如果它找不到匹配的文件,则它将简单地继续执行下一个中间件。
让我们保存Startup.cs文件并刷新浏览器。
您现在可以看到 index.html 文件。您在 wwwroot 中放置的任何内容 - 任何 JavaScript 文件或 CSS 文件或 HTML 文件,您都能够提供它们。
现在,如果您希望 index.html 成为您的默认文件,这是一个 IIS 一直具有的功能。
您始终可以向 IIS 提供一个要查找的默认文件列表。如果有人访问目录的根目录或在本例中访问网站的根目录,并且 IIS 找到名为 index.html 的文件,它将自动提供该文件。
现在让我们从进行一些更改开始。首先,我们需要删除强制错误,然后添加另一个中间件,即 UseDefaultFiles。以下是 Configure 方法的实现。
// This method gets called by the runtime. // Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseDeveloperExceptionPage(); app.UseRuntimeInfoPage(); app.UseDefaultFiles(); app.UseStaticFiles(); app.Run(async (context) => { var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); }
此中间件将查看传入请求,并查看它是否是针对目录的根目录,以及是否存在任何匹配的默认文件。
您可以覆盖此中间件的选项,以告知它要查找哪些默认文件,但 Index.html 默认情况下是默认文件之一。
让我们保存Startup.cs文件,然后转到浏览器中的 Web 应用程序的根目录。
您现在可以看到 index.html 是您的默认文件。安装中间件的顺序很重要,因为如果将 UseDefaultFiles 放在 UseStaticFiles 之后,您将无法获得相同的结果。
如果您要使用 UseDefaultFiles 和 UseStaticFiles,您可能还需要另一个位于 Microsoft.aspnet.staticfiles NuGet 包中的中间件,即FileServer 中间件。这实质上以正确的顺序包含了 Default Files 和 Static Files。
// This method gets called by the runtime. // Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseDeveloperExceptionPage(); app.UseRuntimeInfoPage(); app. UseFileServer(); app.Run(async (context) => { var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); }
让我们再次保存Startup.cs文件。刷新浏览器后,您将看到与以下屏幕截图中显示的结果相同。
ASP.NET Core - 设置 MVC
在本章中,我们将在 FirstAppDemo 应用程序中设置 MVC 框架。我们将通过在 ASP.NET Core 之上构建 Web 应用程序来进行操作,更具体地说,是 ASP.NET Core MVC 框架。从技术上讲,我们可以仅使用中间件构建整个应用程序,但 ASP.NET Core MVC 为我们提供了可以用来轻松创建 HTML 页面和基于 HTTP 的 API 的功能。
要在我们的空项目中设置 MVC 框架,请执行以下步骤:
安装Microsoft.AspNet.Mvc包,它使我们可以访问框架提供的程序集和类。
安装包后,我们需要在运行时注册 ASP.NET MVC 所需的所有服务。我们将在ConfigureServices方法中执行此操作。
最后,我们需要为 ASP.NET MVC 添加中间件以接收请求。从本质上讲,此中间件获取 HTTP 请求,并尝试将该请求定向到我们将编写的 C# 类。
步骤 1 - 让我们转到 NuGet 包管理器,方法是右键单击“管理 NuGet 包”。安装 Microsoft.AspNet.Mvc 包,它使我们可以访问框架提供的程序集和类。
步骤 2 - 安装 Microsoft.AspNet.Mvc 包后,我们需要在运行时注册 ASP.NET Core MVC 所需的所有服务。我们将使用 ConfigureServices 方法执行此操作。我们还将添加一个简单的控制器,并查看该控制器的一些输出。
让我们向此项目添加一个新文件夹,并将其命名为Controllers。我们可以在此文件夹中放置多个控制器,如下面的解决方案资源管理器所示。
现在右键单击 Controllers 文件夹,然后选择添加→类菜单选项。
步骤 3 - 在这里,我们想添加一个简单的C#类,并将此类命名为HomeController,然后单击添加按钮,如上图所示。
这将是我们的默认页面。
步骤 4 - 让我们定义一个返回字符串的单个公共方法,并将该方法命名为 Index,如下面的程序所示。
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FirstAppdemo.Controllers { public class HomeController { public string Index() { return "Hello, World! this message is from Home Controller..."; } } }
步骤 5 - 当您转到网站的根目录时,您希望看到控制器响应。目前,我们将提供我们的 index.html 文件。
让我们进入网站的根目录并删除 index.html 文件。我们希望控制器做出响应,而不是由index.html文件响应。
步骤 6 − 现在,转到 Startup 类中的 Configure 方法,并添加UseMvcWithDefaultRoute中间件。
步骤 7 − 现在刷新网站根目录下的应用程序。
您将遇到 500 错误。错误指出框架无法找到所需的 ASP.NET Core MVC 服务。
ASP.NET Core 框架本身由不同的小型组件组成,这些组件具有非常集中的职责。
例如,有一个组件必须找到并实例化控制器。
为了使 ASP.NET Core MVC 正确运行,该组件需要位于 ASP.NET Core MVC 的服务集合中。
步骤 8 − 除了添加 NuGet 包和中间件之外,我们还需要在 ConfigureServices 中添加 AddMvc 服务。以下是 Startup 类的完整实现。
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; namespace FirstAppDemo { public class Startup { public Startup() { var builder = new ConfigurationBuilder() .AddJsonFile("AppSettings.json"); Configuration = builder.Build(); } public IConfiguration Configuration { get; set; } // This method gets called by the runtime. // Use this method to add services to the container. // For more information on how to configure your application, // visit http://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. // Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseDeveloperExceptionPage(); app.UseRuntimeInfoPage(); app.UseFileServer(); app.UseMvcWithDefaultRoute(); app.Run(async (context) => { var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); } // Entry point for the application. public static void Main(string[] args) => WebApplication.Run<Startup>(args); } }
步骤 9 − 保存Startup.cs文件,然后转到浏览器并刷新它。您现在将收到来自主页控制器的响应。
ASP.NET Core - MVC 设计模式
MVC(模型-视图-控制器)设计模式是一种设计模式,实际上已经存在了几十年,并且已在许多不同的技术中使用,从 Smalltalk 到 C++ 到 Java,现在在 C# 和 .NET 中用作构建用户界面时的设计模式。
MVC 设计模式是软件应用程序用户界面层的常用设计模式。
在较大的应用程序中,通常会将模型-视图-控制器 UI 层与应用程序中的其他设计模式(如数据访问模式和消息传递模式)结合使用。
所有这些将共同构建完整的应用程序堆栈。
MVC 将应用程序的用户界面 (UI) 分为以下三个部分:
模型 − 一组描述您正在处理的数据以及业务逻辑的类。
视图 − 定义应用程序的 UI 将如何显示。它是一个纯 HTML,决定 UI 的外观。
控制器 − 一组处理用户通信、整体应用程序流程和应用程序特定逻辑的类。
MVC 的理念
现在让我们了解 MVC 背后的理念。
其理念是,您将拥有一个名为视图的组件,它完全负责呈现此用户界面,无论它应该是 HTML 还是实际上应该是桌面应用程序上的 UI 小部件。
视图与模型进行交互,而模型包含视图需要显示的所有数据。
在 Web 应用程序中,视图可能根本没有任何代码与之关联。
它可能只包含 HTML,然后是一些表达式,用于说明从模型中获取数据片段并将其插入到您在视图中构建的 HTML 模板中的正确位置。
控制器组织所有内容。当 MVC 应用程序收到 HTTP 请求时,请求将路由到控制器,然后由控制器决定与数据库、文件系统或模型进行交互。
在 MVC 中,控制器接收 HTTP 请求,控制器必须弄清楚如何将信息组合在一起以响应此请求。也许用户正在将浏览器定向到应用程序的 /books URL。因此,控制器需要将信息组合在一起以显示书籍列表。在这种情况下,控制器将构建一个模型。
模型不知道任何有关 HTTP 请求或控制器的信息。
模型仅负责保存用户想要查看的书籍信息以及与该书籍列表相关的任何逻辑。
模型只是我们可以使用的另一个 C# 类,如果您有复杂的模型,则可能有多个类。
模型组合在一起后,控制器就可以选择一个视图来呈现模型。
视图将获取模型中的信息,例如所有书籍和每本书的标题等,并使用这些信息构建 HTML 页面。
然后,该 HTML 在 HTTP 响应中发送回客户端,并且整个 HTTP 请求和响应事务完成。
这些是 MVC 设计模式的基础,并且此模式背后的理念是保持关注点分离。因此,控制器只负责接收请求和构建模型。模型承载我们需要的逻辑和数据进入视图。然后,视图仅负责将该模型转换为 HTML。
ASP.NET Core - 路由
在 MVC 框架中,我们有三个组件,每个组件都专注于工作的特定部分。为了使所有这些都能正常工作,我们需要找到一种方法将这些 HTTP 请求发送到正确的控制器。在 ASP.NET Core MVC 中,此过程称为路由。路由是将 HTTP 请求定向到控制器的过程。
现在让我们了解如何将请求路由到不同的控制器。
ASP.NET Core 中间件需要一种方法来确定是否应将给定的 HTTP 请求发送到控制器进行处理。
MVC 中间件将根据 URL 和我们提供的一些配置信息做出此决定。在本章中,我们将定义此配置信息或您可以在 Startup.cs 中添加 MVC 中间件时所说的路由信息。
这种方法通常称为基于约定的路由。以下是基于约定的路由的代码片段。
routeBuilder.MapRoute("Default", "{controller=Home}/{action=Index}/{id?}");
在这种方法中,我们定义模板来告诉 MVC 如何查看 URL 并查找控制器名称和操作名称,其中控制器是 C# 类,操作是该类上的公共方法。
在上一章中,我们在应用程序中创建了一个控制器(HomeController),它是一个 C# 类,不需要派生自基类或实现接口或具有任何特殊属性。它是一个普通的 C# 类,具有名称 HomeController,并且包含返回字符串的 Index 方法。
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FirstAppdemo.Controllers { public class HomeController { public string Index() { return "Hello, World! this message is from Home Controller..."; } } }
在这里,我们将重点关注路由到控制器。我们还将尝试了解路由的工作原理。
现在让我们回到Startup 类,我们在其中将 MVC 中间件配置到我们的应用程序中。在 Configure 方法内部,我们使用了一个方法UseMvcWithDefaultRoute。
public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseDeveloperExceptionPage(); app.UseRuntimeInfoPage(); app.UseFileServer(); app.UseMvcWithDefaultRoute(); app.Run(async (context) => { var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); }
这为我们提供了一个默认路由规则,允许我们访问HomeController。与其使用UseMvcWithDefaultRoute,不如使用UseMvc,然后使用名为ConfigureRoute的方法在此处配置路由。以下是 Startup.cs 文件的实现。
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using Microsoft.AspNet.Routing; using System; namespace FirstAppDemo { public class Startup { public Startup() { var builder = new ConfigurationBuilder() .AddJsonFile("AppSettings.json"); Configuration = builder.Build(); } public IConfiguration Configuration { get; set; } // This method gets called by the runtime. // Use this method to add services to the container. // For more information on how to configure your application, // visit http://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. // Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseDeveloperExceptionPage(); app.UseRuntimeInfoPage(); app.UseFileServer(); app.UseMvc(ConfigureRoute); app.Run(async (context) => { var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); } private void ConfigureRoute(IRouteBuilder routeBuilder) { //Home/Index routeBuilder.MapRoute("Default", "{controller = Home}/{action = Index}/{id?}"); } // Entry point for the application. public static void Main(string[] args) => WebApplication.Run<Startup>(args); } }
在ConfigureRoute方法内部,您可以配置路由;您可以看到此方法必须采用 IRouteBuilder 类型的参数。路由的目标是描述 ASP.NET Core MVC 将用于处理 HTTP 请求并查找可以响应该请求的控制器的规则。
您可以有一条路由可以将请求映射到不同的控制器。
我们可以告诉 routeBuilder 我们想要映射一条新路由,其名称为“Default”,然后提供最重要的路由信息,即模板。
模板是一个字符串,它将向 ASP.NET Core MVC 描述如何拆分 URL。
在最后一个示例中,我们添加了一个 HomeController,因此您还可以请求以下任何 URL,它们也将被定向到 HomeController 上的 Index 操作。
- https://127.0.0.1:49940
- https://127.0.0.1:49940/Home
- https://127.0.0.1:49940/Home/Index
当浏览器请求http://mysite/或 http://mysite/Home时,它会获取来自 HomeController 的 Index 方法的输出。
您也可以通过更改浏览器中的 URL 来尝试此操作。在此示例中,它是https://127.0.0.1:49940/,只是端口可能不同。
如果将 /Home 或 /Home/Index 附加到 URL 并按下 Enter 键,您将看到相同的结果。
ID 末尾的问号表示此参数是可选的。换句话说,ASP.NET Core MVC 不必在这里看到某种 ID,它可能是数字、字符串或 GUID。
让我们在浏览器中运行应用程序。应用程序运行后,您将看到以下输出。
您可以看到 app.Run 中间件弹出的消息,我们收到此消息的原因是 MVC 中间件看到了该 URL。这是对网站根目录的请求,在 URL 中找不到控制器名称或操作名称。网站根目录放弃了处理该请求,并将请求传递到下一段中间件,即app.Run代码。我们指定的路由模板与默认模板非常不同。
在默认模板中,如果未找到控制器和操作名称,则会应用一些默认值。如果请求进入网站的根目录,则默认控制器名称将为 Home。您可以将其更改为您想要的任何其他控制器,并且默认操作名称可以是 Index。如果需要,您还可以更改默认操作,如下面的程序所示。
private void ConfigureRoute(IRouteBuilder routeBuilder) { //Home/Index routeBuilder.MapRoute("Default", "{controller = Home}/{action = Index}/{id?}"); }
如果请求进入网站的根目录,MVC 不会看到控制器/操作类型的 URL,但它可以使用这些默认值。
让我们保存 Startup.cs 文件并刷新浏览器到网站的根目录。
您现在将看到来自控制器的响应,您还可以转到 /home,这将调用默认操作,即 index。您还可以转到 /home/index,现在 MVC 将从 URL 中提取控制器名称和操作名称。
让我们通过添加另一个类并将其称为AboutController来创建另一个控制器。
让我们添加一些简单的操作方法,这些方法将返回字符串,如下面的程序所示。
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FirstAppDemo.Controllers { public class AboutController { public string Phone() { return "+49-333-3333333"; } public string Country() { return "Germany"; } } }
在此控制器中,您可以看到两个操作方法:Phone 和 Country,它们将分别返回电话号码和国家/地区名称。我们稍后会介绍花哨的 HTML。让我们保存此文件并在根 URL 的末尾指定 /about/phone。
您可以看到上面的屏幕截图中的电话号码。如果指定/about/country,您也将看到国家/地区的名称。
如果转到/about,它将再次通过中间件并转到您的 app.Run 中间件,您将看到以下页面。
在这里,ASP.NET Core MVC 转到 AboutController,但未找到指定的动作。因此,它将默认为 Index,并且此控制器没有 Index 方法,然后请求将转到下一段中间件。
ASP.NET Core - 属性路由
在本章中,我们将学习另一种路由方法,即基于属性的路由。使用基于属性的路由,我们可以在控制器类以及这些类内部的方法上使用 C# 属性。这些属性具有元数据,告诉 ASP.NET Core 何时调用特定控制器。
它是基于约定的路由的替代方法。
路由的评估顺序是它们出现的顺序,也就是你注册它们的顺序,但映射多个路由的情况非常常见,尤其是在你希望在 URL 中拥有不同的参数或不同的文本时。
示例
让我们来看一个简单的例子。打开**FirstAppDemo**项目并在浏览器中运行应用程序。当你指定** /about**时,它将产生以下输出:
我们这里想要的是,当我们指定** /about**时,应用程序应该调用AboutController的Phone操作。在这里,我们可以使用Route属性为该控制器强制执行一些显式的路由。此属性位于命名空间**Microsoft.AspNet.Mvc**中。
以下是**AboutController**的实现,其中添加了属性路由。
using Microsoft.AspNet.Mvc; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FirstAppDemo.Controllers { [Route("about")] public class AboutController { [Route ("")] public string Phone() { return "+49-333-3333333"; } [Route("country")] public string Country() { return "Germany"; } } }
在这里,我们希望这条路由看起来像about,对于Phone操作,我们指定了一个空字符串,这意味着我们不需要指定操作来获取此方法。用户只需要访问 /about即可。对于Country操作,我们在路由属性中指定了“country”。让我们保存AboutController,刷新浏览器并转到 /about,它应该会给你Phone操作的结果。
让我们指定** /about/country**。这将允许你访问Country操作。
如果你希望 URL 的一部分包含控制器的名称,你可以做的是,而不是显式地使用控制器名称,你可以在方括号内使用一个标记controller。这告诉ASP.NET MVC在这个位置使用此控制器的名称,如下面的程序所示。
using Microsoft.AspNet.Mvc; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FirstAppDemo.Controllers { [Route("[controller]")] public class AboutController { [Route ("")] public string Phone() { return "+49-333-3333333"; } [Route("[action]")] public string Country() { return "Germany"; } } }
这样,如果你重命名了控制器,你就不必记住更改路由。对于操作也是如此,并且在控制器和操作之间隐含有一个斜杠(/)。它就像URL内部一样,是控制器和操作之间的一种层次关系。让我们再次保存此控制器。很可能,你会看到相同的结果。
让我们指定 /about/country。这将允许你访问Country操作。
ASP.NET Core - 操作结果
在本章中,我们将讨论操作结果。在前面的章节中,我们一直在使用简单的C#类作为控制器。这些类没有从基类派生,你可以在MVC中使用这种方法,但更常见的是从Microsoft.AspNet.Mvc命名空间中提供的控制器基类派生控制器。
这个基类使我们可以访问有关请求的大量上下文信息,以及帮助我们构建结果以发送回客户端的方法。
你可以在响应中发送回简单的字符串和整数。你还可以发送回复杂的对象,例如表示学生、大学或餐厅等的对象,以及与该对象关联的所有数据。
这些结果通常封装在一个实现IActionResult接口的对象中。
有许多不同的结果类型实现了此接口——可以包含模型或要下载的文件内容的结果类型。
这些不同的结果类型允许我们向客户端发送回JSON或XML或构建HTML的视图。
操作基本上返回不同类型的操作结果。ActionResult类是所有操作结果的基类。以下是不同类型操作结果及其行为的列表。
名称 | 行为 |
---|---|
ContentResult | 返回字符串 |
FileContentResult | 返回文件内容 |
FilePathResult | 返回文件内容 |
FileStreamResult | 返回文件内容。 |
EmptyResult | 不返回任何内容 |
JavaScriptResult | 返回要执行的脚本 |
JsonResult | 返回JSON格式的数据 |
RedirectToResult | 重定向到指定的URL |
HttpUnauthorizedResult | 返回403 HTTP状态代码 |
RedirectToRouteResult | 重定向到不同的操作/不同的控制器操作 |
ViewResult | 作为视图引擎的响应接收 |
PartialViewResult | 作为视图引擎的响应接收 |
示例1
让我们通过打开**HomeController**类并从基于控制器的类派生来执行一个简单的示例。此基类位于**Microsoft.AspNet.Mvc**命名空间中。以下是HomeController类的实现。
using Microsoft.AspNet.Mvc; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FirstAppdemo.Controllers { public class HomeController : Controller { public ContentResult Index() { return Content("Hello, World! this message is from Home Controller using the Action Result"); } } }
你现在可以看到index方法返回ContentResult,它是结果类型之一,所有这些结果类型最终都实现了接口,即**ActionResult**。
在Index方法中,我们将一个字符串传递给Content方法。此Content方法产生一个ContentResult;这意味着Index方法现在将返回**ContentResult**。
让我们保存**HomeController**类并在浏览器中运行应用程序。它将生成以下页面。
你现在可以看到一个响应,它看起来与我们之前看到的响应没有区别。它仍然只是一个纯文本响应。
你可能想知道使用生成**ActionResult**的东西有什么好处。
典型的优势在于,它只是一种正式的方式来封装控制器的决策。
控制器决定接下来做什么,要么返回字符串或HTML,要么返回一个可能被序列化成JSON等的模型对象。
控制器只需要做出这个决定,并且控制器不必直接写入响应其决定的结果。
它只需要返回决定,然后是框架将获取结果并理解如何将该结果转换为可以通过HTTP发送回的内容。
示例2
让我们再举一个例子。在项目中创建一个新文件夹并将其命名为**Models**。在Models文件夹中,我们想要添加一个可以表示员工的类。
在“名称”字段中输入**Employee.cs**,如上图所示。这里,Employee类的实现包含两个属性。
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FirstAppDemo.Models { public class Employee { public int ID { get; set; } public string Name { get; set} } }
在**HomeController**的Index操作方法内部,我们想要返回一个Employee对象。以下是HomeController的实现。
using FirstAppDemo.Models; using Microsoft.AspNet.Mvc; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FirstAppdemo.Controllers { public class HomeController : Controller { public ObjectResult Index() { var employee = new Employee { ID = 1, Name = "Mark Upston"}; return new ObjectResult(employee); } } }
现在,我们将不再返回Content,而是返回另一种类型的结果,称为**ObjectResult**。如果我们想要一个ObjectResult,我们需要创建一个或实例化一个ObjectResult并将一些**model**对象传递给它。
ObjectResult在MVC框架中是特殊的,因为当我们返回一个ObjectResult时,MVC框架会查看此对象。此对象需要在HTTP响应中表示。
此对象应该被序列化成XML或JSON或其他某种格式,最终,决策将根据你在启动时提供给MVC的配置信息做出。如果你不配置任何内容,你只会得到一些默认值,默认值是JSON响应。
保存所有文件并刷新浏览器。你将看到以下输出。
ASP.NET Core - 视图
在ASP.NET Core MVC应用程序中,没有像页面这样的东西,并且当你在URL中指定路径时,它也不包含任何直接对应于页面的内容。在ASP.NET Core MVC应用程序中最接近页面的东西称为视图。
如你所知,在ASP.NET MVC应用程序中,所有传入的浏览器请求都由控制器处理,这些请求被映射到控制器操作。
控制器操作可能会返回一个视图,或者它也可能执行其他类型的操作,例如重定向到另一个控制器操作。
使用MVC框架,创建HTML最流行的方法是使用ASP.NET MVC的Razor视图引擎。
要使用此视图引擎,控制器操作会生成一个**ViewResult**对象,而ViewResult可以携带我们想要使用的Razor视图的名称。
该视图将是文件系统上的一个文件,并且ViewResult还可以将模型对象传递给视图,并且视图在创建HTML时可以使用此模型对象。
当MVC框架看到你的控制器操作生成了一个ViewResult时,框架将在文件系统上找到该视图,执行该视图,该视图生成HTML,并且正是这个HTML框架发送回客户端。
示例
现在让我们来看一个简单的例子,以了解这在我们的应用程序中是如何工作的,方法是更改HomeController Index方法的实现,如下面的程序所示。
using FirstAppDemo.Models; using Microsoft.AspNet.Mvc; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FirstAppdemo.Controllers { public class HomeController : Controller { public ViewResult Index() { var employee = new Employee { ID = 1, Name = "Mark Upston"}; return View(); } } }
在**HomeController**中,我们不再生成**ObjectResult**,而是只返回**View()**方法返回的内容。View方法不返回ObjectResult。它创建了一个新的ViewResult,所以我们也将Index方法的返回类型更改为ViewResult。View方法在这里接受一些参数。我们将在此方法中不带任何其他参数地调用它。让我们保存你的文件并刷新浏览器。
这是因为MVC框架必须出去找到该视图,但现在还没有视图。
默认情况下,在C# ASP.NET项目中的视图是具有*.cshtml扩展名的文件,并且这些视图遵循特定的约定。默认情况下,所有视图都位于项目中的Views文件夹中。
如果不对其提供任何其他信息,ASP.NET MVC将派生视图位置和视图文件名。
如果我们需要从HomeController的Index操作渲染一个视图,MVC框架首先查找该视图的位置是在Views文件夹中。
它将进入一个Home文件夹,然后查找一个名为Index.cshtml的文件——文件名以Index开头,因为我们在Index操作中。
MVC框架还将在Shared文件夹中查找,并且你放置在Shared文件夹中的视图可以在应用程序中的任何地方使用。
为了使我们的视图结果正常工作,让我们在正确的位置创建此Index.cshtml文件。因此,在我们的项目中,我们首先需要添加一个文件夹来包含我们所有的视图,并将其命名为Views。在Views文件夹中,我们将为与我们的HomeController关联的视图添加另一个文件夹,并将该文件夹命名为Home。右键单击Home文件夹并选择**添加**→**新建项**。
在左侧窗格中,选择MVC视图页面,在名称字段中输入**index.cshtml**,然后单击“添加”按钮。
让我们在index.cshtml文件中添加以下代码。
<html xmlns = "http://www.w3.org/1999/xhtml"> <head> <title>Home</title> </head> <body> <h1>Welcome!</h1> <div> This message is from the View... </div> </body> </html>
你现在可以看到一个** *.cshtml文件**。它可以包含HTML标记以及我们在此文件中包含的任何标记,这些标记将直接发送到客户端。保存此文件并刷新浏览器。
现在,Home控制器通过ViewResult已将此视图呈现给客户端,并且index.cshtml文件中包含的所有标记,都是发送给客户端的内容。
让我们回到HomeController和View方法。此View方法有几个不同的重载,并将employee模型作为参数传递。
using FirstAppDemo.Models; using Microsoft.AspNet.Mvc; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FirstAppdemo.Controllers { public class HomeController : Controller { public ViewResult Index() { var employee = new Employee { ID = 1, Name = "Mark Upston"}; return View(employee); } } }
View方法只接受一个模型对象,并且将使用默认视图,即Index。在这里,我们只想传入该模型信息并在Index.cshtml中使用该模型,如下面的程序所示。
<html xmlns = "http://www.w3.org/1999/xhtml"> <head> <title>Home</title> </head> <body> <h1>Welcome!</h1> <div> @Model.Name </div> </body> </html>
当我们在**Razor视图**中使用@符号时,Razor视图引擎将把键入的内容视为C#表达式。Razor视图包含一些我们可以访问的内置成员,这些成员可以在C#表达式中使用。其中最重要的成员是Model。当你使用@Model时,你将获得从控制器传递到视图的模型对象。因此,这里的@Model.Name将在视图中显示员工姓名。
现在让我们保存所有文件。之后,刷新浏览器以查看以下输出。
你现在可以看到员工姓名,如上图所示。
ASP.NET Core - 设置实体框架
在本节中,我们将设置并配置我们的应用程序,以便从 SQL Server 数据库保存和读取数据。
为了使用数据库,我们将使用 Entity Framework,它已重新编写以与新的 .NET Framework 协同工作。如果您以前使用过 EF,您会看到许多熟悉的部分。
在此应用程序中,我们将使用 SQL Server LocalDB。如果您不熟悉 SQL Server,您可以使用任何您喜欢的数据库,例如本地数据库、远程数据库,只要您有权在实例上创建新数据库即可。
LocalDB 是 SQL Server 的一个特殊版本,针对开发人员进行了优化。
Visual Studio 2015 甚至其 Community 版本默认情况下都会安装 LocalDB。
要检查 LocalDB,请转到 Visual Studio 中的**视图 → SQL Server 对象资源管理器**菜单选项。
如果您必须使用 SQL Server,这是一个很棒的工具,因为它允许您浏览数据库和数据,甚至在数据库内创建数据。第一次打开时,可能需要一点时间,但它应该会自动连接到 LocalDB。
安装 Entity Framework
使用 Entity Framework 的第一步是从 NuGet 包管理器安装 Entity Framework NuGet 包,或通过直接编辑**project.json**文件来安装。
现在让我们通过添加以下两个包来直接编辑 project.json 文件。
**EntityFramework.Commands**包帮助我们执行 Entity Framework 的任务,例如根据我们的 C# 实体类创建数据库模式,这些任务可从命令行工具中使用,其中逻辑位于 EntityFramework.Commands 包中。
为了使用此命令行工具,我们需要在 project.json 的 commands 部分中添加一个额外的条目,如下面的屏幕截图所示。
我们只是将其命名为“ef”,它将映射到此 EntityFramework.Commands 包。我们可以使用此“ef”来访问 EntityFramework.Commands 中的一些可用逻辑。
以下是 project.json 文件的实现。
{ "version": "1.0.0-*", "compilationOptions": { "emitEntryPoint": true }, "dependencies": { "Microsoft.AspNet.Mvc": "6.0.0-rc1-final", "Microsoft.AspNet.Diagnostics": "1.0.0-rc1-final", "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final", "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final", "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final", "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final", "EntityFramework.Commands": "7.0.0-rc1-final" } "commands": { "web": "Microsoft.AspNet.Server.Kestrel" }, "frameworks": { "dnx451": { }, "dnxcore50": { } }, "exclude": [ "wwwroot", "node_modules" ], "publishExclude": [ "**.user", "**.vspscc" ] }
ASP.NET Core - DBContext
Entity Framework 使您能够使用称为实体的公共语言运行时 (CLR) 对象查询、插入、更新和删除数据。Entity Framework 将模型中定义的实体和关系映射到数据库。它还提供以下功能:−
将从数据库返回的数据具体化为实体对象。
跟踪对对象所做的更改。
处理并发。
将对象更改传播回数据库。
将对象绑定到控件。
负责以对象形式与数据交互的主要类是 DbContext。使用上下文推荐的方法是定义一个从 DbContext 派生的类,并公开表示上下文中指定实体集合的 DbSet 属性。
在逻辑上,DBContext 映射到具有 DBContext 理解的架构的特定数据库。并且在该 DBContext 类上,您可以创建类型为 DbSet<T> 的属性。泛型类型参数 T 将是实体类型,例如 Employee 是 FirstAppDemo 应用程序中的实体。
示例
让我们举一个简单的例子,我们将创建一个 DbContext 类。在这里,我们需要在 Models 文件夹中添加一个新类,并将其命名为**FirstAppDempDbContext**。即使此类本身不是模型,它也确实将所有模型组合在一起,以便我们可以将它们与数据库一起使用。
从位于 Miscrosoft.Data.Entity 命名空间中的 DbContext 类继承您的上下文类。现在在该类上实现 Employee 的 DbSet。
每个 DbSet 将映射到数据库中的一个表。如果您有一个 Employee 的 DbSet 属性,并且该属性的名称为 Employees,则 Entity Framework 默认情况下会在数据库中查找 Employees 表。
using FirstAppDemo.Models; using Microsoft.Data.Entity; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace OdeToFood.Models { public class FirstAppDemoDbContext : DbContext { public DbSet<Employee> Employees { get; set; } } }
实现非常简单,因为我们只有一个模型要处理。我们只需要一个属性,Employee 的**DbSet**,我们可以将此属性命名为**Employees**。
现在让我们将此类直接插入控制器,然后控制器可以使用**FirstAppDemoDbContext**查询数据库。我们将通过在 HomeController 类中添加一个新类来简化所有这些操作,在该类中我们实现添加员工和获取员工的方法,如下面的程序所示。
using Microsoft.AspNet.Mvc; using FirstAppDemo.ViewModels; using FirstAppDemo.Services; using FirstAppDemo.Entities; using FirstAppDemo.Models; using System.Collections.Generic; using System.Linq; namespace FirstAppDemo.Controllers { public class HomeController : Controller { public ViewResult Index() { var model = new HomePageViewModel(); using (var context = new FirstAppDemoDbContext()) { SQLEmployeeData sqlData = new SQLEmployeeData(context); model.Employees = sqlData.GetAll(); } return View(model); } } public class SQLEmployeeData { private FirstAppDemoDbContext _context { get; set; } public SQLEmployeeData(FirstAppDemoDbContext context) { _context = context; } public void Add(Employee emp) { _context.Add(emp); _context.SaveChanges(); } public Employee Get(int ID) { return _context.Employees.FirstOrDefault(e => e.Id == ID); } public IEnumerable<Employee> GetAll() { return _context.Employees.ToList<Employee>(); } } public class HomePageViewModel { public IEnumerable<Employee> Employees { get; set; } } }
在上面的 SQLEmployeeData 类中,您可以看到我们定义了 Add 方法,该方法会将新的员工对象添加到上下文中,然后保存更改。在 Get 方法中,它将根据 ID 返回员工。而在 GetAll 方法中,它将返回数据库中所有员工的列表。
配置 Entity Framework 服务
为了拥有可用的 Entity Framework DBContext,我们需要更改应用程序的配置。我们需要添加连接字符串,以便我们的 DBContext 知道要连接到哪个服务器以及要查询哪个数据库。
我们将连接字符串放在 JSON 配置文件中。
我们还需要在 Startup 类的 ConfigureServices 方法中添加更多服务。
Entity Framework 就像 ASP.NET 和 MVC 框架一样,Entity Framework 依赖于依赖项注入,为了使注入工作,运行时需要了解 Entity Framework 使用的各种服务。
有一个简单的配置 API 将添加我们所需的所有默认服务。
让我们转到 AppSettings.json 文件并添加连接字符串,如下面的程序所示。
{ "message": "Hello, World! this message is from configuration file...", "database": { "connection": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=FirstAppDemo" } }
现在让我们转到 Startup 类,我们需要为 Entity Framework 正确工作添加一些额外的服务。具体来说,我们需要做三件事与 Entity Framework 相关:−
我们需要添加核心 Entity Framework 服务。
我们还需要添加与 SQL Server 相关的 Entity Framework 服务。
我们需要告诉 Entity Framework 我们的 DBContext。
所有这些都可以通过作为扩展方法在**IServiceCollection**上可用的一些方法来完成,如下面的程序所示。
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddEntityFramework() .AddSqlServer() .AddDbContext<FirstAppDemoDbContext> (option => option.UseSqlServer(Configuration["database:connection"])); }
第一个方法是**AddEntityFramework**。这将添加核心 Entity Framework 服务,即默认服务。
但是由于 Entity Framework 现在设计为与各种数据库(包括非关系数据库)协同工作,因此我们需要进行第二次调用以告诉 Entity Framework 添加其默认的与 SQL Server 相关的服务。
然后我们还需要告诉 Entity Framework 我的**DBContext**类,以便它可以适当地构造该类的实例,我们可以通过第三种方法**AddDbContext**方法来做到这一点。
此方法采用一个泛型类型参数,我们在这里指定 DBContext 派生类的类型,即**FirstAppDemoDbContext**。
在 AddDbContext 内部,我们需要描述 DBContext 的选项。
这可以通过**lambda 表达式**来完成;它是一个操作,我们接收一个选项参数,并且 Entity Framework 可以支持不同的数据库。我们只需要告诉 Entity Framework 此特定的 DBContext 将**UseSqlServer**即可。
此方法需要一个参数,即要使用的**connectionString**。
以下是**Startup.cs**文件的完整实现。
using Microsoft.AspNet.Mvc; using FirstAppDemo.ViewModels; using FirstAppDemo.Services; using FirstAppDemo.Entities; using FirstAppDemo.Models; using System.Collections.Generic; using System.Linq; namespace FirstAppDemo.Controllers { public class HomeController : Controller { public ViewResult Index() { var employee = new Employee { Id = 1, Name = "Mark Upston1" }; using (var context = new FirstAppDemoDbContext()) { SQLEmployeeData sqlData = new SQLEmployeeData(context); sqlData.Add(employee); } //var employee = new Employee { ID = 1, Name = "Mark Upston" }; return View(employee); } } public class SQLEmployeeData { private FirstAppDemoDbContext _context { get; set; } public SQLEmployeeData(FirstAppDemoDbContext context) { _context = context; } public void Add(Employee emp) { _context.Add(emp); _context.SaveChanges(); } public Employee Get(int ID) { return _context.Employees.FirstOrDefault(e => e.Id == ID); } public IEnumerable<Employee> GetAll() { return _context.Employees.ToList<Employee>(); } } }
现在我们需要设置数据库。设置数据库的一种方法是使用 Entity Framework 创建数据库,这是一个两步过程:−
第一步
这包括以下内容:−
将迁移代码添加到我们的项目中。
迁移代码是**C#**代码。这可以执行以在数据库中创建数据库架构。
Entity Framework 可以为我们生成此迁移代码。
Entity Framework 查看数据库和我们的模型,并确定使应用程序工作所需的架构更改。
因此,当我们添加其他模型或对现有模型(如 Employee 类)进行更改时,我们可以继续向项目添加迁移并使数据库架构保持同步。
第二步
这包括以下内容:−
在这里,我们需要显式应用这些迁移来更新数据库。
这两项任务都可以通过在控制台窗口中使用一些简单的命令来实现。
我们已经创建了 project.json。
这就是为什么我们创建了 project.json 以添加一个命令,其中“ef”映射到 EntityFramework.Commands。
让我们打开 Visual Studio 的开发者命令提示符以运行我们需要添加迁移和应用迁移的命令。最简单的方法是转到应用程序根目录。
如果您位于包含 project.json 文件的文件夹中,则您位于正确的文件夹中。在这里,我们需要执行一个名为 dnvm 的命令。这是 .NET 版本管理器,它将告诉系统我们想要使用哪个运行时。
现在让我们使用以下命令。
dnvm list
当您按下 Enter 键时,您将看到以下输出。
我们需要告诉**dnvm**我们想要使用特定的运行时。这将使我们能够访问我们想要执行的 dotnet 命令或 dnx 命令。
执行以下命令。
dnvm use1.0.0-rc1-update1 -p
按 Enter 键。
**dnvm**将设置我们的路径和环境变量以包含一个 bin 目录,这将使我们能够访问此 dnx 实用程序。让我们执行**dnx ef**命令。
这是 .NET 执行环境,使用 dnx,我们可以调用我们在 project.json 文件中列出的命令。执行这些命令通常非常简单。当您键入 dnx ef 时,您将获得一个帮助屏幕。您不必记住所有选项。您可以从 Entity Framework Commands 中查看可用的命令,其中有三个。
首先,我们需要添加迁移以执行以下命令。
dnx ef migrations add v1
按 Enter 键。
Entity Framework 将找到该上下文并查看其中的模型。它将知道没有以前的迁移,因此它将生成第一个迁移。这里,v1 是数据库的版本 1。它将在解决方案资源管理器中创建一个新文件夹并生成代码。
迁移本质上是用于生成 SQL 命令以修改 SQL 数据库中架构的 C# 代码。
using System; using System.Collections.Generic; using Microsoft.Data.Entity.Migrations; using Microsoft.Data.Entity.Metadata; namespace FirstAppDemo.Migrations { public partial class v1 : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable(name: "Employee", columns: table => new { Id = table.Column<int>(nullable: false) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), Name = table.Column<string>(nullable: true) }, constraints: table => { table.PrimaryKey("PK_Employee", x => x.Id); }); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable("Employee"); } } }
您可以看到它将创建一个名为 Employees 的表。
此表格应该有两列——一个 ID 列和一个 Name 列。
按照惯例,当 Entity Framework 看到您有一个名为 Id 的属性时,它会将该属性,或者更确切地说,将该列设为数据库中的主键。
这里,我们将使用 SQL Server。默认情况下,Entity Framework 会将其设为 IdentityColumn,这意味着 SQL Server 会为我们生成 ID。
让我们通过键入“dnx ef database update”命令将这些 ID 应用到数据库中。
您可以看到该命令已应用迁移。
现在让我们转到 SQL Server 对象资源管理器,并刷新数据库,您现在可以看到我们有一个 FirstAppDemo 数据库。
您还可以看到我们的 Employee 表,我们甚至可以查看该表中的列,其中 ID 列是主键。
让我们右键单击 dbo.Employee 表并选择“查看数据”。
在运行应用程序之前,让我们添加一些数据。当我们启动应用程序时,应该会看到数据库中的一些数据。
让我们在这里添加几行数据。
现在让我们更新 index.cshtml 文件。它以表格形式显示所有数据。
@model FirstAppDemo.Controllers.HomePageViewModel <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Home</title> </head> <body> <h1>Welcome!</h1> <table> @foreach (var employee in Model.Employees) { <tr> <td> @Html.ActionLink(employee.Id.ToString(), "Details", new { id = employee.Id }) </td> <td>@employee.Name</td> </tr> } </table> </body> </html>
运行应用程序后,它应该会产生以下输出。
ASP.NET Core - Razor 布局视图
在本章中,我们将了解 Razor 布局视图。大多数网站和 Web 应用程序都希望创建呈现一些公共元素的页面。
您通常在每个页面的顶部区域显示徽标和导航菜单。
您可能还会在侧边栏中提供其他链接和信息,以及可能在页面底部带有某些内容的页脚。
应用程序的每个页面都希望拥有这些共同因素。在这里,我们利用布局视图来避免在编写的每个页面中重复这些因素。
布局视图
现在让我们了解什么是布局视图。
布局视图是一个具有 *.cshtml 扩展名的 Razor 视图。您可以选择以您想要的方式命名布局视图。在本章中,我们将使用名为 _Layout.cshtml 的布局视图。
这是布局视图的常用名称,前导下划线不是必需的。这只是许多开发人员遵循的约定,用于识别不是视图的视图;您将其作为控制器操作的视图结果呈现。
这是一种特殊的视图,但是一旦我们有了布局视图,我们就可以设置我们的控制器视图,例如主页的 Index 视图。
我们可以设置此视图以在特定位置的布局视图内呈现。
这种布局视图方法意味着 Index.cshtml 不需要了解任何关于徽标或顶级导航的信息。
Index 视图只需要呈现控制器操作为此视图提供的模型的特定内容,而布局视图则负责处理其他所有事情。
示例
让我们举一个简单的例子。
如果您有多个视图,您会发现所有视图都包含一定数量的重复标记。它们都将具有一个开始的 HTML 标签、一个 head 标签和一个 body 标签。
即使我们在这个应用程序中没有导航菜单,但在实际应用程序中它很可能也会可用,我们不想在每个视图中重复该标记。
让我们创建一个布局视图,并将布局视图添加到 Views 文件夹内名为 Shared 的新文件夹中。这是一个 MVC 框架知道的约定文件夹。它知道此处的视图可能被应用程序中多个控制器的使用。让我们右键单击 Shared 文件夹并选择“添加”→“新建项”。
在中间窗格中,选择 MVC 视图布局页面。此处的默认名称为 _Layout.cshtml。根据用户选择要运行时使用的布局视图。现在,单击“添加”按钮。这是您为新的布局视图默认获得的内容。
我们希望布局视图负责管理 head 和 body。现在,由于此视图位于 Razor 视图中,因此我们可以使用 C# 表达式。我们仍然可以添加文本。现在我们有一个显示 DateTime.Now 的 div。现在让我们只添加 Year。
<!DOCTYPE html> <html> <head> <meta name = "viewport" content = "width = device-width" /> <title>@ViewBag.Title</title> </head> <body> <div> @DateTime.Now </div> <div> @RenderBody() </div> </body> </html>
在上面的代码中,您将看到 RenderBody 和 ViewBag.Title 等表达式。当 MVC 控制器操作呈现 Index 视图时,并且其中包含布局页面;然后 Index 视图及其生成的 HTML 将放置在 Index 视图中。
这就是对 RenderBody 进行方法调用的位置。我们可以预期我们应用程序中的所有内容视图都将出现在调用 RenderBody 的 div 内部。
此文件中的另一个表达式是 ViewBag.Title。ViewBag 是一个数据结构,可以添加到任何属性和任何您想要添加到 ViewBag 中的数据。我们可以添加 ViewBag.Title、ViewBag.CurrentDate 或我们想要的任何属性到 ViewBag 上。
现在让我们转到 index.cshtml 文件。
@model FirstAppDemo.Controllers.HomePageViewModel <html xmlns = "http://www.w3.org/1999/xhtml"> <head> <title>Home</title> </head> <body> <h1>Welcome!</h1> <table> @foreach (var employee in Model.Employees) { <tr> <td> @Html.ActionLink(employee.Id.ToString(), "Details", new { id = employee.Id }) </td> <td>@employee.Name</td> </tr> } </table> </body> </html>
删除我们不再需要在 Index 视图中使用的标记。因此,我们可以删除 HTML 标签和 head 标签之类的东西。我们也不需要开始的 body 元素或结束标签,如下面的程序所示。
@model FirstAppDemo.Controllers.HomePageViewModel @{ ViewBag.Title = "Home"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h1>Welcome!</h1> <table> @foreach (var employee in Model.Employees) { <tr> <td> @Html.ActionLink(employee.Id.ToString(), "Details", new { id = employee.Id }) </td> <td>@employee.Name</td> </tr> } </table>
我们仍然需要做两件事:
首先,我们需要告诉 MVC 框架我们想要从这个视图中使用布局视图。
其次,我们需要通过向 ViewBag 中添加一些信息来设置适当的标题,如上面的代码所示。
让我们保存所有文件并运行应用程序。运行应用程序后,您将看到以下主页。
ASP.NET Core - Razor 视图启动
在本章中,我们将讨论 Razor 视图启动。MVC 中的 Razor 视图引擎有一个约定,即它会查找任何名为 _ViewStart.cshtml 的文件并在执行单个视图内的代码之前执行此文件内的代码。
ViewStart 文件中的代码不能呈现到页面的 HTML 输出中,但可以用来从单个视图内的代码块中删除重复的代码。
在我们的示例中,如果我们希望每个视图都使用我们在上一章中创建的布局视图,我们可以将设置布局视图的代码放在 ViewStart 中,而不是将代码放在每个视图中。
示例
让我们举一个简单的例子来了解它是如何工作的。在我们的应用程序中,我们不希望每个视图都指定其布局视图为 _Layout.cshtml。因此,右键单击 Views 文件夹并选择“添加”→“新建项”。
ASP.NET MVC 中有一个用于 ViewStart 页面的特定模板,因此在中间窗格中选择 MVC 视图启动页面。这里最重要的部分是此文件名为 _ViewStart.cshtml。现在单击“添加”按钮。
ViewStart 文件的主要用途是设置布局视图。
现在让我们转到 Index.cshtml 文件并将 Layout 行剪切,然后将其添加到 ViewStart 文件中,如下面的程序所示。
@{ Layout = "~/Views/Shared/_Layout.cshtml"; }
当 MVC 框架要呈现视图时,它会查看文件夹层次结构中的某个位置是否存在 ViewStart 文件。
我们已将 _ViewStart 直接放入我们的 Views 文件夹中。这将影响 Views 文件夹内所有文件夹中的所有视图,以及 Home 文件夹和 Shared 文件夹内的视图,以及我们将来可能添加的任何其他控制器文件夹。
如果我们获取 ViewStart 并将其仅放置在 Home 文件夹中,则仅当我们呈现 Home 文件夹中这些视图之一时,这段代码才会执行。
我们甚至可以有多个 ViewStart 文件,因此我们可以在 Views 文件夹中有一个 ViewStart.cshtml,它为所有视图设置布局视图。
但是,如果我们想更改 Home 文件夹中所有视图的默认值,我们可以在 Home 文件夹中添加另一个 ViewStart,将布局设置为其他内容。
让我们保存所有文件并运行应用程序。
您会看到您的主页仍然以与之前相同的方式呈现,并且我们仍然启用了布局视图。
ASP.NET Core - Razor 视图导入
在本章中,我们将讨论 Razor 视图导入。除了 ViewStart 文件外,还有一个 ViewImports 文件,MVC 框架在呈现任何视图时都会查找该文件。
与 ViewStart 文件类似,我们可以将 ViewImports.cshtml 放入文件夹中,并且 ViewImports 文件可以影响文件夹层次结构中的所有视图
此视图是 MVC 的此版本的新增功能,在以前版本的 MVC 中,我们可以使用 XML 配置文件来配置 Razor 视图引擎的某些方面。
这些 XML 文件现在消失了,我们使用代码代替。
ViewImports 文件是我们编写代码并放置常用指令以引入视图所需的命名空间的地方。
如果我们在视图中经常使用某些命名空间,我们可以让 using 指令在我们的 ViewImports 文件中出现一次,而不是在每个视图中都使用 using 指令或键入类的完整命名空间。
示例
让我们举一个简单的例子来了解如何将我们的 using 指令移到 ViewImports 中。在 Index 视图中,我们有一个 using 指令来引入命名空间 FirstAppDemo.Controllers,如下面的程序所示。
@using FirstAppDemo.Controllers @model HomePageViewModel @{ ViewBag.Title = "Home"; } <h1>Welcome!</h1> <table> @foreach (var employee in Model.Employees) { <tr> <td> @Html.ActionLink(employee.Id.ToString(), "Details", new { id = employee.Id }) </td> <td>@employee.Name</td> </tr> } </table>
Using 指令将允许从 Razor 视图生成的代码正确编译。如果没有 using 指令,C# 编译器将无法找到此 Employee 类型。要查看 employee 类型,让我们从 Index.cshtml 文件中删除 using 指令。
@model HomePageViewModel @{ ViewBag.Title = "Home"; } <h1>Welcome!</h1> <table> @foreach (var employee in Model.Employees) { <tr> <td> @Html.ActionLink(employee.Id.ToString(), "Details", new { id = employee.Id }) </td> <td>@employee.Name</td> </tr> } </table>
现在,运行应用程序。
您将看到其中一个错误,指出找不到类型或命名空间 HomePageViewModel。这可能是因为您的几个视图需要相同的 using 指令。因此,与其将其放在每个视图中,不如在 Views 文件夹中创建一个视图导入。这将通过右键单击 Views 文件夹并选择“添加”→“新建项”来向每个视图添加 using 语句。
在中间窗格中,选择 MVC 视图导入页面。默认情况下,名称为 _ViewImports.cshtml。就像 ViewStart 一样,我们不能使用此文件来呈现 HTML,因此让我们单击“添加”按钮。
现在将 using 指令添加到此 _ViewImports.cshtml 文件中,如下所示。
@using FirstAppDemo.Controllers
现在,出现在此文件夹或任何子文件夹中的所有视图都能够使用来自 FirstAppDemo.Controllers 的类型,而无需指定该确切的 using 语句。让我们再次运行您的应用程序,您会看到该视图现在正在工作。
ASP.NET Core - Razor 标签助手
标签助手使服务器端代码能够参与在 Razor 文件中创建和呈现 HTML 元素。标签助手是一项新功能,类似于 HTML 助手,可帮助我们呈现 HTML。
有许多用于常见任务的内置标签助手,例如创建表单、链接、加载资产等。标签助手以 C# 编写,它们根据元素名称、属性名称或父标记来定位 HTML 元素。
例如,内置的 LabelTagHelper 可以定位 HTML <label> 元素,当应用 LabelTagHelper 属性时。
如果您熟悉 HTML 助手,标签助手可以减少 Razor 视图中 HTML 和 C# 之间的显式转换。
为了使用标签助手,我们需要安装一个 NuGet 库,并向使用这些标签助手的视图或视图添加 addTagHelper 指令。让我们右键单击解决方案资源管理器中的项目并选择“管理 NuGet 包…”。
搜索 **Microsoft.AspNet.Mvc.TagHelpers** 并点击安装按钮。
您将收到以下预览对话框。
点击确定按钮。
点击 **我接受** 按钮。安装 Microsoft.AspNet.Mvc.TagHelpers 后,转到 project.json 文件。
{ "version": "1.0.0-*", "compilationOptions": { "emitEntryPoint": true }, "dependencies": { "Microsoft.AspNet.Mvc": "6.0.0-rc1-final", "Microsoft.AspNet.Diagnostics": "1.0.0-rc1-final", "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final", "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final", "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final", "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final", "EntityFramework.Commands": "7.0.0-rc1-final", "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-rc1-final" }, "commands": { "web": "Microsoft.AspNet.Server.Kestrel", "ef": "EntityFramework.Commands" }, "frameworks": { "dnx451": { }, "dnxcore50": { } }, "exclude": [ "wwwroot", "node_modules" ], "publishExclude": [ "**.user", "**.vspscc" ] }
在 dependencies 部分,您将看到添加了 **"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-rc1-final"**。
现在任何人都可以编写标签助手,因此,如果您能想到需要的标签助手,就可以编写自己的标签助手。
您可以将其直接放在应用程序项目中,但需要告诉 Razor 视图引擎有关该标签助手的信息。
默认情况下,即使这些标签助手看起来像是融合到 HTML 中,它们也不会被简单地呈现到客户端。
Razor 将调用一些代码来处理标签助手;它可以从 HTML 中删除自身,也可以添加其他 HTML。
使用标签助手可以做很多很棒的事情,但是您需要将标签助手注册到 Razor,即使是 Microsoft 标签助手,以便 Razor 能够在标记中发现这些标签助手并能够调用处理标签助手的代码。
执行此操作的指令是 addTagHelper,您可以将其放在单个视图中,或者如果您计划在整个应用程序中使用标签助手,则可以使用 ViewImports 文件中的 addTagHelper,如下所示。
@using FirstAppDemo.Controllers @addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
注册程序集中所有标签助手的语法是使用星号逗号(*,)然后是程序集名称 **Microsoft.AspNet.Mvc.TagHelpers**。因为这里的第一部分是类型名称,所以如果只想使用一个标签助手,我们可以在此处列出特定的标签助手。
但是,如果您只想获取此程序集中的所有标签助手,则可以使用 **星号(*)**。标签助手库中提供了许多标签助手。让我们看一下 Index 视图。
@model HomePageViewModel @{ ViewBag.Title = "Home"; } <h1>Welcome!</h1> <table> @foreach (var employee in Model.Employees) { <tr> <td> @Html.ActionLink(employee.Id.ToString(), "Details", new { id = employee.Id }) </td> <td>@employee.Name</td> </tr> } </table>
我们已经使用 **ActionLink** 使用了一个 HTML 助手来生成一个锚标记,该标记将指向允许我们获取员工详细信息的 URL。
让我们首先在主页控制器中添加 Details 操作,如下面的程序所示。
public IActionResult Details(int id) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var model = sqlData.Get(id); if (model == null) { return RedirectToAction("Index"); } return View(model); }
现在我们需要为 Details 操作添加一个视图。让我们在 Views → Home 文件夹中创建一个新视图,并将其命名为 Details.cshtml,然后添加以下代码。
@model FirstAppDemo.Models.Employee <html xmlns = "http://www.w3.org/1999/xhtml"> <head> <title>@Model.Name</title> </head> <body> <h1>@Model.Name</h1> <div>Id: @Model.Id</div> <div> @Html.ActionLink("Home", "Index") </div> </body> </html>
现在让我们运行应用程序。
当您点击员工的 ID 时,它将带您到详细信息视图。
让我们点击第一个员工 ID。
现在要为此使用标签助手,让我们在 index.cshtml 文件中添加以下行并删除 HTML 助手。
<a asp-action = "Details" asp-rout-id = "@employee.Id" >Details</a>
**asp-action = "Details"** 是我们要访问的操作的名称。如果要传递任何参数,则可以使用 asp-route 标签助手,在这里我们希望包含 ID 作为参数,因此我们可以使用 asp-route-Id 标签助手。
以下是 index.cshtml 文件的完整实现。
@model HomePageViewModel @{ ViewBag.Title = "Home"; } <h1>Welcome!</h1> <table> @foreach (var employee in Model.Employees) { <tr> <td> <a asp-action="Details" asp-route-id="@employee.Id" >Details</a> </td> <td>@employee.Name</td> </tr> } </table>
让我们再次运行您的应用程序。运行应用程序后,您将看到以下页面。
以前,我们将 ID 显示为链接文本,但现在我们显示文本 Details。现在,我们点击详细信息,并使用标签助手而不是 HTML 助手创建正确的 URL。
无论您选择使用 **HTML 助手** 还是 **标签助手**,这实际上都取决于个人喜好。许多开发人员发现标签助手更容易编写和维护。
ASP.NET Core - Razor 编辑表单
在本章中,我们将继续讨论标签助手。我们还将在应用程序中添加一个新功能,并使其能够编辑现有员工的详细信息。我们将首先在每个员工旁边添加一个链接,该链接将转到 HomeController 上的 Edit 操作。
@model HomePageViewModel @{ ViewBag.Title = "Home"; } <h1>Welcome!</h1> <table> @foreach (var employee in Model.Employees) { <tr> <td>@employee.Name</td> <td> <a asp-controller = "Home" asp-action = "Details" asp-routeid = "@employee.Id">Details</a> <a asp-controller = "Home" asp-action = "Edit" asp-routeid = "@employee.Id">Edit</a> </td> </tr> } </table>
我们还没有 Edit 操作,但我们需要一个可以编辑的员工 ID。因此,让我们首先通过右键单击 **Views →Home** 文件夹并选择 **添加 → 新建项** 来创建一个新视图。
在中间窗格中,选择 MVC 视图页面;将页面命名为 Edit.cshtml。现在,点击添加按钮。
在 **Edit.cshtml** 文件中添加以下代码。
@model Employee @{ ViewBag.Title = $"Edit {Model.Name}"; } <h1>Edit @Model.Name</h1> <form asp-action="Edit" method="post"> <div> <label asp-for = "Name"></label> <input asp-for = "Name" /> <span asp-validation-for = "Name"></span> </div> <div> <input type = "submit" value = "Save" /> </div> </form>
对于此页面的标题,我们可以说我们想要编辑,然后提供员工姓名。
**Edit** 前面的美元符号将允许运行时用该属性中的值(如员工姓名)替换 Model.Name。
在表单标记内,我们可以使用诸如 asp-action 和 asp-controller 之类的标签助手。这样,当用户提交此表单时,它将直接转到特定的控制器操作。
在这种情况下,我们希望转到同一控制器的 Edit 操作,并且我们希望明确说明此表单上的方法应该使用 HttpPost。
表单的默认方法是 GET,我们不希望使用 GET 操作来编辑员工。
在 label 标记中,我们使用了 asp-for 标签助手,它表示这是模型的 Name 属性的标签。此标签助手可以设置 Html.For 属性以具有正确的值并设置此标签的内部文本,以便它实际显示我们想要的内容,例如员工姓名。
让我们转到 HomeController 类并添加 **Edit** 操作,该操作返回一个视图,该视图为用户提供一个表单来编辑员工,然后我们需要第二个 Edit 操作来响应 HttpPost,如下所示。
[HttpGet] public IActionResult Edit(int id) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var model = sqlData.Get(id); if (model == null) { return RedirectToAction("Index"); } return View(model); }
首先,我们需要一个将响应 GET 请求的 edit 操作。它将采用员工 ID。此处的代码将类似于我们 Details 操作中的代码。我们将首先提取用户想要编辑的员工的数据。我们还需要确保员工确实存在。如果不存在,我们将把用户重定向回 Index 视图。但是,当员工存在时,我们将呈现 Edit 视图。
我们还需要响应表单将发送的 HttpPost。
让我们在 HomeController.cs 文件中添加一个新类,如下面的程序所示。
public class EmployeeEditViewModel { [Required, MaxLength(80)] public string Name { get; set; } }
在将响应 HttpPost 的 Edit 操作中,将采用 EmployeeEditViewModel,而不是员工本身,因为我们只想捕获 Edit.cshtml 文件中表单中的项目。
以下是 Edit 操作的实现。
[HttpPost] public IActionResult Edit(int id, EmployeeEditViewModel input) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var employee = sqlData.Get(id); if (employee != null && ModelState.IsValid) { employee.Name = input.Name; context.SaveChanges(); return RedirectToAction("Details", new { id = employee.Id }); } return View(employee); }
根据我们的路由规则,编辑表单应始终从 URL 中包含 ID 的 URL 传递,例如 **/home/edit/1**。
表单将始终发布回同一个 URL,/home/edit/1。
MVC 框架将能够从 URL 中提取该 ID 并将其作为参数传递。
我们始终需要检查 ModelState 是否有效,并确保此员工在数据库中并且在执行数据库更新操作之前不为空。
如果这些都不成立,我们将返回一个视图并允许用户重试。尽管在具有并发用户的实际应用程序中,如果员工为空,则可能是因为员工详细信息被其他人删除了。
如果该员工不存在,则告诉用户该员工不存在。
否则,检查 ModelState。如果 ModelState 无效,则返回一个视图。这允许修复编辑并使 ModelState 有效。
将名称从 Input 视图模型复制到从数据库检索的员工,并保存更改。SaveChagnes() 方法将把所有这些更改刷新到数据库。
以下是 HomeController 的完整实现。
using Microsoft.AspNet.Mvc; using FirstAppDemo.ViewModels; using FirstAppDemo.Services; using FirstAppDemo.Entities; using FirstAppDemo.Models; using System.Collections.Generic; using System.Linq; using System.ComponentModel.DataAnnotations; namespace FirstAppDemo.Controllers { public class HomeController : Controller { public ViewResult Index() { var model = new HomePageViewModel(); using (var context = new FirstAppDemoDbContext()) { SQLEmployeeData sqlData = new SQLEmployeeData(context); model.Employees = sqlData.GetAll(); } return View(model); } public IActionResult Details(int id) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var model = sqlData.Get(id) if (model == null) { return RedirectToAction("Index"); } return View(model); } [HttpGet] public IActionResult Edit(int id) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var model = sqlData.Get(id); if (model == null) { return RedirectToAction("Index"); } return View(model); } [HttpPost] public IActionResult Edit(int id, EmployeeEditViewModel input) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var employee = sqlData.Get(id); if (employee != null && ModelState.IsValid) { employee.Name = input.Name; context.SaveChanges(); return RedirectToAction("Details", new { id = employee.Id }); } return View(employee); } } public class SQLEmployeeData { private FirstAppDemoDbContext _context { get; set; } public SQLEmployeeData(FirstAppDemoDbContext context) { _context = context; } public void Add(Employee emp) { _context.Add(emp); _context.SaveChanges(); } public Employee Get(int ID) { return _context.Employees.FirstOrDefault(e => e.Id == ID); } public IEnumerable<Employee> GetAll() { return _context.Employees.ToList<Employee>(); } } public class HomePageViewModel { public IEnumerable<Employee> Employees { get; set; } } public class EmployeeEditViewModel { [Required, MaxLength(80)] public string Name { get; set; } } }
让我们编译程序并运行应用程序。
我们现在有一个 Edit 链接可用;让我们通过点击 Edit 链接来编辑 Josh 的详细信息。
让我们将名称更改为 Josh Groban。
点击保存按钮。
您可以看到名称已更改为 Josh Groban,如上面的屏幕截图所示。现在让我们点击主页链接。
在主页上,您现在将看到更新后的名称。
ASP.NET Core - 身份概述
在本章中,我们将简要讨论 ASP.NET Core Identity 框架。ASP.NET Core Identity 框架用于实现表单身份验证。有许多选项可用于识别您的用户,包括 Windows 身份验证和所有第三方身份提供商,例如 Google、Microsoft、Facebook 和 GitHub 等。
Identity 框架是我们将添加到 project.js 文件中应用程序中的另一个依赖项。
此框架允许我们添加用户可以使用本地密码注册和登录的功能。
该框架还支持双因素身份验证、第三方身份提供商和其他功能。
我们将重点关注用户可以注册、登录和注销的场景。
为此,我们需要创建一个 User 实体,此类将继承自 Identity 框架中的基类,并且基类为我们提供了标准的用户属性,例如用户名和电子邮件地址。
我们可以在此类中包含任意数量的其他属性来存储有关我们用户的信息。
我们需要获取此 User 类并将其插入到 Identity 框架提供的 UserStore 类中。
UserStore 是我们的代码将用于创建用户和验证用户密码的类。
最终,UserStore 将与数据库进行通信。Identity 框架支持 Entity Framework 和所有可以与 Entity Framework 一起使用的数据库。
但是您可以实现自己的 UserStore 来与任何数据源一起使用。
为了与 Entity Framework 正确协作,我们的 User 类也将插入到 IdentityDb 类中。
这是一个使用 Entity Framework DBContext 执行实际数据库工作的类。
我们需要通过使现有的 DataContext 类继承自 IdentityDb 而不是 Entity Framework 的 DBContext 来将此 IdentityDb 包含到我们的应用程序中。
正是 IdentityDb 和 UserStore 协同工作来存储用户信息和验证用户密码(数据库中的哈希密码)。
我们需要了解 ASP.NET Core Identity 框架的两个部分
SignInManager
这是 Identity 框架的两个部分之一 -
顾名思义,**SignInManager** 可以在我们验证密码后登录用户。
我们还可以使用此管理器注销用户。
对于表单身份验证,登录和注销是通过管理 cookie 来完成的。
当我们告诉 SignInManager 登录用户时,管理器会向用户的浏览器发出一个 cookie,并且浏览器将在每个后续请求中发送此 cookie。它有助于我们识别该用户。
Identity 中间件
这是框架的第二个部分 -
读取 SignInManager 发送的 Cookie 并识别用户,这发生在框架的最后部分,即 Identity 中间件。
我们需要将此中间件配置到我们的应用程序管道中以处理 SignInManager 设置的 Cookie。我们还将在接下来的几章中看到此中间件的一些其他功能。
ASP.NET Core - 授权属性
在本章中,我们将讨论 Authorize 属性。到目前为止,在我们的应用程序中,我们允许匿名用户执行任何操作。他们可以编辑员工详细信息和查看详细信息,但我们没有创建新员工的功能。让我们首先添加创建功能,然后我们将使用 Authorize 属性限制用户访问。
我们需要首先在 **Views → Home** 文件夹内创建一个新的 MVC 视图页面,并将其命名为 Create.cshtml,然后添加以下代码。
@model Employee @{ ViewBag.Title = "Create"; } <h1>Create</h1> @using (Html.BeginForm()) { <div> @Html.LabelFor(m => m.Name) @Html.EditorFor(m => m.Name) @Html.ValidationMessageFor(m => m.Name) </div> <div> <input type = "submit" value = "Save" /> </div> }
现在,我们将为 POST 和 GET 在 HomeController 中添加 **操作方法**,如下面的程序所示。
[HttpGet] public ViewResult Create() { return View(); } [HttpPost] public IActionResult Create(EmployeeEditViewModel model) { if (ModelState.IsValid) { var employee = new Employee(); employee.Name = model.Name; var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); sqlData.Add(employee); return RedirectToAction("Details", new { id = employee.Id }); } return View(); }
让我们在 Index.cshtml 文件中添加一个指向 **Create 视图** 的链接,如下面的程序所示。
@model HomePageViewModel @{ ViewBag.Title = "Home"; } <h1>Welcome!</h1> <table> @foreach (var employee in Model.Employees) { <tr> <td>@employee.Name <td> <a asp-controller = "Home" asp-action = "Details" asp-routeid = "@employee.Id">Details</a> <a asp-controller = "Home" asp-action = "Edit" asp-routeid = "@employee.Id">Edit</a> </td> </tr> } </table> <div> <a asp-action = "Create">Create</a> </div>
运行应用程序;您将看到以下页面。
在主页上,您将看到“创建”链接。当您单击“创建”链接时,它将带您到“创建”视图。
在“姓名”字段中输入姓名,然后单击“保存”按钮。
您现在将看到新添加的员工的 **详细信息视图**。让我们单击“主页”链接。
在此应用程序中,每个用户都可以创建、编辑员工,并且每个人都可以查看详细信息视图。我们希望更改此行为,以便将来,匿名用户只能查看主页上的员工列表,但其他所有操作都需要用户识别自己并登录。我们可以使用 **Authorize 属性** 来实现这一点。
您可以将 Authorize 属性放置在控制器上或控制器内的单个操作上。
[Authorize] public class HomeController : Controller { //.... }
当我们将 Authorize 属性放置在控制器本身时,授权属性将应用于其中的所有操作。
除非用户通过授权检查,否则 MVC 框架将不允许请求到达受此属性保护的操作。
默认情况下,如果您不使用任何其他参数,Authorize 属性将执行的唯一检查是检查以确保用户已登录,以便我们知道他们的身份。
但是您可以使用参数来指定您喜欢的任何花哨的自定义授权策略。
还有一个 **AllowAnonymous** 属性。当您想在控制器上使用 Authorize 属性来保护其中的所有操作,但又有一个或两个操作您希望取消保护并允许匿名用户访问该特定操作时,此属性很有用。
[AllowAnonymous] public ViewResult Index() { var model = new HomePageViewModel(); using (var context = new FirstAppDemoDbContext()) { SQLEmployeeData sqlData = new SQLEmployeeData(context); model.Employees = sqlData.GetAll(); } return View(model); }
让我们在我们的应用程序中尝试这些属性。在运行的应用程序中,匿名用户可以编辑员工。
我们希望更改此设置,并强制用户在编辑员工之前登录并识别自己。现在让我们进入 HomeController。我们将在此处限制对一两个操作的访问。我们始终可以将 Authorize 属性放置在我们想要保护的那些特定操作上。我们也可以将 Authorize 属性放置在控制器本身,此 Authorize 属性位于 Microsoft.AspNet.Authorization 命名空间中。
现在,我们将使用 Authorize 属性并强制用户识别自己才能进入此控制器,但主页除外,如下面的程序所示。
[Authorize] public class HomeController : Controller { [AllowAnonymous] public ViewResult Index() { var model = new HomePageViewModel(); using (var context = new FirstAppDemoDbContext()) { SQLEmployeeData sqlData = new SQLEmployeeData(context); model.Employees = sqlData.GetAll(); } return View(model); } public IActionResult Details(int id) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var model = sqlData.Get(id); if (model == null) { return RedirectToAction("Index"); } return View(model); } [HttpGet] public IActionResult Edit(int id) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var model = sqlData.Get(id); if (model == null) { return RedirectToAction("Index"); } return View(model); } [HttpPost] public IActionResult Edit(int id, EmployeeEditViewModel input) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var employee = sqlData.Get(id); if (employee != null && ModelState.IsValid) { employee.Name = input.Name; context.SaveChanges(); return RedirectToAction("Details", new { id = employee.Id }); } return View(employee); } [HttpGet] public ViewResult Create() { return View(); } [HttpPost] public IActionResult Create(EmployeeEditViewModel model) { if (ModelState.IsValid) { var employee = new Employee(); employee.Name = model.Name; var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); sqlData.Add(employee); return RedirectToAction("Details", new { id = employee.Id }); } return View(); } }
显示员工列表的主页或 **Index.cshtml** 文件具有 **AllowAnonymous 属性**。现在让我们运行您的应用程序。
按 F12 键,这将打开 **开发者工具**。现在,转到 **网络** 选项卡。
在开发者工具中,我们想观察一些内容,以便我们可以了解事情是如何运作的。当您单击“编辑”链接时,您将看到一个空白页面。
如果您查看开发者工具,您会看到服务器返回的 HTTP 状态代码是 **401 状态代码**。
401 状态代码告诉浏览器该请求未被允许通过,因为它缺少有效的身份验证凭据。这告诉我们 Authorize 属性正在工作。
同样,当您单击主页上的“创建”链接时,您将看到与以下屏幕截图相同的错误。
这里,不好的地方在于用户被留在一个空白页面上,除非他们打开了开发者工具,否则他们可能不知道这是一个身份验证问题。
这就是 Identity 框架可以介入并提供帮助的地方。
Identity 框架可以检测到应用程序的某个部分何时想要返回 401 状态代码,因为用户不允许到达那里,并且 Identity 框架可以将其转换为登录页面,并允许用户解决此问题。
一旦我们安装并配置了 Identity 框架,我们将了解其工作原理。
但现在,我们可以看到 **Authorize 属性** 正在工作。
ASP.NET Core - Identity 配置
在本章中,我们将安装和配置 Identity 框架,这只需要少量的工作。如果您转到 Visual Studio 并创建一个新的 ASP.NET Core 应用程序,并且您选择带有身份验证设置为单个用户帐户的完整 Web 应用程序模板,则该新项目将包含为您设置的所有 Identity 框架位。
我们从一个空项目开始。我们现在将从头开始设置 Identity 框架,这是一种了解完整应用程序模板中所有部分的好方法,因为如果您没有详细地遍历所有代码,它可能会令人困惑。
首先,我们需要安装依赖项,即 **Microsoft.AspNet.Identity**。我们将继续安装 **Microsoft.AspNet.Identity.EntityFramework**,然后实现与 Entity Framework 协同工作的 Identity 框架。
如果我们依赖 Identity.EntityFramework,则该包包含 Identity 包。
如果您构建自己的数据存储,则可以使用 Identity 包。
安装完依赖项后,我们可以创建一个客户 User 类,其中包含我们想要存储的有关用户的所有信息。
对于此应用程序,我们将从 Identity 框架提供的类继承,该类将为我们提供所有基本要素,例如 Username 属性和存储哈希密码的位置。
我们还需要修改我们的 **FirstAppDemoDbContext** 类以从 Identity 框架的 **IdentityDb** 类继承。
IdentityDb 为我们提供了使用 Entity Framework 存储用户信息所需的一切。一旦我们有了 User 类和 **DBContext**,我们就需要使用 Startup 类的 **ConfigureServices** 方法将 Identity 服务配置到应用程序中。
就像我们需要添加服务来支持 MVC 框架一样,Identity 框架也需要向应用程序添加服务才能工作。
这些服务包括 **UserStore** 服务和 **SignInManager** 等服务。
我们将把这些服务注入到我们的控制器中,以便在适当的时间创建用户和发出 Cookie。
最后,在启动的 Configure 方法期间,我们需要添加 Identity 中间件。
此中间件不仅有助于将 Cookie 转换为用户身份,而且还可以确保用户不会看到带有 401 响应的空白页面。
现在让我们按照以下步骤操作。
**步骤 1** - 我们需要继续添加对 Identity 框架的依赖项。让我们将 Microsoft.AspNet.Identity.EntityFramework 依赖项添加到 project.json 文件中。这将包含我们需要的其他所有必要的 Identity 包。
{ "version": "1.0.0-*", "compilationOptions": { "emitEntryPoint": true }, "dependencies": { "Microsoft.AspNet.Mvc": "6.0.0-rc1-final", "Microsoft.AspNet.Diagnostics": "1.0.0-rc1-final", "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final", "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final", "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final", "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final", "EntityFramework.Commands": "7.0.0-rc1-final", "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-rc1-final", "Microsoft.AspNet.Identity.EntityFramework": "3.0.0-rc1-final" }, "commands": { "web": "Microsoft.AspNet.Server.Kestrel", "ef": "EntityFramework.Commands" }, "frameworks": { "dnx451": { }, "dnxcore50": { } }, "exclude": [ "wwwroot", "node_modules" ], "publishExclude": [ "**.user", "**.vspscc" ] }
**步骤 2** - 保存此文件。Visual Studio 会还原包,现在我们可以添加我们的 User 类。让我们通过右键单击 Models 文件夹并选择添加 → 类来添加 User 类。
将此类命名为 User 并单击“添加”按钮,如上图所示。在此类中,您可以添加属性以保存您想要存储的有关用户的任何信息。
**步骤 3** - 让我们从 Identity 框架提供的类派生 User 类。它是 IdentityUser 类,位于 Identity.EntityFramework 命名空间中。
using Microsoft.AspNet.Identity.EntityFramework; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FirstAppDemo.Models { public class User : IdentityUser { } }
**步骤 4**- 现在让我们转到 IdentityUser,将光标放在该符号上,然后按 F12 以查看 Visual Studio 的元数据视图。
#region Assembly Microsoft.AspNet.Identity.EntityFramework, Version = 3.0.0.0, namespace Microsoft.AspNet.Identity.EntityFramework { public class IdentityUser : IdentityUser<string> { public IdentityUser(); public IdentityUser(string userName); } }
**步骤 5** - 您可以看到 IdentityUser 是从字符串的 IdentityUser 派生的。您可以通过从 IdentityUser 派生并指定我们的泛型类型参数来更改主键的类型。您还可以使用主键存储内容,该主键理想情况下是一个整数值。
**步骤 6** - 现在让我们将光标放在字符串的 IdentityUser 上,然后再次按 F12 以转到元数据视图。
您现在可以看到默认情况下与用户相关的所有信息。信息包括以下内容 -
我们不会在此应用程序中使用的字段,但可供使用。
Identity 框架可以跟踪特定用户的失败登录尝试次数,并在一段时间内锁定该帐户。
存储 PasswordHash、PhoneNumber 的字段。我们将使用的两个重要字段是 PasswordHash 和 UserName。
我们还将隐式使用用户的 PrimaryKey 和 ID 属性。如果您需要查询特定用户,也可以使用该属性。
**步骤 7** - 现在,我们需要确保 User 包含在我们的 DBContext 中。因此,让我们打开应用程序中存在的 **FirstAppDemoDBContext**,而不是直接从 DBContext 派生,DBContext 是内置的 Entity Framework 基类,我们现在需要从 IdentityDbContext 派生。
using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.Data.Entity; namespace FirstAppDemo.Models { public class FirstAppDemoDbContext : IdentityDbContext<User> { public DbSet<Employee> Employees { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("Data Source = (localdb)\\MSSQLLocalDB; Initial Catalog = FirstAppDemo;Integrated Security = True; Connect Timeout = 30;Encrypt = False;TrustServerCertificate = True; ApplicationIntent = ReadWrite;MultiSubnetFailover = False"); } } }
**步骤 8** - IdentityDbContext 类也位于 Microsoft.AspNet.Identity.EntityFramework 命名空间中,我们可以指定它应该存储的用户类型。这样,我们添加到 User 类的任何其他字段都会进入数据库。
IdentityDbContext 除了用于存储用户信息外,还引入了额外的 DbSet,用于存储用户角色和用户声明信息。
现在,我们的 User 类已准备就绪。FirstAppDemoDbContext 类已配置为与 Identity 框架配合使用。
现在,我们可以进入 Configure 和 ConfigureServices 来设置 Identity 框架。
步骤 9 − 现在让我们从 ConfigureServices 开始。除了 MVC 服务和 Entity Framework 服务之外,我们还需要添加 Identity 服务。这将添加 Identity 框架依赖的所有服务,以使其正常工作。
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddEntityFramework() .AddSqlServer() .AddDbContext<FirstAppDemoDbContext> (option => option.UseSqlServer(Configuration["database:connection"])); services.AddIdentity<User, IdentityRole>() .AddEntityFrameworkStores<FirstAppDemoDbContext>(); }
AddIdentity 方法接受两个泛型类型参数——用户实体类型和角色实体类型。
这两个泛型类型参数是我们用户的类型——我们刚刚创建的 User 类和我们想要使用的 Role 类。现在,我们将使用内置的 IdentityRole。此类位于 EntityFramework 命名空间中。
当我们使用 Entity Framework 与 Identity 时,还需要调用第二个方法——AddEntityFrameworkStores。
AddEntityFrameworkStores 方法将配置诸如 UserStore 之类的服务,该服务用于创建用户并验证其密码。
步骤 10 − 以下两行是我们配置应用程序服务所需的所有内容。
services.AddIdentity<User, IdentityRole>() .AddEntityFrameworkStores<FirstAppDemoDbContext>();
步骤 11 − 我们还需要添加中间件。插入中间件的位置很重要,因为如果我们过晚地插入中间件,它将永远没有机会处理请求。
如果我们在 MVC 控制器内部需要授权检查,则需要在 MVC 框架之前插入 Identity 中间件,以确保 cookie 和 401 错误能够成功处理。
public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseDeveloperExceptionPage(); app.UseRuntimeInfoPage(); app.UseFileServer(); app.UseIdentity(); app.UseMvc(ConfigureRoute); app.Run(async (context) => { var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); }
步骤 12 − 我们插入中间件的位置就是添加 Identity 中间件的位置。以下是 Startup.cs 文件的完整实现。
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using FirstAppDemo.Services; using Microsoft.AspNet.Routing; using System; using FirstAppDemo.Entities; using Microsoft.Data.Entity; using FirstAppDemo.Models; using Microsoft.AspNet.Identity.EntityFramework; namespace FirstAppDemo { public class Startup { public Startup() { var builder = new ConfigurationBuilder() .AddJsonFile("AppSettings.json"); Configuration = builder.Build(); } public IConfiguration Configuration { get; set; } // This method gets called by the runtime. // Use this method to add services to the container. // For more information on how to configure your application, // visit http://go.microsoft.com/fwlink/?LinkID = 398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddEntityFramework() .AddSqlServer() .AddDbContext<FirstAppDemoDbContext>(option => option.UseSqlServer(Configuration["database:connection"])); services.AddIdentity<User, IdentityRole>() .AddEntityFrameworkStores<FirstAppDemoDbContext>(); } // This method gets called by the runtime. // Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseDeveloperExceptionPage(); app.UseRuntimeInfoPage(); app.UseFileServer(); app.UseIdentity(); app.UseMvc(ConfigureRoute); app.Run(async (context) => { var msg = Configuration["message"]; await context.Response.WriteAsync(msg); }); } private void ConfigureRoute(IRouteBuilder routeBuilder) { //Home/Index routeBuilder.MapRoute("Default", "{controller=Home}/{action=Index}/{id?}"); } // Entry point for the application. public static void Main(string[] args) => WebApplication.Run<Startup>(args); } }
步骤 13 − 现在让我们继续构建应用程序。在下一章中,我们需要添加另一个 Entity Framework 迁移,以确保我们在 SQL Server 数据库中拥有 Identity 架构。
ASP.NET Core - 身份迁移
在本章中,我们将讨论 Identity 迁移。在 ASP.NET Core MVC 中,身份验证和身份功能在 Startup.cs 文件中配置。
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddEntityFramework() .AddSqlServer() .AddDbContext<FirstAppDemoDbContext>option. UseSqlServer(Configuration["database:connection"])); services.AddIdentity<User, IdentityRole>() .AddEntityFrameworkStores<FirstAppDemoDbContext>(); }
每当您对其中一个实体类进行更改或对派生自 DBContext 的类进行更改时,您可能都需要创建一个新的迁移脚本以应用于数据库并将架构与代码中的内容同步。
在我们的应用程序中就是这样,因为我们现在将 FirstAppDemoDbContext 类派生自 IdentityDbContext 类,它包含自己的 DbSet,并且还将创建一个架构来存储其管理的所有实体的信息。
using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.Data.Entity; namespace FirstAppDemo.Models { public class FirstAppDemoDbContext : IdentityDbContext<User> { public DbSet<Employee> Employees { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("Data Source = (localdb)\\MSSQLLocalDB; Initial Catalog = FirstAppDemo;Integrated Security = True; Connect Timeout = 30;Encrypt = False; TrustServerCertificate = True;ApplicationIntent = ReadWrite; MultiSubnetFailover = False"); } } }
现在,让我们打开命令提示符并确保我们位于项目的 project.json 文件所在的目录中。
我们还可以通过键入 dnx ef 来获取 Entity Framework 命令。
我们的 project.json 文件有一个部分将此“ef”关键字映射到 EntityFramework.Commands。
"commands": { "web": "Microsoft.AspNet.Server.Kestrel", "ef": "EntityFramework.Commands" }
我们可以从此处添加迁移。我们还需要为迁移提供一个名称。让我们使用 v2 表示版本 2 并按 Enter 键。
迁移完成后,您将在 migrations 文件夹中有一个 v2 文件。
现在,我们希望通过运行 “dnx ef database update” 命令将该迁移应用于我们的数据库。
Entity Framework 将看到需要应用的迁移,并将执行该迁移。
如果您进入 SQL Server 对象资源管理器,您将看到我们之前创建的 Employee 表。您还将看到一些其他表,这些表用于存储用户、声明、角色以及一些将用户映射到特定角色的映射表。
所有这些表都与 Identity 框架提供的实体相关。
让我们快速浏览一下 users 表。
您现在可以看到 AspNetUsers 表中的列包括存储我们在继承的 Identity User 上看到的那些属性的所有列,以及它的字段,如 UserName 和 PasswordHash。因此,您一直在使用一些内置的 Identity 服务,因为它们还包含创建用户和验证用户密码的功能。
ASP.NET Core - 用户注册
在本章中,我们将讨论用户注册。我们现在拥有一个可用的数据库,现在是时候开始向应用程序添加一些功能了。我们还配置了我们的应用程序,并且拥有一个可用的数据库架构。现在让我们转到应用程序的主页。
按 F12 打开开发者工具,然后单击“编辑”链接。以前,当我们单击“编辑”链接时,MVC 框架检测到 Authorize 属性的存在并返回 401 状态代码,因为用户未登录。
您现在将看到我们在屏幕上收到来自配置文件的消息。
现在让我们转到开发者工具。
您将看到浏览器请求了编辑页面,MVC 框架确定用户无权查看此资源。
因此,在 MVC 框架内部的某个地方生成了 401 状态代码。
我们现在已经到位了 Identity 中间件。Identity 中间件查看将发送给用户的 401 状态代码,并将其替换为 302 状态代码,这是一个重定向状态代码。
Identity 框架知道用户必须先尝试登录才能访问此资源。
Identity 框架将我们重定向到此 URL,如我们在地址栏中看到的——/Account/Login。
这是一个可配置的端点,位于 Identity 框架中,在您注册这些服务和中间件时位于 Startup 中。您可以设置不同的选项,其中一个选项是更改登录 URL。
默认情况下,URL 将为 /Account/Login。目前,我们还没有帐户控制器,因此最终我们要做的是创建一个帐户控制器并允许用户登录。
但在用户甚至登录之前,他们需要在网站上注册并保存其用户名和密码。
登录和注册功能都可以是帐户控制器的部分。
现在让我们继续在 Controllers 文件夹中添加一个新类,并将其命名为 AccountController。我们将从 MVC 框架的基类 Controller 类派生此类。
using Microsoft.AspNet.Mvc; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; namespace FirstAppDemo.Controllers { public class AccountController : Controller { } }
现在,我们需要设置一个功能,以便用户可以注册此网站。
它将非常类似于编辑表单。
当用户想要注册时,我们首先会显示一个表单,允许他们填写所需的信息。然后,他们可以将此表单上传到网站。
然后将此信息保存在数据库中。
现在让我们创建当我们转到 /account/register 时将返回视图的操作。
public class AccountController : Controller { [HttpGet] public ViewResult Register() { return View(); } }
我们不需要查找任何内容,用户将提供我们所需的所有信息。在我们构建该视图的 ViewModel 之前,我们需要确定视图将显示哪些信息。我们还需要确定我们需要从用户那里接收哪些信息?
让我们通过在 AccountController.cs 文件中添加一个新类并将其命名为 RegisterViewModel 来为此场景创建一个视图模型。
让我们创建一些属性来保存用户名、密码以及用户确认密码(两次输入并确保两个密码匹配),如下面的程序所示。
public class RegisterViewModel { [Required, MaxLength(256)] public string Username { get; set; } [Required, DataType(DataType.Password)] public string Password { get; set; } [DataType(DataType.Password), Compare(nameof(Password))] public string ConfirmPassword { get; set; } }
在上面的类中,您可以看到一些可以帮助我们验证此模型的注释。此处的用户名是必需的,如果您查看数据库架构,则保存用户名的列长度为 256 个字符。
我们还将在此处应用 MaxLength 属性。
密码将是必需的,当我们为该密码呈现输入时,我们希望输入类型为 Type Password,以便字符不会显示在屏幕上。
确认密码也将是 DataType Password,然后还有一个额外的 Compare 属性。我们将使用我们可以指定的另一个属性(即 Password 字段)来比较 ConfirmPassword 字段。
现在让我们创建所需的视图。我们需要向 views 添加一个新文件夹并将其命名为 Account,以便与 AccountController 相关的所有视图都将添加到此文件夹中。
现在,右键单击 Account 文件夹并选择添加→新建项。
在中间窗格中,选择 MVC 视图页面并将其命名为 Register.cshtml,然后单击“添加”按钮。
从 Register.cshtml 文件中删除所有现有代码并添加以下代码。
@model RegisterViewModel @{ ViewBag.Title = "Register"; } <h1>Register</h1> <form method = "post" asp-controller = "Account" asp-action = "Register"> <div asp-validation-summary = "ValidationSummary.ModelOnly"></div> <div> <label asp-for = "Username"></label> <input asp-for = "Username" /> <span asp-validation-for = "Username"></span> </div> <div> <label asp-for = "Password"></label> <input asp-for = "Password" /> <span asp-validation-for = "Password"></span> </div> <div> <label asp-for = "ConfirmPassword"></label> <input asp-for = "ConfirmPassword" /> <span asp-validation-for = "ConfirmPassword"></span> </div> <div> <input type = "submit" value = "Register" /> </div> </form>
您现在可以看到我们已将模型指定为我们刚刚创建的 RegisterViewModel。
我们还将使用 ViewBag 设置此页面的标题,并且希望标题为 Register。
我们还需要创建一个包含用户名、密码和确认密码字段的表单。
我们还包括一个将显示验证摘要的 div。当我们使用 ASP 验证摘要时,我们需要指定哪些错误将显示在摘要中。
我们可以让所有错误都显示在摘要区域中,或者我们可以说 ValidationSummary.ModelOnly,并且在摘要中仅显示来自模型验证的错误将是与模型关联的验证错误,而不是该模型的特定属性。
换句话说,如果用户未填写其用户名,但用户名是必需的,则该特定属性将存在验证错误。
但您还可以生成与特定属性无关的模型错误,它们将显示在此 ValidationSummary 中。
在 <form> 标记内,我们有针对 ViewModel 中所有不同字段的标签和输入。
我们需要用户名标签、用户名输入以及用户名验证消息。
我们需要用户输入的其他两个属性相同,并且将有一个标签和输入以及一个用于密码的 span 以及一个标签和一个输入以及一个用于确认密码的 span。
我们不需要为 Password 和 ConfirmPassword 指定输入类型,因为 asp for 标记助手将确保为我们将其输入类型设置为密码。
最后,我们需要有一个显示 Register 的按钮。当用户单击此按钮时,我们将把表单提交回控制器。
在 AccountController 中,我们还需要实现一个 HttpPost Register 操作方法。让我们返回到 AccountController 并添加以下 Register 操作,如下所示:
[HttpPost] public IActionResult Register (RegisterViewModel model) { }
此操作方法将返回 IActionResult。这将接收 RegisterViewModel。现在,我们需要与 Identity 框架交互以确保用户有效,告诉 Identity 框架创建该用户,然后由于他们刚刚创建了帐户,因此继续登录他们。我们将在下一章中介绍如何实现所有这些步骤。
ASP.NET Core - 创建用户
在本章中,我们将讨论如何创建用户。要继续执行此操作,我们需要与 Identity 框架交互以确保用户有效,然后创建该用户,然后继续登录他们。
Identity 框架有两个核心服务,一个是UserManager,另一个是SignInManager。
我们需要将这两个服务注入到我们的控制器中。这样,当我们需要创建用户或登录用户时,就可以调用相应的 API。
让我们为 SignInManager 和 UserManager 添加私有变量,然后在 AccountController 中添加一个构造函数,该构造函数将接收两个参数:类型为 User 的 UserManager 和类型为 User 的 SignInManager。
private SignInManager<User> _signManager; private UserManager<User> _userManager; public AccountController(UserManager<User> userManager, SignInManager<User> signManager){ _userManager = userManager; _signManager = signManager; }
我们将继续使用 AccountController 的 POST 操作方法,并且在 post 操作中我们应该始终进行的第一个检查之一是检查我们的 ModelState 是否有效。
如果 ModelState 有效,那么我们知道用户给了我们用户名和密码并确认了密码;如果不是,我们需要让他们提供正确的信息。
以下是 Register 操作的实现。
[HttpPost] public async Task<IActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { var user = new User { UserName = model.Username }; var result = await _userManager.CreateAsync(user, model.Password); if (result.Succeeded) { await _signManager.SignInAsync(user, false); return RedirectToAction("Index", "Home"); } else { foreach (var error in result.Errors) { ModelState.AddModelError("", error.Description); } } } return View(); }
如果我们的ModelState有效,我们需要与 Identity 框架通信。我们还需要创建一个 User 实体的新实例,并将我们的输入model.Username复制到 User 实体的UserName属性中。
但是,我们不会复制密码,因为在 User 实体中没有地方存储纯文本密码。相反,我们将密码直接传递给 Identity 框架,它将对密码进行哈希处理。
所以我们有一个 userManager。创建一个异步方法,我们需要将用户名传递给它,以便我们可以保存该用户的密码。
此异步方法返回一个结果,告诉我们实例是成功还是失败,如果失败,它会提供一些可能失败的原因。
如果结果成功,我们可以登录刚刚创建帐户的用户,然后要求 SignInManager 登录此用户。现在,将用户重定向回主页,您现在将被认证。
如果结果不成功,那么我们应该尝试告诉用户原因,并且从 UserManager 返回的结果包含一个错误集合,我们可以遍历这些错误并将这些错误添加到 ModelState 中。这些错误将在视图中用于验证标签助手(如验证标签助手),以在页面上显示信息。
在 ModelState.AddModelError 中,我们可以提供一个键来将错误与特定字段关联。我们还将使用空字符串并添加提供的错误描述。
让我们保存所有文件并运行应用程序,然后转到/account/register。
让我们输入一个用户名和一个非常简单的 5 字符密码。
现在,点击注册按钮。
默认情况下,Identity 框架尝试对密码执行一些规则。
密码必须至少包含 6 个字符,一个字符必须是小写,一个必须是大写,并且必须包含一个非数字字符。
这些错误出现在这里的原因是,我们在页面上有一个验证摘要,它会获取从userManager.CreateAsync结果返回的错误。
现在我们对密码规则有了更多的了解,让我们尝试创建一个足够复杂的密码并点击注册。
您现在将看到主页。这意味着操作成功了。现在让我们转到 SQL Server 对象资源管理器。
右键单击dbo.AspNetUsers表并选择查看数据。
您现在可以看到用户已成功创建,并且您还可以在 Users 表中看到一条新记录。您还可以看到一个哈希密码值以及用户名,这是我们使用mark.upston注册的用户名。
ASP.NET Core - 登录和注销
在本章中,我们将讨论登录和注销功能。与登录相比,注销的实现相当简单。让我们继续处理 Layout 视图,因为我们希望构建一个具有某些链接的 UI。这将允许已登录的用户注销并显示用户名。
<!DOCTYPE html> <html> <head> <meta name = "viewport" content = "width = device-width" /> <title>@ViewBag.Title</title> </head> <body> <div> @DateTime.Now </div> <div> @RenderBody() </div> </body> </html>
对于匿名用户,我们将显示一个登录链接。
构建此 UI 所需的所有信息都可从 Razor 视图上下文获取。
首先,让我们在布局视图中添加命名空间System.Security.Claims。
@using System.Security.Claims <!DOCTYPE html> <html> <head> <meta name = "viewport" content = "width = device-width" /> <title>@ViewBag.Title</title> </head> <body> <div> @if (User.IsSignedIn()) { <div>@User.GetUserName()</div> <form method = "post" asp-controller = "Account" aspcontroller = "Logout"> <input type = "submit" value = "Logout"/> </form> } else { <a asp-controller = "Account" asp-action = "Login">Login</a> <a asp-controller = "Account" asp-action = "Register">Register</a> } </div> <div> @DateTime.Now </div> <div> @RenderBody() </div> </body> </html>
在每个 Razor 视图内部都有一个 User 属性,我们希望构建一个 UI 来显示已登录用户的姓名。这里还有一个扩展方法IsSignedIn。
我们可以调用此方法,如果它返回 true,则可以在此处放置一些标记来显示用户名,显示注销按钮。
现在,如果用户已登录,我们可以使用辅助方法GetUserName显示用户的用户名。
我们必须在表单内构建一个注销按钮,该表单将发布到 Web 服务器。必须这样做,因为如果您允许简单的 GET 请求让用户退出,则会创建某些不良情况。
我们将强制此操作为 post,当用户提交此表单时,我们需要做的就是在 AccountController 中实现的注销操作上点击,并注销用户。
如果用户未登录并且我们有一个匿名用户,那么我们需要显示一个链接,该链接将转到 AccountController,具体来说是 Login 操作,并且它可以显示文本 Login。
我们还需要添加一个链接,以便新用户注册并直接转到注册页面。
现在让我们转到 AccountController 并首先实现注销操作,如下面的程序所示。
[HttpPost] public async Task<IActionResult> Logout() { await _signManager.SignOutAsync(); return RedirectToAction("Index", "Home"); }
此操作仅响应 HttpPost。这是一个异步操作。我们将不得不调用 Identity 框架上的另一个异步方法。
我们可以返回 IActionResult 的任务,并且操作名为 Logout。
我们需要做的就是等待SignInManager 的 SignOutAsync方法。
用户上下文现在已更改;我们现在有一个匿名用户。视图将重定向到主页,我们将返回到员工列表。
现在让我们继续构建登录功能。这里,我们需要一对操作,一个响应 HttpGet 请求并显示我们可以用来登录的表单,另一个响应 HttpPost 请求。
首先,我们需要一个新的 ViewModel 来提取登录数据,因为登录与注册非常不同。所以,让我们添加一个新类并将其命名为LoginViewModel。
public class LoginViewModel { public string Username { get; set; } [DataType(DataType.Password)] public string Password { get; set; } [Display(Name ="Remember Me")] public bool RememberMe { get; set; } public string ReturnUrl { get; set; } }
当用户登录时,他们必须提供一些信息,例如用户名、密码。
第三个信息必须是登录 UI。它们带有一个小的复选框,上面写着——“您是否希望记住我”。这是在会话 cookie 和更持久的 cookie 之间进行选择。
为了允许此功能,我们添加了一个布尔属性RememberMe,并使用了 Display 注解。现在,当我们构建标签时,文本Remember Me 将带空格显示。
我们实际上希望作为此 ViewModel 一部分的最后一个信息是拥有一个属性来存储 ReturnUrl。
现在让我们添加将响应 Get 请求的 Login 操作,如下面的程序所示。
[HttpGet] public IActionResult Login(string returnUrl = "") { var model = new LoginViewModel { ReturnUrl = returnUrl }; return View(model); }
我们将returnUrl作为查询字符串中的参数。
returnUrl可能并不总是存在。让我们使用空字符串作为默认值。
我们现在将通过在Views → Account文件夹中添加一个新的 MVC 视图页面来创建一个新视图。
在中间窗格中,选择 MVC 视图页面并将其命名为 Login.cshtml,然后单击添加按钮。让我们在 Login.cshtml 文件中添加以下代码。
@model LoginViewModel @{ ViewBag.Title = "Login"; } <h2>Login</h2> <form method = "post" asp-controller = "Account" asp-action = "Login" asp-route-returnurl = "@Model.ReturnUrl"> <div asp-validation-summary = "ValidationSummary.ModelOnly"></div> <div> <label asp-for = "Username"></label> <input asp-for = "Username" /> <span asp-validation-for = "Username"></span> </div> <div> <label asp-for = "Password"></label> <input asp-for = "Password" /> <span asp-validation-for = "Password"></span> </div> <div> <label asp-for = "RememberMe"></label> <input asp-for = "RememberMe" /> <span asp-validation-for = "RememberMe"></span> </div> <input type = "submit" value = "Login" /> </form>
在此登录视图中,我们将页面的标题设置为 Login,然后我们有一个表单,该表单将发布到AccountLogin操作。
我们需要使用标签助手asp-route-returnurl,以确保 ReturnUrl 存在于表单发布回的 URL 中。
我们需要将该 ReturnUrl 发送回服务器,以便如果用户成功登录,我们可以将其发送到他们试图访问的位置。
您在 asp-route- 后添加的任何内容,id 或 returnurl,无论您在那里有什么,都将以某种方式进入请求,要么进入 URL 路径,要么作为查询字符串参数。
我们有ValidationSummary和用户名、密码和 RememberMe 的输入,然后我们有一个提交按钮。
AccountController,并实现 Post 操作。此操作响应 HttpPost。这将是一个异步方法,因为我们需要调用 Identity 框架并返回 IActionResult 的任务。
[HttpPost] public async Task<IActionResult> Login(LoginViewModel model) { if (ModelState.IsValid) { var result = await _signManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe,false); if (result.Succeeded) { if (!string.IsNullOrEmpty(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl)) { return Redirect(model.ReturnUrl); } else { return RedirectToAction("Index", "Home"); } } } ModelState.AddModelError("","Invalid login attempt"); return View(model); }
我们将其称为 Login,现在我们期望接收 LoginViewModel。
我们需要检查 ModelState 是否有效。如果有效,则通过调用 SignInManager 上的 API 来登录用户。
PasswordSignInAsync方法将返回一个结果,如果结果成功,我们就知道用户已成功登录。
我们还有一个 return URL。如果它是一个有效的本地 URL,我们将重定向到 return URL。
如果用户刚刚登录并且没有任何特定位置要访问,我们将重定向用户到 HomeController 的 Index 操作。
我们可能处于用户提供无效密码或无效用户名的情况。我们还需要添加一个模型错误,提示是否存在无效的登录尝试。这有助于用户了解是否出现错误。
现在让我们保存所有内容并运行应用程序。
我们现在有了登录和注册链接。让我们点击登录链接。
让我们使用我们在上一章中创建的用户登录,方法是指定用户名和密码并选中记住我复选框。
当您点击登录按钮时,浏览器会询问您是否要保存 localhost 的密码。让我们点击是按钮。
现在,让我们通过点击注销按钮注销。
作为匿名用户,让我们尝试编辑员工详细信息。
您现在可以看到我们已被重定向到登录视图。
让我们使用您的用户名和密码登录,并选中记住我复选框。
现在,点击登录按钮。
您现在可以看到我们被定向到我们想要编辑的 URL。这是因为我们适当地处理了 return URL。