写在前面

学了快一年多的C#了!

我最开始学的是winform框架,因为当时项目上要写一个上位机软件。随着使用C#的深入,我发现winform有很多缺陷,比如界面设计完全靠手拖到控件,前后端耦合性强,界面不够炫酷,反正用下来觉得不太喜欢。当然我也用过QT,winform感觉比QT简单好用。慢慢的我又接触了WPF,用过wpf后,我喜欢上了这种编程风格,有点像写html文件,所有的控件都可以用代码实现,代码实现起来也是流畅自然。wpf用了几个月后,我发现wpf很多细节很多时候根本记不住,我慢慢意识到,很多工作上没用到的,没有必要花费大把时间取学习,因为内容太多了,学了不用马上也就忘了。我尝试取关注这门语言的背后原理,从架构的层面去理解它,而不是关注一些细枝末节。

我主要用过这几种编程语言:C++、matlab、python、C#,我想说说对这几种语言的看法:

  1. C++:我用过小半年时间,给我的感觉是:语法严苛,bug难找。当时用的C++都是生成.so文件在linux上运行的,反正用下感觉很不好,一个语法错误,找半天就是找不到原因,很容易让人崩溃。很多时候,需要可视化数据,用C++做个桌面应用程序也不是那么容易。我当然知道,C++的运行速度很快,但我真的不太喜欢这门语言,不仅仅是难,语法结构啥的就是不喜欢。这有点像,两个看不对眼的相亲对象,就算是对方请客,你也不愿意坐下来陪着吃顿饭,我对C++的感觉就是这样,除非万不得已,不然永远不想用它。

  2. matlab: matlab也是一个脚本语言,理工科搞科研的应该都或多或少接触过。matlab不用声名变量类型,都是起个名字就拿来用,调试使数据存储都存储在工作区,方便查看数据。其实matlab看着语法松散,入门难度也不高,但有时报个错,也让人挠头。特别是项目的代码量稍微大一点的话,可读性真的很差。不C++或C#,注释、语法提示、代码结构、数据类型、逻辑都很清晰,matlab做不到这一点,代码一多就是“乱”,它就是适合搞个小脚本,实现一个单一功能,完美契合搞科研这件事,专注科研,而不是代码本身。

  3. python: python 打广告经常说的一句话,人生苦短,我学python。python和matlab同属于脚本语言,它们有什么区别呢?matlab不是开源的,而python是开源的,这就注定python的可玩性比matalab高,也更容易出结果,你想想,你想实现一个小功能,网上那么多python包都是开源的,你拿来用就可以了。但是,这也是python最大的劣势,语法太松散了,有时候会觉得像夏天的天气,爱就像蓝天白云,突然暴风雨。因为松散的原因,你在用一些包的时候(高阶),竟然觉得晦涩难懂,真的是入门容易,会使用包难,包后面的数学逻辑更难。所以想搞深入python高级用法,也要花费很多很多精力,如果你觉得python简单,那是因为 你用了很多人家写的包,当你用数据处理工具包时,比如pandas、numpy等,你会发现即使站在巨人的肩膀上,也并不轻松。还有一点想说,虽然python对变量注释或者查找啥的不太友好,但是我喜欢这门语言,无他,只为pyhcarm,我感觉python能火,pycharmd功不可没。

  4. C#:C#是我目前用过最优秀的编程语言,特别时开源之后的.net core。你可能会怀念python的pip install xxx,其实C#早就有这个功能了,只不过指令是:package-install xxx,只要你能想到的功能,基本都有开源代码,一行指令就能用。C# 的可以说是全栈:桌面、web、server、数据库,还有unity,真的是功能不要太强大,如果你是基于windows开发,那就不要爽上加爽,怎么形象这种感觉呢?你喜欢一个女孩子,那个女孩的好闺蜜告诉你,其实女孩也喜欢你,你听到消息,捧着鲜花还没表白就迈入云端,飘飘欲然。当你准备还没来到女孩面前时,突然有个人从背后叫住你,一群人中间走出一个女孩,她当着所有人的面向你表白,你定睛一看,就是自己的女孩。哎,各位宝啊,收收,我可是一个严肃的程序员,烂漫属于这对幸福的小情侣,而我只有油腻和秃头。

    C#是一门面向对象的编程语言,所以C++中的类,封装、继承、多态等优点,但抛弃了指针、收到内存管理等复杂晦涩的东西,用起来上手相对简单。可能效率略低于C++,但绝对远超python、java。C#背靠宇宙第一编译器visual studio,特别是visual studio 2022,有时候自己代码还在脑子里想着,编译器已经给了提示了,太好用了。.net core因为功能强大,所以用起来肯定更复杂,特别是有些依赖包,绕来绕去,C#基础知识不牢固的,马上劝退,这有点像用复杂的python包,还是要有底子的。

