mcpField
A
appwrite-dotnet

appwrite-dotnet

github.com/appwrite开发28547 次安装1 周前更新
代码仓库

关于 appwrite-dotnet

Appwrite .NET SDK 是供 .NET 开发者在应用中使用 Appwrite 的库,提供身份验证、用户、表格、存储、团队、函数和权限相关 API。

主要功能

  1. 01配置 Appwrite 客户端
  2. 02管理用户与团队
  3. 03创建表格和行
  4. 04上传与列出文件
  5. 05执行无服务器函数
  6. 06设置资源权限

适用场景

  1. 01在 .NET 应用中加入 Appwrite 用户管理
  2. 02读取和写入表格行
  3. 03按访问规则上传文件
  4. 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 类型已弃用。所有新列都应使用明确的列类型。

类型最大字符数索引存储
varchar16,383完整索引(若大小 ≤ 768)行内
text16,383仅前缀页外
mediumtext4,194,303仅前缀页外
longtext1,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()) — 会使该资源可被公开读取

评论

支持 Markdown