使用 visual studio创建webapi项目
需要安装的包
从上到下依次为:
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.Tools
- Mysql.EntityFrameworkCore
- Pomelo.EntityFrameworkCore.MySql
- Swashbuckle.AspNetCore
新建控制器
控制器的命名必须以name+Controlles
的格式命名
using Microsoft.AspNetCore.Mvc;
// 简单示例
namespace test_vs_api.Controllers
{
[Route("/[controller]/[action]")] // 路由为类名加方法名
[ApiController] // api控制器
public class TestController : Controller
{
[HttpGet("/[action]")] // 方法及路径 使用方法名做路径 如果以上使用了action 此处可以省略
public string GetMessage()
{
return "123";
}
}
}
添加数据库连接字符串
在appsettings.json
文件里,添加数据库连接字符串
"ConnectionStrings": {
"MySQLConnection": "server=localhost;port=3306;uid=root;pwd=123456;database=yourdatabaase"
},
server就是主机ip 本地就是localhost,uid是mysql数据库的用户名,pwd是mysql数据库的密码,database是数据库名称
注册数据库上下文
在Program.cs
中
// 注册数据库上下文
builder.Services.AddDbContext<DataContext>(options =>
{
options.UseMySQL(builder.Configuration.GetConnectionString("DefaultConnection")!);
});
创建数据库上下文
首先在根目录创建Data
文件夹,从中创建DataContext.cs
using Microsoft.EntityFrameworkCore;
using test_vs_api.Entities;
namespace test_vs_api.Data
{
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
}
public DbSet<数据库表名> 表名{ get; set; }
}
}
关于迁移数据库
打开visual studio的程序包管理器控制台
在 Visual Studio 中,当您使用 Entity Framework (EF) 作为您的对象关系映射器 (ORM) 时,Package Manager Console
通常用于运行 EF 的命令。dir
命令在大多数命令行环境(包括 PowerShell 和命令提示符)中用于列出目录中的文件和子目录。但在 EF 的上下文中,您提到的 Add-Migration
和 Update-Database
是特定的 EF 命令。
以下是这些命令的简要说明:
- Add-Migration:
- 用途:此命令用于创建一个新的迁移。迁移是 EF 用来更改数据库架构的一种方式。每当您更改了您的数据模型(例如,添加了一个新的类、更改了一个类的属性等),您就需要创建一个迁移来反映这些更改到数据库中。
- 语法:
Add-Migration MigrationName
- 示例:
Add-Migration Initial
会创建一个名为 "Initial" 的新迁移。 - 说明:运行此命令后,EF 会检查您的数据模型与当前数据库架构之间的差异,并为您生成一个迁移类。这个类包含了一个
Up
方法和一个Down
方法。Up
方法用于应用更改,而Down
方法用于撤销这些更改。
- Update-Database:
- 用途:此命令用于应用所有未应用的迁移到您的数据库。
- 语法:
Update-Database
- 说明:当您运行此命令时,EF 会检查已定义的迁移(通常在您的项目中的
Migrations
文件夹中)和数据库中已应用的迁移。然后,它会应用所有尚未在数据库中应用的迁移。
简而言之,Add-Migration
用于创建新的迁移来反映数据模型的更改,而 Update-Database
则用于将这些更改应用到数据库中。这两个命令在开发过程中经常使用,尤其是在数据库架构经常更改的情况下。
或者使用命令行
dotnet ef migrations add yourModelName // 生成迁移
dotnet ef database update // 更新数据库
在控制器中引入数据库上下文
// 引用数据库上下文
private readonly DataContext _context;
public StaffController(DataContext context)
{
_context = context;
}
关于 IActionResult
返回类型
public async Task<IActionResult> GetAllStaff()
{
var staffList = new List<Staff>
{
new Staff
{
id = 1,
name = "张三",
department = "研发部",
sex = "男",
CreateTime = "2024-2-4 16:45:50"
}
};
return Ok(staffList);
}
IActionResult
是一个接口,它表示一个操作的结果。在 ASP.NET Core 中, IActionResult
接口是所有操作结果类型的基类。 IActionResult
接口定义了几个方法,这些方法可以用来返回不同的操作结果。例如, Ok
方法返回一个 200 OK
状态码, BadRequest
方法返回一个 400 Bad Request
状态码, NotFound
方法返回一个 404 Not Found
状态码。
只有使用IActionResult
类型才会return
,Ok()
和 NotFound()
方法,来表示200或404
除此之外的ActionResult
类型
public async Task<ActionResult<List<Staff>>> GetAllStaff()
{
var staffList = new List<Staff>
{
new Staff
{
id = 1,
name = "张三",
department = "研发部",
sex = "男",
CreateTime = "2024-2-4 16:45:50"
}
};
return Ok(staffList);
}
与上一个代码段相比,这个代码段的返回值类型从 IActionResult
改为了 ActionResult<List<Staff>>
。这意味着该方法将返回一个包含 List<Staff>
对象的 ActionResult
对象。
ActionResult
对象是一个泛型类型,它可以包含任何类型的对象。在该代码段中, ActionResult
对象包含了一个 List<Staff>
对象。
当然我自己最直观的感受就是他有了示例数据和下面的一个模式展示
GET请求获取全部数据
[HttpGet] // 方法及路径
public async Task<ActionResult<List<Staff>>> GetAllStaff()
{
var staffList = await _context.Staff.ToListAsync();
return Ok(staffList);
}
GET请求根据id获取单个数据
不需要分页时
[HttpGet("{id}")] // 如果需要传参则需要添加该参数,表示参数为必需
public async Task<ActionResult<List<Staff>>> GetStaff(int id)
{
var staff = await _context.Staff.FindAsync(id); // 找到id相同的数据
if(staff is null)
return NotFound("找不到该员工。");
return Ok(staff);
}
需要分页时
// 分页所需的数据 如果有多个接口需要使用,可以新建模型类以公用
public class PaginatedResult<T>
{
public required List<T> Data { get; set; }
public int TotalCount { get; set; }
public int TotalPages { get; set; }
public int CurrentPage { get; set; }
public int PageSize { get; set; }
}
[HttpGet] // 方法及路径
public async Task<ActionResult<List<Staff>>> GetAllStaff(int page = 1, int size = 10)
{
// 验证页码和每页大小是否合法
if (page < 1) page = 1;
if (size < 1) size = 10;
// 计算总记录数和总页数
var totalCount = await _context.Staff.CountAsync();
var totalPages = (int)Math.Ceiling(totalCount / (double)size);
// 使用Skip和Take方法进行分页查询
var staffList = await _context.Staff
.OrderBy(s => s.id) // 假设我们按ID排序,你可以根据需要修改排序条件
.Skip((page - 1) * size)
.Take(size)
.ToListAsync();
if (page > totalPages)
return BadRequest("没有更多数据!");
// 创建分页结果对象
var paginatedResult = new PaginatedResult<Staff>
{
Data = staffList,
TotalCount = totalCount,
TotalPages = totalPages,
CurrentPage = page,
PageSize = size
};
return Ok(paginatedResult);
}
创建post添加请求时的参数 (类 名)
当我们需要进行添加操作时,我们需要传递添加所需的所有参数。这时我们可以使用创建控制器时提前创建的数据模型类。
通常在自行创建的Entities
或Models
文件夹中。
[HttpPost] // 如果需要传参则需要添加该参数,表示参数为必需
public async Task<IActionResult> AddStaff(Staff staff)
{
if (ModelState.IsValid)
{
staff.CreateTime = DateTime.Now.ToLocalTime(); // 将UTC时间格式转换为本地时间格式
_context.Staff.Add(staff); // 通过数据库上下文添加数据模型类的构造函数
await _context.SaveChangesAsync(); // 异步等待(async await)保存新数据值数据库
// 返回成功消息
return Content("添加成功!", "text/plain");
}
else
{
// 如果模型状态无效,返回错误消息
return BadRequest("无效的请求数据。");
}
}
Put请求更新数据
[HttpPut]
public async Task<IActionResult> UpdateStaff(Staff UpdateStaff)
{
if (ModelState.IsValid)
{
var staff_form = await _context.Staff.FindAsync(UpdateStaff.id); // 找到id相同的数据
if (staff_form is null)
return NotFound("找不到该员工。");
// 依次更改数据字段
staff_form.name = UpdateStaff.name;
staff_form.department = UpdateStaff.department;
staff_form.sex = UpdateStaff.sex;
await _context.SaveChangesAsync();
return Ok("修改成功!");
}
else
{
// 如果模型状态无效,返回错误消息
return BadRequest("无效的请求数据。");
}
}
Delete删除数据
[HttpDelete]
public async Task<IActionResult> DelStaff(int id)
{
var staff = await _context.Staff.FindAsync(id); // 找到id相同的数据
if (staff is null)
return NotFound("找不到该员工。");
_context.Staff.Remove(staff);
await _context.SaveChangesAsync();
return Ok("删除成功!");
}
关于创建时间
由于.NET8 webapi
中默认使用UTC
的时间格式,而北京时间使用的是UTC+8
的时间格式。所以api
中返回的时间会比国内本地时间晚八个小时,我们需要对时间格式进行转换。
使用DateTime.Now.ToLocalTime();
将UTC
时间转换为本地时间,不知道为什么在数据模型类中不管用。所以我在添加功能的api中手动进行转换,虽然swagger
文档显示的仍是UTC
时间,但数据库中已然转换成功。
修改时间格式,将文件放到Entities
文件夹中
// 解决时间格式 ISO 8601 带T的问题
builder.Services.AddControllers().AddJsonOptions(configure =>
{
configure.JsonSerializerOptions.Converters.Add(new DatetimeJsonConverter()); // 使用一个新类更改ISO 8601时间格式
});
关于跨域问题
Cross-Origin Resource Sharing 跨源资源共享 (CORS):
- 是一种 W3C 标准,允许服务器放宽同源策略。
- 不是安全功能,CORS 放松了安全限制。 允许 CORS 并不会使 API 更安全。 有关详细信息,请参阅 CORS 的工作原理。
- 允许服务器显式允许某些跨源请求,同时拒绝其他请求。
- 比早期技术(如 JSONP)更安全、更灵活。
—— 摘自微软ASP.NET Core 8.0 文档
文心一言给出的解决方案
您遇到的错误是由于跨域资源共享(CORS)策略导致的。CORS是一种安全机制,它限制了从一个源(域、协议和端口)加载的网页如何与另一个源的资源进行交互。当您尝试使用Ajax从一个源(例如本地文件系统或localhost:某个端口
)向另一个源(在本例中是https://localhost:7033
)发送请求时,浏览器会检查响应头中是否包含Access-Control-Allow-Origin
字段,以确定是否允许这种跨域请求。
var builder = WebApplication.CreateBuilder(args);
// 添加CORS服务
builder.Services.AddCors(options =>
{
options.AddPolicy("MyCorsPolicy", builder =>
{
builder.WithOrigins("http://localhost:你的前端端口号",
// ...其他源
)
.AllowAnyHeader()
.AllowAnyMethod();
});
});
var app = builder.Build();
// 使用CORS中间件
app.UseCors("MyCorsPolicy");
// 其他中间件和终端配置...
app.Run();
使用正则表达式校验
using System.ComponentModel.DataAnnotations;
namespace test_vs_api.Entities
{
public class User
{
public int id { get; set; }
public required string UserName { get; set; }
public required string Password { get; set; }
[RegularExpression(@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", ErrorMessage = "请输入有效的邮箱地址。")] // 像这样
public required string Email { get; set; }
public required string NickName { get; set; }
public required DateTime CreateTime { get; set; }
}
}
部署在IIs
首先需要安装.NET-hosting
文件,根据使用.NET版本进行安装。打开IIS,安装模块。
添加网站,访问api路径即可
部署在linux服务器
VS发布时部署模式选择独立,目标运行时选择linux对应的版本,数据库与EF迁移记得勾选
如果部署模式
选择框架依赖,那么服务器就要安装dotnet
随后将发布后的项目上传至服务器任意目录中,打开终端输入命令行./your_api
在linux运行api
dotnet your_api.dll // 使用dotnet运行
./your_api // 独立运行
nohup dotnet yourapi.dll & // 在后台持续运行
接口拒绝访问时
在appsettings.json
文件中添加以下代码
// 如果在访问接口时,遇到了http请求被重定向到https的问题,可以删掉https的配置
// 注意端口号必须与launchSettings.json文件中一致
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://*:5000"
},
"Https": {
"Url": "https://*:5001"
}
}
}