概述
ASP.NET Core
ASP.NET 4.x
针对 Windows、macOS 或 Linux 进行生成
针对 Windows 进行生成
Razor 页面 是在 ASP.NET Core 2.x 及更高版本中创建 Web UI 时建议使用的方法。
使用 Web 窗体、SignalR、MVC、Web API、WebHooks 或网页
每个计算机多个版本
每个计算机一个版本
使用 C# 或 F# 通过 Visual Studio、Visual Studio for Mac 或 Visual Studio Code 进行开发
使用 C#、VB 或 F# 通过 Visual Studio 进行开发
比 ASP.NET 4.x 性能更高
良好的性能
选择 .NET Framework 或 .NET Core 运行时
使用 .NET Framework 运行时
基础知识 依赖注入 概述
使用接口抽象化依赖关系实现。
注册服务容器中的依赖关系,ASP.NET Core 提供了一个内置的服务容器 IServiceProvider,服务已在应用的 Startup.ConfigureServices 方法中注册。
将服务注入到使用它的类的构造函数中,框架负责创建依赖关系的实例,并在不再需要时对其进行处理。
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public interface IMyDependency { Task WriteMessage (string message ) ; } public class MyDependency : IMyDependency { private readonly ILogger<MyDependency> _logger; public MyDependency (ILogger<MyDependency> logger ) { _logger = logger; } public Task WriteMessage (string message ) { _logger.LogInformation( "MyDependency.WriteMessage called. Message: {MESSAGE}" , message); return Task.FromResult(0 ); } } public void ConfigureServices (IServiceCollection services ) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddScoped<IMyDependency, MyDependency>(); services.AddTransient<IOperationTransient, Operation>(); services.AddScoped<IOperationScoped, Operation>(); services.AddSingleton<IOperationSingleton, Operation>(); services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty)); services.AddTransient<OperationService, OperationService>(); }
MyDependency 在其构造函数中请求 ILogger,以链式方式使用依赖关系注入并不罕见,每个请求的依赖关系相应地请求其自己的依赖关系,容器解析图中的依赖关系并返回完全解析的服务。
服务生存期 暂时 暂时生存期服务是每次从服务容器进行请求时创建的,这种生存期适合轻量级、无状态的服务。
作用域(Scoped) 作用域生存期服务以每个客户端请求(连接)一次的方式创建。
警告:在中间件内使用有作用域的服务时,请将该服务注入至 Invoke 或 InvokeAsync 方法,请不要通过构造函数注入进行注入,因为它会强制服务的行为与单一实例类似。
单例 单一实例生存期服务是在第一次请求时(或者在运行 ConfigureServices 并且使用服务注册指定实例时)创建的,每个后续请求都使用相同的实例,如果应用需要单一实例行为,建议允许服务容器管理服务的生存期,不要实现单一实例设计模式并提供用户代码来管理对象在类中的生存期。
警告:从单一实例解析有作用域的服务很危险,当处理后续请求时,它可能会导致服务处于不正确的状态。
Razor页面 入门 1 2 3 4 5 6 7 8 9 10 11 # 新建Razor页面项目 $ dotnet new webapp -o RazorPagesMovie # 本地部署 $ dotnet run # 输出 : Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0] User profile is available. Using '/home/hearing/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. Hosting environment: Development Content root path: /home/hearing/WorkSpace/asp/hello Now listening on: https://localhost:5001 Now listening on: http://localhost:5000
项目文件:
Pages 文件夹:包含 Razor 页面和支持文件。 每个 Razor 页面都是一对文件:
一个 .cshtml 文件,其中包含使用 Razor 语法的 C# 代码的 HTML 标记。
一个 .cshtml.cs 文件,其中包含处理页面事件的 C# 代码。
支持文件的名称以下划线开头。例如,_Layout.cshtml 文件可配置所有页面通用的 UI 元素。 此文件设置页面顶部的导航菜单和页面底部的版权声明。
wwwroot文件夹:包含静态文件,如 HTML 文件、JavaScript 文件和 CSS 文件。
appsettings.json:包含配置数据,如连接字符串。
Program.cs:包含程序的入口点。
Startup.cs:包含配置应用行为的代码,例如,是否需要同意 cookie。 有关更多信息,请参见ASP.NET Core 中的应用启动。
MVC 入门 1 2 3 4 5 6 7 8 9 10 11 # 新建Razor页面项目 $ dotnet new mvc -o MvcHello # 本地部署 $ dotnet run # 输出 : Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0] User profile is available. Using '/home/hearing/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. Hosting environment: Development Content root path: /home/hearing/WorkSpace/asp/MvcHello Now listening on: https://localhost:5001 Now listening on: http://localhost:5000
目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 MvcHello ├── appsettings.Development.json ├── appsettings.json ├── bin │ └── Debug │ └── netcoreapp2.2 ├── Controllers │ ├── HelloWorldController.cs │ └── HomeController.cs ├── Models │ └── ErrorViewModel.cs ├── MvcHello.csproj ├── obj │ ├── Debug │ │ └── netcoreapp2.2 │ ├── MvcHello.csproj.nuget.cache │ ├── MvcHello.csproj.nuget.dgspec.json │ ├── MvcHello.csproj.nuget.g.props │ ├── MvcHello.csproj.nuget.g.targets │ └── project.assets.json ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── Views │ ├── Home │ │ ├── Index.cshtml │ │ └── Privacy.cshtml │ ├── Shared │ │ ├── _CookieConsentPartial.cshtml │ │ ├── Error.cshtml │ │ ├── _Layout.cshtml │ │ └── _ValidationScriptsPartial.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml └── wwwroot ├── css │ └── site.css ├── favicon.ico ├── js │ └── site.js └── lib ├── bootstrap ├── jquery ├── jquery-validation └── jquery-validation-unobtrusive
Route 在 Startup类 的Configure 方法中,可能会看到与下面类似的代码:
1 2 3 app.UseMvc(routes => { routes.MapRoute("default" , "{controller=Home}/{action=Index}/{id?}" ); });
在对 UseMvc 的调用中,MapRoute 用于创建单个路由,亦称 default 路由。大多数 MVC 应用使用带有模板的路由,与 default 路由类似。路由模板:
{controller=Home} 将 Home 定义为默认 controller
{action=Index} 将 Index 定义为默认 action
{id?} 将 id 定义为可选参数
路由模板 “{controller=Home}/{action=Index}/{id?}” 可以匹配诸如 /Products/Details/5 之类的 URL 路径,并通过对路径进行标记来提取路由值 { controller = Products, action = Details, id = 5 }。 MVC 将尝试查找名为 ProductsController 的控制器并运行 Details 操作:
1 2 3 public class ProductsController : Controller { public IActionResult Details (int id ) { ... } }
依赖关系注入 控制器 构造函数注入 服务作为构造函数参数添加,并且运行时从服务容器中解析服务,通常使用接口来定义服务。例如,考虑需要当前时间的应用,以下接口公开 IDateTime 服务:
1 2 3 4 5 6 7 8 9 10 11 12 public interface IDateTime { DateTime Now { get ; } } public class SystemDateTime : IDateTime { public DateTime Now { get { return DateTime.Now; } } }
注册:
1 2 3 4 5 6 public void ConfigureServices (IServiceCollection services ) { services.AddSingleton<IDateTime, SystemDateTime>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class HomeController : Controller { private readonly IDateTime _dateTime; public HomeController (IDateTime dateTime ) { _dateTime = dateTime; } public IActionResult Index ( ) { var serverTime = _dateTime.Now; if (serverTime.Hour < 12 ) { ViewData["Message" ] = "It's morning here - Good Morning!" ; } else if (serverTime.Hour < 17 ) { ViewData["Message" ] = "It's afternoon here - Good Afternoon!" ; } else { ViewData["Message" ] = "It's evening here - Good Evening!" ; } return View(); } }
FromServices注入 FromServicesAttribute 允许将服务直接注入到操作方法,而无需使用构造函数注入:
1 2 3 4 5 6 public IActionResult About ([FromServices] IDateTime dateTime ) { ViewData["Message" ] = $"Current server time: {dateTime.Now} " ; return View(); }
视图 ASP.NET Core 支持将依赖关系注入到视图。 这对于视图特定服务很有用,例如仅为填充视图元素所需的本地化或数据。 应尽量在控制器和视图之间保持问题分离。 视图显示的大部分数据应该从控制器传入。
简单示例 可使用 @inject 指令将服务注入到视图中,可将 @inject 看作向视图添加属性,并用 DI 填充该属性。@inject 的语法:@inject <type> <name>
,示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @using System.Threading.Tasks @using ViewInjectSample.Model @using ViewInjectSample.Model.Services @model IEnumerable<ToDoItem > @inject StatisticsService StatsService <!DOCTYPE html > <html > <head > <title > To Do Items</title > </head > <body > <div > <h1 > To Do Items</h1 > <ul > <li > Total Items: @StatsService.GetCount()</li > <li > Completed: @StatsService.GetCompletedCount()</li > <li > Avg. Priority: @StatsService.GetAveragePriority()</li > </ul > <table > <tr > <th > Name</th > <th > Priority</th > <th > Is Done?</th > </tr > @foreach (var item in Model) { <tr > <td > @item.Name</td > <td > @item.Priority</td > <td > @item.IsDone</td > </tr > } </table > </div > </body > </html >
在 Startup.cs 的 ConfigureServices 中为依赖关系注入注册此服务:
1 2 3 4 5 6 7 public void ConfigureServices (IServiceCollection services ) { services.AddMvc(); services.AddTransient<IToDoItemRepository, ToDoItemRepository>(); services.AddTransient<StatisticsService>(); services.AddTransient<ProfileOptionsService>();
填充查找数据 视图注入可用于填充 UI 元素(如下拉列表)中的选项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 using Microsoft.AspNetCore.Mvc;using ViewInjectSample.Model;namespace ViewInjectSample.Controllers { public class ProfileController : Controller { [Route("Profile" ) ] public IActionResult Index ( ) { var profile = new Profile() { Name = "Steve" , FavColor = "Blue" , Gender = "Male" , State = new State("Ohio" ,"OH" ) }; return View(profile); } } } using System.Collections.Generic;namespace ViewInjectSample.Model.Services { public class ProfileOptionsService { public List<string > ListGenders ( ) { return new List<string >() {"Female" , "Male" }; } public List<State> ListStates ( ) { return new List<State>() { new State("Alabama" , "AL" ), new State("Alaska" , "AK" ), new State("Ohio" , "OH" ) }; } public List<string > ListColors ( ) { return new List<string >() { "Blue" ,"Green" ,"Red" ,"Yellow" }; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @using System.Threading.Tasks @using ViewInjectSample.Model.Services @model ViewInjectSample.Model.Profile @inject ProfileOptionsService Options <!DOCTYPE html > <html > <head > <title > Update Profile</title > </head > <body > <div > <h1 > Update Profile</h1 > Name: @Html.TextBoxFor(m => m.Name) <br /> Gender: @Html.DropDownList("Gender", Options.ListGenders().Select(g => new SelectListItem() { Text = g, Value = g })) <br /> State: @Html.DropDownListFor(m => m.State.Code, Options.ListStates().Select(s => new SelectListItem() { Text = s.Name, Value = s.Code})) <br /> Fav. Color: @Html.DropDownList("FavColor", Options.ListColors().Select(c => new SelectListItem() { Text = c, Value = c })) </div > </body > </html >
Controller
驻留在项目的根级别“Controllers”文件夹中
继承自 Microsoft.AspNetCore.Mvc.Controller
控制器是一个可实例化的类,其中下列条件至少某一个为 true:
类名称带有“Controller”后缀
该类继承自带有“Controller”后缀的类
使用 [Controller] 属性修饰该类
控制器上的公共方法(除了那些使用 [NonAction] 属性装饰的方法)均为操作。操作上的参数会绑定到请求数据,并使用模型绑定进行验证。所有模型绑定的内容都会执行模型验证。 ModelState.IsValid 属性值指示模型绑定和验证是否成功。
操作方法应包含用于将请求映射到某个业务关注点的逻辑,业务关注点通常应当表示为控制器通过依赖关系注入来访问的服务,然后,操作将业务操作的结果映射到应用程序状态。
操作可以返回任何内容,但是经常返回生成响应的 IActionResult(或异步方法的 Task)的实例,操作方法负责选择响应的类型,操作结果会做出响应。
添加控制器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using Microsoft.AspNetCore.Mvc;using System.Text.Encodings.Web;namespace MvcHello.Controllers { public class HelloWorldController : Controller { public string Index ( ) { return "This is my default action..." ; } public string Welcome ( ) { return "This is the Welcome action method..." ; } } }
View 创建视图 在 Views / [ControllerName] 文件夹中创建特定于控制器的视图。 控制器之间共享的视图都将置于 Views/Shared 文件夹。 要创建一个视图,请添加新文件,并将其命名为与 .cshtml 文件扩展名相关联的控制器操作的相同名称。
Razor 标记以 @ 符号开头。 通过将 C# 代码放置在用大括号 ({ … }) 括住的 Razor 代码块内,运行 C# 语句。如下所示:
1 2 3 4 5 6 7 @{ ViewData["Title" ] = "About" ; } <h2>@ViewData["Title" ].</h2> <h3>@ViewData["Message" ]</h3> <p>Use this area to provide additional information.</p>
添加视图 在 HelloWorldController 类中,将 Index 方法替换为以下代码:
1 2 3 public IActionResult Index ( ) { return View(); }
为 HelloWorldController 添加 Index 视图。
添加一个名为“Views/HelloWorld”的新文件夹。
向 Views/HelloWorld 文件夹添加名为“Index.cshtml”的新文件,并添加内容。
更改标题和页脚等 修改Views/Shared/_Layout.cshtml 文件中的相关内容,并确认 Views/_ViewStart.cshtml 文件:
1 2 3 @{ Layout = "_Layout" ; }
Views/_ViewStart.cshtml 文件将 Views/Shared/_Layout.cshtml 文件引入到每个视图中。 可以使用 Layout 属性设置不同的布局视图,或将它设置为 null,这样将不会使用任何布局文件。
控制器传递数据 强类型数据 (ViewModel) 使用 viewmodel 将数据传递给视图可让视图充分利用强类型检查。 强类型化(或强类型)意味着每个变量和常量都有明确定义的类型(例如 string、int 或 DateTime)。 在编译时检查视图中使用的类型是否有效。
使用 @model 指令指定模型。 使用带有 @Model 的模型:
1 2 3 4 5 6 7 8 @model WebApplication1.ViewModels.Address <h2>Contact</h2> <address> @Model.Street<br> @Model.City, @Model.State @Model.PostalCode<br> <abbr title="Phone" >P:</abbr> 425.555 .0100 </address>
为了将模型提供给视图,控制器将其作为参数进行传递:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public IActionResult Contact ( ) { ViewData["Message" ] = "Your contact page." ; var viewModel = new Address() { Name = "Microsoft" , Street = "One Microsoft Way" , City = "Redmond" , State = "WA" , PostalCode = "98052-6399" }; return View(viewModel); }
弱类型数据(ViewData、ViewData 属性和 ViewBag) 与强类型不同,弱类型(或松散类型)意味着不显式声明要使用的数据类型,可以使用弱类型数据集合将少量数据传入及传出控制器和视图。ViewBag 在 Razor 页中不可用。
ViewData 是通过 string 键访问的 ViewDataDictionary 对象。 字符串数据可以直接存储和使用,而不需要强制转换,但是在提取其他 ViewData 对象值时必须将其强制转换为特定类型。可以让控制器将视图模板所需的动态数据(参数)放置在视图模板稍后可以访问的 ViewData 字典中,将Welcome方法改为如下内容:
1 2 3 4 5 6 7 public IActionResult Welcome (string name, int numTimes = 1 ) { ViewData["Message" ] = "Hello " + name; ViewData["NumTimes" ] = numTimes; return View(); }
创建一个名为 Views/HelloWorld/Welcome.cshtml 的 Welcome 视图模板,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 @{ ViewData["Title"] = "Welcome"; } <h2 > Welcome</h2 > <ul > @for (int i = 0; i < (int )ViewData ["NumTimes "]; i ++) { <li>@ViewData["Message"]</li> } </ul >
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public IActionResult SomeAction ( ) { ViewData["Greeting" ] = "Hello" ; ViewData["Address" ] = new Address() { Name = "Steve" , Street = "123 Main St" , City = "Hudson" , State = "OH" , PostalCode = "44236" }; return View(); }
在视图中处理数据:
1 2 3 4 5 6 7 8 9 10 11 12 @{ // Since Address isn't a string, it requires a cast. var address = ViewData["Address"] as Address; } @ViewData["Greeting"] World! <address > @address.Name<br > @address.Street<br > @address.City, @address.State @address.PostalCode </address >
Model 可以结合 Entity Framework Core (EF Core) 使用来处理数据库,EF Core 是对象关系映射 (ORM) 框架,可以简化需要编写的数据访问代码。要创建的模型类称为 POCO 类(源自“简单传统 CLR 对象”),因为它们与 EF Core 没有任何依赖关系,它们只定义将存储在数据库中的数据的属性。
建表 在数据库中建立表,需要与实体类对应。
安装相关包 通过Nuget包管理器安装MySql.Data.EntityFrameworkCore包:
1 $ dotnet add package MySql.Data.EntityFrameworkCore
添加实体类 在“Models”文件夹下添加“User.cs”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using System;using System.ComponentModel.DataAnnotations;namespace MvcHello.Models { public class User { [Key ] public int Id { get ; set ; } public string Name { get ; set ; } public string Password { get ; set ; } } }
User 类可包含:
数据库需要 Id 字段以获取主键。
[DataType(DataType.Date)]:DataType 属性指定数据的类型 (Date)。 通过此特性:
用户无需在数据字段中输入时间信息。
仅显示日期,而非时间信息。
添加数据库上下文类 在根目录下加上 DataAccess 目录做为数据库操作目录,在该目录下加上 Base 目录做数据库上下文目录,在Base目录下添加上下文类,继承 DbContext 类,通过构造函数注入数据库连接,添加 DbSet 实体属性,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using Microsoft.EntityFrameworkCore;namespace MvcHello.DataAccess.Base { public class UserDbContext : DbContext { public UserDbContext (DbContextOptions<UserDbContext> options ): base (options ) { } public DbSet<MvcHello.Models.User> User { get ; set ; } } }
添加数据库连接配置(MySQL) 将连接字符串添加到 appsettings.json 文件:
1 2 3 4 5 6 7 8 9 10 11 { "Logging" : { "LogLevel" : { "Default" : "Warning" } }, "AllowedHosts" : "*" , "ConnectionStrings" : { "UserDbContext" : "server=localhost;user id=root;password=123456;database=asp;charset=utf8;sslMode=None" } }
添加数据库操作服务类 在 DataAccess 目录下新建 Interface 目录,用于保存数据库操作的接口,在该目录下新建 IUserDao 接口,在接口里增加相关数据库添、删、改、查接口,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 namespace MvcHello.DataAccess.Interface { public interface IUserDao { bool CreateUser (User user ) ; IEnumerable<User> GetUsers ( ) ; User GetUserByID (int id ) ; bool UpdateUser (User user ) ; bool UpdateNameByID (int id, string name ) ; bool DeleteUserByID (int id ) ; } }
在 DataAccess 目录下新建 Implement 目录,用于保存数据库操作接口的实现,在该目录下新建 UserDao 类,继承 IUserDao 接口,实现接口里的数据库操作方法,在构造函数注入 UserDbContext 数据库上下文,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 namespace MvcHello.DataAccess.Implement { public class UserDao : IUserDao { public UserDbContext Context; public UserDao (UserDbContext context ) { Context = context; } public bool CreateUser (User user ) { Context.User.Add(user); return Context.SaveChanges() > 0 ; } public IEnumerable<User> GetUsers ( ) { return Context.User.ToList(); } public User GetUserByID (int id ) { return Context.User.SingleOrDefault(s => s.Id == id); } public bool UpdateUser (User user ) { Context.User.Update(user); return Context.SaveChanges() > 0 ; } public bool UpdateNameByID (int id, string name ) { var state = false ; var user = Context.User.SingleOrDefault(s => s.Id == id); if (user != null ) { user.Name = name; state = Context.SaveChanges() > 0 ; } return state; } public bool DeleteUserByID (int id ) { var user = Context.User.SingleOrDefault(s => s.Id == id); Context.User.Remove(user); return Context.SaveChanges() > 0 ; } } }
控制器中注入使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public ActionResult<string > Create (string name, string password ) { if (string .IsNullOrEmpty(name.Trim())) { return "姓名不能为空" ; } if (string .IsNullOrEmpty(password.Trim())) { return "密码不能为空" ; } var user = new User() { Name = name, Password = password }; var result = iHelloDao.CreateUser(user); if (result) { return "学生插入成功" ; } else { return "学生插入失败" ; } } public ActionResult<string > Gets ( ) { var names = "没有数据" ; var users = iHelloDao.GetUsers(); if (users != null ) { names = "" ; foreach (var s in users) { names += $"{s.Name} \r\n" ; } } return names; }
在Starup.cs中注册数据库服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void ConfigureServices (IServiceCollection services ) { services.Configure<CookiePolicyOptions>(options => { options.CheckConsentNeeded = context => true ; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddDbContext<UserDbContext>(d => d.UseMySQL(Configuration.GetConnectionString("UserDbContext" ))); services.AddScoped<IUserDao, UserDao>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }