文章概要


ASP.NET Core 2.1 快速学习、入门系列教程,这个入门系列教程为了帮助大家快速上手ASP.NET Core。


本教程包含且不限于:

  • 使用VS Code开发ASP.NET Core应用
  • ASP.NET Core MVC 开发(路由、控制器、视图)
  • EF Core(连接MySQL,基础数据库操作示例)
  • NLog日志组件使用
  • ASP.NET Core 中间件等等

本教程示例代码:
https://github.com/ken-io/asp.net-core-tutorial
https://gitee.com/ken-io/asp.net-core-tutorial

使用ASP.NET Core 构建第一个Web应用

一、前言

1、本文主要内容

  • Visual Studio Code 开发环境配置
  • 使用 ASP.NET Core 构建Web应用
  • ASP.NET Core Web 应用启动类说明
  • ASP.NET Core Web 项目结构说明

    2、本教程环境信息

    | 软件/环境 | 说明 | | —- | —- | | 操作系统 | Windows 10 | | SDK | 2.1.401 | | ASP.NET Core | 2.1.3 | | IDE | Visual Studio Code 1.27 | | 浏览器 | Chrome 69 |

3、前置知识

你可能需要的前置知识

  • VS Code + .NET Core快速开始

https://ken.io/serie/dotnet-core-quickstart

  • C#语法学习

http://www.runoob.com/csharp/csharp-tutorial.html

二、环境安装与配置

1、SDK 下载与安装

  • 下载

下载地址:https://www.microsoft.com/net/download
跨平台,根据自己的需求选择即可。
这里我下载的是:SDK 2.1.401,你可以选择2.1.x的最新版本

  • 安装

略,一直下一步即可,没什么需要特别注意的。
如果你真想了解,可以参考:https://ken.io/note/dotnet-core-qucikstart-helloworld-windows

2、VS Code下载&安装

  • VS Code 下载

下载地址:https://code.visualstudio.com/download
反正VS Code跨平台,根据自己的需要选择就可以了,

  • VS Code 安装

略,一直下一步即可,没什么特别注意的。
如果你用的macOS,直接拖动到应用程序目录即可,更简单快捷。

3、VS Code配置

  • 基础扩展安装 | 扩展 | 说明 | | —- | —- | | C# | 包括语法高亮显示、智能感知、定义、查找所有引用等。调试支持。网络核心(CoreCLR)。 | | Chinese (Simplified) | 简体中文补丁包 |

快捷键(Ctrl+Shift+X)进入扩展管理页,直接搜索扩展名安装即可,或者点击左侧工具栏图标进入扩展管理页

macOS版本快捷键是 Shift+Commnad+X

ASP.NET Core 2.1 入门教程 - 图1

三、VS Code 开发 ASP.NET Core Web项目

1、项目创建

  • 通过命令行创建项目
    1. #创建项目目录
    2. mkdir projects
    3. #进入项目目录
    4. cd projects
    5. #创建项目
    6. dotnet new web -n helloweb

    2、VS Code打开项目

    菜单:文件->打开,选择项目目录打开项目
    项目打开后,VS Code会检测到缺少两个必须的Package:OmniSharp、.NET Core Debugger
    并且会自动帮你安装
    1. Downloading package 'OmniSharp for Windows (.NET 4.6 / x64)' (31017 KB).................... Done!
    2. Installing package 'OmniSharp for Windows (.NET 4.6 / x64)'
    3. Downloading package '.NET Core Debugger (Windows / x64)' (41984 KB).................... Done!
    4. Installing package '.NET Core Debugger (Windows / x64)'
    5. Finished
    安装完成后VS Code会提示:

    Required assets to build and debug are missing from ‘helloweb’. Add them?

ASP.NET Core 2.1 入门教程 - 图2
选择Yes即可。
这时候,可以看一下左侧资源管理器,我们可以看到.vscode目录添加了两个配置文件:launch.json,tasks.json。
项目的编译和调试配置文件就已经准备好了
ASP.NET Core 2.1 入门教程 - 图3

3、VS Code启动项目

我们直接按下F5,或者菜单:调试->启动调试启动项目
ASP.NET Core 默认绑定是5001端口,而且ASP.NET Core 2.1之后默认绑定了HTTPS,项目启动成功后,VS Code会帮我们打开默认浏览器并访问:https://localhost:5001
因为我们并没有配置SSL证书,所以浏览器会发出警告⚠️,以Chrome为例:
ASP.NET Core 2.1 入门教程 - 图4
这时候,我们点击高级,救护出现继续访问的入口
ASP.NET Core 2.1 入门教程 - 图5
我们点击继续访问,就会出现Hello World!
ASP.NET Core 2.1 入门教程 - 图6

4、修改绑定协议HTTPS为HTTP

接着我们可以修改配置去掉HTTPS协议绑定
打开Properties/launchSettings.json文件

  1. {
  2. "iisSettings": {
  3. "windowsAuthentication": false,
  4. "anonymousAuthentication": true,
  5. "iisExpress": {
  6. "applicationUrl": "http://localhost:53122",
  7. "sslPort": 44309
  8. }
  9. },
  10. "profiles": {
  11. "IIS Express": {
  12. "commandName": "IISExpress",
  13. "launchBrowser": true,
  14. "environmentVariables": {
  15. "ASPNETCORE_ENVIRONMENT": "Development"
  16. }
  17. },
  18. "helloweb": {
  19. "commandName": "Project",
  20. "launchBrowser": true,
  21. "applicationUrl": "https://localhost:5001;http://localhost:5000",
  22. "environmentVariables": {
  23. "ASPNETCORE_ENVIRONMENT": "Development"
  24. }
  25. }
  26. }
  27. }

iisSettings、profiles.helloweb配置节点都有启动绑定配置,因为VS Code启动项目默认是不通过IIS来host的,iisSettings选项我们忽略即可。

  1. "helloweb": {
  2. "commandName": "Project",
  3. "launchBrowser": true,
  4. "applicationUrl": "http://localhost:5001",
  5. "environmentVariables": {
  6. "ASPNETCORE_ENVIRONMENT": "Development"
  7. }
  8. }

将applicationUrl修改为http://localhost:5001
然后重启项目(Ctrl+Shift+F5)机会看到干净纯洁的Hello World!
ASP.NET Core 2.1 入门教程 - 图7

5、项目启动简介

  • 应用程序入口类

    1. public class Program
    2. {
    3. public static void Main(string[] args)
    4. {
    5. CreateWebHostBuilder(args).Build().Run();
    6. }
    7. public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    8. WebHost.CreateDefaultBuilder(args)
    9. .UseStartup<Startup>();
    10. }

    在应用启动的时候,会执行CreateWebHostBuilder方法,在这个方法中通过类Startup创建了默认了HostBuilder

  • 应用启动类

    1. public class Startup
    2. {
    3. ConfigureServices(IServiceCollection services)
    4. {
    5. }
    6. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    7. {
    8. if (env.IsDevelopment())
    9. {
    10. app.UseDeveloperExceptionPage();
    11. }
    12. app.Run(async (context) =>
    13. {
    14. await context.Response.WriteAsync("Hello World!");
    15. });
    16. }
    17. }

    | 方法 | 说明 | | —- | —- | | ConfigureServices | 用于配置应用启动时加载的Service | | Configure | 用于配置HTTP请求管道 |

web项目模板默认在项目启动的时候调用IApplicationBuilder.run方法,在当前HTTP上下文(HttpContext)中输出了Hello World!

context.Response.WriteAsync(“Hello World!”);

四、备注

1、项目结构说明

根目录/文件 说明
.vscode目录 VS Code项目配置目录,相当于.vs、.idea文件夹
bin目录 编译输出目录,相当于Java项目的target目录
obj目录 编译配置与中间目录,用于存放编译配置与编译中间结果
Properties目录 用于存放项目配置
wwwroot目录 静态文件目录
helloweb.csproj文件 项目描述文件
Program.cs文件 应用程序入口类文件
Startup.cs文件 ASP.NET Core Web应用启动类文件,用于项目启动前进行相关配置

2、附录

  • 本文代码示例

https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-01

使用ASP.NET Core MVC框架构建Web应用

一、前言

1、本文主要内容

  • 使用dotnet cli创建基于解决方案(sln+csproj)的项目
  • 使用Visual Studio Code开发基于解决方案(sln+csproj)的项目
  • Visual Studio Code Solution插件( vscode-solution-explorer)基础使用介绍
  • 基于 .NET Core web项目模板构建 ASP.NET Core MVC Web应用
  • ASP.NET Core MVC框架上手

    2、本教程环境信息

    | 软件/环境 | 说明 | | —- | —- | | 操作系统 | Windows 10 | | SDK | 2.1.401 | | ASP.NET Core | 2.1.3 | | IDE | Visual Studio Code 1.27 | | 浏览器 | Chrome 69 |

3、前置知识

你可能需要的前置知识

  • MVC框架/模式介绍

https://baike.baidu.com/item/mvc

  • 控制反转(IOC)原则与依赖注入(DI)

ASP.NET Core 默认集成了DI。所有官方模块的引入都要使用DI的方式引入。
https://baike.baidu.com/item/IOC

二、项目准备

1、项目创建

.NET平台的项目构建有两个概念:解决方案(Solution)、项目(Project)。
所有的项目开发,不论是Web项目,还是控制台应用程序,都必须基于Project来构建。而Solution的作用就是把Project组织起来
如果项目简单,我们只需要基于Project来构建项目即可,但是当项目需要分层解耦时,我们如果在Project创建目录来隔离并不能起到硬性隔离的作用,毕竟只要在一个Project中就可以引用。而通过Project来分层就可以做到硬性隔离的效果。而且基于Project的代码复用更简洁合理(编译产出.dll可以在其他项目中引用等)

解决方案(Solution)+ 项目(Project)就相当于用Maven构建的Java项目中,顶层Project和Project的关系。

  • 创建项目目录

    1. #创建项目目录
    2. mkdir Ken.Tutorial
    3. #进入项目目录
    4. cd Ken.Tutorial
  • 创建解决方案文件

    1. dotnet new sln -n Ken.Tutorial
  • 创建Web项目

    1. dotnet new web -n Ken.Tutorial.Web
  • 将项目添加到解决方案中

    1. dotnet sln add Ken.Tutorial.Web

    2、VS Code 配置

  • 安装基于Solution开发 .NET Core 项目的扩展 | 扩展名 | 说明 | | —- | —- | | vscode-solution-explorer | 创建、删除、重命名或移动解决方案、解决方案文件夹和项目。管理项目引用。 |

VS Code 扩展管理页直接搜索扩展名安装即可,本次安装的版本是:0.2.33

三、VS Code开发基于解决方案的项目说明

1、VS Code项目配置

菜单:文件->打开文件夹,选择项目目录打开项目
因为已经安装了VS Code的C#扩展和Solution扩展,所以也会提示缺失相关配置
C#扩展提示:

Required assets to build and debug are missing from ‘helloweb’. Add them?

ASP.NET Core 2.1 入门教程 - 图8
这是因为项目缺少编译、调试配置,选择Yes即可
vscode-solution-explorer扩展提示:

Would you like to create the vscode-solution-explorer templates folder?

ASP.NET Core 2.1 入门教程 - 图9
这是因为vscode-solution-explorer插件需要项目中的解决方案提供相应的模板。
所有插件默认的配置文件,都会放在.vscode文件夹中
ASP.NET Core 2.1 入门教程 - 图10
资源管理器中除了默认的面板,我们安装的Solution插件还会提供友好的Solution Explorer。这个视图的风格,有VS(Visual Studio)的既视感。
后续项目开发完全可以隐藏默认资源管理器,使用Solution Explorer就好。

2、Solution Explorer菜单介绍

  • Solution鼠标右键菜单介绍

ASP.NET Core 2.1 入门教程 - 图11

菜单 快捷键 说明
Add existing project / 添加已存在的项目(Project)
Add new project / 新建项目(Project)
Create folder Ctrl+Shift+F 创建文件夹
Open File / 打开解决方案文件(.sln)
Rename F2 修改解决方案名称
Build / 编译解决方案(Solution)
Clean / 清理解决方案(Solution)的编译输出
Pack / 解决方案(Solution)打包
Publish / 发布解决方案(Solution)
Restore / 恢复解决方案(Solution)
Test / 执行解决方案(Solution)中的单元测试
  • Project鼠标右键菜单介绍

ASP.NET Core 2.1 入门教程 - 图12

菜单 快捷键 说明
Add package / 添加package
Add reference / 引用解决方案中的其他项目
Create file Ctrl+Shift+A 创建文件
Create folder Ctrl+Shift+F 创建文件夹
Move / 移动项目(Project)
Remove project from solution Del 从解决方案中移除项目(Project)
Paste Ctrl+V 粘贴
Open File / 打开项目文件(.csproj)
Rename F2 修改解决方案名称
Build / 编译项目(Project)
Clean / 清理项目(Project)的编译输出
Pack / 项目(Project)打包
Publish / 发布项目(Project)
Restore / 恢复项目(Project)
Test / 执行项目(Project)中的单元测试

四、ASP.NET Core MVC 输出HelloWorld

1、引入 ASP.NET Core MVC

修改应用启动类(Startup.cs),引入MVC模块并配置默认路由

  1. public class Startup
  2. {
  3. public void ConfigureServices(IServiceCollection services)
  4. {
  5. //引入MVC模块
  6. services.AddMvc();
  7. }
  8. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  9. {
  10. if (env.IsDevelopment())
  11. {
  12. app.UseDeveloperExceptionPage();
  13. }
  14. app.UseMvc(routes =>
  15. {
  16. //配置默认路由
  17. routes.MapRoute(
  18. name: "Default",
  19. template: "{controller}/{action}",
  20. defaults: new { controller = "Home", action = "Index" }
  21. );
  22. });
  23. }
  24. }

2、创建Controller与Action

  • 创建HomeController

