Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion CommunityToolkit.Aspire.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@
<Project Path="examples/rust/CommunityToolkit.Aspire.Hosting.Rust.AppHost/CommunityToolkit.Aspire.Hosting.Rust.AppHost.csproj" />
<Project Path="examples/rust/CommunityToolkit.Aspire.Hosting.Rust.ServiceDefaults/CommunityToolkit.Aspire.Hosting.Rust.ServiceDefaults.csproj" />
</Folder>
<Folder Name="/examples/seaweedfs/">
<File Path="examples/seaweedfs/README.md" />
<Project Path="examples/seaweedfs/SeaweedFS.ApiService/SeaweedFS.ApiService.csproj" />
<Project Path="examples/seaweedfs/SeaweedFS.AppHost/SeaweedFS.AppHost.csproj" Id="6e0ec4a6-d1d2-4aed-9a5b-b053c46e41b3" />
<Project Path="examples/seaweedfs/SeaweedFS.ServiceDefaults/SeaweedFS.ServiceDefaults.csproj" />
</Folder>
<Folder Name="/examples/sftp/">
<Project Path="examples/sftp/CommunityToolkit.Aspire.Hosting.Sftp.ApiService/CommunityToolkit.Aspire.Hosting.Sftp.ApiService.csproj" />
<Project Path="examples/sftp/CommunityToolkit.Aspire.Hosting.Sftp.AppHost/CommunityToolkit.Aspire.Hosting.Sftp.AppHost.csproj" />
Expand Down Expand Up @@ -236,6 +242,7 @@
<Project Path="src/CommunityToolkit.Aspire.Hosting.RavenDB/CommunityToolkit.Aspire.Hosting.RavenDB.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.Redis.Extensions/CommunityToolkit.Aspire.Hosting.Redis.Extensions.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.Rust/CommunityToolkit.Aspire.Hosting.Rust.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.SeaweedFS/CommunityToolkit.Aspire.Hosting.SeaweedFS.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.Sftp/CommunityToolkit.Aspire.Hosting.Sftp.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.Solr/CommunityToolkit.Aspire.Hosting.Solr.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.csproj" />
Expand All @@ -253,6 +260,7 @@
<Project Path="src/CommunityToolkit.Aspire.Minio.Client/CommunityToolkit.Aspire.Minio.Client.csproj" />
<Project Path="src/CommunityToolkit.Aspire.OllamaSharp/CommunityToolkit.Aspire.OllamaSharp.csproj" />
<Project Path="src/CommunityToolkit.Aspire.RavenDB.Client/CommunityToolkit.Aspire.RavenDB.Client.csproj" />
<Project Path="src/CommunityToolkit.Aspire.SeaweedFS.Client/CommunityToolkit.Aspire.SeaweedFS.Client.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Sftp/CommunityToolkit.Aspire.Sftp.csproj" />
<Project Path="src/CommunityToolkit.Aspire.SurrealDb/CommunityToolkit.Aspire.SurrealDb.csproj" />
<Project Path="src\CommunityToolkit.Aspire.Hosting.Keycloak.Extensions\CommunityToolkit.Aspire.Hosting.Keycloak.Extensions.csproj" />
Expand Down Expand Up @@ -293,12 +301,13 @@
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Ollama.Tests/CommunityToolkit.Aspire.Hosting.Ollama.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.OpenTelemetryCollector.Tests/CommunityToolkit.Aspire.Hosting.OpenTelemetryCollector.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.PapercutSmtp.Tests/CommunityToolkit.Aspire.Hosting.PapercutSmtp.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Perl.Tests/CommunityToolkit.Aspire.Hosting.Perl.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions.Tests/CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.PowerShell.Tests/CommunityToolkit.Aspire.Hosting.PowerShell.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Perl.Tests/CommunityToolkit.Aspire.Hosting.Perl.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Redis.Extensions.Tests/CommunityToolkit.Aspire.Hosting.Redis.Extensions.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Rust.Tests/CommunityToolkit.Aspire.Hosting.Rust.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.SeaweedFS.Tests/CommunityToolkit.Aspire.Hosting.SeaweedFS.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Sftp.Tests/CommunityToolkit.Aspire.Hosting.Sftp.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Solr.Tests/CommunityToolkit.Aspire.Hosting.Solr.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests.csproj" />
Expand All @@ -316,6 +325,7 @@
<Project Path="tests/CommunityToolkit.Aspire.Minio.Client.Tests/CommunityToolkit.Aspire.Minio.Client.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.OllamaSharp.Tests/CommunityToolkit.Aspire.OllamaSharp.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.RavenDB.Client.Tests/CommunityToolkit.Aspire.RavenDB.Client.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.SeaweedFS.Client.Tests/CommunityToolkit.Aspire.SeaweedFS.Client.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Sftp.Tests/CommunityToolkit.Aspire.Sftp.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.SurrealDb.Tests/CommunityToolkit.Aspire.SurrealDb.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Testing/CommunityToolkit.Aspire.Testing.csproj" />
Expand Down
4 changes: 3 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
<PackageVersion Include="Aspire.Hosting.MySql" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Hosting.SqlServer" Version="$(AspireVersion)" />
<PackageVersion Include="AspNetCore.HealthChecks.Network" Version="9.0.0" />
<PackageVersion Include="AWSSDK.S3" Version="4.0.23.3" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="10.0.8" />
</ItemGroup>
<ItemGroup Label="Core Packages">
<!-- AspNetCore packages -->
Expand Down Expand Up @@ -139,4 +141,4 @@
<!-- Override to address https://github.com/advisories/GHSA-6c8g-7p36-r338 -->
<PackageVersion Include="SharpCompress" Version="1.0.0" />
</ItemGroup>
</Project>
</Project>
54 changes: 54 additions & 0 deletions examples/seaweedfs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# SeaweedFS Aspire Integration Example

