后端与接口
使用 Appwrite .NET SDK 构建…
当需要在 ASP.NET、Blazor 等 .NET 服务端应用中接入 Appwrite,并实现用户管理、数据库 CRUD、文件存储、团队权限、Serverless Functions 与 SSR 会话认证时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
使用 Appwrite .NET SDK特定
解决思路
- 安装 Appwrite .NET SDK:运行 `dotnet add package Appwrite`。
- 创建客户端:引用 `using Appwrite; using Appwrite.Services; using Appwrite.Models;`,用 `new Client().SetEndpoint(...).SetProject(...).SetKey(...)` 初始化;endpoint 使用 `https://<REGION>.cloud.appwrite.io/v1`,project 从环境变量 `APPWRITE_PROJECT_ID` 读取,API key 从 `APPWRITE_API_KEY` 读取。
- 用户管理:用 `new Users(client)`;创建用户 `users.Create(ID.Unique(), email, null, password, name)`;列出用户 `users.List(new List<string> { Query.Limit(25) })`;获取用户 `users.Get(userId)`;删除用户 `users.Delete(userId)`。
- 数据库操作:新代码用 `TablesDB`,不要用已弃用的 `Databases`;仅当现有代码依赖或用户明确要求时用 `Databases`。
- SDK 方法调用优先用命名参数,如 `databaseId:`;仅当现有代码已用位置参数或用户明确要求时用位置参数。
- 初始化数据库服务:`var tablesDB = new TablesDB(client);`。
- 创建数据库:`tablesDB.Create(ID.Unique(), 数据库名)`。
- 创建行:`tablesDB.CreateRow(databaseId, tableId, ID.Unique(), new Dictionary<string, object> { { 字段名, 值 } })`。
- 查询行:`tablesDB.ListRows(databaseId, tableId, new List<string> { Query.Equal(字段, 值), Query.Limit(10) })`。
- 获取行:`tablesDB.GetRow(databaseId, tableId, rowId)`。
- 更新行:`tablesDB.UpdateRow(databaseId, tableId, rowId, new Dictionary<string, object> { { 字段名, 新值 } })`。
- 删除行:`tablesDB.DeleteRow(databaseId, tableId, rowId)`。
- 字符串列类型:旧 `string` 类型已弃用;所有新列使用显式列类型。
- 选择 `varchar`:最大 16,383 字符,若 size ≤ 768 可全索引,内联存储在行内;适合短且需索引的字段,如名称、slug、标识符。
- 选择 `text`:最大 16,383 字符,仅前缀索引,离页存储。
- 选择 `mediumtext`:最大 4,194,303 字符,仅前缀索引,离页存储。
- 选择 `longtext`:最大 1,073,741,823 字符,仅前缀索引,离页存储。
- 注意存储差异:`varchar` 内联存储并计入 64 KB 行大小限制;`text`、`mediumtext`、`longtext` 离页存储,行内仅 20 字节指针,不消耗行大小预算,且不需要 `size`。
- 创建表时显式指定字符串列类型:`tablesDB.CreateTable(databaseId, ID.Unique(), 表名, new List<object> { new { key = ..., type = varchar/text/mediumtext/longtext, size = ..., required = ... } })`。
- 查询过滤:使用 `Query.Equal`(== 或传数组表示 IN)、`Query.NotEqual`、`Query.LessThan`、`Query.LessThanEqual`、`Query.GreaterThan`、`Query.GreaterThanEqual`、`Query.Between`、`Query.IsNull`、`Query.IsNotNull`、`Query.StartsWith`、`Query.EndsWith`、`Query.Contains`、`Query.Search`(全文搜索需索引)。
- 排序:使用 `Query.OrderAsc`、`Query.OrderDesc`。
- 分页:使用 `Query.Limit(25)`(默认 25,最大 100)、`Query.Offset(0)`;优先用游标分页 `Query.CursorAfter(rowId)` 或 `Query.CursorBefore(rowId)`。
- 选择与逻辑:使用 `Query.Select` 选择字段;`Query.Or` 表示 OR;`Query.And` 表示 AND(默认)。
- 文件存储:用 `new Storage(client)`;上传 `storage.CreateFile(bucketId, ID.Unique(), InputFile.FromPath(路径))`;列出 `storage.ListFiles(bucketId)`;删除 `storage.DeleteFile(bucketId, fileId)`。
- InputFile 工厂方法:`InputFile.FromPath(路径)`、`InputFile.FromBytes(byteArray, 文件名, MIME类型)`、`InputFile.FromStream(stream, 文件名, MIME类型, size)`(Stream 必须传 size)。
- 团队:用 `new Teams(client)`;创建 `teams.Create(ID.Unique(), 团队名)`;列出 `teams.List()`;创建成员邀请 `teams.CreateMembership(teamId: ..., roles: new List<string> { 角色 }, email: ...)`;列出成员 `teams.ListMemberships(teamId)`;更新成员角色 `teams.UpdateMembership(teamId, membershipId, new List<string> { 新角色 })`;删除 `teams.Delete(teamId)`。
- 团队角色权限:设置权限时用 `Role.Team(teamId)` 表示所有团队成员,或 `Role.Team(teamId, 角色)` 表示特定团队角色。
- Serverless Functions:用 `new Functions(client)`;执行 `functions.CreateExecution(functionId, body: JSON字符串)`;列出执行 `functions.ListExecutions(functionId)`。
- 编写 .NET 函数处理程序:入口文件 `src/Main.cs`,实现 `public async Task<RuntimeOutput> Main(RuntimeContext context)`。
- 在函数中读取请求:`context.Req.Body` 原始字符串、`context.Req.BodyJson` 解析后的 JsonElement、`context.Req.Headers` 字典、`context.Req.Method`、`context.Req.Path`、`context.Req.Query` 查询参数字典。
- 在函数中记录日志:调用 `context.Log(...)`。
- 在函数中返回响应:GET 时返回 `context.Res.Json(...)`;还可返回 `context.Res.Text(...)`、`context.Res.Empty()`、`context.Res.Redirect(...)`。
- SSR 认证:ASP.NET、Blazor Server 等 .NET SSR 应用用服务端 SDK 处理认证;需要两个客户端:admin client 用 API key,创建会话并绕过速率限制,可复用为单例;session client 用会话 cookie,代表用户操作,必须每请求创建,不能共享。
- 构建 admin client:`new Client().SetEndpoint(...).SetProject(...).SetKey(Environment.GetEnvironmentVariable(APPWRITE_API_KEY))`。
- 构建 session client:`new Client().SetEndpoint(...).SetProject(...)`;从 `Request.Cookies` 读取 cookie 名 `a_session_[PROJECT_ID]`;若存在则 `sessionClient.SetSession(session)`。
- 邮箱/密码登录(ASP.NET Minimal API):在 `app.MapPost` 路由 `/login` 中,用 admin client 创建 `new Account(adminClient)`,调用 `account.CreateEmailPasswordSession(body.Email, body.Password)`;把 `session.Secret` 写入名为 `a_session_[PROJECT_ID]` 的 cookie,设置 `HttpOnly = true`、`Secure = true`、`SameSite = SameSiteMode.Strict`、`Path = /`;返回成功。
- 已认证请求:在 `app.MapGet` 路由 `/user` 中读取 `a_session_[PROJECT_ID]` cookie;若不存在返回 Unauthorized;用 endpoint、project、session 创建 session client;用 `new Account(sessionClient)` 调用 `account.Get()` 获取用户并返回。
- OAuth2 SSR 流程第一步:重定向到 OAuth 提供方;用 admin client 创建 `new Account(adminClient)`,调用 `account.CreateOAuth2Token(provider: OAuthProvider.Github, success: 成功回调URL, failure: 失败回调URL)`,然后 `Results.Redirect(redirectUrl)`。
- OAuth2 SSR 流程第二步:处理成功回调 `/oauth/success`,接收 `userId` 和 `secret`;用 admin client 的 `account.CreateSession(userId, secret)` 换取 session;把 `session.Secret` 写入 `a_session_[PROJECT_ID]` cookie 并设置 `HttpOnly`、`Secure`、`SameSite = SameSiteMode.Strict`、`Path = /`;返回成功。
- Cookie 安全:始终用 `HttpOnly`、`Secure`、`SameSite = SameSiteMode.Strict` 防止 XSS;cookie 名必须是 `a_session_<PROJECT_ID>`。
- 转发用户代理:调用 `sessionClient.SetForwardedUserAgent(ctx.Request.Headers[User-Agent])` 记录终端用户浏览器信息,用于调试和安全。
- 错误处理:用 try/catch 捕获 `AppwriteException`;读取 `e.Message`(可读消息)、`e.Code`(HTTP 状态码 int)、`e.Type`(错误类型,如 document_not_found)、`e.Response`(完整响应体)。
- 常见错误码:`401` 未授权(缺失/无效 session 或 API key)、`403` 禁止(权限不足)、`404` 未找到(资源不存在)、`409` 冲突(重复 ID 或唯一约束)、`429` 速率限制(请求过多)。
- 权限与角色:Appwrite 用权限字符串控制资源访问;每个权限将动作 `read`、`update`、`delete`、`create`、`write`(write = create + update + delete)与角色目标配对;默认没有用户有权限,除非在行/文件级显式设置权限或从表/存储桶设置继承;权限是字符串数组,用 `Permission` 和 `Role` 辅助方法构建。
- 数据库行权限:调用 `tablesDB.CreateRow(databaseId, tableId, ID.Unique(), 数据字典, new List<string> { Permission.Read(Role.User(userId)), Permission.Update(Role.User(userId)), Permission.Read(Role.Team(teamId)), Permission.Read(Role.Any()) })`。
- 文件上传权限:调用 `storage.CreateFile(bucketId, ID.Unique(), InputFile.FromPath(路径), new List<string> { Permission.Read(Role.Any()), Permission.Update(Role.User(userId)), Permission.Delete(Role.User(userId)) })`。
- 何时设置权限:需要按资源访问控制时,设置行/文件级权限;若表内所有行共享同一规则,在表/存储桶级配置权限,并让行权限留空。
- 常见权限错误:忘记设置权限会导致资源对所有人(包括创建者)不可访问;对 `Role.Any()` 使用 `write`/`update`/`delete` 会让任何用户(包括未认证访客)修改或删除资源;对敏感数据使用 `Permission.Read(Role.Any())` 会使其公开可读。
适用边界 · 注意事项
- 不适用于未接入 Appwrite 或未配置 endpoint、project ID、API key 的项目。
- 不适用于纯前端或无服务端密钥、只想使用客户端 SDK 的场景。
- 不适用于现有代码已深度依赖旧 API 且不允许迁移或改动的情况。