asp.net core 教程
写在前面
学了快一年多的C#了!
我最开始学的是winform框架,因为当时项目上要写一个上位机软件。随着使用C#的深入,我发现winform有很多缺陷,比如界面设计完全靠手拖到控件,前后端耦合性强,界面不够炫酷,反正用下来觉得不太喜欢。当然我也用过QT,winform感觉比QT简单好用。慢慢的我又接触了WPF,用过wpf后,我喜欢上了这种编程风格,有点像写html文件,所有的控件都可以用代码实现,代码实现起来也是流畅自然。wpf用了几个月后,我发现wpf很多细节很多时候根本记不住,我慢慢意识到,很多工作上没用到的,没有必要花费大把时间取学习,因为内容太多了,学了不用马上也就忘了。我尝试取关注这门语言的背后原理,从架构的层面去理解它,而不是关注一些细枝末节。
各位宝,开始asp.net core了,我也希望像盖房子一样,把整个asp.net core 的知识填充进去,直到大楼起。
新建项目
新建项目时会有以下几个选项:
- asp.net core web 应用:只提供一个后端接口,供前端调用
- asp.net core 空 :创建一个空模板,需要手动添加所有所需的依赖项、中间件、控制器等
- asp.net core web 应用(模型-视图-控制器):前端+后端
下一步会弹出这个框,默认即可。
框架选择.net 8,.net 8的新特性可以自己上网搜一下。
使用顶级语句:原来的代码中都有public static void Main(string[] args),使用顶级语句后,main()函数也没有了,有点像python 的格式。
MVC
如何通俗理解MVC
宝啊,我学这个的时候也是一头雾水,我想用最简单的话把它说明白。
M是单词Model的缩写,我们就把它理解成为一个类,Model怎么是类呢?假设有个学生类,类里有姓名、年龄、专业等信息,其实这些都是模型的一部分,所以我们还会把模型叫做模型类,或者模型数据,就是用来存放各种数据类型和值的地方。
V是View的缩写,即视图,这个好理解,用来展示数据的地方。
C是Controller的缩写,控制字面意思就是对某种事物操作,这里的控制就是对模型和视图进行控制。对模型控制,比如说你想知道某个学生的名字,那么你就要对学生类进行操作,然后拿到学生的名字。对视图控制,比如用户从界面上选择了某件商品到购物车,那么这个用户就对界面进行了控制。
宝啊,上面可能解释不太好,但我尽力了。可能你还是懵,下面我给了一个例子
MVC架构—文件夹详解
下图是我创建的一个MVC模型,我觉得要搞懂MVC,需要先把这其中的文件夹搞清楚
-
Connected Services
你连接了哪些服务,比如你连接上了微软的云服务,这里就会显示出来。
-
Properties
在ASP.NET Core MVC项目中,Properties
文件夹包含一个launchSettings.json
文件,这个文件包含了启动应用程序所需的所有信息。它提供了关于执行应用程序时要执行的操作的配置详细信息,并包含IIS设置、应用程序URL、身份验证、SSL端口详细信息等。
- 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,分别在对应的文件夹下创建相应的代码
在Models文件夹下创建一个Person类模型:
Person.cs
namespace WebApplication1.Models
{
//这里用的是record,是C#中的语法,省的写构造函数
public record Person(string Name,bool IsDog,DateTime CreateDatatime);
}
创建一个控制Person的控制类:
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>
运行之后:
这好像不是我们要写的,原因使我们要在端口号后面加上我们的视图名字,结果如下,这才是我们想要的结果。
一个简单的 Web API
我们先看web API 和mvc的项目上有什么不同:
- web API 中没有了View,不能创建视图了
- web API 中同样没有Models文件夹,但是模型类仍然在,比如Person类,我们把模型类直接写在工程文件目录下了
下面的代码实现了从后端到前端的整个过程,前端使用hbuilder创建一个登录页面,访问服务器API接口,并且要使用内置浏览器(跨域没有解决)
前端代码
将下面的前端代码运行在HBuilder上,运行时就选择Hbuilder自带的浏览器。这里的前端代码用来访问web 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
};
//这里的地址要和web API提供的接口一致
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.json
和 secrets.json
文件用于存储配置信息,但它们的优先级是不同的。
-
appsettings.json:
- 这是默认的配置文件,通常包含应用程序的常规设置,可以被添加到源代码管理中(例如 Git)。
- 这些设置通常是应用程序需要的,但不是很敏感的信息,例如数据库连接字符串(不包含用户名和密码)。
-
secrets.json:
- 用于存储敏感数据,例如 API 密钥、数据库连接字符串的用户名和密码等。
secrets.json
文件不应该被添加到源代码管理中,以确保敏感信息不会被泄露。- 在开发环境中,
secrets.json
覆盖appsettings.json
中的相同设置。
优先级顺序
在 ASP.NET Core 项目中加载配置时,会按照特定的顺序加载多个配置文件。如果相同的键存在于多个文件中,后加载的文件中的值将覆盖先前加载的文件中的值。
通常的加载顺序如下:
appsettings.json
appsettings.[Environment].json
(例如appsettings.Development.json
)secrets.json
(仅在开发环境中)- 环境变量
- 命令行参数
因此,如果 appsettings.json
和 secrets.json
中存在相同的键,secrets.json
中的值将具有更高的优先级,并覆盖 appsettings.json
中的值。
为了安全性,建议在生产环境中使用环境变量或密钥管理服务(如 Azure Key Vault)来存储敏感信息,而不是使用 secrets.json
文件。在开发环境中,可以使用 secrets.json
来模拟生产环境的行为。
从数据库读取配置文件
乍一看,是不是觉得很唬人,宝啊,其实没有那么难的。
什么是配置文件? 比如你写了个客户端,现在需要连接服务器,既然要连接,服务器的IP地址、端口号这些信息得有吧,你首先想到我在代码里直接赋值,这样当然可以,但是如果服务器地址发生变化,你不可能又打开visual studio重新写个地址,然后再生产一个客户端,实际操作起来不现实。我们通常把经常需要改变的东西,专门写个文件存储起来,然后程序去读取这个文件,这样非专业人员也可以修改配置文件了,而不需要程序员动代码。
早期的(现在也有),把配置文件写到一个txt文本或者.ini文件中,这种方式很好用,但大规模读取还是不如从数据据读取好,所以我们现在要做的就是从数据库读取配置文件。项目文件主要有以下这些:
mysql数据库表结构和存储数据也贴两张图:
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 傲娇的博主要敷面膜去了