我可能花了很大笔墨在夸c#,但我并没有贬低其他语言的意思,只不过我不喜欢和稀泥,在表达立场的时候要态度鲜明,所以上面的说辞有抬高C#之嫌。一门语言存在,肯定有它存在的道理,好不好主要也还是看要开发什么项目。可能将来鸿蒙要在国内流行,但普通人其实没有多少选择的,我是个普通人,我选择all in C#。这个教程是关于asp.net core的,主要记录我认为是重要的知识点,宝啊,如果你C#没有基础,那就先花点时间看一下基础,不然你会懵的。

各位宝,开始asp.net core了,我也希望像盖房子一样,把整个asp.net core 的知识填充进去,直到大楼起。

新建项目

新建项目时会有以下几个选项:

  1. asp.net core web 应用:是一个只提供一个后端接口,供前端调用
  2. asp.net core 空 :创建一个空模板,需要手动添加所有所需的依赖项、中间件、控制器等
  3. asp.net core web 应用(模型-视图-控制器):前端+后端

asp.net core 教程-LMLPHP

下一步会弹出这个框,默认即可。

框架选择.net 8,.net 8的新特性可以自己上网搜一下。
使用顶级语句:原来的代码中都有public static void Main(string[] args),使用顶级语句后,main()函数也没有了,有点像python 的格式。

asp.net core 教程-LMLPHP

Get和Post

GET和POST是HTTP协议中最常用的两种请求方法,它们用于客户端(如浏览器)与服务器之间的通信。这两种方法在实现和用途上有一些关键的区别。

GET

定义与用途:

  • 数据获取:GET主要用于从服务器检索(或“获取”)信息。它是只读的,意味着它不应该被用于更改服务器上的资源。
  • 请求参数:GET请求的参数通常附加在URL的末尾,以查询字符串的形式出现(例如,example.com/search?query=test)。
  • 缓存:GET请求可以被缓存,这意味着响应可以被存储起来以供以后的相同请求快速访问。
  • 安全性:因为GET请求的参数暴露在URL中,所以它们不应该用于传输敏感信息(如密码或信用卡信息)。
  • 幂等性:GET请求是幂等的,意味着多次执行相同的GET请求不会有副作用或改变服务器上的状态。

示例:当你在浏览器的地址栏输入一个URL并按下回车,或者点击一个链接时,你通常就是在发送一个GET请求。

POST

定义与用途:

  • 数据提交:POST主要用于向服务器提交数据,通常用于表单提交、文件上传或创建/更新资源。
  • 请求体:与GET不同,POST请求的参数存储在请求体中,而不是URL里。这使得POST更加适合传输大量或敏感的数据。
  • 无缓存:POST请求通常不会被缓存,并且每次都会触发服务器上的一些动作或处理。
  • 安全性:由于数据在请求体中发送,POST比GET更安全一些,但仍然需要通过HTTPS等加密方式来保护敏感信息。
  • 非幂等性:POST请求通常是非幂等的,意味着多次执行相同的POST请求可能会在服务器上产生不同的效果或改变状态。

示例:当你在网页表单中输入信息并点击提交按钮时,你通常就是在发送一个POST请求。

总结:

  • GET 通常用于从服务器检索信息,而 POST 通常用于向服务器发送信息。
  • GET请求的参数附加在URL中,而POST请求的参数存储在请求体中。
  • GET请求可以被缓存和书签化,而POST请求则不能。
  • GET通常用于不改变服务器状态的操作,而POST用于可能改变状态的操作。