在Ken.Tutorial.Web项目中添加文件夹:Controllers,并在文件夹中创建类HomeController类型选择为:class

  1. using System;
  2. namespace Ken.Tutorial.Web.Controllers
  3. {
  4. public class HomeController
  5. {
  6. }
  7. }
  • 引用MVC命名空间,并继承与Controller

    1. using System;
    2. using Microsoft.AspNetCore.Mvc;
    3. namespace Ken.Tutorial.Web.Controllers
    4. {
    5. public class HomeController : Controller
    6. {
    7. }
    8. }

    ControllerName=Home

  • 定义Action:Index

    1. using System;
    2. using Microsoft.AspNetCore.Mvc;
    3. namespace Ken.Tutorial.Web.Controllers
    4. {
    5. public class HomeController : Controller
    6. {
    7. public IActionResult Index()
    8. {
    9. return Content("Hello World!");
    10. }
    11. }
    12. }

    ActionName=Index

    3、项目启动与访问测试

  • 修改协议与端口

修改Ken.Tutorial.Web项目Properties文件夹中launchSettings.json文件,使用HTTP协议并监听端口5001

  1. "Ken.Tutorial.Web": {
  2. "commandName": "Project",
  3. "launchBrowser": true,
  4. "applicationUrl": "http://localhost:5001",
  5. "environmentVariables": {
  6. "ASPNETCORE_ENVIRONMENT": "Development"
  7. }
  • 启动项目

按下F5启动项目,项目启动成功后,VS Code会帮我们打开默认浏览器并访问:http://localhost:5001
ASP.NET Core 2.1 入门教程 - 图13
之所以显示HomeController中Index(Action)的返回内容,是因为我们前面定义了默认路由可以从{controller}/{action}访问路径对应Action,而我们又定义了默认值:
controller = "Home", action = "Index"

  1. routes.MapRoute(
  2. name: "Default",
  3. template: "{controller}/{action}",
  4. defaults: new { controller = "Home", action = "Index" }
  5. );

我们也可以通过http://localhost:5001/home/index显示访问

五、ASP.NET Core 视图基础使用

1、创建返回View的Action

HomeController添加Action:Time

  1. public IActionResult Time()
  2. {
  3. //将当前服务器时间放入ViewBag中
  4. ViewBag.ServerTime = DateTime.Now;
  5. return View("Time");
  6. }

2、创建视图文件

在项目中创建文件夹 Views,并创建对应的HomeController视图子文件夹:Home
之所以这样创建文件夹,是因为当我们返回视图时,只指定ViewName,而不指定完整的路径。ASP.NET Core MVC框架会默认在以下项目目录中依次读取视图文件:

  • /Views/{ControllerName}
  • /Views/Shared
  • /Pages/Shared

如果找到视图文件便会渲染视图,如果没找到便会抛出异常。
创建视图文件 /Views/Home/Time.cshtml

  1. @ViewBag.ServerTime -ken.io

视图渲染时@ ViewBag.ServerTime会输出Action中赋值的内容,
-ken.io会被作为字符串渲染

3、启动项目测试

按下F5启动项目,项目启动成功后在浏览器中输入http://localhost:5001/home/time并访问,将会看到以下输出:
ASP.NET Core 2.1 入门教程 - 图14

六、备注

1、附录

  • 本文代码示例

https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-02

ASP.NET Core MVC路由入门

一、前言

1、本文主要内容

  • ASP.NET Core MVC路由工作原理概述
  • ASP.NET Core MVC带路径参数的路由示例
  • ASP.NET Core MVC固定前/后缀的路由示例
  • ASP.NET Core MVC正则表达式匹配路由示例
  • ASP.NET Core MVC路由约束与自定义路由约束
  • ASP.NET Core MVC RouteAttribute绑定式路由使用介绍

    2、本教程环境信息

    | 软件/环境 | 说明 | | —- | —- | | 操作系统 | Windows 10 | | SDK | 2.1.401 | | ASP.NET Core | 2.1.3 | | IDE | Visual Studio Code 1.27 | | 浏览器 | Chrome 69 |

本篇代码基于上一篇进行调整:https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-02

3、前置知识

你可能需要的前置知识

  • MVC框架/模式介绍

https://baike.baidu.com/item/mvc

  • 正则表达式

http://www.runoob.com/regexp/regexp-tutorial.html

二、ASP.NET Core MVC 路由简介

1、ASP.NET Core MVC路由工作原理概述

ASP.NET Core MVC路由的作用就是将应用接收到请求转发到对应的控制器去处理。
应用启动的时候会将路由中间件(RouterMiddleware)加入到请求处理管道中,并将我们配置好的路由加载到路由集合(RouteCollection)中。当应用接收到请求时,会在路由管道(路由中间件)中执行路由匹配,并将请求交给对应的控制器去处理。
另外,需要特别注意的是,路由的匹配顺序是按照我们定义的顺序从上之下匹配的,遵循是的先配置先生效的原则。

2、路由配置参数说明

参数名 说明
name 路由名称,不可重复
template 路由模板,可在模板中以{name}格式定义路由参数
defaults 配置路由参数默认值
constraints 路由约束

在路由配置中,MVC框架内置了两个参数,controller,action。
路由匹配通过后,需要根据这两个参数将当前请求交由对应的Controller+Action去处理。所以,这两个参数缺少任何一个,都会导致路由无法正常工作。
通常我们有两个选择:

  • 在template中指定{controller},{action}参数
  • 在默认值中为controller、action指定默认值

    三、ASP.NET Core MVC 路由示例

    1、准备工作

    为了方便我们进行测试,我们先准备好承接路由的Controller&Action

  • 创建TutorialController

在Controllers文件夹下新增控制器TutorialController.cs并继承于Controller

  1. using System;
  2. using Microsoft.AspNetCore.Mvc;
  3. namespace Ken.Tutorial.Web.Controllers
  4. {
  5. public class TutorialController : Controller
  6. {
  7. }
  8. }
  • 增加Action:Index

    1. public IActionResult Index()
    2. {
    3. return Content("ASP.NET Core Tutorial by ken from ken.io");
    4. }
  • 增加Action:Welcome

    1. public IActionResult Welcome(string name, int age)
    2. {
    3. return Content($"Welcome {name}(age:{age}) !");
    4. }

    2、带路径参数的路由

    路由配置:

    1. routes.MapRoute(
    2. name: "TutorialPathValueRoute",
    3. template: "{controller}/{action}/{name}/{age}"
    4. );

    此路由适配URL:

  • /tutorial/welcome/ken/20

不适配URL:

  • /tutorial/welcome/ken

如果我们希望不在路径中设置age,也可以被路由到,那么可以将age指定为可选参数,将模板中的{age}修改为{age?}即可

  1. routes.MapRoute(
  2. name: "TutorialPathValueRoute",
  3. template: "{controller}/{action}/{name}/{age?}"
  4. );

此路由适配URL:

  • /tutorial/welcome/ken/20
  • /tutorial/welcome/ken
  • /tutorial/welcome/ken?age=20

    3、固定前后缀的路由

    固定前缀路由配置:

    1. routes.MapRoute(
    2. name: "TutorialPrefixRoute",
    3. template: "jiaocheng/{action}",
    4. defaults: new { controller = "Tutorial" }
    5. );

    此路由适配URL:

  • /jiaocheng/index

  • /jiaocheng/welcome

由于路径参数中不包含controller参数,所以需要在默认值中指定。
固定后缀路由配置

  1. routes.MapRoute(
  2. name: "TutorialSuffixRoute",
  3. template: "{controller}/{action}.html"
  4. );

此路由适配URL:

  • /tutorial/index.html
  • /tutorial/welcome.html
  • /home/index.html
  • /home/time.html

固定后缀的路由适用于伪静态等诉求
固定前后缀可以根据自己的需求结合起来使用。
当然,你也可以在路由模板中间设定固定值。

四、ASP.NET Core MVC 路由约束

1、路由约束介绍

路由约束主要是用于约束路由参数,在URL格式满足路有模板要求之后,进行参数检查。如果参数不满足路由约束,那么依然会返回未匹配该路由。最常用的可能就是参数类型校验、参数长度校验、以及通过正则满足的复杂校验。
在开始之前需要在Startup.cs中引用相关命名空间

  1. using Microsoft.AspNetCore.Routing;
  2. using Microsoft.AspNetCore.Routing.Constraints;

2、参数长度约束

路由配置:约束name长度不能>5

  1. routes.MapRoute(
  2. name: "TutorialLengthRoute",
  3. template: "hello/{name}/{age?}",
  4. defaults: new { controller = "Tutorial", action = "Welcome", name = "ken" },
  5. constraints: new { name = new MaxLengthRouteConstraint(5) }
  6. );

此路由适配

  • /hello
  • /hello/ken
  • /hello/ken/1000

次路由不适配

  • /hello/kenaaaa

我们也可以直接在模板中配置路由约束:

  1. routes.MapRoute(
  2. name: "TutorialLengthRoute2",
  3. template: "hello2/{name:maxlength(5)}/{age?}",
  4. defaults: new { controller = "Tutorial", action = "Welcome", name = "ken" }
  5. );

3、参数范围约束

路由配置:约束 1<=age<=150

  1. routes.MapRoute(
  2. name: "TutorialLengthRoute",
  3. template: "hello/{name}/{age?}",
  4. defaults: new { controller = "Tutorial", action = "Welcome", name = "ken" },
  5. constraints: new { age = new CompositeRouteConstraint(new IRouteConstraint[] {
  6. new IntRouteConstraint(),
  7. new MinRouteConstraint(1),
  8. new MaxRouteConstraint(150) }) }
  9. );

此路由适配:

  • /hello/ken/1
  • /hello/ken/150

此路由不适配

  • /hello/ken/1000

我们也可以直接在模板中配置路由约束:

  1. routes.MapRoute(
  2. name: "TutorialLengthRoute2",
  3. template: "hello2/{name}/{age:range(1,150)?}",
  4. defaults: new { controller = "Tutorial", action = "Welcome", name = "ken" }
  5. );

4、带有正则表达式约束的路由

路由配置:

  1. routes.MapRoute(
  2. name: "TutorialRegexRoute",
  3. template: "welcome/{name}",
  4. defaults: new { controller = "Tutorial", Action = "Welcome" },
  5. constraints: new { name = @"k[a-z]*" }
  6. );

此路由适配:

  • /welcome/k
  • /welcome/ken
  • /welcome/kevin

此路由不适配

  • /welcome/k1
  • /welcome/keN
  • /welcome/tom

这里我们用正则表达式约束了参数name,必须通过正则k[a-z]*匹配通过,即:以小写字母k开头,且后续可跟0到多个小写字母
我们也可以直接在模板中配置路由约束:

  1. routes.MapRoute(
  2. name: "TutorialRegexRoute2",
  3. template: "welcome2/{name:regex(k[a-z]*)}",
  4. defaults: new { controller = "Tutorial", Action = "Welcome" }
  5. );

5、自定义路由约束

1、创建自定义约束

在项目根目录创建目录Common,并在目录创建类:NameRouteConstraint.cs,然后实现接口:IRouteConstraint

  1. using System;
  2. using Microsoft.AspNetCore.Http;
  3. using Microsoft.AspNetCore.Routing;
  4. namespace Ken.Tutorial.Web.Common
  5. {
  6. public class NameRouteConstraint : IRouteConstraint
  7. {
  8. public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
  9. {
  10. string name = values["name"]?.ToString();
  11. if (name == null) return true;
  12. if (name.Length > 5 && name.Contains(",")) return false;
  13. return true;
  14. }
  15. }
  16. }

这里我们约束当name长度>5时,name中不能包含,
2、路由配置
引入命名空间

  1. using Ken.Tutorial.Web.Common;

在ConfigureServices引入路由约束

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. //引入MVC模块
  4. services.AddMvc();
  5. //引入自定义路由约束
  6. services.Configure<RouteOptions>(options =>
  7. {
  8. options.ConstraintMap.Add("name", typeof(NameRouteConstraint));
  9. });
  10. }

配置路由

  1. routes.MapRoute(
  2. name: "TutorialDiyConstraintRoute",
  3. template: "diy/{name}",
  4. defaults: new { controller = "Tutorial", action = "Welcome" },
  5. constraints: new { name = new NameRouteConstraint() }
  6. );

此路由适配:

  • /diy/ken
  • /diy/ken,
  • /diy/kenny

此路由不适配

  • /diy/kenny,

当然,按照惯例,依然可以在模板中配置路由约束

  1. routes.MapRoute(
  2. name: "TutorialDiyConstraintRoute2",
  3. template: "diy2/{name:name}",
  4. defaults: new { controller = "Tutorial", action = "Welcome" }
  5. );

五、ASP.NET Core MVC 绑定式路由配置

1、路由配置风格

  • 集中式配置

前面章节提到的路由配置都是在Startup类中进行的集中式路由配置,集中配置的路由,除了template中没有配置{controller}参数,默认都是对所有控制器(Controller)生效的。这种集中配置的方式一般我们只要配置一个默认路由,其他情况我们只需要不满足默认模板的情况下进行配置即可。尤其是对URL没有友好度要求的应用,例如:后台管理系统

  • 分散式配置/绑定式配置

对于集中式路由配置的方式,如果某个Controller/Action配置了特殊路由,对于代码阅读就会不太友好。不过没关系,ASP.NET Core MVC也提供了RouteAttribute可以让我们在Controller或者Action上直接指定路由模板。
不过要强调的是,一个控制器只能选择其中一种路由配置,如果控制器标记了RouteAttribute进行路由配置,那么集中式配置的路由将不对其生效。

2、绑定式路由配置

在项目Controllers目中新建TestController.cs继承与Controller
并配置Action与路由

  1. using System;
  2. using Microsoft.AspNetCore.Mvc;
  3. namespace Ken.Tutorial.Web.Controllers
  4. {
  5. [Route("/test")]
  6. public class TestController : Controller
  7. {
  8. [Route("")]
  9. [Route("/test/home")]
  10. public IActionResult Index()
  11. {
  12. return Content("ASP.NET Core RouteAttribute test by ken from ken.io");
  13. }
  14. [Route("servertime")]
  15. [Route("/t/t")]
  16. public IActionResult Time(){
  17. return Content($"ServerTime:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} - ken.io");
  18. }
  19. }
  20. }
