ASP.NET Core – 快速指南
ASP.NET Core – 快速指南
ASP.NET Core – 概述
ASP.NET Core 是来自 Microsoft 的新 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 核心
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 堆栈。
-
基于云就绪环境的配置。
-
对依赖注入的内置支持。
-
使用 HTML 使 Razor 标记更自然的标签助手。
-
能够在 IIS 上托管或在您自己的进程中自托管。
ASP.NET Core – 环境设置
ASP.NET Core 是对 ASP.NET 的重大重新设计。本主题介绍 ASP.NET Core 中的新概念,并解释它们如何帮助您开发现代 Web 应用程序。
要在您的应用程序中使用 ASP.NET Core,您的系统中必须安装以下内容 –
- 微软 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 下载Tooling Preview 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 Tooling Preview 2。
步骤 8 – 选中复选框并单击安装。
步骤 9 – 安装完成后,您将看到以下消息。
第 10 步– 您现在已准备好使用 ASP.NET Core 启动您的应用程序。
ASP.NET Core – 新项目
在本章中,我们将讨论如何在 Visual Studio 中创建一个新项目。
安装 Visual Studio 2015 工具后,您可以从File → New Project菜单选项开始构建新的 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 上运行的新项目。
在左侧窗格中,选择Templates → 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。
在这里,我们将从一个空模板开始。这将帮助我们从头开始构建它。让我们选择 Empty 模板,关闭云中的 Host,然后单击 OK。
Visual Studio 现在将在一段时间内启动该项目。在解决方案资源管理器窗口中,您将看到该项目中的所有文件。
让我们运行此应用程序,您可以通过按 Ctrl+F5 或转到“调试”菜单来执行此操作。进入 Debug 菜单后,选择Start without Debugging。
此应用程序只能显示 Hello World!这在localhost:57741上运行。在窗口系统托盘中,您还可以看到 IIS Express 正在运行。
该站点的名称是FirstAppDemo。如果您一直使用 ASP.NET 与框架的早期版本进行编程,那么您将与 Visual Studio 交互的方式以及 Visual Studio 使用 IIS Express 托管您的应用程序的方式,所有这些方面都会很熟悉。
ASP.NET Core – 项目布局
在本章中,我们将讨论 ASP.NET 核心项目如何出现在文件系统上以及不同的文件和目录如何协同工作。
让我们打开在上一章中创建的FirstAppDemo项目。
在解决方案资源管理器窗口中,右键单击解决方案节点并选择在文件资源管理器中打开文件夹。
您现在将看到包含两个文件的根目录:FirstAppDemo.sln和global.json。
FirstAppDemo.sln是一个解决方案文件。默认情况下,Visual Studio 多年来一直使用此扩展程序,如果要在 Studio 中打开应用程序并对其进行处理,可以双击该文件。
还有一个global.json文件。让我们在 Visual Studio 中打开这个文件。
在文件中,项目的设置很重要。此项目设置告诉 ASP.NET 在哪里查找您的源代码以及哪些文件夹包含您的项目。
源文件有两个可能的文件夹“ src ”和一个“ test ”文件夹。除非您的项目和源代码位于这两个文件夹之一中,否则将无法构建代码。如果需要,您可以更改这些设置。
Windows 资源管理器在磁盘上有“src”文件夹。您没有测试文件夹。在 test 文件夹中,您可以放置单元测试项目。让我们双击“src”文件夹。
您可以看到 FirstAppDemo 项目和 Web 应用程序。现在,双击该文件夹。
这些是应用程序的源代码文件,您还可以在解决方案资源管理器窗口中看到此文件夹结构。这是因为在当前版本的 ASP.NET Core 中,文件系统决定了项目中的内容。
如果将新文件添加到磁盘,该文件将添加到项目中。如果删除文件,则该文件将从项目中删除。一切都保持同步,这与以前版本的 ASP.NET Core 略有不同,其中一个项目文件,一个 *.cs proj 文件,其中包含项目中所有内容的清单。
ASP.NET Core 还会在文件更改或出现新文件时编译您的应用程序。
例子
让我们通过在文本编辑器中打开Startup.cs文件来查看一个简单示例。
正是这行代码响应对您的应用程序的每个 HTTP 请求,它只响应 Hello World!
让我们通过说“ Hello World!这个 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 核心 – 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 中。frameworks 部分是 project.json 的另一个重要部分,该部分告诉 ASP.NET 您的应用程序可以使用哪些 .NET 框架。
“构架”: { “netcoreapp1.0”:{ “进口”:[ "dotnet5.6", “便携式-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 框架要少,但它确实具有 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 文件加载该文本,它的确切位置并不重要。
-
我们将只设置一个场景,以便我们没有这个硬编码的字符串。
在解决方案资源管理器中,右键单击您的项目节点并选择Add → New Item。
在左窗格中,选择已安装 → 代码,然后在中间窗格中,选择 JSON 文件。调用此文件AppSettings.json并单击添加按钮,如上图所示。
我们也可以让我们的程序从文件中读取文本,而不是让 Hello World! Startup.cs 中的字符串。让我们在AppSettings.json 文件中添加以下代码。
{ "message": "Hello, World! this message is from configuration file..." }
现在我们需要从 Startup.cs 文件访问此消息。下面是Startup.cs文件的实现,它将从 JSON 文件中读取上述消息。
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 Not Found 错误。
例子
现在让我们举一个简单的例子来了解更多关于中间件的信息。我们使用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 接口上的扩展方法来添加中间件。
默认情况下,新的空项目中有两个中间件 –
- IISP平台处理程序
- 使用 app.Run 注册的中间件
IISP平台处理程序
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 文件夹,然后选择Add → New Item。
在中间窗格中,选择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 是您的默认文件。安装中间件的顺序很重要,因为如果在 UseStaticFiles 之后使用 UseDefaultFiles,则不会得到相同的结果。
如果您打算使用 UseDefaultFiles 和 UseStaticFiles,您可能还需要另一个位于 Microsoft.aspnet.staticfiles、NuGet 包中的中间件,即FileServer 中间件。这基本上包括以正确顺序排列的默认文件和静态文件。
// 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 文件夹并选择Add → Class菜单选项。
第 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 的服务集合中才能正常运行。
第 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 和我们提供的一些配置信息做出此决定。在本章中,我们将定义此配置信息,或者您可以在添加 MVC 中间件时在 Startup.cs 中说路由信息。
-
这种方法通常称为基于约定的路由。以下是常规路由的代码片段。
routeBuilder.MapRoute("Default", "{controller=Home}/{action=Index}/{id?}");
-
在这种方法中,我们定义了模板,告诉 MVC 如何查看 URL 并找到控制器名称和操作名称,其中控制器是 C# 类,而操作是该类的公共方法。
在上一章中,我们在应用程序中创建了一个控制器(HomeController),它是一个 C# 类,不需要从基类派生或实现接口或具有任何特殊属性。它是一个名为 HomeController 的普通 C# 类,它包含返回字符串的 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 操作。
- http://localhost:49940
- http://localhost:49940/首页
- http://localhost:49940/Home/Index
-
当浏览器请求http://mysite/ 或 http://mysite/Home 时,它会从 HomeController 的 Index 方法获取输出。
-
您也可以通过更改浏览器中的 URL 来尝试此操作。在这个例子中,它是http://localhost: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.html。您还可以转到 /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 中有不同的参数,或者如果你想在 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 操作,我们在 route 属性中指定了“country”。让我们保存 AboutController,刷新您的浏览器并转到 /about 并应该为您提供 Phone 操作。
让我们指定/about/country。这将使您能够进入该国家/地区行动。
如果您希望 URL 的一部分包含您的控制器的名称,您可以做的不是显式使用控制器名称,而是可以在方括号内使用令牌控制器。这告诉 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。这将使您能够进入该国家/地区行动。
ASP.NET Core – 操作结果
在本章中,我们将讨论操作结果。在前面的章节中,我们一直使用简单的 C# 类作为控制器。这些类不是从基类派生的,您可以将此方法用于 MVC,但从 Microsoft.AspNet.Mvc 命名空间中提供的控制器基类派生控制器更为常见。
-
这个基类让我们可以访问很多关于请求的上下文信息,以及帮助我们构建结果发送回客户端的方法。
-
您可以在响应中发回简单的字符串和整数。您还可以发回复杂对象,例如代表学生、大学或餐厅等的对象,以及与该对象关联的所有数据。
-
这些结果通常被封装到一个实现 IActionResult 接口的对象中。
-
有许多不同的结果类型实现了这个接口——结果类型可以包含模型或下载文件的内容。
-
这些不同的结果类型可以让我们将 JSON 发送回客户端或 XML 或构建 HTML 的视图。
动作基本上返回不同类型的动作结果。ActionResult 类是所有操作结果的基础。以下是不同类型的操作结果及其行为的列表。
Name | 行为 |
---|---|
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 的类。
如上图所示,在 Name 字段中输入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 action 方法中,我们想要返回一个 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); } } }
现在,我们将返回一种不同类型的结果,即ObjectResult,而不是返回内容。如果我们想要一个 ObjectResult,我们需要创建或实例化一个 ObjectResult 并将一些模型对象传递给它。
-
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 文件夹并选择Add → New Item。
在左窗格中,选择 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 方法有几个不同的重载,并将员工模型作为参数传递。
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.Name 将在视图中显示员工姓名。
现在让我们保存所有文件。之后,刷新浏览器以查看以下输出。
您现在可以看到如上图所示的员工姓名。
ASP.NET Core – 设置实体框架
在本章中,我们将设置和配置我们的应用程序以保存和读取 SQL Server 数据库中的数据。
为了使用数据库,我们将使用实体框架,该框架经过重新编写以与新的 .NET 框架一起使用。如果您过去曾与 EF 合作过,您会看到许多熟悉的作品。
-
在此应用程序中,我们将使用 SQL Server LocalDB。如果您对 SQL Server 不满意,您可以使用任何您喜欢的数据库,例如本地数据库、远程数据库,只要您有在实例上创建新数据库的权限即可。
-
LocalDB 是专为开发人员优化的 SQL Server 特殊版本。
-
Visual Studio 2015 甚至它的社区版都会默认安装 LocalDB。
要检查 LocalDB,请转到Visual Studio 中的“视图”→“SQL Server 对象资源管理器”菜单选项。
如果您必须使用 SQL Server,这是一个很好的工具,因为它允许您探索数据库和浏览数据,甚至在数据库中创建数据。第一次打开它可能需要一点时间,但它应该会自动连接到 LocalDB。
安装实体框架
使用实体框架的第一步是从 NuGet 包管理器或通过直接编辑project.json文件来安装实体框架 NuGet 包。
现在让我们通过添加以下两个包来直接编辑 project.json 文件。
该EntityFramework.Commands包帮助我们像创建基于我们的C#实体类数据库架构与实体框架执行任务,这些任务都可以从一个命令行工具,其中的逻辑生活EntityFramework.Commands包内。
为了使用这个命令行工具,我们需要在 project.json 的命令部分添加一个额外的条目,如下面的屏幕截图所示。
我们刚刚称它为“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 核心 – DBContext
实体框架使您能够使用称为实体的公共语言运行时 (CLR) 对象查询、插入、更新和删除数据。实体框架将模型中定义的实体和关系映射到数据库。它还提供设施 –
-
将从数据库返回的数据具体化为实体对象。
-
跟踪对对象所做的更改。
-
处理并发。
-
将对象更改传播回数据库。
-
将对象绑定到控件。
负责将数据作为对象进行交互的主要类是 DbContext。使用上下文的推荐方法是定义一个派生自 DbContext 的类,并公开表示上下文中指定实体集合的 DbSet 属性。
从逻辑上讲,DBContext 映射到具有 DBContext 理解的架构的特定数据库。在该 DBContext 类上,您可以创建类型为 DbSet<T> 的属性。泛型类型参数 T 将是一种实体类型,例如 Employee 是 FirstAppDemo 应用程序中的一个实体。
例子
让我们举一个简单的例子,我们将在其中创建一个 DbContext 类。在这里,我们需要在 Models 文件夹中添加一个新类并将其命名为FirstAppDempDbContext。尽管这个类本身不是一个模型,但它确实将我们所有的模型放在一起,以便我们可以将它们与数据库一起使用。
从 Miscrosoft.Data.Entity 命名空间中的 DbContext 类继承您的上下文类。现在在该类上实现 Employee 的 DbSet。
每个 DbSet 将映射到数据库中的一个表。如果您有一个属性 DbSet 的员工,并且该属性的名称是员工,则实体框架将默认在您的数据库中查找员工表。
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 方法中,它将返回数据库中所有员工的列表。
配置实体框架服务
要拥有可用的实体框架 DBContext,我们需要更改应用程序的配置。我们需要添加一个连接字符串,以便我们的 DBContext 知道要转到哪个服务器以及要查询哪个数据库。
-
我们将把连接字符串放在一个 JSON 配置文件中。
-
我们还需要在 Startup 类的 ConfigureServices 方法中添加更多服务。
-
实体框架,就像 ASP.NET 和 MVC 框架一样,实体框架依赖于依赖注入,为了注入工作,运行时需要了解实体框架使用的各种服务。
-
有一个简单的配置 API 可以添加我们需要的所有默认服务。
让我们转到 AppSettings.json 文件并添加连接字符串,如以下程序所示。
{ "message": "Hello, World! this message is from configuration file...", "database": { "connection": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=FirstAppDemo" } }
现在让我们转到 Startup 类,我们需要在其中添加一些额外的服务以使实体框架正常工作。具体来说,我们需要做三件与实体框架相关的事情 –
-
我们需要添加核心实体框架服务。
-
我们还需要添加 SQL Server 相关的实体框架服务。
-
我们需要告诉实体框架我们的 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 添加其默认的 SQL Server 相关服务。
-
然后我们还需要将我的DBContext类告诉实体框架,以便它可以适当地构造该类的实例,我们可以通过第三个方法AddDbContext方法来做到这一点。
-
这个需要一个泛型类型参数,我们在其中指定 DBContext 派生类的类型FirstAppDemoDbContext。
-
在 AddDbContext 内部,我们需要描述 DBContext 的选项。
-
这可以通过lambda 表达式来完成;这是我们接收选项参数的操作,并且实体框架可以支持不同的数据库。我们需要做的就是告诉实体框架这个特定的 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>(); } } }
现在我们需要设置数据库。设置数据库的一种方法是使用实体框架创建数据库,这是一个两步过程 –
第一步
这涉及以下内容 –
-
将迁移代码添加到我们的项目中。
-
迁移代码是C#代码。可以执行此操作以在数据库模式中创建数据库。
-
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 时,您将看到一个帮助屏幕。您不必记住所有选项。您可以从实体框架命令中看到可用的命令,其中包括三个。
首先,我们需要添加迁移来执行以下命令。
dnx ef migrations add v1
按 Enter。
实体框架将找到该上下文并查看其中的模型。它将知道之前没有迁移,因此它将生成第一次迁移。这里,v1 是数据库的版本 1。它将在解决方案资源管理器中创建一个新文件夹并生成代码。
迁移本质上是一个 C# 代码,用于生成 SQL 命令以修改 SQL 数据库中的架构。
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 和一个名称列。
-
按照惯例,当实体框架看到您有一个名为 Id 的属性时,它将使该属性成为数据库中的主键,或者更确切地说,使该列成为主键。
-
在这里,我们将使用 SQL Server。默认情况下,实体框架会将其设为 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的布局视图。
-
这是布局视图的通用名称,不需要前导下划线。这只是许多开发人员用来标识不是视图的视图的约定;您将其呈现为控制器操作的视图结果。
-
这是一种特殊的视图,但是一旦我们有了布局视图,我们就可以像主页的索引视图一样设置控制器视图。
-
我们可以设置此视图以在特定位置的 Layout 视图内呈现。
-
这种布局视图方法意味着 Index.cshtml 不需要了解有关徽标或顶级导航的任何信息。
-
Index 视图只需要渲染控制器动作给这个视图的模型的特定内容,而 Layout 视图负责其他所有事情。
例子
让我们举一个简单的例子。
如果您有多个视图,那么您将看到所有视图都将包含一定数量的重复标记。它们都有一个开始的HTML 标签、一个head 标签和一个body 标签。
即使我们在此应用程序中没有导航菜单,但在实际应用程序中它也可能可用,我们不想在每个视图中复制该标记。
让我们创建一个布局视图,然后将布局视图添加到Views文件夹中名为 Shared 的新文件夹中。这是 MVC 框架知道的常规文件夹。它知道这里的视图可能被应用程序中的多个控制器使用。让我们右键单击 Shared 文件夹并选择 Add → New Item。
在中间窗格中,选择 MVC 视图布局页面。这里的默认名称是 _Layout.cshtml。根据用户选择要在运行时使用的布局视图。现在,单击“添加”按钮。这就是新布局视图的默认设置。
我们希望 Layout 视图负责管理头部和身体。现在,由于此视图位于 Razor 视图中,我们可以使用 C# 表达式。我们仍然可以添加文字文本。我们现在有一个显示 DateTime.Now 的 div。现在让我们添加年份。
<!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 控制器操作呈现索引视图时,它涉及到一个布局页面;然后索引视图和它产生的 HTML 将被放置在索引视图中。
这是对 RenderBody 的方法调用所在的位置。我们可以期望整个应用程序中的所有内容视图都出现在调用 RenderBody 的 div 中。
该文件中的另一个表达式是 ViewBag.Title。ViewBag 是一种数据结构,可以添加到任何属性和任何您想要放入 ViewBag 中的数据。我们可以在 ViewBag 上添加 ViewBag.Title、ViewBag.CurrentDate 或我们想要的任何属性。
现在让我们转到 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>
删除索引视图中不再需要的标记。因此,我们可以删除 HTML 标签和 head 标签之类的东西。我们也不需要开始正文元素或结束标签,如下面的程序所示。
@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 框架我们要使用 Layout 视图。
-
其次,我们需要通过向 ViewBag 添加一些信息来设置适当的标题,如上面的代码所示。
让我们保存所有文件并运行应用程序。运行应用程序后,您将看到以下主页。
ASP.NET Core – Razor 视图开始
在本章中,我们将讨论 Razor View Start。MVC 中的 Razor 视图引擎有一个约定,它将查找名为_ViewStart.cshtml 的任何文件并执行该文件中的代码。在单个视图中执行代码之前。
-
ViewStart 文件中的代码无法呈现到页面的 HTML 输出中,但可用于从各个视图中的代码块中删除重复代码。
-
在我们的示例中,如果我们希望每个视图都使用我们在上一章中创建的布局视图,我们可以将设置布局视图的代码放在 ViewStart 中,而不是将代码放在每个视图中。
例子
让我们举一个简单的例子来看看它是如何工作的。在我们的应用程序中,我们不希望每个视图都指定其布局视图是_Layout.cshtml。因此,右键单击 Views 文件夹并选择Add → New Item。
在 ASP.NET MVC 中有一个用于 ViewStart 页面的特定模板,因此在中间窗格中选择 MVC View Start Page。这里最重要的部分是这个文件被命名为_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 将布局设置为其他内容。
让我们保存所有文件并运行应用程序。
您将看到您的主页仍然按照以前的方式呈现,并且我们仍然具有有效的 Layout 视图。
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>
使用指令将允许从 Razor 视图生成的代码正确编译。如果不使用指令,C# 编译器将无法找到此 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 文件夹中创建一个视图导入。这将向每个视图添加using 语句,只需右键单击 Views 文件夹并选择 Add → New Item。
在中间窗格中,选择 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 可以在应用 LabelTagHelper 属性时定位 HTML <label> 元素。
-
如果您熟悉 HTML Helpers,Tag Helpers 会减少 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" ] }
在依赖项部分,您将看到添加了“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。因为这里的第一部分是一个类型名称,如果你只想使用一个特定的标签助手,我们可以在这里列出一个特定的标签助手。
但是,如果您只想获取此程序集中的所有标签助手,则可以使用asterisk(*)。标签助手库中有许多标签助手可用。让我们看看索引视图。
@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>
我们已经有了一个 HTML 助手,它使用ActionLink生成一个锚标签,该标签将指向一个 URL,允许我们获取员工的详细信息。
让我们首先在 home 控制器中添加 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-行动=“细节”是我们想要去的动作名称。如果您想传递任何参数,您可以使用 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 显示为链接文本,但现在我们将显示文本详细信息。现在,我们单击详细信息并使用标签助手而不是 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文件夹并选择Add → New Items来创建一个新视图。
在中间窗格中,选择 MVC View Page;调用页面 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 操作编辑员工。
-
在标签标签中,我们使用了 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 请求的编辑操作。它将需要一个员工 ID。此处的代码将类似于我们在 Details 操作中的代码。我们将首先提取用户要编辑的员工的数据。我们还需要确保员工确实存在。如果它不存在,我们会将用户重定向回索引视图。但是当员工存在时,我们将呈现编辑视图。
我们还需要响应表单将发送的 HttpPost。
让我们在 HomeController.cs 文件中添加一个新类,如以下程序所示。
public class EmployeeEditViewModel { [Required, MaxLength(80)] public string Name { get; set; } }
在将响应 HttpPost 的编辑操作中,将采用 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 有效。
-
将输入视图模型中的姓名复制到从数据库中检索到的员工并保存更改。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; } } }
让我们编译程序并运行应用程序。
我们现在有一个可用的编辑链接;让我们通过单击“编辑”链接来编辑 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 框架支持实体框架和所有可以与实体框架一起使用的数据库。
-
但是您可以实现自己的 UserStore 来处理任何数据源。
-
为了正确使用实体框架,我们的 User 类还将插入 IdentityDb 类。
-
这是一个使用实体框架 DBContext 来完成实际数据库工作的类。
-
我们需要通过让我们现有的 DataContext 类继承自 IdentityDb 而不是实体框架的 DBContext 来将此 IdentityDb 包含到我们的应用程序中。
-
IdentityDb 和 UserStore 共同存储用户信息并验证用户密码,即数据库中的散列密码。
我们需要了解 ASP.NET Core Identity Framework 的两个部分
登录管理器
这是身份框架的两部分之一 –
-
顾名思义,一旦我们验证了密码,SignInManager就可以让用户登录。
-
我们也可以使用这个管理器来注销用户。
-
使用表单身份验证,登录和注销是通过管理 cookie 来完成的。
-
当我们告诉 SignInManager 让用户登录时,管理器会向用户的浏览器发出一个 cookie,浏览器将在每个后续请求中发送这个 cookie。它可以帮助我们识别该用户。
身份中间件
这是框架的第二部分 –
-
读取 SignInManager 发送的 cookie 并识别用户,这发生在框架的最后一部分,即身份中间件中。
-
我们需要将此中间件配置到我们的应用程序管道中,以处理由 SignInManager 设置的 cookie。我们还将在接下来的几章中看到这个中间件的一些其他特性。
ASP.NET Core – 授权属性
在本章中,我们将讨论授权属性。到目前为止,在我们的应用程序中,我们已经允许匿名用户做任何事情。他们可以编辑员工详细信息和查看详细信息,但我们没有创建新员工的功能。让我们首先添加创建功能,然后我们将使用 Authorize 属性限制用户访问。
我们需要首先在Views → Home文件夹中创建一个新的 MVC View 页面并将其命名为 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> }
我们现在将在 HomeController 中为 POST 和 GET添加action 方法,如下面的程序所示。
[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 View的链接,如下面的程序所示。
@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 属性放在控制器本身时,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 框架可以检测到应用程序的某个部分何时因为不允许用户进入而想要返回 401 状态代码,并且 Identity 框架可以将其转换为登录页面并允许用户解决此问题。
-
一旦我们安装和配置了 Identity 框架,我们将看到它是如何工作的。
-
但是现在,我们可以看到Authorize 属性正在起作用。
ASP.NET Core – 身份配置
在本章中,我们将安装和配置 Identity 框架,这需要一些工作。如果您转到 Visual Studio 并创建一个新的 ASP.NET Core 应用程序,并选择将身份验证设置为单个用户帐户的完整 Web 应用程序模板,则该新项目将包含为您设置的 Identity 框架的所有部分。
我们从一个空项目开始。我们现在将从头开始设置 Identity 框架,这是了解完整应用程序模板中所有部分的好方法,因为如果您没有详细了解所有代码,可能会感到困惑。
首先,我们需要安装依赖项,即Microsoft.AspNet.Identity。我们将继续安装Microsoft.AspNet.Identity.EntityFramework,然后实现与实体框架配合使用的 Identity 框架。
-
如果我们依赖 Identity.EntityFramework,则该包包含 Identity 包。
-
如果您构建自己的数据存储,则只需使用 Identity 包即可。
-
安装依赖项后,我们可以创建一个客户 User 类,其中包含我们要存储的有关用户的所有信息。
-
对于这个应用程序,我们将继承 Identity 框架提供的一个类,该类将为我们提供所有基本要素,如 Username 属性和存储散列密码的位置。
-
我们还需要修改FirstAppDemoDbContext类以从 Identity 框架的IdentityDb类继承。
-
IdentityDb 为我们提供了使用实体框架存储为用户信息所需的一切。一旦我们设置了 User 类和DBContext,我们就需要使用 Startup 类的ConfigureServices方法将 Identity 服务配置到应用程序中。
-
就像我们需要添加服务来支持 MVC 框架一样,Identity 框架需要将服务添加到应用程序中才能工作。
-
这些服务包括像服务UserStore服务和SignInManager。
-
我们会将这些服务注入到我们的控制器中,以在适当的时候创建用户并发布 cookie。
-
最后,在启动的Configure方法中,我们需要添加Identity中间件。
-
该中间件不仅有助于将 cookie 转换为用户身份,还可以确保用户不会看到带有 401 响应的空白页面。
现在让我们按照下面给出的步骤进行操作。
步骤 1 – 我们需要通过添加对身份框架的依赖来继续。让我们将 Microsoft.AspNet.Identity.EntityFramework 依赖项添加到 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", "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 文件夹并选择 Add → Class 添加 User 类。
调用此类 User 并单击上面的屏幕截图中的 Add 按钮。在这个类中,您可以添加属性来保存您想要存储的关于用户的任何信息。
第 3 步– 让我们从 Identity 框架提供的类派生 User 类。它是 Identity.EntityFramework 命名空间中的 IdentityUser 类。
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 转到元数据视图。
默认情况下,您现在可以查看与用户相关的所有信息。信息包括以下内容 –
-
我们不会在此应用程序中使用但可以使用的字段。
-
身份框架可以跟踪特定用户的失败登录尝试次数,并可以在一段时间内锁定该帐户。
-
用于存储 PasswordHash 和 PhoneNumber 的字段。我们将使用的两个重要字段是 PasswordHash 和 UserName。
-
我们还将隐式使用用户的主键和 ID 属性。如果您需要查询特定用户,也可以使用该属性。
第 7 步– 现在,我们需要确保用户包含在我们的 DBContext 中。因此,让我们打开应用程序中的FirstAppDemoDBContext,而不是直接从 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 服务和我们的实体框架服务,我们还需要添加我们的身份服务。这将添加身份框架依赖于完成其工作的所有服务。
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 命名空间中。
-
当我们使用带有 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 – 我们插入中间件的位置是我们将添加身份中间件的位置。下面是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 步– 现在让我们继续构建应用程序。在下一章中,我们需要添加另一个实体框架迁移,以确保我们的 SQL Server 数据库中有 Identity 架构。
ASP.NET Core – 身份迁移
在本章中,我们将讨论身份迁移。在 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 派生类进行更改时,您很有可能必须创建一个新的迁移脚本以应用于数据库并使架构与代码中的内容同步.
在我们的应用程序中就是这种情况,因为我们现在从 IdentityDbContext 类派生我们的 FirstAppDemoDbContext 类,它包含自己的 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来获取实体框架命令。
我们的 project.json 文件有一个部分将这个“ef”关键字与 EntityFramework.Commands 映射。
"commands": { "web": "Microsoft.AspNet.Server.Kestrel", "ef": "EntityFramework.Commands" }
我们可以从这里添加迁移。我们还需要为迁移提供一个名称。让我们将 v2 用于版本 2,然后按 Enter。
迁移完成后,您的迁移文件夹中将有一个 v2 文件。
我们现在想通过运行“dnx ef database update”命令将该迁移应用到我们的数据库中。
实体框架将看到需要应用的迁移,并将执行该迁移。
如果您进入 SQL Server 对象资源管理器,您将看到我们之前创建的 Employee 表。您还将看到一些必须存储用户、声明、角色的附加表,以及一些将用户映射到特定角色的映射表。
所有这些表都与 Identity 框架提供的实体相关。
让我们快速浏览一下users 表。
您现在可以看到 AspNetUsers 表中的列包含存储我们在继承自的 Identity User 上看到的所有属性的列,以及它的字段,如 UserName 和 PasswordHash。因此,您一直在使用一些内置身份服务,因为它们还包含创建用户和验证用户密码的功能。
ASP.NET Core – 用户注册
在本章中,我们将讨论用户注册。我们现在有一个可用的数据库,是时候开始向应用程序添加一些功能了。我们还配置了我们的应用程序,我们有一个可用的数据库模式。现在让我们转到应用程序主页。
按 F12 打开开发人员工具,然后单击“编辑”链接。以前,当我们单击 Edit 链接时,MVC 框架检测到 Authorize 属性的存在并返回 401 状态代码,因为用户未登录。
您现在将看到我们从配置文件在屏幕上收到一条消息。
现在让我们转到开发人员工具。
-
您将看到浏览器请求编辑页面,MVC 框架决定用户无权查看此资源。
-
所以在 MVC 框架内的某个地方,生成了 401 状态代码。
-
我们现在已经有了 Identity 中间件。Identity 中间件查看将发送给用户的 401 状态代码,并将其替换为 302 状态代码,这是一个重定向状态代码。
-
Identity 框架知道用户必须尝试登录才能访问此资源。
-
Identity 框架将我们定向到此 URL,正如我们在地址栏中所看到的 – /Account/Login。
-
当您注册这些服务和中间件时,这是一个带有身份框架的可配置端点,位于启动内部。您可以设置不同的选项,其中一个选项是更改登录 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。
让我们创建一些属性来保存用户名、密码和用户 ConfirmPassword,方法是键入两次并确保两个密码匹配,如下面的程序所示。
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,这样字符就不会显示在屏幕上。
-
在确认密码也将是数据类型的密码,然后还有一个额外的属性比较。我们将 ConfirmPassword 字段与我们可以指定的其他属性(即 Password 字段)进行比较。
现在让我们创建我们需要的视图。我们需要在视图中添加一个新文件夹并将其命名为 Account,因此所有与 AccountController 相关的视图都将添加到此文件夹中。
现在,右键单击“帐户”文件夹并选择“添加”→“新项目”。
在中间窗格中,选择 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 中的所有不同字段提供了标签和输入。
-
我们需要用户名的标签、用户名的输入以及用户名的验证消息。
-
我们需要用户输入的另外两个属性是相同的,它们将有一个标签和输入以及一个用于密码的跨度以及一个标签和一个用于 ConfirmPassword 的输入和跨度。
-
我们不需要为 Password 和 ConfirmPassword 指定输入类型,因为asp for tag helper 会确保将输入类型设置为我们的密码。
-
最后,我们需要有一个显示为Register的按钮。当用户单击此按钮时,我们会将表单提交回控制器。
在 AccountController 中,我们还需要实现一个 HttpPost Register 动作方法。让我们回到 AccountController 并添加以下注册操作如下 –
[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 有效,那么我们知道用户给了我们一个用户名和密码并确认了密码;如果没有,我们需要要求他们提供正确的信息。
-
这是注册操作的实现。
[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 框架,后者将对密码进行哈希处理。
-
所以我们有一个用户管理器。创建一个异步方法,我们必须在其中传递用户名,以便我们可以保存该用户的密码。
-
这个 Async 方法返回一个结果,告诉我们实例是成功还是失败,如果失败,它会给我们一些失败的可能原因。
-
如果结果成功,我们可以让刚刚创建帐户的用户登录,然后要求 SignInManager 为该用户签名。现在,将用户重定向回主页,您现在将通过身份验证。
-
如果结果不成功,那么我们应该尝试告诉用户原因,从 UserManager 返回的结果有一个错误集合,我们可以迭代这些错误并将这些错误添加到 ModelState 中。这些错误将在标签助手(如验证标签助手)的视图中可用,以在页面上显示信息。
-
在 ModelState.AddModelError 中,我们可以提供一个键来将错误与特定字段相关联。我们还将使用一个空白字符串并添加所提供错误的描述。
让我们保存所有文件并运行应用程序并转到/account/register。
让我们输入一个用户名和一个非常简单的 5 个字符的密码。
现在,单击注册按钮。
默认情况下,Identity 框架会尝试强制执行一些有关密码的规则。
密码必须至少有6个字符,1个小写,1个大写,1个非数字字符。
这些错误出现在此处的原因是我们在页面上有一个验证摘要,该摘要收集了从userManager.CreateAsync结果返回的错误。
现在我们对密码规则有了更多的了解,让我们尝试创建一个足够复杂的密码,然后单击注册。
您现在将看到主页。这意味着操作成功了。现在让我们转到 SQL Server 对象资源管理器。
右键单击dbo.AspNetUsers表并选择View Data。
您现在可以看到用户已成功创建,您还可以在“用户”表中看到一条新记录。您还可以看到散列密码值以及用户名,这是我们在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>
-
对于匿名用户,我们将显示登录链接。
-
您可以从 Razor 视图上下文中获得构建此 UI 所需的所有信息。
-
首先,让我们在您的布局视图中添加命名空间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 REQUEST 允许用户退出,它会产生某些令人讨厌的情况。
-
我们将强制这是一个帖子,当用户提交这个表单时,我们需要做的就是点击注销操作,我们将通过 AccountController 实现,并注销用户。
-
如果用户没有登录并且我们有一个匿名用户,那么我们需要显示一个链接,该链接将转到 AccountController,特别是登录操作,它可以显示文本登录。
-
我们还需要添加一个链接,供新用户注册并直接进入注册页面。
现在让我们转到 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; } }
-
当用户登录时,他们必须提供一些信息,如用户名、密码。
-
第三条信息必须是登录用户界面。这些带有一个小复选框,上面写着——“你想记住我吗”。这是我们想要一个会话 cookie 还是我们想要一个更永久的 cookie 之间的选择。
-
为了实现此功能,我们添加了一个布尔属性RememberMe,并使用了 Display 注释。现在,当我们构建标签时,文本“记住我”会显示一个空格。
-
作为此 ViewModel 的一部分,我们实际想要的最后一个信息是拥有一个用于存储 ReturnUrl 的属性。
现在让我们添加将响应 Get 请求的 Login 操作,如以下程序所示。
[HttpGet] public IActionResult Login(string returnUrl = "") { var model = new LoginViewModel { ReturnUrl = returnUrl }; return View(model); }
-
我们将returnUrl作为查询字符串中的参数。
-
该RETURNURL可能不会永远存在。让我们有一个空字符串作为默认值。
我们现在将通过在Views → Account文件夹中添加一个新的 MVC View Page 来获得一个新视图。
在中间窗格中,选择 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>
-
在这个登录视图中,我们将页面的标题设置为登录,然后我们有一个将发布到AccountLogin操作的表单。
-
我们需要使用标签助手asp-route-returnurl来确保 ReturnUrl 位于表单回发到的 URL 中。
-
我们需要将该 ReturnUrl 发送回服务器,以便如果用户成功登录,我们可以将其发送到他们试图到达的地方。
-
您在 asp-route-、id 或 returnurl 之后添加的任何内容,无论您在那里有什么,都将进入请求的某处,进入 URL 路径或作为查询字符串参数。
-
我们有ValidationSummary和用户名、密码和记住我的输入,然后我们有一个提交按钮。
` 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); }
-
我们称之为登录,现在我们期望收到一个 LoginViewModel。
-
我们需要检查 ModelState 是否有效。如果有效,则通过调用 SignInManager 上的 API 来登录用户。
-
该PasswordSignInAsync方法会返回一个结果,如果结果成功了,我们知道用户成功登录。
-
我们还有一个返回 URL。如果它是一个有效的本地 URL,我们将被重定向到返回 URL。
-
如果用户刚刚登录并且没有任何特定的去处,我们会将用户重定向到 HomeController 的 Index 操作。
-
我们可能会遇到用户提供无效密码或无效用户名的情况。我们还需要添加一个模型错误,提示是否存在无效登录尝试。这有助于用户知道是否出现问题。
现在让我们保存所有内容并运行应用程序。
我们现在有登录和注册链接。让我们点击登录链接。
让我们通过指定用户名和密码使用我们在上一章中创建的用户登录,然后选中“记住我”复选框。
当您单击“登录”按钮时,浏览器会询问您是否要保存本地主机的密码。让我们点击是按钮。
现在,让我们通过单击注销按钮注销。
作为匿名用户,让我们尝试编辑员工详细信息。
您现在可以看到我们已被重定向到登录视图。
让我们使用您的用户名和密码登录并选中“记住我”复选框。
现在,单击登录按钮。
您现在可以看到我们被定向到要编辑的 URL。这是因为我们适当地处理了返回 URL。