MVC-模型控制视图

如何通俗理解MVC

模型控制视图(Model-View-Controller,MVC)是一种常见的软件架构模式,用于实现应用程序的组织和分离。这个模式将应用程序分为三个主要组件:模型(Model)、视图(View)和控制器(Controller)。每个组件都有特定的职责,协同工作以实现应用程序的功能。

通俗来说:

  1. 模型(Model):是应用程序的核心组件,负责处理数据和应用程序的业务逻辑。它封装了与数据相关的操作和规则,包括数据模型和业务逻辑。数据模型定义了应用程序中使用的数据结构,以及与数据相关的操作,例如数据库查询、数据验证等。业务逻辑处理数据的计算、验证和操作,确保数据的完整性和一致性。
  2. 视图(View):负责将信息显示给用户。它是用户界面的组成部分,用于呈现模型中的数据。视图通常包含HTML、CSS和JavaScript等前端技术,用于创建用户友好的界面。
  3. 控制器(Controller):处理用户输入的信息,负责从视图读取数据,控制用户输入,并向模型发送数据。它是应用程序中处理用户交互的部分,负责管理与用户交互的控制逻辑。每个视图都有一个相关的控制器组件。控制器接受用户的输入,例如鼠标点击或键盘输入,并将其转换为对模型或视图的请求。

MVC模式通过将信息的内部表示(模型)与信息的呈现方式(视图)分离开来,并接受用户的请求(控制器),简化了复杂度的管理,使程序结构更加直观。这种分离还允许有效的代码重用,即可以将模型和视图的实现代码分离,从而使同一个程序可以使用不同的表现形式。

以电子商务网站中的购物车功能为例来解释MVC模式。

模型(Model)

  • 存储商品信息,如商品名称、价格、库存数量等。
  • 提供方法以添加商品到购物车、更新购物车中的商品数量以及计算购物车总价。
  • 管理数据验证,例如确保购物车中的商品数量不超过库存数量。

视图(View)

  • 显示商品列表,包括商品的名称、描述和价格。
  • 显示购物车图标,显示购物车中商品的数量。
  • 当用户点击商品时,显示商品详情页面。
  • 显示购物车页面,包括购物车中商品的名称、数量和价格,以及结算按钮。

控制器(Controller)

  • 当用户点击商品列表中的商品时,控制器会从视图获取所选商品的信息,并将其添加到购物车模型中。
  • 当用户点击购物车图标时,控制器会从购物车模型获取购物车信息,并将其传递给视图进行显示。
  • 当用户更新购物车中的商品数量时,控制器会捕获这个操作,更新购物车模型中的数据,并重新计算购物车总价。
  • 当用户点击结算按钮时,控制器会处理结算逻辑,包括验证用户信息、生成订单等。

例如,用户在电子商务网站上浏览商品时的流程如下:

  1. 用户在商品列表页面选择一个商品并点击它。
  2. 控制器捕获这个点击事件,从商品列表视图获取所选商品的信息。
  3. 控制器调用模型的方法将所选商品添加到购物车中。
  4. 购物车模型更新后,控制器将更新的购物车信息传递给购物车视图进行显示。
  5. 用户可以在购物车页面查看已添加的商品,并点击结算按钮进行结算。
  6. 控制器处理结算逻辑,并与模型进行交互以完成订单生成和支付等流程。

这个例子展示了MVC模式在电子商务网站中的实际应用,通过分离模型、视图和控制器的职责,使得代码更加模块化、可维护和可扩展。

MVC架构—文件夹详解

下图是我创建的一个MVC模型,我觉得要搞懂MVC,需要先把这其中的文件夹搞清楚
asp.net core 教程-LMLPHP

Connected Services

在ASP.NET Core MVC中,“Connected Service”(连接的服务)是一个功能,它允许开发者将外部服务或API集成到他们的ASP.NET Core应用程序中。这提供了一种简化添加和使用服务的方法,使开发者能够更轻松地利用第三方服务或内部服务来增强他们的应用程序功能。

