C#

技术栈
后端框架
dotnetcsharpmicrosoftobject-orientedasync-awaitlinq

概览

C# 技术栈概览

C#(读作 C Sharp)是微软于 2000 年发布的现代面向对象编程语言,由 Anders Hejlsberg 主导设计。运行在 .NET 平台(原 .NET Framework,现统一为 .NET 6/7/8+)之上,是 .NET 生态的旗舰语言。

解决什么问题

  • Windows 桌面开发:WinForms / WPF 快速构建企业级桌面应用
  • Web 后端开发:ASP.NET Core 构建高性能 REST API 和 MVC 网站
  • 游戏开发:Unity 引擎首选脚本语言
  • 移动开发:Xamarin / MAUI 跨平台移动应用
  • 云服务:Azure 深度集成

关键特性

  • 强类型、面向对象:类、接口、继承、多态、泛型
  • LINQ:语言集成查询,集合操作极为简洁
  • async/await:原生异步编程模型
  • 跨平台:.NET Core 起支持 Windows / Linux / macOS
  • 丰富的生态:NuGet 包管理,微软官方维护,企业级支持
  • 模式匹配、记录类型(C# 9+):函数式编程特性不断增强

安装

C# / .NET 安装指南

1. 环境准备

要求 说明
操作系统 Windows 10+ / macOS 12+ / Linux (Ubuntu 20.04+)
内存 建议 4GB+
磁盘空间 .NET SDK 约 1GB

2. 安装命令

Windows / macOS

dotnet.microsoft.com 下载 .NET SDK 安装程序,双击安装即可。

推荐通过 winget(Windows):

winget install Microsoft.DotNet.SDK.8

macOS (Homebrew)

brew install --cask dotnet-sdk

Linux (Ubuntu/Debian)

wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt update
sudo apt install -y dotnet-sdk-8.0

验证安装

dotnet --version
# 输出示例: 8.0.100

3. 常见安装问题

问题 解决方案
dotnet 命令找不到 重启终端或手动将安装路径加入 PATH
Linux 上依赖缺失 sudo apt install -y libicu-dev libssl-dev
macOS 安全提示 系统偏好设置 → 安全性与隐私 → 允许
需要特定版本 安装多个 SDK 并存,通过 global.json 指定项目版本
NuGet 还原慢 配置国内镜像:修改 nuget.config,使用 https://api.nuget.org/v3/index.json 或华为/阿里云镜像

示例

Hello World - ASP.NET Core Minimal API.md

Alpine.js 标签页与模态框

目标

用纯 HTML + Alpine.js 指令实现标签页切换和模态弹窗——无需写一行 JS。

完整代码

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>Alpine.js Demo</title>
  <style>
    body { font-family: system-ui; max-width: 600px; margin: 50px auto; background: #f8fafc; padding: 20px; }
    .tabs { display: flex; gap: 0; margin-bottom: 0; }
    .tab-btn { padding: 10px 20px; border: 1px solid #e2e8f0; background: #f1f5f9; cursor: pointer; border-bottom: none; border-radius: 8px 8px 0 0; }
    .tab-btn.active { background: white; color: #3b82f6; font-weight: bold; }
    .tab-panel { background: white; padding: 24px; border: 1px solid #e2e8f0; border-radius: 0 0 8px 8px; min-height: 150px; }
    .modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 100; }
    .modal-box { background: white; padding: 30px; border-radius: 12px; max-width: 400px; width: 90%; box-shadow: 0 20px 60px rgba(0,0,0,0.3); }
    .btn { padding: 10px 20px; background: #3b82f6; color: white; border: none; border-radius: 6px; cursor: pointer; }
    .btn-danger { background: #ef4444; }
    .toast { position: fixed; bottom: 20px; right: 20px; padding: 12px 24px; border-radius: 8px; color: white; animation: slideIn 0.3s ease; }
    @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
  </style>
  <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body>

  <h1>🏗 Alpine.js Demo</h1>
  <p style="color: #666;">纯 HTML 属性驱动的交互 —— 零 JS 代码</p>

  <!-- 标签页组件 -->
  <div x-data="{ activeTab: 'vue' }">
    <div class="tabs">
      <button class="tab-btn" :class="{ active: activeTab === 'vue' }" @click="activeTab = 'vue'">Vue</button>
      <button class="tab-btn" :class="{ active: activeTab === 'react' }" @click="activeTab = 'react'">React</button>
      <button class="tab-btn" :class="{ active: activeTab === 'svelte' }" @click="activeTab = 'svelte'">Svelte</button>
    </div>
    <div class="tab-panel">
      <div x-show="activeTab === 'vue'">
        <h3>Vue 3</h3>
        <p>渐进式框架,尤雨溪创建。Composition API + 响应式系统。</p>
      </div>
      <div x-show="activeTab === 'react'">
        <h3>React 18</h3>
        <p>Facebook 出品,函数式组件 + Hooks 生态丰富。</p>
      </div>
      <div x-show="activeTab === 'svelte'">
        <h3>Svelte</h3>
        <p>编译时框架,无虚拟 DOM,极致轻量。</p>
      </div>
    </div>
  </div>

  <!-- 模态框 + Toast -->
  <div x-data="{ showModal: false, toast: '' }" style="margin-top: 30px; text-align: center;">
    <button class="btn" @click="showModal = true">打开详情</button>

    <!-- 模态框 -->
    <div x-show="showModal" class="modal-overlay" @click.self="showModal = false">
      <div class="modal-box" @click.stop>
        <h2>📄 项目详情</h2>
        <p><strong>项目名称:</strong>校园二手交易平台</p>
        <p><strong>技术栈:</strong>Vue3 + NestJS + MySQL</p>
        <p><strong>完成度:</strong>85%</p>
        <div style="display: flex; gap: 10px; margin-top: 20px;">
          <button class="btn" @click="showModal = false; toast = '已确认提交 ✅'">确认</button>
          <button class="btn btn-danger" @click="showModal = false">取消</button>
        </div>
      </div>
    </div>

    <!-- Toast 通知 -->
    <div x-show="toast" x-transition class="toast" style="background: #22c55e;"
         x-init="setTimeout(() => toast = '', 2000)" x-text="toast"></div>
  </div>

</body>
</html>

运行步骤

  1. 复制代码保存为 index.html
  2. 浏览器直接打开

预期输出

  • 点击标签页按钮切换不同框架介绍
  • 点击"打开详情"弹出模态框,点遮罩或取消关闭
  • 点击确认后弹绿色 Toast,2 秒后自动消失
  • 整个页面没有一行自定义 JS,全是 HTML 属性驱动

LINQ 数据查询与 async-await 实践.md

Alpine.js 标签页与模态框

目标

用纯 HTML + Alpine.js 指令实现标签页切换和模态弹窗——无需写一行 JS。

完整代码

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>Alpine.js Demo</title>
  <style>
    body { font-family: system-ui; max-width: 600px; margin: 50px auto; background: #f8fafc; padding: 20px; }
    .tabs { display: flex; gap: 0; margin-bottom: 0; }
    .tab-btn { padding: 10px 20px; border: 1px solid #e2e8f0; background: #f1f5f9; cursor: pointer; border-bottom: none; border-radius: 8px 8px 0 0; }
    .tab-btn.active { background: white; color: #3b82f6; font-weight: bold; }
    .tab-panel { background: white; padding: 24px; border: 1px solid #e2e8f0; border-radius: 0 0 8px 8px; min-height: 150px; }
    .modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 100; }
    .modal-box { background: white; padding: 30px; border-radius: 12px; max-width: 400px; width: 90%; box-shadow: 0 20px 60px rgba(0,0,0,0.3); }
    .btn { padding: 10px 20px; background: #3b82f6; color: white; border: none; border-radius: 6px; cursor: pointer; }
    .btn-danger { background: #ef4444; }
    .toast { position: fixed; bottom: 20px; right: 20px; padding: 12px 24px; border-radius: 8px; color: white; animation: slideIn 0.3s ease; }
    @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
  </style>
  <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body>

  <h1>🏗 Alpine.js Demo</h1>
  <p style="color: #666;">纯 HTML 属性驱动的交互 —— 零 JS 代码</p>

  <!-- 标签页组件 -->
  <div x-data="{ activeTab: 'vue' }">
    <div class="tabs">
      <button class="tab-btn" :class="{ active: activeTab === 'vue' }" @click="activeTab = 'vue'">Vue</button>
      <button class="tab-btn" :class="{ active: activeTab === 'react' }" @click="activeTab = 'react'">React</button>
      <button class="tab-btn" :class="{ active: activeTab === 'svelte' }" @click="activeTab = 'svelte'">Svelte</button>
    </div>
    <div class="tab-panel">
      <div x-show="activeTab === 'vue'">
        <h3>Vue 3</h3>
        <p>渐进式框架,尤雨溪创建。Composition API + 响应式系统。</p>
      </div>
      <div x-show="activeTab === 'react'">
        <h3>React 18</h3>
        <p>Facebook 出品,函数式组件 + Hooks 生态丰富。</p>
      </div>
      <div x-show="activeTab === 'svelte'">
        <h3>Svelte</h3>
        <p>编译时框架,无虚拟 DOM,极致轻量。</p>
      </div>
    </div>
  </div>

  <!-- 模态框 + Toast -->
  <div x-data="{ showModal: false, toast: '' }" style="margin-top: 30px; text-align: center;">
    <button class="btn" @click="showModal = true">打开详情</button>

    <!-- 模态框 -->
    <div x-show="showModal" class="modal-overlay" @click.self="showModal = false">
      <div class="modal-box" @click.stop>
        <h2>📄 项目详情</h2>
        <p><strong>项目名称:</strong>校园二手交易平台</p>
        <p><strong>技术栈:</strong>Vue3 + NestJS + MySQL</p>
        <p><strong>完成度:</strong>85%</p>
        <div style="display: flex; gap: 10px; margin-top: 20px;">
          <button class="btn" @click="showModal = false; toast = '已确认提交 ✅'">确认</button>
          <button class="btn btn-danger" @click="showModal = false">取消</button>
        </div>
      </div>
    </div>

    <!-- Toast 通知 -->
    <div x-show="toast" x-transition class="toast" style="background: #22c55e;"
         x-init="setTimeout(() => toast = '', 2000)" x-text="toast"></div>
  </div>

</body>
</html>

运行步骤

  1. 复制代码保存为 index.html
  2. 浏览器直接打开

预期输出

  • 点击标签页按钮切换不同框架介绍
  • 点击"打开详情"弹出模态框,点遮罩或取消关闭
  • 点击确认后弹绿色 Toast,2 秒后自动消失
  • 整个页面没有一行自定义 JS,全是 HTML 属性驱动

01-hello-world-minimal-api.md

C# Hello World — ASP.NET Core Minimal API

目标

用 C# 的 ASP.NET Core Minimal API 创建一个 Web 服务,访问根路径返回 JSON 格式的 "Hello World"。

完整代码

// Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// 根路由 — Hello World
app.MapGet("/", () => Results.Ok(new
{
    message = "Hello World",
    timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
}));

// 健康检查
app.MapGet("/health", () => Results.Ok(new { status = "ok" }));

app.Run();

运行步骤

# 1. 创建项目
dotnet new web -n HelloApi
cd HelloApi

# 2. 将上述代码替换 Program.cs

# 3. 运行
dotnet run

测试

curl http://localhost:5000/
# 输出: {"message":"Hello World","timestamp":1717000000}

curl http://localhost:5000/health
# 输出: {"status":"ok"}

预期输出

$ dotnet run
Building...
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000

要点说明

  • WebApplication.CreateBuilder() 创建应用构建器(自动加载配置、DI 容器、日志)
  • MapGet("/", ...) 定义 GET 路由,Lambda 作为处理器
  • Results.Ok() 返回 HTTP 200 + JSON 自动序列化
  • Minimal API 无需 Controller 类,适合微服务和小型 API

02-controller-rest-api.md

C# ASP.NET Core — Controller RESTful API

目标

使用 Controller 模式构建完整的用户管理 RESTful API,模拟数据库 CRUD 操作。

完整代码

// Models/User.cs
public record User(int Id, string Name, string Email);

// Models/CreateUserRequest.cs
public record CreateUserRequest(string Name, string Email);

// Models/UpdateUserRequest.cs
public record UpdateUserRequest(string? Name, string? Email);

// Controllers/UsersController.cs
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private static readonly List<User> _users = new()
    {
        new(1, "Alice", "alice@example.com"),
        new(2, "Bob", "bob@example.com"),
    };
    private static int _nextId = 3;

    // GET /api/users
    [HttpGet]
    public IActionResult GetAll() =>
        Ok(new { total = _users.Count, data = _users });

    // GET /api/users/{id}
    [HttpGet("{id:int}")]
    public IActionResult GetById(int id)
    {
        var user = _users.FirstOrDefault(u => u.Id == id);
        return user is null ? NotFound(new { error = "User not found" }) : Ok(user);
    }

    // POST /api/users
    [HttpPost]
    public IActionResult Create([FromBody] CreateUserRequest request)
    {
        if (string.IsNullOrWhiteSpace(request.Name) ||
            string.IsNullOrWhiteSpace(request.Email))
            return BadRequest(new { error = "name and email are required" });

        var user = new User(_nextId++, request.Name, request.Email);
        _users.Add(user);
        return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
    }

    // PUT /api/users/{id}
    [HttpPut("{id:int}")]
    public IActionResult Update(int id, [FromBody] UpdateUserRequest request)
    {
        var user = _users.FirstOrDefault(u => u.Id == id);
        if (user is null) return NotFound(new { error = "User not found" });

        var updated = user with
        {
            Name = request.Name ?? user.Name,
            Email = request.Email ?? user.Email
        };
        _users[_users.IndexOf(user)] = updated;
        return Ok(updated);
    }

    // DELETE /api/users/{id}
    [HttpDelete("{id:int}")]
    public IActionResult Delete(int id)
    {
        var user = _users.FirstOrDefault(u => u.Id == id);
        if (user is null) return NotFound(new { error = "User not found" });
        _users.Remove(user);
        return NoContent();
    }
}

Program.cs(注册控制器)

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(); // 注册控制器
var app = builder.Build();
app.MapControllers();              // 映射控制器路由
app.Run();

测试

curl http://localhost:5000/api/users
curl -X POST http://localhost:5000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Charlie","email":"charlie@example.com"}'
curl -X PUT http://localhost:5000/api/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice Updated"}'
curl -X DELETE http://localhost:5000/api/users/3

要点说明

  • [ApiController] 自动模型验证、400 响应、路由推断
  • record 类型提供值相等、with 表达式、不可变性
  • [FromBody] 从 JSON 请求体绑定参数
  • CreatedAtAction 返回 201 + Location 头
  • NoContent() 返回 204(删除成功标准)

Hello World 与 LINQ 入门.md

C# Hello World 与 LINQ 入门

目标

展示 .NET 8 控制台应用的 Hello World,以及 LINQ 查询、async/await 的基本用法。

完整代码

// Program.cs - .NET 8 顶级语句
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

// 1. Hello World
Console.WriteLine("Hello, .NET 8 World!");

// 2. 记录类型 (C# 9+)
var students = new List<Student>
{
    new("张三", 22, 88.5),
    new("李四", 21, 92.0),
    new("王五", 23, 76.5),
    new("赵六", 22, 95.0),
    new("钱七", 21, 85.0),
};

// 3. LINQ 查询:找出年龄 > 21 且分数 >= 90 的学生
var topStudents = from s in students
                  where s.Age > 21 && s.Score >= 90
                  orderby s.Score descending
                  select new { s.Name, s.Score, Grade = GetGrade(s.Score) };

Console.WriteLine("\n优秀学生:");
foreach (var s in topStudents)
{
    Console.WriteLine($"  {s.Name}: {s.Score}分, 等级: {s.Grade}");
}

// 方法链式写法
var avgScore = students.Where(s => s.Age == 22).Average(s => s.Score);
Console.WriteLine($"\n22岁学生平均分: {avgScore:F1}");

// 4. async/await 异步
Console.WriteLine("\n异步操作演示:");
await FetchDataAsync();

Console.WriteLine("\n按任意键退出...");
Console.ReadKey();

// 辅助方法
static string GetGrade(double score) => score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    >= 60 => "D",
    _ => "F"
};

static async Task FetchDataAsync()
{
    Console.WriteLine("  开始请求数据...");
    await Task.Delay(1500);  // 模拟异步 I/O
    Console.WriteLine("  数据获取完成!");
}

// 记录类型
record Student(string Name, int Age, double Score);

运行步骤

# 创建项目
dotnet new console -n HelloCSharp
cd HelloCSharp

# 替换 Program.cs 内容后运行
dotnet run

预期输出

Hello, .NET 8 World!

优秀学生:
  赵六: 95分, 等级: A
  张三: 88.5分, 等级: B

22岁学生平均分: 91.8

异步操作演示:
  开始请求数据...
  数据获取完成!

关键点

  • 顶级语句:无需写 Mainclass Program,简洁上手
  • LINQ:两种写法——查询语法(类似 SQL)和方法链
  • 模式匹配switch 表达式优雅处理多条件判断
  • async/await:非阻塞异步,适合 I/O 密集型操作
  • record:不可变数据类,自动生成 Equals/ToString

教程

入门篇:ASP.NET Core Web API 实战.md

Alpine.js 入门教程:给你的 HTML 添加超能力

一、Alpine.js 是什么?

Alpine.js 的口号是"像写 Tailwind CSS 一样写 JavaScript"。它让你用简单的 HTML 属性为页面添加交互,无需构建工具、无需虚拟 DOM,只有 15 个属性 + 6 个魔法方法 + 1 个全局状态。非常适合后端开发者给模板引擎(Laravel Blade、Django Templates、Rails ERB 等)添加交互,或为静态网站注入轻量级动态行为。

二、核心指令速览

指令 作用 示例
x-data 定义组件作用域和数据 x-data="{ open: false }"
x-model 双向数据绑定 <input x-model="name">
@click / x-on: 事件绑定 <button @click="open = !open">
x-show 条件显示(display:none) <div x-show="open">
x-if 条件渲染(移除DOM) <template x-if="open">
x-for 循环渲染 <template x-for="item in items">
x-text 输出文本(防XSS) <span x-text="name">
x-html 输出HTML(慎用) <div x-html="raw">
x-bind: 属性绑定 <img :src="url">
x-effect 监听变化执行回调 <div x-effect="fetch(url)">
x-ref 组件内部引用 <div x-ref="myDiv">
x-init 组件初始化钩子 <div x-init="fetchData()">

三、魔法方法

<!-- $store - 全局状态 -->
<span x-text="$store.theme.darkMode"></span>

<!-- $dispatch - 事件分发 -->
<button @click="$dispatch('custom-event', { data: 42 })">

<!-- $watch - 监听数据变化 -->
<div x-init="$watch('count', val => console.log(val))">

<!-- $refs - 访问 DOM -->
<div x-ref="input" x-init="$refs.input.focus()">

<!-- $nextTick - 下一个 tick -->
<div x-init="$nextTick(() => console.log('DOM 已更新'))">

<!-- $el - 当前 DOM 元素 -->
<div x-init="console.log($el.tagName)">

四、分步实战:Todo 应用

第一步:基础结构

<div x-data="{
  todos: [],
  newTodo: '',
  addTodo() {
    if (this.newTodo.trim()) {
      this.todos.push({ id: Date.now(), text: this.newTodo, done: false });
      this.newTodo = '';
    }
  },
  removeTodo(id) {
    this.todos = this.todos.filter(t => t.id !== id);
  }
}">

第二步:表单与列表

<form @submit.prevent="addTodo">
  <input x-model="newTodo" placeholder="输入任务..." />
  <button type="submit">添加</button>
</form>

<template x-for="todo in todos" :key="todo.id">
  <li>
    <span :class="{ 'line-through': todo.done }" x-text="todo.text"></span>
    <button @click="todo.done = !todo.done">✓</button>
    <button @click="removeTodo(todo.id)">✕</button>
  </li>
</template>

第三步:持久化到 localStorage

<div x-data="{ todos: [] }"
     x-init="todos = JSON.parse(localStorage.getItem('todos') || '[]');
             $watch('todos', val => localStorage.setItem('todos', JSON.stringify(val)))">

五、全局状态(Alpine.store)

<script defer src="...alpine.min.js"></script>
<script>
  document.addEventListener('alpine:init', () => {
    Alpine.store('theme', {
      dark: false,
      toggle() { this.dark = !this.dark; }
    });
  });
</script>

<!-- 任意组件中使用 -->
<div :class="$store.theme.dark ? 'bg-black' : 'bg-white'">
  <button @click="$store.theme.toggle()">切换主题</button>
</div>

六、最佳实践

  1. x-data 对象保持简单——复杂逻辑抽到外部 JS 文件
  2. x-if 用 template 标签——避免残留 DOM 节点
  3. 列表必加 :key——性能关键
  4. $dispatch 实现父子组件通信——松耦合

七、思考题

  1. Alpine.js 和 Vue.js 的 v-model 有什么区别?
  2. 如何在 Alpine 组件间共享状态?(提示:$store / $dispatch / 自定义事件)
  3. 为什么 Alpine.js 适合"渐进增强"而 React 不太适合?

进阶篇:依赖注入与中间件深度解析.md

Alpine.js 入门教程:给你的 HTML 添加超能力

一、Alpine.js 是什么?

Alpine.js 的口号是"像写 Tailwind CSS 一样写 JavaScript"。它让你用简单的 HTML 属性为页面添加交互,无需构建工具、无需虚拟 DOM,只有 15 个属性 + 6 个魔法方法 + 1 个全局状态。非常适合后端开发者给模板引擎(Laravel Blade、Django Templates、Rails ERB 等)添加交互,或为静态网站注入轻量级动态行为。

二、核心指令速览

指令 作用 示例
x-data 定义组件作用域和数据 x-data="{ open: false }"
x-model 双向数据绑定 <input x-model="name">
@click / x-on: 事件绑定 <button @click="open = !open">
x-show 条件显示(display:none) <div x-show="open">
x-if 条件渲染(移除DOM) <template x-if="open">
x-for 循环渲染 <template x-for="item in items">
x-text 输出文本(防XSS) <span x-text="name">
x-html 输出HTML(慎用) <div x-html="raw">
x-bind: 属性绑定 <img :src="url">
x-effect 监听变化执行回调 <div x-effect="fetch(url)">
x-ref 组件内部引用 <div x-ref="myDiv">
x-init 组件初始化钩子 <div x-init="fetchData()">

三、魔法方法

<!-- $store - 全局状态 -->
<span x-text="$store.theme.darkMode"></span>

<!-- $dispatch - 事件分发 -->
<button @click="$dispatch('custom-event', { data: 42 })">

<!-- $watch - 监听数据变化 -->
<div x-init="$watch('count', val => console.log(val))">

<!-- $refs - 访问 DOM -->
<div x-ref="input" x-init="$refs.input.focus()">

<!-- $nextTick - 下一个 tick -->
<div x-init="$nextTick(() => console.log('DOM 已更新'))">

<!-- $el - 当前 DOM 元素 -->
<div x-init="console.log($el.tagName)">

四、分步实战:Todo 应用

第一步:基础结构

<div x-data="{
  todos: [],
  newTodo: '',
  addTodo() {
    if (this.newTodo.trim()) {
      this.todos.push({ id: Date.now(), text: this.newTodo, done: false });
      this.newTodo = '';
    }
  },
  removeTodo(id) {
    this.todos = this.todos.filter(t => t.id !== id);
  }
}">

第二步:表单与列表

<form @submit.prevent="addTodo">
  <input x-model="newTodo" placeholder="输入任务..." />
  <button type="submit">添加</button>
</form>

<template x-for="todo in todos" :key="todo.id">
  <li>
    <span :class="{ 'line-through': todo.done }" x-text="todo.text"></span>
    <button @click="todo.done = !todo.done">✓</button>
    <button @click="removeTodo(todo.id)">✕</button>
  </li>
</template>

第三步:持久化到 localStorage

<div x-data="{ todos: [] }"
     x-init="todos = JSON.parse(localStorage.getItem('todos') || '[]');
             $watch('todos', val => localStorage.setItem('todos', JSON.stringify(val)))">

五、全局状态(Alpine.store)

<script defer src="...alpine.min.js"></script>
<script>
  document.addEventListener('alpine:init', () => {
    Alpine.store('theme', {
      dark: false,
      toggle() { this.dark = !this.dark; }
    });
  });
</script>

<!-- 任意组件中使用 -->
<div :class="$store.theme.dark ? 'bg-black' : 'bg-white'">
  <button @click="$store.theme.toggle()">切换主题</button>
</div>

六、最佳实践

  1. x-data 对象保持简单——复杂逻辑抽到外部 JS 文件
  2. x-if 用 template 标签——避免残留 DOM 节点
  3. 列表必加 :key——性能关键
  4. $dispatch 实现父子组件通信——松耦合

七、思考题

  1. Alpine.js 和 Vue.js 的 v-model 有什么区别?
  2. 如何在 Alpine 组件间共享状态?(提示:$store / $dispatch / 自定义事件)
  3. 为什么 Alpine.js 适合"渐进增强"而 React 不太适合?

01-aspnet-core-web-api入门.md

C# 入门篇:ASP.NET Core Web API 实战

背景

ASP.NET Core 是微软开源的跨平台 Web 框架,在 TechEmpower 基准测试中常年 Top 10。本教程带你构建一个完整的图书管理 Web API。

核心概念

1. 中间件管道

请求依次经过每个中间件,构成管道(Pipeline)。内置中间件:异常处理、静态文件、路由、认证、CORS。

var app = builder.Build();
app.UseExceptionHandler("/error");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

2. 依赖注入(DI)

.NET 内置 IoC 容器。注册服务后,通过构造函数自动注入。

// 注册(Program.cs)
builder.Services.AddScoped<IBookService, BookService>();
builder.Services.AddDbContext<AppDbContext>(...);

// 注入(Controller)
public class BooksController : ControllerBase
{
    private readonly IBookService _service;
    public BooksController(IBookService service) => _service = service;
}

3. 配置系统

支持 appsettings.json、环境变量、命令行参数,自动合并。

// appsettings.json
{
  "ConnectionStrings": {
    "Default": "Data Source=app.db"
  }
}
// 读取
var connStr = builder.Configuration.GetConnectionString("Default");

分步操作

第一步:创建项目

dotnet new webapi -n BookApi
cd BookApi
dotnet add package Microsoft.EntityFrameworkCore.Sqlite

第二步:定义模型与 DbContext

// Models/Book.cs
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public string Author { get; set; } = "";
    public decimal Price { get; set; }
}

// Data/AppDbContext.cs
public class AppDbContext : DbContext
{
    public DbSet<Book> Books => Set<Book>();
    public AppDbContext(DbContextOptions<AppDbContext> opts) : base(opts) {}
}

第三步:创建 Service 层

public interface IBookService
{
    Task<List<Book>> GetAllAsync();
    Task<Book?> GetByIdAsync(int id);
    Task<Book> CreateAsync(Book book);
    Task<bool> UpdateAsync(int id, Book book);
    Task<bool> DeleteAsync(int id);
}

第四步:编写 Controller

利用 [ApiController] 属性自动处理模型验证错误。

第五步:数据库迁移

dotnet ef migrations add InitialCreate
dotnet ef database update

思考题

  1. AddScopedAddSingletonAddTransient 三种生命周期有什么区别?
  2. 中间件顺序为什么重要?如果 UseAuthentication 放在 UseAuthorization 之后会怎样?
  3. Entity Framework Core 的 AsNoTracking() 什么场景使用?

02-efcore-异步编程与性能.md

C# 进阶篇:EF Core、异步编程与性能优化

背景

Entity Framework Core 是 .NET 最流行的 ORM。掌握其高级用法与异步编程模式,是构建高性能 .NET 应用的基础。

核心概念

1. Task-based 异步模式

// ❌ 同步阻塞
public IActionResult GetBooks()
{
    var books = _context.Books.ToList();  // 阻塞线程
    return Ok(books);
}

// ✅ 异步非阻塞
public async Task<IActionResult> GetBooksAsync()
{
    var books = await _context.Books.ToListAsync(); // 释放线程
    return Ok(books);
}

2. EF Core 查询优化

// ❌ N+1 查询 — 每个 Book 的 Author 单独查
var books = await _context.Books.ToListAsync();
foreach (var b in books) Console.WriteLine(b.Author.Name);

// ✅ 预加载 — 一次 JOIN 搞定
var books = await _context.Books
    .Include(b => b.Author)
    .ToListAsync();

// ✅ 投影 — 只查需要的列
var result = await _context.Books
    .Select(b => new { b.Title, AuthorName = b.Author.Name })
    .ToListAsync();

3. 并发处理

// 乐观并发 — 用 RowVersion 检测冲突
public class Book
{
    // ...
    [Timestamp]
    public byte[] RowVersion { get; set; }
}

// 乐观并发异常处理
try
{
    await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
    // 冲突处理:重试 / 合并 / 通知用户
}

4. 原生 SQL 和存储过程

// 参数化查询防注入
var books = await _context.Books
    .FromSqlRaw("SELECT * FROM Books WHERE Price > {0}", minPrice)
    .ToListAsync();

// 存储过程
var result = await _context.Database
    .ExecuteSqlRawAsync("EXEC UpdateBookPrice @id, @price",
        new SqliteParameter("@id", id),
        new SqliteParameter("@price", price));

5. CancellationToken

[HttpGet]
public async Task<IActionResult> GetBooks(CancellationToken ct)
{
    var books = await _context.Books.ToListAsync(ct);
    return Ok(books);
}
// 客户端断开连接时自动取消查询

性能检查清单

优化项 方法
查询性能 投影(Select)、分页、索引、无跟踪查询
内存 AsNoTracking() 只读查询、流式枚举
并发 连接池(AddDbContextPool)、限流中间件
序列化 System.Text.Json 源码生成(AOT)
启动 ReadyToRun、Native AOT 编译

思考题

  1. ConfigureAwait(false) 在 ASP.NET Core 中还需要吗?为什么?
  2. EF Core 的 IQueryableIEnumerable 有什么区别?为什么要把 IQueryable 传给 OData?
  3. Native AOT 有什么限制?哪些场景不适合 AOT 编译?

C# ASP.NET Core 毕设入门教程.md

C# / ASP.NET Core 毕设入门教程

前言

C# 在毕设中的三大主战场:ASP.NET Core Web API 后端WPF/WinForms 桌面应用Unity 游戏开发。本教程聚焦毕设最常用的 ASP.NET Core + EF Core 后端开发。


第一章:快速搭建 Web API 项目

三板斧创建项目

dotnet new webapi -n MyProject
cd MyProject
dotnet run
# 浏览器访问 http://localhost:5000/swagger

项目结构

MyProject/
├── Controllers/       # API 控制器
├── Models/            # 数据模型
├── Services/          # 业务逻辑
├── Data/              # DbContext
├── appsettings.json   # 配置文件
└── Program.cs         # 入口

第二章:构建一个完整的 CRUD API

Model

public class TodoItem
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public bool IsCompleted { get; set; }
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

DbContext

public class AppDbContext : DbContext
{
    public DbSet<TodoItem> Todos => Set<TodoItem>();
    public AppDbContext(DbContextOptions<AppDbContext> o) : base(o) {}
}

Controller(最小 API 风格)

app.MapGet("/todos", async (AppDbContext db) => await db.Todos.ToListAsync());

app.MapPost("/todos", async (TodoItem todo, AppDbContext db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();
    return Results.Created($"/todos/{todo.Id}", todo);
});

app.MapDelete("/todos/{id}", async (int id, AppDbContext db) =>
{
    var todo = await db.Todos.FindAsync(id);
    if (todo is null) return Results.NotFound();
    db.Todos.Remove(todo);
    await db.SaveChangesAsync();
    return Results.NoContent();
});

第三章:毕设常用模式

三层架构

Controller → Service → Repository → DbContext

JWT 认证(毕设加分项)

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options => {
        options.TokenValidationParameters = new() {
            ValidateIssuer = true,
            ValidateAudience = true,
            // ... 配置密钥和签发者
        };
    });

全局异常处理

app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        context.Response.StatusCode = 500;
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync("{\"error\":\"服务器内部错误\"}");
    });
});

第四章:毕设答辩要点

演示流程建议

  1. 启动项目 dotnet run
  2. 打开 Swagger 页面展示 API 列表
  3. 用 Swagger 调几个接口展示 CRUD
  4. 展示数据库中的数据(SQLite 用 DB Browser 打开)

常见答辩问题

  • "为什么选 ASP.NET Core 而不是 Spring Boot?" → 强调 .NET 生态、LINQ 开发效率、学习曲线
  • "数据库用的什么?" → SQLite 零部署,适合毕设
  • "安全性怎么保证?" → JWT 认证、参数校验、CORS 配置

思考题

  1. RESTful API 的设计原则有哪些?你的接口是否符合?
  2. async/await 在 ASP.NET Core 中的作用是什么?
  3. EF Core 的 Include()ThenInclude() 解决了什么问题?

参考资料

  1. [1] Microsoft. C# 语言规范. 2024.
  2. [2] Jeffrey Richter. CLR via C# (4th Edition). 2012.
  3. [3] Andrew Lock. ASP.NET Core in Action (3rd Edition). 2023.
  4. [4] Konrad Kokosa. Pro .NET Memory Management. 2018.
  5. [5] Microsoft. .NET 官方文档. 2024.
  6. [6] Joseph Albahari, Ben Albahari. C# 12 in a Nutshell. 2024.
  7. [7] Andrew Lock. ASP.NET Core in Action, 3rd Edition. 2024.
  8. [8] Microsoft. Entity Framework Core 官方文档. 2024.
  9. [9] Microsoft. C# Language Specification. 2023. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/
  10. [10] Jon Skeet. C# in Depth (4th Edition). 2019.
  11. [11] Andrew Lock. ASP.NET Core in Action (3rd Edition). 2023.
  12. [12] Andrew Troelsen, Phil Japikse. Pro C# 10 with .NET 6. 2022.