This example demonstrates how to integrate and consume a SeaweedFS cluster within a .NET Aspire distributed application. It showcases both the **S3-Compatible API** and the **Native Filer API** in a single cohesive setup.

## Project Structure

* **SeaweedFS.AppHost:** The Aspire orchestrator. It spins up the SeaweedFS Docker container, enables the S3 Gateway and Data Volumes, and injects the dynamic connection strings into the API.
* **SeaweedFS.ServiceDefaults:** Standard Aspire telemetry, resilience, and health check configurations.
* **SeaweedFS.ApiService:** A minimal API application that registers both the `IAmazonS3` client and the `SeaweedFSFilerClient` to interact with the storage cluster.

## Running the Example

1. Ensure you have [Docker Desktop](https://www.docker.com/products/docker-desktop/) or Podman running on your machine.
2. Open a terminal in this directory (`aspire/seaweedfs/`).
3. Run the AppHost:

```dotnetcli
dotnet run --project SeaweedFS.AppHost

```

4. Open the **Aspire Dashboard** URL provided in the console output.
5. Wait for both the `seaweedfs` container and the `apiservice` to show as **Healthy**.

## Exploring the Endpoints

The `SeaweedFS.ApiService` exposes endpoints to test both storage approaches. You can trigger them using Swagger (if enabled) or using tools like `curl` or Postman.

### S3 Endpoints (AWS SDK Compatibility)

These endpoints use the injected `IAmazonS3` client.

* **Create a Bucket:**
`POST /s3/buckets?bucketName=my-bucket`
* **Upload a File:**
`POST /s3/upload?bucketName=my-bucket&key=test.txt`
*(Send raw text in the body)*
* **Download a File:**
`GET /s3/download?bucketName=my-bucket&key=test.txt`

### Filer Endpoints (Native SeaweedFS API)

These endpoints use the injected `SeaweedFSFilerClient` performing direct HTTP calls to the cluster.

* **Upload a File to Root:**
`POST /filer/upload?fileName=native-file.txt`
*(Send raw text in the body)*
* **List Directory Contents:**
`GET /filer/list`
*(Returns a JSON representation of the Filer's directory structure)*

## Viewing Data in SeaweedFS

Since `SeaweedFS.AppHost` maps a data volume using `.WithDataVolume()`, any file you upload using the endpoints above will be persisted in the Docker volume. If you stop the AppHost and run it again, your data will still be accessible.
119 changes: 119 additions & 0 deletions examples/seaweedfs/SeaweedFS.ApiService/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using Amazon.S3;
using Amazon.S3.Model;
using CommunityToolkit.Aspire.SeaweedFS.Client;
using Microsoft.AspNetCore.Mvc;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// Add service defaults (OpenTelemetry, HealthChecks, etc.)
builder.AddServiceDefaults();

// Add essential web services
builder.Services.AddProblemDetails();

// =========================================================================
// 🌊 REGISTER SEAWEEDFS CLIENTS
// =========================================================================
// Basic Setup (Endpoints and credentials are automatically injected by AppHost)
builder.AddSeaweedFSS3Client("seaweedfs");
builder.AddSeaweedFSFilerClient("seaweedfs");

/* // -------------------------------------------------------------------------
// ADVANCED SETUP EXAMPLES
// Uncomment and use the blocks below to override client settings programmatically.
// -------------------------------------------------------------------------

builder.AddSeaweedFSS3Client("seaweedfs", settings =>
{
// The Endpoint is injected automatically, but you can override protocol rules:
settings.UseSsl = false;
settings.ForcePathStyle = true; // Required by SeaweedFS architecture

// Deep configuration of the underlying AWS SDK:
settings.ConfigureS3Config = s3Config =>
{
s3Config.Timeout = TimeSpan.FromSeconds(30);
s3Config.MaxErrorRetry = 3;
};
});

builder.AddSeaweedFSFilerClient("seaweedfs", settings =>
{
// Example of disabling health checks for the Filer specifically
settings.DisableHealthChecks = true;
});
*/

WebApplication app = builder.Build();

app.UseExceptionHandler();

// ==========================================
// 🌊 S3 ENDPOINTS (AWS Compatibility)
// ==========================================
RouteGroupBuilder s3Group = app.MapGroup("/s3").WithTags("S3 API");

s3Group.MapPost("/buckets", async ([FromQuery] string bucketName, IAmazonS3 s3Client) =>
{
await s3Client.PutBucketAsync(new PutBucketRequest { BucketName = bucketName });
return Results.Ok(new { Message = "Bucket successfully created via S3 API." });
});

s3Group.MapPost("/upload", async ([FromQuery] string bucketName, [FromQuery] string key, [FromBody] string content, IAmazonS3 s3Client) =>
{
await s3Client.PutObjectAsync(new PutObjectRequest
{
BucketName = bucketName,
Key = key,
ContentBody = content
});
return Results.Ok(new { Message = "File successfully uploaded to bucket via S3 API." });
});

s3Group.MapGet("/download", async ([FromQuery] string bucketName, [FromQuery] string key, IAmazonS3 s3Client) =>
{
GetObjectResponse response = await s3Client.GetObjectAsync(bucketName, key);
using StreamReader reader = new(response.ResponseStream);
string content = await reader.ReadToEndAsync();

return Results.Ok(new
{
Bucket = System.Text.Encodings.Web.HtmlEncoder.Default.Encode(bucketName),
Key = System.Text.Encodings.Web.HtmlEncoder.Default.Encode(key),
Content = content
});
});

// ==========================================
// 📁 FILER ENDPOINTS (Native API)
// ==========================================
RouteGroupBuilder filerGroup = app.MapGroup("/filer").WithTags("Filer API");

filerGroup.MapGet("/list", async (SeaweedFSFilerClient filerClient) =>
{
HttpRequestMessage request = new(HttpMethod.Get, "/");
request.Headers.Add("Accept", "application/json");

HttpResponseMessage response = await filerClient.HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();

string content = await response.Content.ReadAsStringAsync();
return Results.Content(content, "application/json");
});

filerGroup.MapPost("/upload", async ([FromQuery] string fileName, [FromBody] string content, SeaweedFSFilerClient filerClient) =>
{
StringContent stringContent = new(content);

string safeFileName = Uri.EscapeDataString(fileName.TrimStart('/'));

// Uploads the file directly to the root of the Filer
HttpResponseMessage response = await filerClient.HttpClient.PutAsync($"/{safeFileName}", stringContent);
response.EnsureSuccessStatusCode();

return Results.Ok(new { Message = "File successfully uploaded via native Filer API." });
});

app.MapDefaultEndpoints();

app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"profiles": {
"SeaweedFS.ApiService": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:4530;http://localhost:4531"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<ItemGroup>
<ProjectReference Include="..\SeaweedFS.ServiceDefaults\SeaweedFS.ServiceDefaults.csproj" />
<ProjectReference Include="..\..\..\src\CommunityToolkit.Aspire.SeaweedFS.Client\CommunityToolkit.Aspire.SeaweedFS.Client.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
</ItemGroup>

</Project>
9 changes: 9 additions & 0 deletions examples/seaweedfs/SeaweedFS.ApiService/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
18 changes: 18 additions & 0 deletions examples/seaweedfs/SeaweedFS.AppHost.TypeScript/apphost.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createBuilder } from "./.aspire/modules/aspire.mjs";

const builder = await createBuilder();

// 1. S3-Compatible API Gateway
const seaweedS3 = await builder.addSeaweedFS("seaweedfs-s3");
await seaweedS3.withS3();
await seaweedS3.withDataVolume();

// 2. Native Filer API Only
const seaweedFiler = await builder.addSeaweedFS("seaweedfs-filer");
await seaweedFiler.withFiler();

// Get connection strings to ensure the expressions are evaluated properly by ATS
const _s3ConnectionString = await seaweedS3.connectionStringExpression();
const _filerConnectionString = await seaweedFiler.connectionStringExpression();

await builder.build().run();
17 changes: 17 additions & 0 deletions examples/seaweedfs/SeaweedFS.AppHost.TypeScript/aspire.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"appHost": {
"path": "apphost.mts",
"language": "typescript/nodejs"
},
"sdk": {
"version": "13.4.0-preview.1.26275.15"
},
"profiles": {
"http": {
"applicationUrl": "http://localhost:15333"
}
},
"packages": {
"CommunityToolkit.Aspire.Hosting.SeaweedFS": "../../../src/CommunityToolkit.Aspire.Hosting.SeaweedFS/CommunityToolkit.Aspire.Hosting.SeaweedFS.csproj"
}
}
17 changes: 17 additions & 0 deletions examples/seaweedfs/SeaweedFS.AppHost.TypeScript/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// @ts-check

import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';

export default defineConfig({
files: ['apphost.mts'],
extends: [tseslint.configs.base],
languageOptions: {
parserOptions: {
projectService: true,
},
},
rules: {
'@typescript-eslint/no-floating-promises': ['error', { checkThenables: true }],
},
});
Loading