使用Connected Services,你可以:

  1. 添加身份验证服务:例如,通过Azure Active Directory (AAD)、OAuth、OpenID Connect等添加身份验证机制。

  2. 调用外部API:例如,连接到REST API、SOAP服务或其他Web服务。

  3. 集成云服务:例如,Azure Blob Storage、Azure Cosmos DB、AWS服务等,以便存储数据或执行其他云操作。

  4. 使用消息队列和事件:例如,通过RabbitMQ、Azure Service Bus等实现异步通信。

  5. 集成数据库服务:例如,Entity Framework Core提供程序,用于连接到SQL Server、MySQL、PostgreSQL等数据库。

  6. 添加推送通知:例如,通过Firebase Cloud Messaging、Azure Notification Hubs等发送推送通知。

Connected Services通过为选定的服务生成必要的配置代码和客户端库来工作。这通常通过以下几个步骤完成:

  1. 在Visual Studio或其他支持Connected Services的IDE中打开你的ASP.NET Core项目。

  2. 右键点击项目,选择“Add” -> “Connected Service”或者使用相应的IDE功能。

  3. 从提供的服务列表中选择你想要集成的服务。

  4. 根据向导的提示提供必要的配置信息,如API密钥、连接字符串等。

  5. 点击“完成”或“添加”,IDE将为你生成必要的代码和配置文件。

一旦Connected Service被添加到项目中,你就可以在项目代码中直接使用这些服务,而无需手动编写大量的配置和初始化代码。这大大简化了与外部服务的集成过程,并允许开发者更加专注于应用程序的业务逻辑。

Properties

在ASP.NET Core MVC项目中,Properties文件夹包含一个launchSettings.json文件,这个文件包含了启动应用程序所需的所有信息。它提供了关于执行应用程序时要执行的操作的配置详细信息,并包含IIS设置、应用程序URL、身份验证、SSL端口详细信息等³。这些信息对于正确地运行和调试你的ASP.NET Core MVC应用程序至关重要。

wwwroot

在ASP.NET Core MVC项目中,wwwroot文件夹是一个特殊的目录,用于存放静态资源文件,如HTML、CSS、JavaScript、图片等。这些文件通常直接由客户端浏览器请求并下载,不需要通过服务器端的代码处理。

依赖项

整个项目运行需要的依赖项目.当你用using Microsoft.AspNetCore.Mvc, Microsoft.AspNetCore.Mvc就是一个依赖项目.

Controllers

负责处理来自浏览器的请求并返回响应.

Models

负责表示和管理应用程序的数据和业务逻辑。

Views

Views 负责呈现数据给用户,并且通常与特定的Controller动作相关联。

代码实例

我们在Visual studio 中创建一个MVC的项目,这时项目下有三个文件夹:Models,Controllers,views,分别在对应的文件夹下创建相应的代码

创建一个模型:

Person.cs

using Microsoft.AspNetCore.Mvc;
using WebApplication1.Models;

namespace WebApplication1.Controllers
{
    public class TestController : Controller
    {
        public IActionResult Demo1()  //Action方法:操作方法  这里的方法要和需要渲染的视图的名字一致
        {
            //return View();
            Person p1 = new Person("keson", true, DateTime.Now);
            return View(p1);
        }
    }
}

创建一个控制类:

TestController.cs

using Microsoft.AspNetCore.Mvc;
using WebApplication1.Models;

namespace WebApplication1.Controllers
{
    public class TestController : Controller
    {
        public IActionResult Demo1()  //Action方法:操作方法  这里的方法要和需要渲染的视图的名字一致
        {
            //return View();
            Person p1 = new Person("keson", true, DateTime.Now);
            return View(p1);
        }
    }
}

创建一个视图:Demo1.cshtml

//model理解为类,Model理解为实例
@model WebApplication1.Models.Person  //申明视图用这个模型(类)的数据
<div>姓名:@Model.Name</div>  
<div>是否VIP:@Model.IsVIP</div>
<p>@Model.CreateDatatime</p>

API模型(前后端分离)

下面的代码实现了从后端到前端的整个过程,前端使用hbuilder创建一个登录页面,访问服务器API接口,并且要使用内置浏览器(跨域没有解决)