配置项 说明
[Route(“/test”)] 表示该Controller访问路由前缀为/test,必须以/开头
[Route(“”)] 表示以Controller的路由配置为前缀访问该Action;可以通过/test路由到该Action
[Route(“/test/home”)] 表示忽略Controller的路由配置;可以通过/test/home路由到该Action
[Route(“servertime”)] 表示以Controller的路由配置为前缀访问该Action;可以通过/test/servertime路由到该Action
[Route(“/t/t”)] 表示忽略Controller的路由配置;可以通过/t/t路由到该Action

RouteAttribute中配置的参数,就相当于我们集中式配置中的路由模板(template),最终框架还是帮我们初始化成路由规则,以[Route(“/test/home”)]为例,相当于生成了以下路由配置:

  1. routes.MapRoute(
  2. name: "Default",
  3. template: "test/home",
  4. defaults: new { controller = "Test", action = "Index" }
  5. );

当然,我们也可以在[Route]配置中使用模板参数,而且依然可以在模板中使用约束,自定义约束也没问题。

  1. [Route("welcome/{name:name}")]
  2. public IActionResult Welcome(string name){
  3. return Content($"Welcome {name} !");
  4. }

最大的区别就是不能定义默认值了,可能也不需要了,你说是吧。^_^

六、备注

1、附录

  • 本文代码示例

https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-03

  • 本文参考

https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/routing?view=aspnetcore-2.1

ASP.NET Core MVC控制器入门

一、前言

1、本教程主要内容

  • ASP.NET Core MVC控制器简介
  • ASP.NET Core MVC控制器操作简介
  • ASP.NET Core MVC控制器操作简介返回类型简介
  • ASP.NET Core MVC控制器操作简介返回类型示例
  • ASP.NET Core MVC控制器参数映射逻辑说明
  • ASP.NET Core MVC控制器参数映射/获取示例

    2、本教程环境信息

    | 软件/环境 | 说明 | | —- | —- | | 操作系统 | Windows 10 | | SDK | 2.1.401 | | ASP.NET Core | 2.1.3 | | IDE | Visual Studio Code 1.27 | | 浏览器 | Chrome 69 |

本篇代码以下代码进行调整:https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-02

3、前置知识

你可能需要的前置知识

  • MVC框架/模式介绍

https://baike.baidu.com/item/mvc

二、ASP.NET Core MVC 控制器简介

1、ASP.NET Core MVC 控制器概述

在MVC Web框架中,路由模块会对接收到的请求进行匹配并转交由对应的控制器(Controller)进行处理。
控制器的作用就是处理接收到的请求,解析用户输入并执行对应程序理逻辑,然后返回对应的输出。
用户的输入可以是QueryString、FormData、也可以是HTTP Header、HTTP Body。
控制器的输出格式通常是:HTML、JSON、XML、普通文本

2、控制器(Controller)的定义

所有Controller类都必须直接或间接继承于Microsoft.AspNetCore.Mvc.ControllerBase。为了搭配视图引擎使用,ASP.NET Core MVC 框架内置了 Microsoft.AspNetCore.Mvc.Controller类,提供了一些视图引擎需要的特性。所以,默认我们继承该类即可。

3、控制器(Controller)的命名

Controller类的类名(ClassName)推荐以Controller为结尾(不区分大小写)。
例如:

  • HomeController
  • TestController

路由模块去掉结尾的Controller作为ControllerName。
那么对应的ControllerName则分别是HomeTest。这也是路由映射到Controller的主要标识。
当然,你也可以不以Controller作为控制器类名(ClassName)的固定后缀,那么路由模块会以完整的类名(ClassName)作为ControllerName

在 ASP.NET MVC框架中,控制器(Controller)类名必须以Controller作为后缀,但是在 ASP.NET Core MVC框架中去掉了这个限制。

以下Controller的定义都是可以的:

  1. //推荐
  2. public class HomeController : Controller
  3. {
  4. //ControllerName=Home
  5. }
  6. public class HomeController : BaseController
  7. {
  8. //ControllerName=Home
  9. }
  10. public class Test : Controller
  11. {
  12. //ControllerName=Test
  13. }

三、ASP.NET Core MVC 控制器操作简介

1、ASP.NET Core MVC 控制器操作概述

控制器(Controller)操作(Action)就是控制器接收到请求后实际用与处理请求的程序方法/函数。
Controller接收到请求后根据路由的ActionName找到对应的Action,然后将用户的输入映射到该Action的参数,最终Action实际执行完成后再返回对应的输出。

2、控制器操作(Action)的定义

Action必须是控制器中定义的公有非静态方法,例如:

  1. public class HomeController : Controller
  2. {
  3. public IActionResult Index()
  4. {
  5. return Content("Hello World ! -ken.io");
  6. }
  7. public string Test()
  8. {
  9. return "test";
  10. }
  11. public void DoSomething()
  12. {
  13. //DoSomething
  14. }
  15. }

按照默认的路由配置:

  • Action:Index() 将响应/home/index的请求
  • Action: Test() 将响应/home/test的请求
  • Action: DoSomething() 将响应/home/dosomething的请求

如果你在Controller定义了一个公有的非静态方法,但不想让这个方法处理请求,那么可以标记为NonAction

  1. public class HomeController : Controller
  2. {
  3. [NonAction]
  4. public void LogicMethod(){
  5. }
  6. }

3、控制器操作(Action)返回类型说明

ASP.NET Core MVC 限定 Action返回类型必须是实现了Microsoft.AspNetCore.Mvc.IActionResult接口的类型,框架本身提供了该接口的默认实现Microsoft.AspNetCore.Mvc.ActionResult,并提供了ActionResult类的子类,用于输出不同内容格式的需求。
不过在定义Action方法的时候,返回值类型也可以定义成string、int等,这些自定义的返回类型会在返回到响应流之前被框架自动包装到合适的ActionResult子类型中。
常用的ActionResult子类说明

Action返回类型 Controller内置方法 说明
ViewResult View() 将视图数据交由Razor视图引擎渲染
PartialViewResult PartialView() 将视图数据交由Razor视图引擎部分视图(PartialView)渲染
ContentResult Content() 返回自定义文本
JsonResult Json() 返回对象的JSON序列化结果
FileResult File() 返回要写入响应中的二进制输出
RedirectResult Redirect() 重定向到指定的Url
RedirectToRouteResult RedirectToAction(),RedirectToRoute() 重定向到指定的Action或者路由
EmptyResult / 在Action返回null或者Action定义返回关键字是void时会被包装为EmptyResult

四、ASP.NET Core MVC Action方法返回类型示例

1、准备工作

在Controllers文件夹中新建ActionResultTestController.cs并继承于Controller类用于测试。

  1. using System;
  2. using Microsoft.AspNetCore.Mvc;
  3. namespace Ken.Tutorial.Web.Controllers
  4. {
  5. public class ActionResultTestController : Controller
  6. {
  7. }
  8. }

在Startup.cs配置该测试控制器专用路由

  1. //配置ActionResult测试专用路由
  2. routes.MapRoute(
  3. name: "ActionResultTest",
  4. template: "art/{action}",
  5. defaults: new { controller = "ActionResultTest"}
  6. );

2、ContentResult使用示例

定义返回ContentResult的Action

  1. public IActionResult ContentTest()
  2. {
  3. return Content("Content Result Test --ken.io");
  4. }

启动项目,浏览器访问 {host:port}/art/contenttest,将看到以下输出:

  1. ContentResult Test by ken.io

3、JsonResult使用示例

定义返回JsonResult的Action

  1. public IActionResult JsonTest()
  2. {
  3. return Json(new { Message = "JsonResult Test", Author = "ken.io" });
  4. }

启动项目,浏览器访问 {host:port}/art/jsontest,将看到以下输出

  1. {
  2. "message": "JsonResult Test",
  3. "author": "ken.io"
  4. }

4、FileResult使用示例

定义返回FileResult的Action

  1. public IActionResult FileTest()
  2. {
  3. var bytes = Encoding.Default.GetBytes("FileResult Test by ken.io");
  4. return File(bytes, "application/text", "filetest.txt");
  5. }

启动项目,浏览器访问 {host:port}/art/jsontest,将会下载文件filetest.txt。
文件内容为:

  1. FileResult Test by ken.io

5、Redirect使用示例

定义返回Redirect相关的Action

  1. public IActionResult RedirectTest()
  2. {
  3. return Redirect("https://ken.io");
  4. }
  5. public IActionResult RedirectToActionTest()
  6. {
  7. return RedirectToAction("jsontest");
  8. }
  9. public IActionResult RedirectToRouteTest()
  10. {
  11. return RedirectToRoute("Default", new { Controller = "home", Action = "index" });
  12. }

启动项目,浏览器访问测试:

  • 访问 /art/redirecttest,将跳转到 https://ken.io
  • 访问 /art/redirecttoactiontest,将跳转到 /art/jsontest
  • 访问 /art/redirecttoroutetest,将跳转到 /

    五、ASP.NET Core MVC Action方法参数映射示例

    1、Action参数映射说明

    路由将请求交由对应的Controller处理时,Controller会找到对应的Action方法,并从RouteData或HTTP请求数据(QueryString、FormData、Header等)找到执行该方法所需要的参数的值。
    如果未找到参数对应的数据,且该参数类型是可以为null的类型,则null将作为参数值传递递,否则将会引发一场。
    另外,Action方法也可以不定义参数,手动从RouteData或HTTP请求数据(QueryString、FormData、Header等)获取对应的参数值。

    2、准备工作

    在Controllers文件夹中新建ParamsMappingTestController.cs并继承于Controller类用于测试。

    1. using System;
    2. using Microsoft.AspNetCore.Mvc;
    3. namespace Ken.Tutorial.Web.Controllers
    4. {
    5. public class ParamsMappingTestController:Controller
    6. {
    7. }
    8. }

    在Startup.cs配置该测试控制器专用路由

    1. //配置参数映射测试专用路由
    2. routes.MapRoute(
    3. name: "ParamsMappingTest",
    4. template: "pmt/{action}/{id?}",
    5. defaults: new { controller = "ParamsMappingTest"}
    6. );

    3、基础参数映射示例

    定义接收路由参数的Action

    1. public IActionResult GetId(int id)
    2. {
    3. return Content($"Action params mapping test by ken.io, id:{id}");
    4. }

    启动应用,浏览器访问 /pmt/getid/1024 或者 /pmt/getid?id=1024,将会看到以下输出:

    1. Action params mapping test by ken.io, id:1024

    或者通过PostMan等工具post访问 /pmt/getid 在HTTP Header参数增加id=1024并发送请求,也会看到同样输出
    ASP.NET Core 2.1 入门教程 - 图15

    4、数组参数参数映射示例

    定义接收数组参数的Action

    1. public IActionResult GetArray(string[] id)
    2. {
    3. var message = "Action params mapping test by ken.io,id:";
    4. if (id != null)
    5. {
    6. message += string.Join(",", id);
    7. }
    8. return Content(message);
    9. }

    应用启动,浏览器访问 /pmt/getarray/1,2 或者 /pmt/getarray?id=1,2,将会看到以下输出:

    1. Action params mapping test by ken.io,id:1,2

    或者通过PostMan等工具post访问 /pmt/getarray 并设置表单参数并发送请求,也会看到同样输出
    ASP.NET Core 2.1 入门教程 - 图16

    5、自定类型参数映射示例

    在项目根目录创建Models文件夹,并创建Person.cs类文件

    1. public class Person
    2. {
    3. public string Name { get; set; }
    4. public int Age { get; set; }
    5. }

    定义接收自定义参数的Action

    1. public IActionResult GetPerson(Person person)
    2. {
    3. return Json(new { Message = "Action params mapping test by ken.io", Data = person });
    4. }

    应用启动,浏览器访问 /pmt/getperson?name=ken&age=18,将会看到以下输出:

    1. {
    2. "message": "Action params mapping test by ken.io",
    3. "data": {
    4. "name": "ken",
    5. "age": 18
    6. }
    7. }

    或者通过PostMan等工具post访问 /pmt/getperson 并设置表单参数并发送请求,也会看到同样输出
    ASP.NET Core 2.1 入门教程 - 图17

    6、自定义类型数组参数映射示例

    定义接收自定义类型数组参数的Action

    1. public IActionResult GetPersonList(List<Person> person)
    2. {
    3. return Json(new { Message = "Action params mapping test by ken.io", Data = person });
    4. }

    启动应用,浏览器访问 /pmt/getpersonlist?person[0].name=ken&person[0].age=18&person[1].name=tom&person[1].age=20
    将会看到以下输出:

    1. {
    2. "message": "Action params mapping test by ken.io",
    3. "data": [
    4. {
    5. "name": "ken",
    6. "age": 18
    7. },
    8. {
    9. "name": "tom",
    10. "age": 20
    11. }
    12. ]
    13. }

    或者通过PostMan等工具post访问 /pmt/getpersonlist 并设置表单参数并发送请求,也会看到同样输出
    ASP.NET Core 2.1 入门教程 - 图18

    7、JSON类型参数映射示例

    定义接收JSON类型参数的Action

    1. public IActionResult GetPersonJson([FromBody]Person person)
    2. {
    3. return Json(new { Message = "Action params mapping test by ken.io", Data = person });
    4. }

    启动应用,这时候我们就只能通过PostMan工具进行测试了
    首先设置 Content-Type=application/json
    ASP.NET Core 2.1 入门教程 - 图19
    然后设置JSON表单参数并发送请求,就会看到对应输出
    ASP.NET Core 2.1 入门教程 - 图20

    8、手动获取参数示例

    定义手动获取参数的Action

    1. public IActionResult GetByHand()
    2. {
    3. return Json(new
    4. {
    5. Id = RouteData.Values["id"],
    6. Name = Request.Query["name"]
    7. });
    8. }

    应用启动后,浏览器访问 /pmt/getbyhand/1024?name=ken&name=tom&age=18
    将看到以下输出:

    1. {
    2. "id": "1024",
    3. "name": [
    4. "ken",
    5. "tom"
    6. ]
    7. }

    RouteData.Values[“id”]:从路由数据中获取数据
    Request.Query[“name”]:从Url参数中获取数据
    Request.Form[“name”]:从表单参数中获取数据

    六、备注

    1、附录

  • 本文代码示例

