关于 appwrite-dotnet
Appwrite .NET SDK 是供 .NET 开发者在应用中使用 Appwrite 的库,提供身份验证、用户、表格、存储、团队、函数和权限相关 API。
主要功能
- 01配置 Appwrite 客户端
- 02管理用户与团队
- 03创建表格和行
- 04上传与列出文件
- 05执行无服务器函数
- 06设置资源权限
适用场景
- 01在 .NET 应用中加入 Appwrite 用户管理
- 02读取和写入表格行
- 03按访问规则上传文件
- 04执行 Appwrite 函数
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ appwrite/ skills --skill appwrite-dotnet安装后在 Claude Code 中输入/appwrite-dotnet确认已加载。
详细介绍
Appwrite .NET SDK
安装
dotnet add package Appwrite
设置客户端
using Appwrite;
using Appwrite.Services;
using Appwrite.Models;
var client = new Client()
.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.SetProject(Environment.GetEnvironmentVariable("APPWRITE_PROJECT_ID"))
.SetKey(Environment.GetEnvironmentVariable("APPWRITE_API_KEY"));
代码示例
用户管理
var users = new Users(client);
// Create user
var user = await users.Create(ID.Unique(), "[email protected]", null, "password123", "User Name");
// List users
var list = await users.List(new List<string> { Query.Limit(25) });
// Get user
var fetched = await users.Get("[USER_ID]");
// Delete user
await users.Delete("[USER_ID]");
数据库操作
注意: 所有新代码都应使用
TablesDB,而不是已弃用的Databases类。只有当现有代码库已依赖Databases,或用户明确要求时,才使用Databases。提示: SDK 方法调用应优先使用命名参数(例如
databaseId: "...")。只有当现有代码库已使用位置参数,或用户明确要求时,才使用位置参数。
var tablesDB = new TablesDB(client);
// Create database
var db = await tablesDB.Create(ID.Unique(), "My Database");
// Create row
var doc = await tablesDB.CreateRow("[DATABASE_ID]", "[TABLE_ID]", ID.Unique(),
new Dictionary<string, object> { { "title", "Hello World" } });
// Query rows
var results = await tablesDB.ListRows("[DATABASE_ID]", "[TABLE_ID]",
new List<string> { Query.Equal("title", "Hello World"), Query.Limit(10) });
// Get row
var row = await tablesDB.GetRow("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]");
// Update row
await tablesDB.UpdateRow("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]",
new Dictionary<string, object> { { "title", "Updated" } });
// Delete row
await tablesDB.DeleteRow("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]");
字符串列类型
注意: 旧版
string类型已弃用。所有新列都应使用明确的列类型。
| 类型 | 最大字符数 | 索引 | 存储 |
|---|---|---|---|
varchar | 16,383 | 完整索引(若大小 ≤ 768) | 行内 |
text | 16,383 | 仅前缀 | 页外 |
mediumtext | 4,194,303 | 仅前缀 | 页外 |
longtext | 1,073,741,823 | 仅前缀 | 页外 |
varchar以行内方式存储,并计入 64 KB 的行大小限制。短小且需要索引的字段(如名称、slug 或标识符)应优先使用此类型。text、mediumtext和longtext以页外方式存储(行内仅保存一个 20 字节的指针),因此不会占用行大小额度。这些类型不需要设置size。
// Create table with explicit string column types
await tablesDB.CreateTable("[DATABASE_ID]", ID.Unique(), "articles",
new List<object> {
new { key = "title", type = "varchar", size = 255, required = true }, // inline, fully indexable
new { key = "summary", type = "text", required = false }, // off-page, prefix index only
new { key = "body", type = "mediumtext", required = false }, // up to ~4 M chars
new { key = "raw_data", type = "longtext", required = false }, // up to ~1 B chars
});
查询方法
// Filtering
Query.Equal("field", "value") // == (or pass array for IN)
Query.NotEqual("field", "value") // !=
Query.LessThan("field", 100) // <
Query.LessThanEqual("field", 100) // <=
Query.GreaterThan("field", 100) // >
Query.GreaterThanEqual("field", 100) // >=
Query.Between("field", 1, 100) // 1 <= field <= 100
Query.IsNull("field") // is null
Query.IsNotNull("field") // is not null
Query.StartsWith("field", "prefix") // starts with
Query.EndsWith("field", "suffix") // ends with
Query.Contains("field", "sub") // contains
Query.Search("field", "keywords") // full-text search (requires index)
// Sorting
Query.OrderAsc("field")
Query.OrderDesc("field")
// Pagination
Query.Limit(25) // max rows (default 25, max 100)
Query.Offset(0) // skip N rows
Query.CursorAfter("[ROW_ID]") // cursor pagination (preferred)
Query.CursorBefore("[ROW_ID]")
// Selection & Logic
Query.Select(new List<string> { "field1", "field2" })
Query.Or(new List<string> { Query.Equal("a", 1), Query.Equal("b", 2) }) // OR
Query.And(new List<string> { Query.GreaterThan("age", 18), Query.LessThan("age", 65) }) // AND (default)
Query.Relationship("comments", new List<string> { Query.Equal("approved", true), Query.Limit(25) }) // related documents
// Aggregations (optional alias)
Query.Count("*", "total")
Query.Sum("amount", "revenue")
Query.Avg("score", "avgScore")
Query.Min("price", "minPrice")
Query.Max("price", "maxPrice")
// Grouping
Query.GroupBy(new List<string> { "status", "year" })
Query.Having(new List<string> { Query.GreaterThan("total", 1) })
Query.Distinct()
// Joins (table id, left key, right key, operator, alias)
Query.Join("orders", "$id", "customerId")
Query.LeftJoin("orders", "$id", "customerId", "=", "ord")
Query.LeftJoin("orders", "ord", new List<string> {
Query.On("$id", "customerId"),
Query.Equal("ord.status", "paid"),
})
// Fluent builder. Pass Build() into list methods.
var queries = new Query.Builder()
.Equal("status", "published")
.LeftJoin("orders", "$id", "customerId", "=", "ord")
.Count("*", "total")
.GroupBy(new List<string> { "status" })
.Limit(25)
.Build();
await tablesDB.ListRows("[DATABASE_ID]", "[TABLE_ID]", queries);
文件存储
var storage = new Storage(client);
// Upload file
var file = await storage.CreateFile("[BUCKET_ID]", ID.Unique(), InputFile.FromPath("/path/to/file.png"));
// List files
var files = await storage.ListFiles("[BUCKET_ID]");
// Delete file
await storage.DeleteFile("[BUCKET_ID]", "[FILE_ID]");
InputFile 工厂方法
using Appwrite.Models;
InputFile.FromPath("/path/to/file.png") // from filesystem path
InputFile.FromBytes(byteArray, "file.png", "image/png") // from byte[]
InputFile.FromStream(stream, "file.png", "image/png", size) // from Stream (size required)
团队
var teams = new Teams(client);
// Create team
var team = await teams.Create(ID.Unique(), "Engineering");
// List teams
var list = await teams.List();
// Create membership (invite user by email)
var membership = await teams.CreateMembership(
teamId: "[TEAM_ID]",
roles: new List<string> { "editor" },
email: "[email protected]"
);
// List memberships
var members = await teams.ListMemberships("[TEAM_ID]");
// Update membership roles
await teams.UpdateMembership("[TEAM_ID]", "[MEMBERSHIP_ID]", new List<string> { "admin" });
// Delete team
await teams.Delete("[TEAM_ID]");
基于角色的访问: 设置权限时,对所有团队成员使用
Role.Team("[TEAM_ID]"),对特定团队角色使用Role.Team("[TEAM_ID]", "editor")。
无服务器函数
var functions = new Functions(client);
// Execute function
var execution = await functions.CreateExecution("[FUNCTION_ID]", body: "{\"key\": \"value\"}");
// List executions
var executions = await functions.ListExecutions("[FUNCTION_ID]");
编写函数处理程序(.NET 运行时)
// src/Main.cs — Appwrite Function entry point
using System.Text.Json;
public async Task<RuntimeOutput> Main(RuntimeContext context)
{
// context.Req.Body — raw body (string)
// context.Req.BodyJson — parsed JSON (JsonElement)
// context.Req.Headers — headers (Dictionary)
// context.Req.Method — HTTP method
// context.Req.Path — URL path
// context.Req.Query — query params (Dictionary)
context.Log($"Processing: {context.Req.Method} {context.Req.Path}");
if (context.Req.Method == "GET")
return context.Res.Json(new { message = "Hello from Appwrite Function!" });
return context.Res.Json(new { success = true }); // JSON
// context.Res.Text("Hello"); // plain text
// context.Res.Empty(); // 204
// context.Res.Redirect("https://..."); // 302
}
服务器端渲染(SSR)身份验证
使用 .NET 框架(ASP.NET、Blazor Server 等)的 SSR 应用会使用服务端 SDK处理身份验证。你需要两个客户端:
- 管理员客户端 — 使用 API 密钥创建会话并绕过速率限制(可复用的单例)
- 会话客户端 — 使用会话 Cookie 代表用户执行操作(每个请求创建一个,切勿共享)
using Appwrite;
using Appwrite.Services;
// Admin client (reusable)
var adminClient = new Client()
.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.SetProject("[PROJECT_ID]")
.SetKey(Environment.GetEnvironmentVariable("APPWRITE_API_KEY"));
// Session client (create per-request)
var sessionClient = new Client()
.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.SetProject("[PROJECT_ID]");
var session = Request.Cookies["a_session_[PROJECT_ID]"];
if (session != null)
{
sessionClient.SetSession(session);
}
电子邮件/密码登录(ASP.NET Minimal API)
app.MapPost("/login", async (HttpContext ctx, LoginRequest body) =>
{
var account = new Account(adminClient);
var session = await account.CreateEmailPasswordSession(body.Email, body.Password);
// Cookie name must be a_session_<PROJECT_ID>
ctx.Response.Cookies.Append("a_session_[PROJECT_ID]", session.Secret, new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Path = "/",
});
return Results.Ok(new { success = true });
});
已验证身份的请求
app.MapGet("/user", async (HttpContext ctx) =>
{
var session = ctx.Request.Cookies["a_session_[PROJECT_ID]"];
if (session == null) return Results.Unauthorized();
var sessionClient = new Client()
.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.SetProject("[PROJECT_ID]")
.SetSession(session);
var account = new Account(sessionClient);
var user = await account.Get();
return Results.Ok(user);
});
OAuth2 SSR 流程
// Step 1: Redirect to OAuth provider
app.MapGet("/oauth", async () =>
{
var account = new Account(adminClient);
var redirectUrl = await account.CreateOAuth2Token(
provider: OAuthProvider.Github,
success: "https://example.com/oauth/success",
failure: "https://example.com/oauth/failure"
);
return Results.Redirect(redirectUrl);
});
// Step 2: Handle callback — exchange token for session
app.MapGet("/oauth/success", async (HttpContext ctx, string userId, string secret) =>
{
var account = new Account(adminClient);
var session = await account.CreateSession(userId, secret);
ctx.Response.Cookies.Append("a_session_[PROJECT_ID]", session.Secret, new CookieOptions
{
HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict, Path = "/",
});
return Results.Ok(new { success = true });
});
Cookie 安全性: 始终使用
HttpOnly、Secure和SameSite = SameSiteMode.Strict来防止 XSS。Cookie 名称必须为a_session_<PROJECT_ID>。
转发用户代理: 调用
sessionClient.SetForwardedUserAgent(ctx.Request.Headers["User-Agent"]),记录最终用户的浏览器信息,以便调试和保障安全。
错误处理
using Appwrite;
try
{
var row = await tablesDB.GetRow("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]");
}
catch (AppwriteException e)
{
Console.WriteLine(e.Message); // human-readable message
Console.WriteLine(e.Code); // HTTP status code (int)
Console.WriteLine(e.Type); // error type (e.g. "document_not_found")
Console.WriteLine(e.Response); // full response body
}
常见错误代码:
| 代码 | 含义 |
|---|---|
401 | 未授权 — 缺少或无效的会话/API 密钥 |
403 | 禁止访问 — 权限不足 |
404 | 未找到 — 资源不存在 |
409 | 冲突 — ID 重复或违反唯一性约束 |
429 | 请求过多 — 请求频率受限 |
权限与角色(重要)
Appwrite 使用权限字符串控制对资源的访问。每项权限都将一个操作(read、update、delete、create,或授予 create、update 和 delete 权限的 write)与一个角色目标配对。默认情况下,没有用户可以访问,除非在行/文件级别显式设置权限,或从表/存储桶设置继承权限。权限是字符串数组,使用 Permission 和 Role 助手构建。
using Appwrite;
// Permission and Role are included in the main namespace
带权限的数据库行
var doc = await tablesDB.CreateRow("[DATABASE_ID]", "[TABLE_ID]", ID.Unique(),
new Dictionary<string, object> { { "title", "Hello World" } },
new List<string>
{
Permission.Read(Role.User("[USER_ID]")), // specific user can read
Permission.Update(Role.User("[USER_ID]")), // specific user can update
Permission.Read(Role.Team("[TEAM_ID]")), // all team members can read
Permission.Read(Role.Any()), // anyone (including guests) can read
});
设置权限的文件上传
var file = await storage.CreateFile("[BUCKET_ID]", ID.Unique(),
InputFile.FromPath("/path/to/file.png"),
new List<string>
{
Permission.Read(Role.Any()),
Permission.Update(Role.User("[USER_ID]")),
Permission.Delete(Role.User("[USER_ID]")),
});
何时设置权限: 需要按资源控制访问权限时,请设置行级/文件级权限。如果表中的所有行都使用相同的规则,请在表/存储桶级别配置权限,并将行权限留空。
常见错误:
- 忘记设置权限 — 该资源将对所有用户(包括创建者)不可用
- 对
write/update/delete使用Role.Any()— 允许任何用户(包括未经身份验证的访客)修改或删除该资源- 对敏感数据使用
Permission.Read(Role.Any())— 会使该资源可被公开读取