前端代码

<!DOCTYPE html>
<html>
	<head>
		<title>Login Page</title>
	</head>
	<body>
		<h1>Login</h1>
		<form id="loginForm">
			<label for="userName">Username:</label>
			<input type="text" id="userName" required><br><br>
			<label for="password">Password:</label>
			<input type="password" id="password" required><br><br>
			<button type="submit">Login</button>
		</form>

		<script>
			document.getElementById('loginForm').addEventListener('submit', function(event) {
				event.preventDefault(); // 阻止表单默认提交行为    
				const userName = document.getElementById('userName').value;
				const password = document.getElementById('password').value;
				const loginData = {
					UserName: userName,
					Password: password
				};
				fetch('http://localhost:5001/Login/Login', {
						method: 'POST',
						headers: {
							'Content-Type': 'application/json'
						},
						body: JSON.stringify(loginData)
					})
					.then(response => {
						if (response.ok) {
							return response.json();
						} else {
							throw new Error('Error occurred during login');
						}
					})
					.then(data => {
						// 处理登录响应数据    
						console.log(data); // 在控制台打印响应数据,您可以根据需要进行处理    
					})
					.catch(error => {
						// 处理错误情况    
						console.error(error); // 在控制台打印错误信息,您可以根据需要进行处理    
					});
			});
		</script>
	</body>
</html>

后端代码

设置一个固定端口,在appsettings里设置

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Urls": "http://localhost:5001"  //给后端设置一个固定端口
}

代码实现

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;

namespace WebAPILogin.Controllers
{
    [Route("[controller]/[action]")]
    [ApiController]
    public class LoginController : ControllerBase
    {
        [HttpPost]
        public LoginResponse Login(LoginRequest req)
        {
            if(req.UserName == "admin" && req.Password == "123456")
            {
                var items = Process.GetProcesses().Select(p =>
                            new ProcessInfo ( p.Id, p.ProcessName, p.WorkingSet64 ));
                return new LoginResponse(true,items.ToArray());
            }
            else
            {
                return new LoginResponse(false, null);
            }
        }
    }

    public record LoginRequest(string UserName,string Password);
    public record ProcessInfo(int Id,string Name,long WorkingSet);
    public record LoginResponse(bool ok, ProcessInfo[]? ProcessInfos);
}

文件配置优先级

在 ASP.NET Core 项目中,appsettings.jsonsecrets.json 文件用于存储配置信息,但它们的优先级是不同的。

  1. appsettings.json:

    • 这是默认的配置文件,通常包含应用程序的常规设置,可以被添加到源代码管理中(例如 Git)。
    • 这些设置通常是应用程序需要的,但不是很敏感的信息,例如数据库连接字符串(不包含用户名和密码)。
  2. secrets.json:

    • 用于存储敏感数据,例如 API 密钥、数据库连接字符串的用户名和密码等。
    • secrets.json 文件不应该被添加到源代码管理中,以确保敏感信息不会被泄露。
    • 在开发环境中,secrets.json 覆盖 appsettings.json 中的相同设置。

优先级顺序

在 ASP.NET Core 项目中加载配置时,会按照特定的顺序加载多个配置文件。如果相同的键存在于多个文件中,后加载的文件中的值将覆盖先前加载的文件中的值。

通常的加载顺序如下:

  1. appsettings.json
  2. appsettings.[Environment].json (例如 appsettings.Development.json)
  3. secrets.json (仅在开发环境中)
  4. 环境变量
  5. 命令行参数

因此,如果 appsettings.jsonsecrets.json 中存在相同的键,secrets.json 中的值将具有更高的优先级,并覆盖 appsettings.json 中的值。

为了安全性,建议在生产环境中使用环境变量或密钥管理服务(如 Azure Key Vault)来存储敏感信息,而不是使用 secrets.json 文件。在开发环境中,可以使用 secrets.json 来模拟生产环境的行为。

从数据库读取配置文件

乍一看,是不是觉得很唬人,宝啊,其实没有那么难的。