https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-04

ASP.NET Core MVC 视图传值入门

一、前言

1、本教程主要内容

  • ASP.NET Core MVC 视图引擎(Razor)简介
  • ASP.NET Core MVC 视图(Razor)ViewData使用示例
  • ASP.NET Core MVC 视图(Razor)ViewBag使用示例
  • ASP.NET Core NVC 视图(Razor)强类型传值(ViewModel)页示例

    2、本教程环境信息

    | 软件/环境 | 说明 | | —- | —- | | 操作系统 | Windows 10 | | SDK | 2.1.401 | | ASP.NET Core | 2.1.3 | | IDE | Visual Studio Code 1.28 | | 浏览器 | Chrome 70 |

本篇代码基于以下代码进行调整:https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-02

3、前置知识

你可能需要的前置知识

  • MVC框架/模式介绍

https://baike.baidu.com/item/mvc

4、准备工作

VS Code 本身不提供 ASP.NET Core MVC 视图引擎(Razor)的智能感知。
幸运的是,VS Code C#扩展 从 1.17.0 版本开始支持Razor视图引擎的智能感知。
所以,我们要将VS Code C#扩展升级到最新版本。

二、ASP.NET Core MVC 视图引擎(Razor)简介

1、ASP.NET Core MVC 视图引擎(Razor)概述

在MVC架构模式中,视图引擎/模板引擎负责将控制器(Controller)提供的数据结合视图模板进行渲染我们需要的格式(通常是HTML)。控制器(Controller)再将渲染的结果返回给请求的客户端。
在 ASP.NET Core MVC框架中,提供了视图引擎:Razor。
Razor提供了后缀为.cshtml的视图模板。Razor视图模板支持使用Razor标记语言以及C#进行编写。使用起来非常方便。

Razor 就相当于Java平台常用的 Freemarker、Thymeleaf

2、Razor视图模板文件位置与指定

视图文件位置

Razor视图模板文件通常放在根目录Views文件夹对应控制器的子目录中。
例如: ~/Views/Home/Time.cshtml。
这是因为按照 ASP.NET Core MVC框架的约定,当我们在控制器(Controller)返回一个视图(return View();)时,如果只指定了视图名称(ViewName),并没有指定视图的完成路径,那么MVC框架将按照以下顺序查找视图:

  • Views/[ControllerName]/[ViewName].cshtml
  • Views/Shared/[ViewName].cshtml

    视图指定方式

  • 隐式指定

    1. public class HomeController : Controller
    2. {
    3. public IActionResult Test(){
    4. return View();
    5. }
    6. }

    当没有指定ViewName的时候,ViewName=ActionName=”Test”;
    框架将按照约定顺序查找视图文件

  • 显示指定视图名

    1. public class HomeController : Controller
    2. {
    3. public IActionResult Test(){
    4. return View("Test");
    5. }
    6. public IActionResult TestAbc(){
    7. return View("abc");
    8. }
    9. }

    分别手动指定了视图名;ViewName=”Test”、ViewName=”abc”;
    框架将按照约定顺序查找视图文件

  • 显示指定视图文件

    1. public class HomeController : Controller
    2. {
    3. public IActionResult Test(){
    4. return View("Views/Test.cshtml");
    5. }
    6. }

    固定查找 Views/Test.cshtml 视图文件

    三、Razor视图引擎传递数据

    1、准备工作

  • 创建RenderDataController

在Controllers文件夹下新增控制器RenderDataController.cs并继承于Controller

  1. using System;
  2. using Microsoft.AspNetCore.Mvc;
  3. namespace Ken.Tutorial.Web.Controllers
  4. {
  5. public class RenderDataController : Controller
  6. {
  7. }
  8. }
  • 创建对应视图文件夹

在Views目录下创建文件夹RenderData

2、弱类型参数传递数据

弱类型参数说明

  • ViewData
    • 派生自 ViewDataDictionary,因此它有可用的字典属性,如 ContainsKey、Add、Remove 和 Clear。
    • 字典中的键是字符串,因此允许有空格。 示例:ViewData[“ken”]
    • 任何非 string 类型均须在视图中进行强制转换才能使用 ViewData。
  • ViewBag

    • 派生自 DynamicViewData,因此它可使用点表示法 (@ViewBag.SomeKey = ) 创建动态属性,且无需强制转换。
    • ViewBag 的语法使添加到控制器和视图的速度更快。
    • ViewBag 更易于检查 NULL 值。 示例:@ViewBag.Person?.Name

      ViewData使用示例

  • 创建Action:ViewDataDemo

    1. public IActionResult ViewDataDemo()
    2. {
    3. ViewData["name"] = "ken";
    4. ViewData["birthday"] = new DateTime(2000, 1, 1);
    5. ViewData["hobby"] = new string[] { "跑步", "阅读", "Coding" };
    6. return View();
    7. }
  • 创建视图:ViewDataDemo.cshtml

    1. @{
    2. var hobby =ViewData["hobby"] as String[];
    3. }
    4. <h3>@ViewData["title"]</h3>
    5. <ul>
    6. <li>姓名:@ViewData["name"]</li>
    7. <li>生日:@ViewData["birthday"]</li>
    8. <li>爱好:@hobby[0] , @hobby[1]</li>
    9. </ul>
  • 访问测试

启动项目,访问 /renderdata/viewdatademo 将会看到:
ASP.NET Core 2.1 入门教程 - 图21

ViewBag使用示例

  • 创建Action:ViewBagDemo

    1. public IActionResult ViewBagDemo()
    2. {
    3. ViewBag.Title = "ViewBag传值示例";
    4. ViewBag.Name = "ken";
    5. ViewBag.Birthday = new DateTime(2000, 1, 1);
    6. ViewBag.Hobby = new string[] { "跑步", "阅读", "Coding" };
    7. return View();
    8. }
  • 创建视图:ViewBagDemo.cshtml

    1. @{
    2. var hobby =ViewBag.Hobby as String[];
    3. }
    4. <h3>@ViewBag.Title</h3>
    5. <ul>
    6. <li>姓名:@ViewBag.Name</li>
    7. <li>生日:@ViewBag.Birthday</li>
    8. <li>爱好:@hobby[0] , @hobby[1]</li>
    9. </ul>
  • 访问测试

启动项目,访问 /renderdata/viewbagdemo 将会看到:
ASP.NET Core 2.1 入门教程 - 图22

3、强类型参数传递数据

强类型参数说明