什么是配置文件? 比如你写了个客户端,现在需要连接服务器,既然要连接,服务器的IP地址、端口号这些信息得有吧,你首先想到我在代码里直接赋值,这样当然可以,但是如果服务器地址发生变化,你不可能又打开visual studio重新写个地址,然后再生产一个客户端,实际操作起来不现实。我们通常把经常需要改变的东西,专门写个文件存储起来,然后程序去读取这个文件,这样非专业人员也可以修改配置文件了,而不需要程序员动代码。

早期的(现在也有),把配置文件写到一个txt文本或者.ini文件中,这种方式很好用,但大规模读取还是不如从数据据读取好,所以我们现在要做的就是从数据库读取配置文件。项目文件主要有以下这些:

asp.net core 教程-LMLPHP

mysql数据库表结构和存储数据也贴两张图:

asp.net core 教程-LMLPHP
asp.net core 教程-LMLPHP

Program.cs

using MySql.Data.MySqlClient;
using StackExchange.Redis;
using System.Reflection.PortableExecutable;
using 综合配置项目;

var builder = WebApplication.CreateBuilder(args); //创建了一个WebApplicationBuilder实例,用于配置和构建Web应用程序

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

/*读取配置文件的几种方式:
     1.从标准json文件中读取:json文件一般在工程文件目录下,比如appsettings.json;
        builder.Configuration.AddJsonFile("appsettings.json", optional:false,reloadOnChange:true);
     2.快捷方式:该方式是约定好的,配置文件可以来自工程目录,也可以是secrts.json;
        builder.Configuration.GetConnectionString("ConStr") //此处要注意GetConnectionString方法的参数是定死的
     3.从json文件里提取子项;
        string s1 = builder.Configuration.GetSection("Student")["Name"];
     4.从数据库提取配置项:数据库有三个字段:Id、Name("Redis")、Value("localhost:6379,password=123456")
        string? constr = builder.Configuration.GetSection("Redis").Value;
 */

/*获取连接redis数据库的字符串
    1.通过ConfigurationOptions
        var configurationOptions = new ConfigurationOptions
            {
                EndPoints = { "localhost:6379" },  // 例如: "localhost:6379"  
                Password = "123456"     // 在这里设置你的Redis密码  
            };
    2.通过string
        string? s = "localhost:6379,password=123456";   //简写形式
    3.通过数据库
        string? constr = builder.Configuration.GetSection("Redis").Value;
 */

string? conStr = builder.Configuration.GetConnectionString("ConStr");
builder.Configuration.AddDbConfiguration(() => new MySqlConnection(conStr), reloadOnChange: true, reloadInterval: TimeSpan.FromSeconds(2));

builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>   //入参不能省略,出参可以省
{
    string? constr = builder.Configuration.GetSection("Redis").Value;
    return ConnectionMultiplexer.Connect(constr);
});

//配置文件内容映射到实体类
builder.Services.AddOptions().Configure<SmtpSettings>(builder.Configuration.GetSection("Smtp"));
//builder.Services.Configure<SmtpSettings>(builder.Configuration.GetSection("Smtp")); //与上面的写法一致,会自动添加AddOptions(

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Student": {
    "Name": "keson",
    "age": 18
  },
  "ConnectionStrings": {
    "ConStr": "server=localhost;user=root;password=123456;database=test"
  }
}

SmtpSettings.cs

namespace 综合配置项目
{
    public class SmtpSettings
    {
        public string? Server {  get; set; }
        public string? UserName{ get; set; }
        public int Password { get; set; }
        public override string ToString()
        {
            return $"Smtp: Server={Server},UserName={UserName},Password={Password}";
        }

    }
}

SmtpController.cs

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using StackExchange.Redis;

namespace 综合配置项目.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class SmtpController : ControllerBase
    {
        private readonly IOptions<SmtpSettings> optSmtp;
        private readonly IConnectionMultiplexer connectionMultiplexer;
        public SmtpController(IOptions<SmtpSettings> optSmtp, IConnectionMultiplexer connectionMultiplexer)
        {
            this.optSmtp = optSmtp;
            this.connectionMultiplexer = connectionMultiplexer;
        }
        
    [HttpGet]
        public string GetSmtp()
        {
            var ping = connectionMultiplexer.GetDatabase(0).Ping();
            Console.WriteLine(optSmtp.ToString());
            return optSmtp.Value.ToString() + "|" + ping;
        }
    }
}

数据缓存

用户是怎么从服务器拿到东西的?首先用户发出请求,服务器收到请求并解析,然后回复客户端需要的数据。问题就出在这了,服务器回复客户端的数据是存储在哪里呢?通常来说,数据当然是存储在数据库上,所以上述过程变成:用户请求、服务器接受请求并解析、服务器从数据库拿数据、把从数据库拿到的数据返回给客户。

问题是一个服务器可不止一个客户端,搞不好成千上万个客户端,如果每个客户端向服务器发出请求时,服务器都操作一下数据库,数据可受不了。其实很多时候,客户端向服务器发出的请求都是相同的,那能不能将客户端经常访问的数据单独放到数据库外边,这样不就减少数据的访问次数了吗,这就是缓存。这有点像运行内存和内存的关系,需要高频率访问的数据就用运行内存,其他放到内存中。

Program.cs

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddMemoryCache();
builder.Services.AddLogging();
builder.Services.AddStackExchangeRedisCache(opt =>
{
    opt.Configuration = "localhost:6379,password=123456";
    opt.InstanceName = "cache1_";
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Book.cs

public record Book(long Id,string Name);

MyDBContext.cs

public class MyDbContext
{
    public static Task<Book?>GetByIdAsync(long id)
    {
        var result =GetById(id);
        return Task.FromResult(result);
    } 
    public static Book? GetById(long Id)
    {
        switch (Id)
        {
            case 1:
                return new Book(1, "西游记");                    
            case 2:
                return new Book(2, "水浒传");
            case 3:
                return new Book(3, "三国演义");
            case 4:
                return new Book(4, "红楼梦");
            default:
                return null;
        }
    }
}

TestController.cs

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using System.Text.Json;

namespace Cache
{
    [Route("[controller]/[action]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        private readonly IMemoryCache memoryCache;
        private readonly ILogger<TestController> logger;
        private readonly IDistributedCache distributedCache;

        public TestController(IMemoryCache memoryCache, ILogger<TestController> logger, IDistributedCache distributedCache)
        {
            this.memoryCache = memoryCache;
            this.logger = logger;
            this.distributedCache = distributedCache;
        }

        [HttpGet]
        public async Task<ActionResult<Book?>> memoryCacheTest(long id)
        {

            Console.WriteLine($"开始执行GetBookById,id={id}");
            Book? b = await memoryCache.GetOrCreateAsync("Book" + id, async (e) =>
            {
                Console.WriteLine($"缓存里没有找到");
                /*
                  e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10); //绝对时间过期策略
                     e.SlidingExpiration = TimeSpan.FromSeconds(10);    //滑动时间策略                
                e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(Random.Shared.
                    Next(1, 15));    //随机过期时间
                */
                Book? d = await MyDbContext.GetByIdAsync(id);
                Console.WriteLine("从数据库的查询结果是:" + (d == null ? "null" : d));

                return d;
            });

            Console.WriteLine($"GetOrCreateAsync结果:");
            if (b == null)
            {
                return NotFound($"找不到id={id}的书");
            }
            else
            {
                return b;
            }
        }

        [HttpGet]
        public async Task<ActionResult<Book?>> redisTest(long id)
        {
            Book? book;
            string? s = await distributedCache.GetStringAsync("Book" + id);
            if(s == null)
            {
                Console.WriteLine("从数据中取数据");
                book = await MyDbContext.GetByIdAsync(id);
                await distributedCache.SetStringAsync("Book" + id, JsonSerializer.Serialize(book));
            }
            else
            {
                Console.WriteLine("从缓存中取数据");
                book = JsonSerializer.Deserialize<Book?>(s);
            }

            if (book == null)
            {
                return NotFound($"找不到id={id}的书");
            }
            else
            {
                return book;
            }
        }

        [HttpGet]
        public DateTime GetTime()
        {
            return DateTime.Now;
        }
    }
}

-----------------------现在是北京时间20231225 22:43 傲娇的博主要敷面膜去了

12-26 18:40