视图强类型通常称为ViewModel,我们可以在return View();时指定视图参数/对象。并在视图文件(.cshtml)中通过 [@model](https://github.com/model) 语法指定对应的类型,这样我们可以在视图文件(.cshtml)中使用Model关键字来使用传输到视图的该类型的实例。

强类型参数示例

  • 创建Person类

在项目根目录创建Models文件夹并在文件中创建Person.cs

  1. using System;
  2. namespace Ken.Tutorial.Web.Models
  3. {
  4. public class Person
  5. {
  6. public string Name { get; set; }
  7. public DateTime Birthday { get; set; }
  8. public string[] Hobby { get; set; }
  9. }
  10. }
  • 创建Action:ViewModelDemo

    1. public IActionResult ViewModelDemo()
    2. {
    3. ViewBag.Title = "ViewModel传值示例";
    4. var person = new Person
    5. {
    6. Name = "ken",
    7. Birthday = new DateTime(2000, 1, 1),
    8. Hobby = new string[] { "跑步", "阅读", "Coding" }
    9. };
    10. //等同于 return View("ViewModelDemo", person);
    11. return View(person);
    12. }
  • 创建视图:ViewModelDemo.cshtml

    1. @model Ken.Tutorial.Web.Models.Person;
    2. <h3>@ViewBag.Title</h3>
    3. <ul>
    4. <li>姓名:@Model.Name</li>
    5. <li>生日:@Model.Birthday</li>
    6. <li>爱好:@Model.Hobby[0] , @Model.Hobby[1]</li>
    7. </ul>
  • 访问测试

启动项目,访问 /renderdata/viewmodeldemo 将会看到:
ASP.NET Core 2.1 入门教程 - 图23

四、备注

1、附录

  • 本文代码示例

https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-05

  • 本文参考

https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/overview?view=aspnetcore-2.1

ASP.NET Core MVC 视图布局入门

一、前言

1、本教程主要内容

  • ASP.NET Core MVC (Razor)视图母版页教程
  • ASP.NET Core MVC (Razor)带有Section的视图母版页教程
  • ASP.NET Core MVC (Razor)视图全局代码(_ViewStart.cshtml)教程

    2、本教程环境信息

    | 软件/环境 | 说明 | | —- | —- | | 操作系统 | Windows 10 | | SDK | 2.1.401 | | ASP.NET Core | 2.1.3 | | IDE | Visual Studio Code 1.28 | | 浏览器 | Chrome 70 |

本篇代码以下代码进行调整:https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-02

3、准备工作

VS Code 本身不提供 ASP.NET Core MVC 视图引擎(Razor)的智能感知。
幸运的是,VS Code C#扩展 从 1.17.0 版本开始支持Razor视图引擎的智能感知。
所以,我们要将VS Code C#扩展升级到最新版本。

二、母版页视图模板

网页中往往有通用的布局,比如导航、底部等等,这些页面中共用的部分,就需要放在母版页里面。
这样每个页面只用关注本页面要完成的功能/内容即可。提高了开发效率,也降低了公共部分的维护成本。
Razor视图引擎原生提供了Layout的概念,作为视图布局的基础,可以让我们在视图中引用另外一个视图作为该视图的母版。

1、创建布局页(Layout)作为母版页

在项目根目录Views文件夹中创建子目录Shared,并在Shared目录中创建母版页 _Layout.cshtml

通常公共的Razor视图文件名都以_开头

  1. <html>
  2. <head>
  3. <title>@ViewBag.Title - Ken.Tutorial</title>
  4. </head>
  5. <body>
  6. <h1>Ken.Tutorial</h1>
  7. @RenderBody()
  8. </body>
  9. </html>

@ViewBag.Title 用于当前应用该模板的视图自定义标题
@RenderBody(`表示渲染当前应用该母版的视图,并填充到当前位置。

2、创建视图作为子页面

创建视图并指定母版页(Layout)

/Views/Home中新建文件Index.cshtml
在页面中可以通过以下方式指定母版页

  • 指定母版页名字

    1. @{
    2. Layout = "_Layout";
    3. }
  • 指定母版页完整路径

    1. @{
    2. Layout = " /Views/Shared/_Layout.cshtml";
    3. }

    以上两种方式任选其一即可

    1. @{
    2. Layout = "_Layout";
    3. }
    4. <h3>@ViewBag.Title</h3>
    5. @ViewBag.Message

    修改Action

    调整 HomeController.cs中Action:Index(),通过视图输出Message

    1. public IActionResult Index()
    2. {
    3. ViewBag.Title = "Home";
    4. ViewBag.Message = "Hello World ! -ken.io";
    5. return View();
    6. }

    3、访问测试

    启动项目,访问 / 或者 /home/index 将会看到:
    ASP.NET Core 2.1 入门教程 - 图24

    三、带片段的母版页视图模板

    通过母版页,我们可以方便的共用一些页面内容或者功能。但是对于一些特殊的子页面可能需要重写母版页中一些内容,或者在母版页中插入自己想呈现的内容,而不是只能将子页面呈现在固定的位置。
    Razor视图引擎提供了Section的概念,我们可以在视图中定义Section,然后再母版视图中通过RenderSection方式加载视图定义的Section

    1、Section的定义与加载

    Section定义

    Section定义在子页面才有效。
    Section定义示例:

    1. @section test{
    2. <p>Section Content</p>
    3. }

    @section:定义Section的关键字
    test:SectionName,命名规则同C#变量名一样,字母或下划线开头后面可以跟字母、下划线、数字

    Section加载

    在母版页中可以通过[@RenderSection](https://github.com/RenderSection)()方法加载子页面中定义的Section

    RenderSection只有在母版页(Layout)中使用才有效

  • 强制加载

    1. @RenderSection("test")
  • 子页面中有定义就加载

    1. @RenderSection("test", false)
  • 子页面中有定义就加载,没有就显示默认内容

    1. @if(IsSectionDefined("test"))
    2. {
    3. RenderSection("test");
    4. }
    5. else
    6. {
    7. <p>Layout Content</p>
    8. }

    2、Section使用示例

    创建Controller与Action

    Controllers文件夹中创建LayoutController.cs

    1. using System;
    2. using Microsoft.AspNetCore.Mvc;
    3. namespace Ken.Tutorial.Web.Controllers
    4. {
    5. public class LayoutController : Controller
    6. {
    7. public IActionResult SectionDemo()
    8. {
    9. return View();
    10. }
    11. }
    12. }

    创建带有Section视图

    Views文件夹中创建Layout文件夹并创建视图文件:SectionDemo.cshtml

    1. @{
    2. Layout = "_Layout";
    3. ViewBag.Title = "SectionDemo";
    4. }
    5. <h3>@ViewBag.Title</h3>
    6. <p>Section Demo by ken.io</p>
    7. @section footer{
    8. <p>Section Footer</p>
    9. }

    修改模板页

    修改 _Layout.cshtml 增加Section加载

    1. <html>
    2. <head>
    3. <title>@ViewBag.Title - Ken.Tutorial</title>
    4. </head>
    5. <body>
    6. <div class="header">
    7. <h1>Ken.Tutorial</h1>
    8. <hr/>
    9. </div>
    10. <div class="content">
    11. @RenderBody()
    12. </div>
    13. <div class="footer">
    14. <hr/>
    15. @if(IsSectionDefined("footer"))
    16. {
    17. RenderSection("footer");
    18. }
    19. else
    20. {
    21. <p>Layout Footer</p>
    22. }
    23. </div>
    24. </body>
    25. </html>

    3、访问测试

    启动项目,通过浏览器进行访问测试://layout/sectiondemo
    访问 /,将看到:
    ASP.NET Core 2.1 入门教程 - 图25
    访问/layout/sectiondemo将看到:
    ASP.NET Core 2.1 入门教程 - 图26

    四、视图呈现之前的全局代码

    Razor视图引擎,提供了在视图呈现之前执行代码的入口。
    这个入口是一个约定的文件即:_ViewStart.cshtml,我们可以通过该文件定义全局视图呈现前执行的代码,也是定义某个文件夹下的视图呈现前需要执行的代码。
    完整路径示例:

  • /Views/_ViewStart.cshtml

  • /Views/Home/_ViewStart.cshtml

如果两个_ViewStart.cshtml文件同时存在,那么/Views/_ViewStart.cshtml的执行优先级高于/Views/Home/_ViewStart.cshtml

全局代码示例

Views文件夹下创建视图文件_ViewStart.cshtml

  1. @{
  2. Layout = "_Layout";
  3. }

这里我们通过全局代码,将所有视图的母版页都指定为_Layout
这样我们在视图子页面就不用逐一制定母版页了。
如果我们将Index.cshtml中指定的Layout注释掉

  1. @{
  2. //Layout = "_Layout";
  3. }

然后启动项目,访问 /,依然看到:
ASP.NET Core 2.1 入门教程 - 图27

局部全局代码示例

/Views/Home文件夹下创建视图文件_ViewStart.cshtml

  1. @{
  2. Layout = null;
  3. }

这里我们局部全局代码,将在/Views/Home文件夹下的所有视图的母版页都指定为null,默认不引用任何母版页。
这时我们启动项目,访问 / ,将看到:
ASP.NET Core 2.1 入门教程 - 图28

五、备注

1、附录

  • 本文代码示例

https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-06

  • 本文参考

https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/overview?view=aspnetcore-2.1

ASP.NET Core MVC 分部视图入门

一、前言

1、本教程主要内容

  • ASP.NET Core MVC (Razor)分部视图简介
  • ASP.NET Core MVC (Razor)分部视图基础教程
  • ASP.NET Core MVC (Razor)强类型分部视图教程

    2、本教程环境信息

    | 软件/环境 | 说明 | | —- | —- | | 操作系统 | Windows 10 | | SDK | 2.1.401 | | ASP.NET Core | 2.1.3 | | IDE | Visual Studio Code 1.30 | | 浏览器 | Chrome 70 |

本篇代码以下代码进行调整:https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-06

3、准备工作

VS Code 本身不提供 ASP.NET Core MVC 视图引擎(Razor)的智能感知。
幸运的是,VS Code C#扩展 从 1.17.0 版本开始支持Razor视图引擎的智能感知。
所以,我们要将VS Code C#扩展升级到最新版本。
另外,要特意说明的是,在VS Code 1.30版本,解决方案(Solution)视图的视图入口改到了侧边工具栏
ASP.NET Core 2.1 入门教程 - 图29

二、ASP.NET Core MVC (Razor)分部视图简介

1、Razor分部视图概述

在Razor视图引擎中,我们可以定义.cshtml文件作为“视图”来渲染需要呈现给用户的内容。对于所有页面共用的部分,我们可以定义母版页(Layout)让视图继承共用的部分。当有些公共的部分我们只在某些页面用到,不需要每个页面都用到。或者这个公共的内容需要作为模板使用多次,母版页就不适合承担这样的作用。这时候我们可以使用分部视图来实现。

2、Razor分部视图定义与引用

Razor分部视图定义

视图与分部视图在定义上并没有本质的不同,均是创建.cshtml文件作为视图使用,只是在渲染的时候作为分部视图来渲染/加载。
在之前提到过,通常公共的Razor视图文件名都以_开头并放在/Views/Shared文件夹中,分部视图也不例外。
例如:/Views/Shared/_PartialViewTest.cshtml
如果分部视图只在某个控制器返回的视图中引用,也可以创建在该控制器对应的视图目录。
例如:/Views/Home/_PartialViewTest.cshtml

Razor分部视图引用
  1. //同步引用
  2. @Html.Partial("_PartialViewTest")
  3. //异步引用(官方推荐)
  4. @await Html.PartialAsync("_PartialViewTest")

微软官方更推荐使用异步加载的方式,因为同步加载可能会出现程序死锁的情况

如果没有使用异步方式,会收到编译器警告:warning MVC1000: Use of IHtmlHelper.Partial may result in application deadlocks. Consider using Tag Helper or IHtmlHelper.PartialAsync.

如果你非常在意性能,也可以使用 Html.RenderPartialAsync 呈现分部视图。 这种方式会直接呈现分部视图的内容,而不会组装成 IHtmlContent 对象放回。

  1. @{
  2. await Html.RenderPartialAsync("_PartialViewTest");
  3. }

由于 Html.RenderPartialAsync并不会返回任何内容,所以需要在Razor语句块中调用
Razor分部视图查找顺序同视图相同:

  • Views/[ControllerName]/[PartialViewName].cshtml
  • Views/Shared/[PartialViewName].cshtml

当然,你也可以直接指定完整路径,例如:

  1. @await Html.PartialAsync("/Views/Home/_PartialViewTest.cshtml")

三、 Razor分部视图基础使用

1、定义分部视图

/Views/Shared目录下创建视图 ‘_DateTimeInfo.cshtml’

  1. 当前时间:@DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")
  2. 当前星期:@DateTime.Now.DayOfWeek

2、创建视图并引用分部视图

/Views目录下创建目录Partial,并在/Views/Partial 目录下创建文件 Demo.cshtml

  1. @{
  2. ViewBag.Title = "PartialView Demo";
  3. }
  4. <h3>@ViewBag.Title</h3>
  5. <p>PartialView Demo by ken.io</p>
  6. @Html.Partial("_DateTimeInfo")
  7. <hr/>
  8. @await Html.PartialAsync("_DateTimeInfo")

3、创建控制器

/Controllers 目录下创建PartialController.cs并创建对应Action

  1. using System;
  2. using Microsoft.AspNetCore.Mvc;
  3. namespace Ken.Tutorial.Web.Controllers
  4. {
  5. public class PartialController : Controller
  6. {
  7. public IActionResult Demo()
  8. {
  9. return View();
  10. }
  11. }
  12. }

4、访问测试

启动项目,访问 /partial/demo ,将会看到
ASP.NET Core 2.1 入门教程 - 图30

四、带参数的Razor分部视图

1、视图对象准备

在项目根目录中创建模型目录Models,并在下面创建对象NoteViewModel.cs

  1. using System;
  2. namespace Ken.Tutorial.Web.Models
  3. {
  4. public class NoteViewModel
  5. {
  6. public string Title { get; set; }
  7. public DateTime PublishTime { get; set; }
  8. public string Body { get; set; }
  9. }
  10. }

2、定义分部视图

/Views/Shared目录下创建视图 ‘_NoteInfo.cshtml’

  1. @model Ken.Tutorial.Web.Models.NoteViewModel;
  2. <h3>@Model.Title</h3>
  3. <span>@Model.PublishTime.ToString("yyyy-MM-dd")</span>
  4. <p>@Model.Body</p>

实际上就是创建强类型分部视图:-D

3、创建视图并引用分部视图

/Views/Partial 目录下创建文件 DemoWithParams.cshtml

  1. @using Ken.Tutorial.Web.Models;
  2. @{
  3. ViewBag.Title = "PartialView With Params Demo";
  4. }
  5. <h3>@ViewBag.Title</h3>
  6. <p>PartialView With Params Demo by ken.io</p>
  7. @await Html.PartialAsync("_NoteInfo", new NoteViewModel() { Title = "这是一个分部视图测试笔记", PublishTime = DateTime.Now, Body = "这是笔记的内容" })

4、在控制器中编写对应Action

在控制器 PartialController.cs 中增加以下 Action:

  1. public IActionResult DemoWithParams()
  2. {
  3. return View();
  4. }

5、访问测试

启动项目,访问 /partial/demowithparams ,将会看到
ASP.NET Core 2.1 入门教程 - 图31
如果是文章列表页,用起来会显得更方便。

五、备注

1、附录

  • 本文代码示例

https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-07

  • 本文参考

https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/partial?view=aspnetcore-2.1

ASP.NET Core + Entity Framework Core 数据访问入门

一、前言

1、本教程主要内容

  • ASP.NET Core MVC 集成 EF Core 介绍&操作步骤
  • ASP.NET Core MVC 使用 EF Core + Linq to Entity 访问MySQL数据库
  • ASP.NET Core MVC 使用 EF Core + 原生SQL访问MySql数据库
  • EF Core + MySQL数据库插入数据后获取自增列的值

    Entity Framework Core 简称为 EF Core

2、本教程环境信息

软件/环境 说明
操作系统 Windows 10
SDK 2.1.401
ASP.NET Core 2.1.3
MySQL 8.0.x
IDE Visual Studio Code 1.30
浏览器 Chrome 70
VS Code插件 版本 说明
C# 1.17.1 提供C#智能感知, .NET Core 调试、编译等
vscdoe-solution-explorer 0.3.1 提供解决方案视图

本篇代码以下代码进行调整:https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-02

3、准备工作

安装MySQL数据库:https://dev.mysql.com/downloads/mysql/

  • Windows环境

下载安装包后双击安装,一直下一步即可。

  • MacOS环境

参考:https://ken.io/note/macos-mysql8-install-config-tutorial

  • CentOS环境

参考:https://ken.io/note/centos-mysql8-setup

4、前置知识

  • 控制反转(IOC)原则与依赖注入(DI)

ASP.NET Core 默认集成了DI。所有官方模块的引入都要使用DI的方式引入。
https://baike.baidu.com/item/IOC

  • Linq使用教程

https://docs.microsoft.com/zh-cn/dotnet/csharp/tutorials/working-with-linq

二、EF Core + MySQL 前置准备

EF Core 全称:Entity Framework Core,为微软为 .NET Core平台开发的ORM框架。对应是 .NET Framework平台的 Entity Framework(EF),无论是EF还是EF Core都可以说是 .NET 平台开发效率最高的ORM框架。

1、引入 EF Core + MySQL Provider

EF Core已经集成在 ASP.NET Core 中,但默认并不支持MySQL,如果需要连接MySQL,需要添加MySQL相关的Provider,这里我选择的是:Pomelo.EntityFrameworkCore.MySql
任意命令行操作即可,我用的是VS Code自带的命令行

  1. //进入项目根目录: Ken.Tutorial.Web
  2. cd Ken.Tutorial.Web
  3. //添加Package
  4. dotnet add package Pomelo.EntityFrameworkCore.MySql

这里我添加的 MySql.Data.EntityFrameworkCore 版本是 8.0.13,如果你想跟我使用一样的版本,可以使用以下命令:

  1. dotnet add package Pomelo.EntityFrameworkCore.MySql --version 2.1.4

2、创建MySQL库表

  • 创建数据库

    1. CREATE DATABASE ken_tutorial;
  • 创建表

    1. USE ken_tutorial;
    2. DROP TABLE IF EXISTS `user`;
    3. CREATE TABLE `user` (
    4. `id` int(11) NOT NULL AUTO_INCREMENT,
    5. `name` varchar(255) DEFAULT NULL,
    6. `age` int(11) DEFAULT NULL,
    7. `hobby` varchar(500) DEFAULT NULL,
    8. PRIMARY KEY (`id`)
    9. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    3、配置链接字符串

    在项目根目录Ken.Tutorial.Web中创建配置文件appsettings.json,并写入以下配置

    1. {
    2. "ConnectionStrings": {
    3. "testdb": "server=localhost;database=Ken.Tutorial;uid=root;pwd=root;"
    4. }
    5. }

    4、实体&DbContext准备

    创建user表对应实体

    在项目根目录Ken.Tutorial.Web中创建目录Models,并在其中创建类:UserEntity.cs

    1. using System;
    2. using System.ComponentModel.DataAnnotations;
    3. using System.ComponentModel.DataAnnotations.Schema;
    4. namespace Ken.Tutorial.Web.Models
    5. {
    6. [Table("user")]
    7. [Serializable]
    8. public class UserEntity
    9. {
    10. [Key]//主键
    11. [DatabaseGenerated(DatabaseGeneratedOption.Identity)]//自增列
    12. [Column("id")]
    13. public int Id { get; set; }
    14. [Column("name")]
    15. public string Name { get; set; }
    16. [Column("age")]
    17. public int Age { get; set; }
    18. [Column("hobby")]
    19. public string Hobby { get; set; }
    20. }
    21. }

    创建DBContext

    在项目根目录Ken.Tutorial.Web中创建目录Repositories,并在其中创建类:TutorialDbContext.cs

    1. using System;
    2. using Microsoft.EntityFrameworkCore;
    3. using Microsoft.Extensions.Configuration;
    4. namespace Ken.Tutorial.Web.Repositories
    5. {
    6. public class TutorialDbContext : DbContext
    7. {
    8. private IConfiguration Configuration { get; }
    9. public TutorialDbContext(IConfiguration configuration)
    10. {
    11. this.Configuration = configuration;
    12. }
    13. protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    14. {
    15. optionsBuilder.UseMySql(Configuration.GetConnectionString("testdb"));
    16. }
    17. ppublic DbSet<UserEntity> Users { get; set; }
    18. }
    19. }

    TutorialDbContext 继承了 DbContext,然后再Context初始化时配置MySQL链接。

    由于 ASP.NET Core 默认使用了DI组件,所以我们取配置文件,就需要在构造函数中获取 IConfiguration 注入的实例。

在Startup.cs配置注入,以便数据访问类可以访问DI方式获取TutorialDbContext

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. //引入MVC模块
  4. services.AddMvc();
  5. //配置DbContext注入
  6. services.AddTransient<TutorialDbContext>();
  7. }

三、EF Core + Linq to entity 访问数据库

1、创建Linq To Entity 数据访问类

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Ken.Tutorial.Web.Models;
  5. namespace Ken.Tutorial.Web.Repositories
  6. {
  7. public class TutorialRepository
  8. {
  9. private TutorialDbContext DbContext { get; }
  10. public TutorialRepository(TutorialDbContext dbContext)
  11. {
  12. //在构造函数中注入DbContext
  13. this.DbContext = dbContext;
  14. }
  15. //添加
  16. public int Add(UserEntity user)
  17. {
  18. using (DbContext)
  19. {
  20. //由于我们在UserEntity.Id配置了自增列的Attribute,EF执行完成后会自动把自增列的值赋值给user.Id
  21. DbContext.Users.Add(user);
  22. return DbContext.SaveChanges();
  23. }
  24. }
  25. //删除
  26. public int Delete(int id)
  27. {
  28. using (DbContext)
  29. {
  30. var userFromContext = DbContext.Users.FirstOrDefault(u => u.Id == id);
  31. DbContext.Users.Remove(userFromContext);
  32. return DbContext.SaveChanges();
  33. }
  34. }
  35. //更新
  36. public int Update(UserEntity user)
  37. {
  38. using (DbContext)
  39. {
  40. var userFromContext = DbContext.Users.FirstOrDefault(u => u.Id == user.Id);
  41. userFromContext.Name = user.Name;
  42. userFromContext.Age = user.Age;
  43. userFromContext.Hobby = user.Hobby;
  44. return DbContext.SaveChanges();
  45. }
  46. }
  47. //查询
  48. public UserEntity QueryById(int id)
  49. {
  50. using (DbContext)
  51. {
  52. return DbContext.Users.FirstOrDefault(u => u.Id == id);
  53. }
  54. }
  55. //查询集合
  56. public List<UserEntity> QueryByAge(int age)
  57. {
  58. using (DbContext)
  59. {
  60. return DbContext.Users.Where(u => u.Age == age).ToList();
  61. }
  62. }
  63. //查看指定列
  64. public List<string> QueryNameByAge(int age)
  65. {
  66. using (DbContext)
  67. {
  68. return DbContext.Users.Where(u => u.Age == age).Select(u => u.Name).ToList();
  69. }
  70. }
  71. //分页查询
  72. public List<UserEntity> QueryUserPaging(int pageSize, int page)
  73. {
  74. using (DbContext)
  75. {
  76. return DbContext.Users.Skip(pageSize * (page - 1)).Take(pageSize).ToList();
  77. }
  78. }
  79. //事务:将年龄<0的用户修改年龄为0
  80. public int FixAge()
  81. {
  82. using (DbContext)
  83. {
  84. using (var transaction = DbContext.Database.BeginTransaction())
  85. {
  86. try
  87. {
  88. var userListFromContext = DbContext.Users.Where(u => u.Age < 0);
  89. foreach (UserEntity u in userListFromContext)
  90. {
  91. u.Age = 0;
  92. }
  93. var count = DbContext.SaveChanges();
  94. transaction.Commit();
  95. return count;
  96. }
  97. catch
  98. {
  99. transaction.Rollback();
  100. return 0;
  101. }
  102. }
  103. }
  104. }
  105. }
  106. }

2、创建测试API

Controllers文件夹下创建EfCoreController.cs作为访问入口

  1. using System;
  2. using Ken.Tutorial.Web.Models;
  3. using Ken.Tutorial.Web.Repositories;
  4. using Microsoft.AspNetCore.Mvc;
  5. namespace Ken.Tutorial.Web.Controllers
  6. {
  7. public class EfCoreController : Controller
  8. {
  9. public TutorialRepository Repository { get; }
  10. public EfCoreController(TutorialRepository repository)
  11. {
  12. this.Repository = repository;
  13. }
  14. public IActionResult Add(UserEntity user)
  15. {
  16. var message = Repository.Add(user) > 0 ? "success" : "failed";
  17. return Json(new { Message = message, User = user });
  18. }
  19. public IActionResult Delete(int id)
  20. {
  21. var message = Repository.Delete(id) > 0 ? "success" : "failed";
  22. return Json(new { Message = message });
  23. }
  24. public IActionResult Update(UserEntity user)
  25. {
  26. var message = Repository.Update(user) > 0 ? "success" : "failed";
  27. return Json(new { Message = message, User = user });
  28. }
  29. public IActionResult QueryById(int id)
  30. {
  31. var user = Repository.QueryById(id);
  32. return Json(new { User = user });
  33. }
  34. public IActionResult QueryByAge(int age)
  35. {
  36. var users = Repository.QueryByAge(age);
  37. return Json(new { Users = users });
  38. }
  39. public IActionResult QueryNameByAge(int age)
  40. {
  41. var users = Repository.QueryNameByAge(age);
  42. return Json(new { Users = users });
  43. }
  44. public IActionResult QueryUserPaging(int pageSize, int page)
  45. {
  46. var users = Repository.QueryUserPaging(pageSize, page);
  47. return Json(new { Users = users });
  48. }
  49. public IActionResult FixAge()
  50. {
  51. var count = Repository.FixAge();
  52. return Json(new { FixCount = count });
  53. }
  54. }
  55. }

3、启动测试

  • 在Startup.cs中配置Repository注入

    1. public void ConfigureServices(IServiceCollection services)
    2. {
    3. //其他代码省略
    4. //配置Repository注入
    5. services.AddTransient<TutorialRepository>();
    6. }
  • 启动项目并测试 | API | 示例 | | —- | —- | | 添加用户 | /efcore/add?name=ken&age=18&hobby=coding | | 删除用户 | /efcore/delete?id=0 | | 更新用户 | /efcore/update?id=1&name=ken&age=666&hobby=codingOrGaming | | 查询单个用户 | /efcore/querybyid?id=0 | | 查询多个用户 | /efcore/querybyage?age=18 | | 查询多个用户名 | /efcore/querynamebyage?age=18 | | 分页查询用户 | /efcore/queryuserpaging?pagesize=3&page=1 | | 修复异常年龄 | /efcore/fixage |

四、EF Core + 原生SQL 访问数据库

1、创建EF Core + 原生SQL 数据访问类

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Data.SqlClient;
  5. using System.Linq;
  6. using Ken.Tutorial.Web.Models;
  7. using Microsoft.EntityFrameworkCore;
  8. using MySql.Data.MySqlClient;
  9. namespace Ken.Tutorial.Web.Repositories
  10. {
  11. public class TutorialWithSqlRepository
  12. {
  13. private TutorialDbContext DbContext { get; }
  14. public TutorialWithSqlRepository(TutorialDbContext dbContext)
  15. {
  16. //在构造函数中注入DbContext
  17. this.DbContext = dbContext;
  18. }
  19. //添加
  20. public int Add(UserEntity user)
  21. {
  22. using (var connection = DbContext.Database.GetDbConnection())
  23. {
  24. connection.Open();
  25. var command = connection.CreateCommand() as MySqlCommand;
  26. command.CommandText = "INSERT INTO user (name,age,hobby) VALUES(@name,@age,@hobby)";
  27. command.Parameters.Add(new MySqlParameter()
  28. {
  29. ParameterName = "@name",
  30. DbType = DbType.String,
  31. Value = user.Name
  32. });
  33. command.Parameters.Add(new MySqlParameter()
  34. {
  35. ParameterName = "@age",
  36. DbType = DbType.Int32,
  37. Value = user.Age
  38. });
  39. command.Parameters.Add(new MySqlParameter()
  40. {
  41. ParameterName = "@hobby",
  42. DbType = DbType.String,
  43. Value = user.Hobby
  44. });
  45. var count = command.ExecuteNonQuery();
  46. //获取插入时产生的自增列Id并赋值给user.Id使用
  47. user.Id = (int)command.LastInsertedId;
  48. return count;
  49. }
  50. }
  51. //删除
  52. public int Delete(int id)
  53. {
  54. using (DbContext)
  55. {
  56. return DbContext.Database.ExecuteSqlCommand(
  57. "DELETE FROM user WHERE id={0}", id);
  58. }
  59. }
  60. //更新
  61. public int Update(UserEntity user)
  62. {
  63. using (DbContext)
  64. {
  65. return DbContext.Database.ExecuteSqlCommand(
  66. "UPDATE user SET name={0}, age={1}, hobby={2} WHERE id={3}",
  67. user.Name, user.Age, user.Hobby, user.Id);
  68. }
  69. }
  70. //查询
  71. public UserEntity QueryById(int id)
  72. {
  73. using (DbContext)
  74. {
  75. return DbContext.Users.FromSql("SELECT id,name,age,hobby FROM user WHERE id={0}", id).FirstOrDefault();
  76. }
  77. }
  78. //查询集合
  79. public List<UserEntity> QueryByAge(int age)
  80. {
  81. using (DbContext)
  82. {
  83. return DbContext.Users.FromSql("SELECT id,name,age,hobby FROM user WHERE age={0}", age).ToList();
  84. }
  85. }
  86. //查看指定列
  87. public List<string> QueryNameByAge(int age)
  88. {
  89. using (DbContext)
  90. {
  91. return DbContext.Users.FromSql("SELECT id,name FROM user WHERE age={0}", age).Select(u => u.Name).ToList();
  92. }
  93. }
  94. //分页查询
  95. public List<UserEntity> QueryUserPaging(int pageSize, int page)
  96. {
  97. using (DbContext)
  98. {
  99. return DbContext.Users.FromSql("SELECT id,name,age,hobby FROM user LIMIT {0},{1}", pageSize * (page - 1), pageSize).ToList();
  100. }
  101. }
  102. //事务:将年龄<0的用户修改年龄为0
  103. public int FixAge()
  104. {
  105. using (DbContext)
  106. {
  107. using (var connection = DbContext.Database.GetDbConnection())
  108. {
  109. //打开连接
  110. connection.Open();
  111. //开启事务
  112. using (var transaction = connection.BeginTransaction())
  113. {
  114. try
  115. {
  116. //获取命令对象
  117. var command = connection.CreateCommand();
  118. command.Transaction = transaction;
  119. command.CommandText = "UPDATE user SET age=@age WHERE age<@age";
  120. command.Parameters.Add(new MySqlParameter()
  121. {
  122. ParameterName = "@age",
  123. DbType = DbType.Int32,
  124. Value = 0
  125. });
  126. var count = command.ExecuteNonQuery();
  127. transaction.Commit();
  128. return count;
  129. }
  130. catch (Exception ex)
  131. {
  132. connection.Close();
  133. transaction.Rollback();
  134. return 0;
  135. }
  136. }
  137. }
  138. }
  139. }
  140. }
  141. }

2、创建测试API

Controllers文件夹下创建EfCoreWithSqlController.cs作为访问入口

  1. using System;
  2. using Ken.Tutorial.Web.Models;
  3. using Ken.Tutorial.Web.Repositories;
  4. using Microsoft.AspNetCore.Mvc;
  5. namespace Ken.Tutorial.Web.Controllers
  6. {
  7. public class EfCoreWithSqlController : Controller
  8. {
  9. public TutorialWithSqlRepository Repository { get; }
  10. public EfCoreWithSqlController(TutorialWithSqlRepository repository)
  11. {
  12. this.Repository = repository;
  13. }
  14. public IActionResult Add(UserEntity user)
  15. {
  16. var message = Repository.Add(user) > 0 ? "success" : "failed";
  17. return Json(new { Message = message, User = user });
  18. }
  19. public IActionResult Delete(int id)
  20. {
  21. var message = Repository.Delete(id) > 0 ? "success" : "failed";
  22. return Json(new { Message = message });
  23. }
  24. public IActionResult Update(UserEntity user)
  25. {
  26. var message = Repository.Update(user) > 0 ? "success" : "failed";
  27. return Json(new { Message = message, User = user });
  28. }
  29. public IActionResult QueryById(int id)
  30. {
  31. var user = Repository.QueryById(id);
  32. return Json(new { User = user });
  33. }
  34. public IActionResult QueryByAge(int age)
  35. {
  36. var users = Repository.QueryByAge(age);
  37. return Json(new { Users = users });
  38. }
  39. public IActionResult QueryNameByAge(int age)
  40. {
  41. var users = Repository.QueryNameByAge(age);
  42. return Json(new { Users = users });
  43. }
  44. public IActionResult QueryUserPaging(int pageSize, int page)
  45. {
  46. var users = Repository.QueryUserPaging(pageSize, page);
  47. return Json(new { Users = users });
  48. }
  49. public IActionResult FixAge()
  50. {
  51. var count = Repository.FixAge();
  52. return Json(new { FixCount = count });
  53. }
  54. }
  55. }

3、启动测试

  • 在Startup.cs中配置Repository注入

    1. public void ConfigureServices(IServiceCollection services)
    2. {
    3. //其他代码省略
    4. //配置Repository注入
    5. services.AddTransient<TutorialWithSqlRepository>();
    6. }
  • 启动项目并测试 | API | 示例 | | —- | —- | | 添加用户 | /efcorewithsql/add?name=ken&age=18&hobby=coding | | 删除用户 | /efcorewithsql/delete?id=0 | | 更新用户 | /efcorewithsql/update?id=1&name=ken&age=666&hobby=codingOrGaming | | 查询单个用户 | /efcorewithsql/querybyid?id=0 | | 查询多个用户 | /efcorewithsql/querybyage?age=18 | | 查询多个用户名 | /efcorewithsql/querynamebyage?age=18 | | 分页查询用户 | /efcorewithsql/queryuserpaging?pagesize=3&page=1 | | 修复异常年龄 | /efcorewithsql/fixage |

五、备注

1、附录

  • 本文代码示例

https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-08

  • 本文参考

https://docs.microsoft.com/zh-cn/ef/core
https://www.learnentityframeworkcore.com
https://mysql-net.github.io/MySqlConnector/tutorials/net-core-mvc/

ASP.NET Core 中间件(Middleware)入门

一、前言

1、本教程主要内容

  • ASP.NET Core 中间件介绍
  • 通过自定义 ASP.NET Core 中间件实现请求验签

    2、本教程环境信息

    | 软件/环境 | 说明 | | —- | —- | | 操作系统 | Windows 10 | | SDK | 2.1.401 | | ASP.NET Core | 2.1.3 | | MySQL | 8.0.x | | IDE | Visual Studio Code 1.32.3 | | 浏览器 | Chrome 70 |
VS Code插件 版本 说明
C# 1.17.1 提供C#智能感知, .NET Core 调试、编译等
vscdoe-solution-explorer 0.3.1 提供解决方案视图

本篇代码以下代码进行调整:https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-02

3、前置知识

可能需要的前置知识

  • C# 委托(Delegate)

http://www.runoob.com/csharp/csharp-delegate.html

  • C# 扩展方法

https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/extension-methods

二、ASP.NET Core 中间件介绍

1、ASP.NET Core 中间件基本说明

当 ASP.NET Core MVC应用从Kestrel接收到请求,会建立HttpContext并交由Application来处理请求。在Application中会有一个处理该请求的通道,这就是ASP.NET Core 管道,通常称之为:请求处理管道
在这个管道中,有一系列有序处理请求的组件,就是中间件(Middleware)。
ASP.NET Core 2.1 入门教程 - 图32
图中蓝色的部分可以认为是系统内置比较靠前的中间件或者我们自定义的中间件,MVC是一个特殊的中间件且通常放在最后,所以这里单独画出来

对于MVC中间件,如果请求的URL与路由匹配,那么后面的中间件均不会生效。所以MVC通常放在最后。

ASP.NET Core中会内置一些中间件,例如:身份验证、静态文件处理、MVC等。每个中间件在接受到请求后都可以选择是交由下一个中间件处理还是直接返回结果。例如:

  • 身份验证中间件验证未通过会直接引导到登陆页
  • 静态文件中间件判断为静态文件就会直接返回静态文件内容

所以,中间件可以理解为请求处理管道中的请求处理器。我们也可以通过自定义中间件注册到管道中来干预请求。

2、ASP.NET Core 中间件基础使用

在程序中,中间件是基于委托来构建的。在应用启动时通过IApplicationBuilder注册到通道中。
具体见启动类Startup.cs

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  2. {
  3. if (env.IsDevelopment())
  4. {
  5. app.UseDeveloperExceptionPage();
  6. }
  7. app.UseMvc(routes =>
  8. {
  9. //配置默认路由
  10. routes.MapRoute(
  11. name: "Default",
  12. template: "{controller}/{action}",
  13. defaults: new { controller = "Home", action = "Index" }
  14. );
  15. });
  16. }

UseDeveloperExceptionPageUseMvc都是接口IApplicationBuilder的扩展方法。

三、使用 ASP.NET Core 中间件实现请求验签

如果你开发的API是为手机App服务的,那么你的API是一定要暴露给公网的,如果有人拿到API地址进行非法请求,获取用户信息或者是篡改数据,用户隐私、数据就会受到损害。这是很不安全的,我们可以让客户端请求的时候必须携带签名,在服务器端鉴权(验证签名)通过了再放行,这样就安全很多了。

1、创建验签中间件

在项目Ken.Tutorial.Web创建目录Middlewares,然后创建类:TokenCheckMiddleware.cs

  1. using System;
  2. using System.Security.Cryptography;
  3. using System.Text;
  4. using System.Threading.Tasks;
  5. using Microsoft.AspNetCore.Http;
  6. namespace Ken.Tutorial.Web.Middlewares
  7. {
  8. public class TokenCheckMiddleware
  9. {
  10. private readonly RequestDelegate _next;
  11. public TokenCheckMiddleware(RequestDelegate requestDelegate)
  12. {
  13. this._next = requestDelegate;
  14. }
  15. public Task Invoke(HttpContext context)
  16. {
  17. //先从Url取token,如果取不到就从Form表单中取token
  18. var token = context.Request.Query["token"].ToString() ?? context.Request.Form["token"].ToString();
  19. if (string.IsNullOrWhiteSpace(token))
  20. {
  21. //如果没有获取到token信息,那么久返回token missing
  22. return context.Response.WriteAsync("token missing");
  23. }
  24. //获取前1分钟和当前的分钟
  25. var minute0 = DateTime.Now.AddMinutes(-1).ToString("yyyy-MM-dd HH:mm");
  26. var minute = DateTime.Now.ToString("yyyy-MM-dd HH:mm");
  27. //当token和前一分钟或当前分钟任一时间字符串的MD5哈希一致,就认为是合法请求
  28. if (token == MD5Hash(minute) || token == MD5Hash(minute0))
  29. {
  30. return _next.Invoke(context);
  31. }
  32. //如果token未验证通过返回token error
  33. return context.Response.WriteAsync("token error");
  34. }
  35. public string MD5Hash(string value)
  36. {
  37. using (var md5 = MD5.Create())
  38. {
  39. var result = md5.ComputeHash(Encoding.ASCII.GetBytes(value));
  40. var strResult = BitConverter.ToString(result);
  41. return strResult.Replace("-", "");
  42. }
  43. }
  44. }
  45. }

由于是侧重自定义中间件,所有验签的逻辑就写的非常简单,如果实际项目使用,可以按照自己需求调整

2、创建扩展方法

Middlewares目录下新建类:MiddlewareExtension.cs

  1. using Microsoft.AspNetCore.Builder;
  2. namespace Ken.Tutorial.Web.Middlewares
  3. {
  4. public static class MiddlewareExtension
  5. {
  6. public static IApplicationBuilder UseTokenCheck(this IApplicationBuilder builder)
  7. {
  8. return builder.UseMiddleware<TokenCheckMiddleware>();
  9. }
  10. }
  11. }

这里我们通过扩展方法,将TokenCheckMiddleware挂在接口IApplicationBuilder

3、中间件注册/引用

在启动类Startup.csConfigure方法中注册/引用中间件

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  2. {
  3. //省略部分代码
  4. app.UseTokenCheck();
  5. app.UseMvc(routes =>
  6. {
  7. //省略路由配置代码
  8. });
  9. }

如果你觉得扩展方法有点多余,也可以直接使用UseMiddleware方法注册

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  2. {
  3. //省略部分代码
  4. app.UseMiddleware<TokenCheckMiddleware>();
  5. app.UseMvc(routes =>
  6. {
  7. //省略路由配置代码
  8. });
  9. }

这里要注意的是,如果你是一个MVC应用,请一定要把MVC这个中间件作为最后一个注册。因为中间件是按照注册顺序被调用的。如果放在MVC之后,请求的URL也有对应路由适配,那么整个请求已经被MVC接管。后面的中间件就不会被调用了。

4、验签中间件测试

启动应用,然后验证不同情况下的访问结果

URL Response
localhost:5001 token missing
localhost:5001?token=test token error
localhost:5001?token=3D76FEA1D0ADD0C7639B73023436C6EA Hello World ! -ken.io

为了方便测试,MD5哈希的值我们可以在线生成:http://tool.chinaz.com/tools/md5.aspx
把当前分钟,例如:2019-03-27 23:23 通过MD5在线生成那就是3D76FEA1D0ADD0C7639B73023436C6EA

四、备注

  • 本文代码示例

https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-09

  • 本文参考

https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.1

  • 延伸阅读

https://www.cnblogs.com/artech/p/inside-asp-net-core-pipeline.html

ASP.NET Core 日志记录(NLog)入门

一、前言

1、本教程主要内容

  • ASP.NET Core + 内置日志组件记录控制台日志
  • ASP.NET Core + NLog 按天记录本地日志
  • ASP.NET Core + NLog 将日志按自定义LoggerName分类记录到不同目录
  • ASP.NET Core + NLog 按文件大小归档记录本地日志
  • NLog配置文件常用配置项说明

    2、本教程环境信息

    | 软件/环境 | 说明 | | —- | —- | | 操作系统 | Windows 10 | | SDK | 2.1.401 | | ASP.NET Core | 2.1.3 | | IDE | Visual Studio Code 1.33.0 | | 浏览器 | Chrome 73 |
VS Code插件 版本 说明
C# 1.18.0 提供C#智能感知, .NET Core 调试、编译等
vscdoe-solution-explorer 0.3.1 提供解决方案视图
XML Tools 2.4.0 提供XML高亮,格式化、XML树等

本篇代码以下代码进行调整:https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-02

二、ASP.NET Core 内置日志组件使用

ASP.NET Core内置日志组件,可以将日志输出在控制台

1、应用程序启动时配置日志

修改Program.cs,在WebHostBuilder构建时配置日志

  1. //需要引入的命名空间
  2. using Microsoft.Extensions.Logging;
  1. public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
  2. WebHost.CreateDefaultBuilder(args)
  3. .UseStartup<Startup>()
  4. .ConfigureLogging(logging =>
  5. {
  6. logging.ClearProviders();
  7. logging.SetMinimumLevel(LogLevel.Info);
  8. logging.AddConsole();
  9. });
配置项 说明
ClearProviders() 清除日志提供程序,通常在引入第三方日志组件时使用
SetMinimumLevel(LogLevel.Information) 设置日志级别为Information
AddConsole() 添加日志提供程序->控制台

这里设置项,主要是SetMinimumLevel(LogLevel.Information),我们把日志级别设置为Information,可以减少很多控制台日志输出

2、在控制中记录日志

修改HomeController.cs,通过构造函数注入ILogger

  1. //需要引入的命名空间
  2. using Microsoft.Extensions.Logging;
  1. private readonly ILogger<HomeController> _logger;
  2. public HomeController(ILogger<HomeController> logger)
  3. {
  4. this._logger = logger;
  5. }
  1. public IActionResult Index()
  2. {
  3. _logger.LogInformation("------\r\nindex:hello world\r\n------");
  4. return Content("Hello World ! -ken.io");
  5. }
  6. public IActionResult CheckPhone(string phone)
  7. {
  8. _logger.LogInformation($"------\r\ncheck phone:{phone}\r\n------");
  9. var result = true;
  10. var message = "pass";
  11. if (string.IsNullOrWhiteSpace(phone))
  12. {
  13. result = false;
  14. message = "phone number is empty";
  15. _logger.LogError($"------\r\ncheck phone:{message}\r\n------");
  16. }
  17. else if (phone.Length != 11)
  18. {
  19. result = false;
  20. message = "wrong phone number length";
  21. _logger.LogWarning($"------\r\ncheck phone:{message}\r\n------");
  22. }
  23. return Json(new { Result = result, Phone = phone, Message = message });
  24. }

这里日志内容中包含的\r\n转义符在控制台输出时/写入文件时表示换行,这里加入\r\n---主要是为了日志输出时,方便快速找到我们主动记录的日志。

3、验证

启动应用,访问 localhost:5001,会看到控制台输出了Hello World
ASP.NET Core 2.1 入门教程 - 图33
访问 localhost:5001/home/checkphone,将会看到Error日志
ASP.NET Core 2.1 入门教程 - 图34
访问 localhost:5001/home/checkphone?phone=000,将会看到警告日志
ASP.NET Core 2.1 入门教程 - 图35
访问 localhost:5001/home/checkphone?phone=16666666666,就只会看到Info级别日志了
ASP.NET Core 2.1 入门教程 - 图36

三、ASP.NET Core + NLog 记录本地日志

1、安装NLog Package

在控制台使用命令安装NLog包:

  1. //进入项目目录
  2. cd Ken.Tutorial.Web
  3. //使用命令安装nlog
  4. dotnet add package NLog.Web.AspNetCore --version 4.8.1

这里我安装的指定版本:4.8.1,如果不指定版本号,默认安装最新版本。

2、引入NLog

修改Program.cs,在WebHostBuilder构建时配置日志

  1. //需要引入的命名空间
  2. using NLog.Web;
  1. public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
  2. WebHost.CreateDefaultBuilder(args)
  3. .UseStartup<Startup>()
  4. .ConfigureLogging(logging =>
  5. {
  6. logging.ClearProviders();
  7. logging.SetMinimumLevel(LogLevel.Info);
  8. logging.AddConsole();
  9. }).UseNLog();//UseNLog

3、配置NLog

在项目根目录Ken.Tutorial.Web新建NLog配置文件nlog.config

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true">
  4. <targets>
  5. <target name="defaultlog" xsi:type="File" keepFileOpen="false" encoding="utf-8"
  6. fileName="${basedir}/logs/${level}/${shortdate}.log"
  7. layout="${longdate}|${level:uppercase=true}|${logger}|${message}" />
  8. </targets>
  9. <rules>
  10. <logger name="*" minlevel="trace" writeTo="defaultlog" />
  11. </rules>
  12. </nlog>

NLog配置文件一般主要包含两个部分,节点配置日志写入目标,节点配置匹配路由到的规则。

主要配置项 ken.io的说明
${basedir}/logs/${level}/${shortdate}.log 表示在程序运行目录,分日志级别按天写入日志文件
${longdate}|${level:uppercase=true}|${logger}|${message} 日志内容格式:时间+日志级别+LoggerName+日志内容
支持将任意级别、任意LoggerName的日志写入target:defaultlog

为了能让程序运行时能够读取到配置文件,需要修改Ken.Tutorial.Web.csproj文件,在生成运行文件到bin文件夹时,也把nlog.config复制过去。在工具栏切换到资源管理器视图,双击文件即可修改:
ASP.NET Core 2.1 入门教程 - 图37

  1. <ItemGroup>
  2. <Content Update="nlog.config" CopyToOutputDirectory="PreserveNewest" />
  3. </ItemGroup>

4、NLog测试

启动应用,分别访问以下链接:

  • localhost:5001/home/checkphone
  • localhost:5001/home/checkphone?phone=000
  • localhost:5001/home/checkphone?phone=16666666666

然后在资源管理器视图下,查看bin/Debug目录,会看到日志已经按照配置文件的格式生成了对应的日志目录及文件。
ASP.NET Core 2.1 入门教程 - 图38
同时也会发现,nlog.config按照之前的配置也被复制到了程序运行目录。

  • Info级别日志

ASP.NET Core 2.1 入门教程 - 图39

  • Warn级别日志

ASP.NET Core 2.1 入门教程 - 图40

  • Error级别日志

ASP.NET Core 2.1 入门教程 - 图41
这里Info日志是比较多的,我们可以通过配置rules,只输出程序本身主动记录的日志。

  1. <logger name="Ken.Tutorial.*" minlevel="trace" writeTo="defaultlog" />

四、NLog使用进阶

1、通过自定义LoggerName归纳日志

增加NLog配置,根据LoggerName创建目录

  1. <!--按照LoggerName分类-->
  2. <target name="customlog" xsi:type="File" keepFileOpen="false" encoding="utf-8"
  3. fileName="${basedir}/logs/${logger}/${shortdate}-${level}.log"
  4. layout="${longdate}|${level:uppercase=true}|${logger}|${message}" />
  1. <!--记录LoggerName以log结尾的日志-->
  2. <logger name="*log" minlevel="trace" writeTo="customlog" />

HomeController中添加测试归纳日志的Action

  1. public IActionResult TestLog()
  2. {
  3. var logger = NLog.LogManager.GetLogger("testlog");
  4. logger.Trace("这是Trace日志");
  5. logger.Debug("这是Debug日志");
  6. logger.Info("这是Info日志");
  7. logger.Warn("这是警告日志");
  8. logger.Error("这是错误日志");
  9. return Content("ok");
  10. }

这里直接通过NLog.LogManager.GetLogger创建Logger,并没有使用内置日志Microsoft.Extensions.Logging.ILogger的实例,所以可以自定义LoggerName,另外,我们在应用启动时配置的最低日志级别等也不会对这种方式生效,是可以输出Trace、Debug级别的日志的。
启动应用,访问:localhost:5001/home/testlog,然后就可以在资源管理器界面看到在logs文件夹下按照LoggerName生成的日志目录,并按照日志级别生成了不同的日志文件:
ASP.NET Core 2.1 入门教程 - 图42

2、按照日志文件大小归档日志

增加NLog配置,按照日志文件大小归档

  1. <!--按照日志文件大小归档-->
  2. <target name="archivelog" xsi:type="File" keepFileOpen="false" encoding="utf-8"
  3. fileName="${basedir}/logs/${logger}/${shortdate}-current.log"
  4. archiveFileName="${basedir}/logs/${logger}/${shortdate}.{####}.log"
  5. archiveAboveSize="1024000"
  6. archiveNumbering="Sequence"
  7. maxArchiveFiles="100"
  8. layout="${longdate}|${level:uppercase=true}|${logger}|${message}" />

这里配置当日志文件大小在超过约1mb的时候归档(这里偷懒用1000b代替1kb),最多归档100个日志文件,当归档日志文件超过100个时会把最早归档的日志删除。
定义专属规则:

  1. <logger name="logmany" minlevel="trace" writeTo="archivelog" />

HomeController中添加测试归档日志的Action

  1. public IActionResult TestLogMany()
  2. {
  3. var logger = NLog.LogManager.GetLogger("logmany");
  4. for (int i = 0; i <= 30000; i++)
  5. {
  6. logger.Info("ASP.NET Core入门教程,这里是日志内容,测试NLog的日志归档功能,ken的杂谈(https://ken.io)");
  7. }
  8. return Content("ok");
  9. }

这里为了方便测试,直接用for循环,连续写入日志30000次
启动应用,访问:localhost:5001/home/testlogmany,然后就可以在资源管理器界面看到在logs/logmany文件夹下按照文件大小归档的日志
ASP.NET Core 2.1 入门教程 - 图43

五、备注

1、NLog常用配置项说明

target节点/属性 ken.io的说明
target 日志写入目标,可以配置写入类型、写入模板、文件名等
name TargetName,需要唯一,在rules节点引用
xsi:type 目标类型,支持文件(File)、数据库(Database)、邮件(Mail)
keepFileOpen 保持文件打开,不用每次写入日志时都打开、关闭文件,因为可以提高性能,默认值为:false
encoding 文件编码,此处配置为:utf-8
fileName 日志文件名,包含日志完整路径和文件名,支持模板语法/变量
archiveFileName 归档日志文件名,包含日志完整路径和文件名,支持模板语法/变量
archiveNumbering 归档序号方式
maxArchiveFiles 最大归档日志文件数
layout 日志内容模板,内置了一些语法/变量
模板语法/变量 ken.io的说明
${basedir} 程序当前运行目录
${level} 日志级别
${shortdate} 当前日期,例如:2019-04-05
${longdate} 当前时间,精确到毫秒,例如:2019-04-05 14:10:22.4372
${uppercase:${level}} 把内容格式化成大写
${logger} LoggerName,日志记录器名称,通常是Logger初始化所在类完整名称,例如:Ken.Tutorial.Web.Controllers.HomeController
${machinename} 机器名
${message} 日志内容
rules节点/属性 ken.io的说明
logger 日志记录器路由规则配置
name LoggerName匹配,支持完整匹配和模糊匹配,例如:mylog、Ken.*
minlevel 最低日志级别
maxlevel 最高日志级别
level 限制单一的日志级别
levels 指定一个或多个日志级别,用,间隔
writeTo 指定一个或多个target,用,间隔
final 在匹配到该规则之后不再匹配后续规则,默认值:false
enabled 是否启用该规则,默认值:true

2、附录

  • 本文代码示例

https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-10

  • 本文参考

https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1
https://github.com/NLog/NLog.Web/wiki/Getting-started-with-ASP.NET-Core-2
https://github.com/nlog/NLog/wiki

ASP.NET Core 应用发布与部署指南/教程

一、前言

1、本教程主要包含哪些内容?

  1. 将ASP.NET Core项目发布到本地目录
  2. 将ASP.NET Core项目发布后传输到服务器并配置启动
  3. 将Nginx作为访问入口,配置反向代理

    2、本篇环境信息

  • 开发环境: | 用途 | 工具&版本 | | —- | —- | | 操作系统 | Windows 10 | | 开发工具 | Visual Studio 2017(15.7.5) | | SDK | .NET Core SDK 2.1 | | 传输工具 | FlashFxp |

  • 部署环境 | 用途 | 工具&版本 | | —- | —- | | Linux Server | CentOS 7 | | SDK | .NET Core SDK 2.1 | | 传输工具 | Vsftp | | 反向代理 | Nginx 1.12.2 |

3、准备工作

  • ASP.NET Core 示例项目 | 项 | 说明 | | —- | —- | | SDK | .NET Core SDK 2.1 | | 项目模板 | ASP.NET Core Web App(MVC) | | 项目名称 | HelloWeb | | 解决方案名称 | HelloWeb | | 解决方案根目录 | D:\Projects\Test\HelloWeb |

项目创建完成后,需要修改Program.cs文件
手动指定启动的Url为:http://*:5000

  1. public class Program
  2. {
  3. public static void Main(string[] args)
  4. {
  5. CreateWebHostBuilder(args).Build().Run();
  6. }
  7. public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
  8. WebHost.CreateDefaultBuilder(args)
  9. .UseStartup<Startup>()
  10. .UseUrls("http://*:5000");
  11. }

http://*:5000 可以兼容 http://localhost:5000http://127.0.0.1:5000http://所在机器ip:5000,方便我们部署到Linux Server后进行测试

  • CentOS 7 部署环境

CentOS 7虚拟机安装:https://ken.io/note/hyper-v-course-setup-centos

  1. 部署FTP服务:https://ken.io/note/centos-quickstart-ftpserver
  2. 部署 .NET Core SDK 2.1:https://ken.io/note/centos7-.netcore2.1-setup
  3. 部署Nginx:https://ken.io/note/centos-quickstart-nginx-setup

    二、发布到本地

    1、命令行发布

    在cmd命令行操作
    1. #进入项目根目录(HelloWeb.csproj所在目录)
    2. d: && cd D:\Projects\Test\HelloWeb\HelloWeb
    3. #执行publish命令
    4. dotnet publish -c release
    5. #dotnet publish -h可以查看publish可选参数
    6. #-c release 表示以Release方式进行编译

    2、Visual Studio图形界面操作

  • 在项目的鼠标右键菜单中选择:发布

ASP.NET Core 2.1 入门教程 - 图44

  • 在左侧选择文件夹,并创建配置

ASP.NET Core 2.1 入门教程 - 图45

  • 在发布操作面板中选择发布即可

ASP.NET Core 2.1 入门教程 - 图46

3、本地运行测试

发布完成后,可以在D:\Projects\Test\HelloWeb\HelloWeb\bin\Release\netcoreapp2.1\publish查看发布后的文件输出。
在cmd命令行操作:

  1. #进入发布输出目录
  2. d: && cd D:\Projects\Test\HelloWeb\HelloWeb\bin\Release\netcoreapp2.1\publish
  3. #启动应用
  4. dotnet HelloWeb.dll
  5. #启动成功会输出以下信息
  6. Hosting environment: Production
  7. Content root path: D:\Projects\Test\HelloWeb\HelloWeb\bin\Release\netcoreapp2.1\publish
  8. Now listening on: http://[::]:5000
  9. Application started. Press Ctrl+C to shut down.

通过浏览器访问:localhost:5000,验证是否正常

三、部署

1、环境配置&启动测试

通过XShell连接到CentOS7服务器进行操作

  • 创建站点目录并授权

    1. #创建站点根目录
    2. sudo mkdir -p /webroot/helloweb
    3. #创建站点应用目录
    4. sudo mkdir -p /webroot/helloweb/app
    5. #创建站点日志目录
    6. sudo mkdir -p /webroot/helloweb/logs
    7. #目录授权
    8. sudo chmod 777 /webroot/helloweb/app
    9. sudo chmod 777 /webroot/helloweb/logs
  • 开放端口

    1. #添加可访问端口
    2. sudo firewall-cmd --add-port=5000/tcp --permanent
    3. #重新加载防火墙策略
    4. sudo firewall-cmd --reload
  • 启动应用

通过FTP将传输到/webroot/helloweb/app
通过命令启动:

  1. #进入app目录并通过dotnet命令启动站点
  2. cd /webroot/helloweb/app/
  3. dotnet HelloWeb.dll
  4. #启动成功后,将会输出:
  5. Hosting environment: Production
  6. Content root path: /webroot
  7. Now listening on: http://[::]:5000
  8. Application started. Press Ctrl+C to shut down.

这时候通过浏览器访问 http://<centos-ip>:5000即可

如果网站样式没有被正常加载,那应该是访问wwwroot下面的静态文件时404了。 这是因为 ASP.NET Core默认是以命令执行所在的目录作为应用根目录读取文件的 所以,一定要在HelloWeb.dll所在目录执行dotnet命令,不然会读取不到wwwroot目录下的静态文件

2、配置启动&停止脚本

显示启动的方式,如果关闭XShell的连接窗口,那这个应用的进行就被关掉了。所以可以用nohup命令启动,脚本示例:

  1. nohup dotnet HelloWeb.dll &

nohup由于是隐私启动,那应用的关闭就要找到进程id后才能关闭,所以还是干脆配置启动、停止脚本来操作更方便一些

  • 创建启动脚本

创建start.sh文件

  1. sudo vi /webroot/helloweb/start.sh

脚本内容:

  1. #!/bin/sh
  2. cd $(cd "$(dirname "$0")"; pwd)
  3. APP_NAME=HelloWeb.dll
  4. echo "start begin..."
  5. echo $APP_NAME
  6. cd app
  7. nohup dotnet $APP_NAME >>../logs/info.log 2>>../logs/error.log &
  8. cd ..
  9. sleep 5
  10. if test $(pgrep -f $APP_NAME|wc -l) -eq 0
  11. then
  12. echo "start failed"
  13. else
  14. echo "start successed"
  15. fi
  • 创建停止脚本

创建stop.sh文件

  1. sudo vi /webroot/helloweb/stop.sh

脚本内容:

  1. #!/bin/sh
  2. cd $(cd "$(dirname "$0")"; pwd)
  3. APP_NAME=HelloWeb.dll
  4. PROCESS=`ps -ef|grep $APP_NAME|grep -v grep |awk '{ print $2}'`
  5. while :
  6. do
  7. kill -9 $PROCESS > /dev/null 2>&1
  8. if [ $? -ne 0 ];then
  9. break
  10. else
  11. continue
  12. fi
  13. done
  14. echo 'stop success!'
  • 脚本测试

    1. #启动应用
    2. sh /webroot/helloweb/start.sh
    3. #启动成功输出
    4. start begin...
    5. HelloWeb.dll
    6. start successed
    7. #停止应用
    8. sh /webroot/helloweb/stop.sh
    9. #停止成功后输出
    10. stop success!

    四、开机启动&反向代理配置

    1、配置开机启动

  • 将停止脚本标记为可执行文件

    1. sudo chmod +x /webroot/helloweb/stop.sh
  • 创建helloweb服务

    1. #创建服务文件
    2. sudo vi /usr/lib/systemd/system/helloweb.service
    3. #文件内容
    4. [Unit]
    5. Description=helloweb
    6. After=network.target
    7. [Service]
    8. WorkingDirectory=/webroot/helloweb/app
    9. ExecStart=/usr/bin/dotnet /webroot/helloweb/app/HelloWeb.dll
    10. ExecStop=/webroot/helloweb/stop.sh
    11. Restart=always
    12. RestartSec=10
    13. [Install]
    14. WantedBy=multi-user.target

    如果你配置了多个版本的 .NET Core环境,记得把/usr/bin/dotnet换成对应的路径

  • 设置服务开机启动&开启服务

    1. #设置服务开机启动
    2. sudo systemctl enable helloweb
    3. #启动服务
    4. sudo systemctl start helloweb

    2、Nginx反向代理配置

  • 创建helloweb站点配置

    1. #新建配置文件
    2. sudo vi /etc/nginx/conf.d/helloweb.conf
    3. #反向代理配置内容
    4. server {
    5. listen 80; #监听80端口
    6. server_name helloweb.mydomain.com; #绑定的域名
    7. location / { #转发或处理
    8. proxy_pass http://localhost:5000;
    9. }
    10. error_page 500 502 503 504 /50x.html;#错误页
    11. location = /50x.html {
    12. root /usr/share/nginx/html;
    13. }
    14. }
  • 重载NGINX配置

    1. sudo nginx -s reload
  • 开放防火墙端口

    1. #添加可访问端口
    2. sudo firewall-cmd --add-port=80/tcp --permanent
    3. #重新加载防火墙策略
    4. sudo firewall-cmd --reload
  • 访问测试:

通过设置本地hosts将helloweb.mydomain.com指向服务器IP
然后通过浏览器访问: helloweb.mydomain.com 即可