![]() |
VOOZH | about |
dotnet add package HeadlessPluginApi --version 1.0.1
NuGet\Install-Package HeadlessPluginApi -Version 1.0.1
<PackageReference Include="HeadlessPluginApi" Version="1.0.1" />
<PackageVersion Include="HeadlessPluginApi" Version="1.0.1" />Directory.Packages.props
<PackageReference Include="HeadlessPluginApi" />Project file
paket add HeadlessPluginApi --version 1.0.1
#r "nuget: HeadlessPluginApi, 1.0.1"
#:package HeadlessPluginApi@1.0.1
#addin nuget:?package=HeadlessPluginApi&version=1.0.1Install as a Cake Addin
#tool nuget:?package=HeadlessPluginApi&version=1.0.1Install as a Cake Tool
Thư viện Plugin Core - Tích hợp với nền tảng Low-Code Headless của Thần Nông
HeadlessPluginApi là thư viện Core Plugin cho phép developer tự do phát triển các plugin độc lập với host application. Plugin sau khi phát triển xong có thể triển khai trên host app như một module có thể addon vào hệ thống chính.
| Vấn Đề | Giải Pháp của HeadlessPluginApi |
|---|---|
| ❌ Monolith khó mở rộng | ✅ Plugin-based architecture - Tách biệt logic nghiệp vụ |
| ❌ Deploy lại toàn bộ app khi thay đổi nhỏ | ✅ Hot reload/unload - Load plugin mới không cần restart |
| ❌ Khó kiểm soát quyền truy cập | ✅ JsonLogic Authorization - Phân quyền linh hoạt theo context |
| ❌ Thiếu chuẩn hoá API | ✅ Auto schema generation - Tự động tạo OpenAPI schema |
| ❌ Vendor lock-in | ✅ Isolated AssemblyLoadContext - Plugin hoàn toàn độc lập |
PluginName_1.0.0.dll)plugin.json)Task<object?> return typeIServiceProvider, UserSessionContext, CancellationTokenPluginName.CommandName để tránh conflict┌─────────────────────────────────────────────────────────────────┐
│ Host Application │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ PluginEngine (IPluginEngine) │ │
│ │ • Load/Unload/Reload lifecycle │ │
│ │ • Metadata persistence (MongoDB) │ │
│ └───────────┬─────────────────────────────────┬─────────────┘ │
│ │ │ │
│ ┌───────────▼──────────┐ ┌──────────▼──────────────┐ │
│ │ PluginLoader │ │ PluginRegistry │ │
│ │ (McMaster.Plugins) │ │ (Thread-safe cache) │ │
│ │ • Isolated ALC │ │ • GetPlugin() │ │
│ │ • Auto-discovery │ │ • Resolve(command) │ │
│ └──────────────────────┘ └─────────────────────────┘ │
│ │ │
│ ┌───────────▼──────────────┐ │
│ │ PluginDispatcher │ │
│ │ • Route commands │ │
│ │ • Authorization check │ │
│ │ • Scoped DI │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
┌──────────▼──────────┐
│ Plugin DLL │
│ ┌──────────────┐ │
│ │ PluginEntry │ │
│ │ (IPluginEntry)│ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────▼───────┐ │
│ │ Commands │ │
│ │ (IPluginCmd) │ │
│ └──────────────┘ │
└─────────────────────┘
1. HTTP Request → POST /api/plugin/execute
{
"command": "DemoCalculator.add",
"payload": { "a": 10, "b": 5 }
}
2. PluginDispatcher.ExecuteAsync()
├─ Resolve plugin & command → PluginRegistry
├─ Authorization check → JsonLogicAuthEvaluator
├─ Create scoped ServiceProvider
├─ Bind parameters (UserSessionContext, payload, IServiceProvider, CancellationToken)
└─ Invoke command.ExecuteAsync() via reflection
3. Command Execution
public async Task<object?> ExecuteAsync(
UserSessionContext ctx,
int a,
int b,
IServiceProvider services,
CancellationToken ct)
{
var logger = services.GetRequiredService<ILogger<AddCommand>>();
logger.LogInformation("Adding {A} + {B}", a, b);
return new { result = a + b };
}
4. Response → PluginExecuteResponse
{
"success": true,
"result": { "result": 15 },
"executionTimeMs": 12
}
dotnet add package HeadlessPluginApi --version 1.0.1
Hoặc trong .csproj:
<ItemGroup>
<PackageReference Include="HeadlessPluginApi" Version="1.0.1" />
</ItemGroup>
# Core dependency (bắt buộc)
dotnet add package Headless.SharedServices
# Optional dependencies (nếu chưa có)
dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package Microsoft.Extensions.Hosting
using HeadlessPluginApi.Abstractions;
public class MyPlugin : PluginEntryBase
{
protected override void OnLoad(IServiceProvider hostServices)
{
Logger?.LogInformation("MyPlugin loading...");
}
public override void OnInit()
{
Logger?.LogInformation("MyPlugin initialized with {Count} commands",
GetCommands().Count);
}
public override void OnUnload()
{
Logger?.LogInformation("MyPlugin unloading...");
}
}
<PropertyGroup>
<AssemblyTitle>MyPlugin</AssemblyTitle>
<Version>1.0.0</Version>
<AssemblyVersion>1.0.0</AssemblyVersion>
</PropertyGroup>
using HeadlessPluginApi.Abstractions;
using Headless.Contracts.Models.Users;
public class AddCommand : PluginCommand
{
public override string CommandName => "add";
public override string Description => "Cộng hai số";
public async Task<object?> ExecuteAsync(
UserSessionContext context,
int a,
int b,
CancellationToken cancellationToken)
{
return new
{
result = a + b,
operation = "add",
executedBy = context.user?.userName ?? "Anonymous"
};
}
}
public class SaveUserCommand : PluginCommand
{
public override string CommandName => "save_user";
public async Task<object?> ExecuteAsync(
UserSessionContext context,
string username,
string email,
IServiceProvider services,
CancellationToken cancellationToken)
{
// Resolve services từ host
var db = services.GetRequiredService<IMultiScopeDBRepository>();
var logger = services.GetRequiredService<ILoggerFactory>()
.CreateLogger<SaveUserCommand>();
logger.LogInformation("Saving user: {Username}", username);
var user = new User { Username = username, Email = email };
await db.InsertOne(user, cancellationToken: cancellationToken);
return new { success = true, userId = user.Id };
}
}
// Program.cs (ASP.NET Core)
using HeadlessPluginApi.Hosting;
var builder = WebApplication.CreateBuilder(args);
// ✅ Register plugin services
builder.Services.AddPluginModule(options =>
{
options.pluginFolder = Path.Combine(builder.Environment.ContentRootPath, "Plugins");
options.autoLoadOnStartup = true;
options.enableManagementApi = true;
});
// ✅ Register host services (plugins có thể dùng)
builder.Services.AddSingleton<IMultiScopeDBRepository, MyDbRepo>();
builder.Services.AddSingleton<ICacheRepository, InMemoryCacheRepo>();
var app = builder.Build();
// ✅ Map plugin endpoints
app.MapPluginEndpoints();
app.Run();
# Build plugin
cd MyPlugin
dotnet build -c Release
# Upload qua API
curl -X POST http://localhost:5000/api/plugin/upload \
-F "file=@bin/Release/net8.0/MyPlugin.dll"
curl -X POST http://localhost:5000/api/plugin/load \
-H "Content-Type: application/json" \
-d '{"pluginPath": "MyPlugin"}'
curl -X POST http://localhost:5000/api/plugin/execute \
-H "Content-Type: application/json" \
-d '{
"command": "MyPlugin.add",
"payload": { "a": 10, "b": 5 }
}'
Response:
{
"success": true,
"result": {
"result": 15,
"operation": "add",
"executedBy": "admin@example.com"
},
"executionTimeMs": 8
}
public class MyService
{
private readonly IPluginDispatcher _dispatcher;
private readonly UserSessionContext _ctx;
public async Task<object?> ExecutePluginCommand()
{
var payload = JsonDocument.Parse(@"{""a"": 10, ""b"": 5}").RootElement;
var result = await _dispatcher.ExecuteAsync(
"MyPlugin.add",
payload,
_ctx,
CancellationToken.None);
return result;
}
}
using HeadlessPluginApi.Authorization;
public class DeleteUserCommand : PluginCommand
{
public override string CommandName => "delete_user";
// ✅ Chỉ admin mới được xóa user
[RequireAuth(
rule: @"{
""==="": [
{""var"": ""user.role""},
""admin""
]
}",
message: "Chỉ admin mới có quyền xóa người dùng")]
public async Task<object?> ExecuteAsync(
UserSessionContext context,
string userId,
CancellationToken cancellationToken)
{
// Implementation
}
}
1. PluginDispatcher.ExecuteAsync()
│
2. PluginAuthorizationEvaluator.EvaluateCommandAsync()
│
├─ Check [RequireAuth] attribute
│
├─ Build JsonLogic context:
│ {
│ "user": { "userId": "abc", "role": "admin", ... },
│ "payload": { "userId": "user123" },
│ "command": "delete_user"
│ }
│
├─ Evaluate rule với JsonLogic.Net
│ {"===": [{"var": "user.role"}, "admin"]}
│
└─ Return AuthorizationResult
• Succeeded = true → Continue execution
• Succeeded = false → Throw PluginAuthorizationException (403)
// Rule 1: Chỉ admin hoặc chính chủ mới được sửa
[RequireAuth(
rule: @"{
""or"": [
{""==="": [{""var"": ""user.role""}, ""admin""]},
{""==="": [{""var"": ""user.userId""}, {""var"": ""payload.userId""}]}
]
}"]
// Rule 2: Chỉ được thực hiện trong giờ hành chính (8-17h)
[RequireAuth(
rule: @"{
""and"": [
{"">="": [{""var"": ""context.hour""}, 8]},
{""<="": [{""var"": ""context.hour""}, 17]}
]
}")]
using HeadlessPluginApi.Attributes;
public class CreateOrderCommand : PluginCommand
{
public override string CommandName => "create_order";
public async Task<object?> ExecuteAsync(
UserSessionContext context,
[CommandParameter(
description: "Mã khách hàng",
required: true,
example: "CUST001")]
string customerId,
[CommandParameter(
description: "Danh sách sản phẩm (product_id và quantity)",
required: true)]
List<OrderItem> items,
[CommandParameter(
description: "Ghi chú đơn hàng",
required: false)]
string? notes,
CancellationToken cancellationToken)
{
// Implementation
}
}
GET /api/plugin/commands/MyPlugin.create_order/schema
Response:
{
"success": true,
"pluginName": "MyPlugin",
"commandName": "create_order",
"parameterSchema": {
"parameters": [
{
"name": "customerId",
"type": "string",
"description": "Mã khách hàng",
"required": true,
"example": "CUST001"
},
{
"name": "items",
"type": "array",
"description": "Danh sách sản phẩm (product_id và quantity)",
"required": true,
"itemType": "OrderItem"
},
{
"name": "notes",
"type": "string",
"description": "Ghi chú đơn hàng",
"required": false
}
]
},
"authorization": {
"requiresAuth": false
}
}
✅ ĐÚNG (recommended):
MyPlugin_1.0.0.dll
MyPlugin_1.0.1.dll
MyPlugin_1.1.0.dll
MyPlugin_2.0.0.dll
⚠️ Hỗ trợ (legacy):
MyPlugin.20250116120000000.dll (timestamp format)
MyPlugin.dll (fallback - không khuyến nghị)
# Version 1.0.0 (initial)
POST /api/plugin/upload
→ Uploaded: MyPlugin_1.0.0.dll ✅
# Version 1.0.0 again (duplicate)
POST /api/plugin/upload
→ 409 Conflict: Version 1.0.0 already exists ❌
# Version 1.1.0 (upgrade)
POST /api/plugin/upload
→ Uploaded: MyPlugin_1.1.0.dll ✅
# Reload để dùng version mới
POST /api/plugin/reload/MyPlugin
→ Loads MyPlugin_1.1.0.dll (latest version)
// PluginLoader tự động chọn DLL theo thứ tự:
1. Highest semantic version (1.2.0 > 1.1.0 > 1.0.1)
2. Latest timestamp (nếu dùng format cũ)
3. Latest file modification time
# Bước 1: Build plugin mới
dotnet build -c Release
# Bước 2: Upload version mới
curl -X POST http://localhost:5000/api/plugin/upload \
-F "file=@bin/Release/net8.0/MyPlugin.dll"
# Bước 3: Reload
curl -X POST http://localhost:5000/api/plugin/reload/MyPlugin
⚠️ Lưu ý:
POST /api/plugin/reload-all
Behavior:
Plugins/ folder| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| POST | /api/plugin/upload |
Upload DLL/ZIP với version check | ✅ Root |
| POST | /api/plugin/load |
Load plugin từ path | ✅ Root |
| POST | /api/plugin/unload/{name} |
Unload plugin | ✅ Root |
| POST | /api/plugin/reload/{name} |
Reload plugin | ✅ Root |
| POST | /api/plugin/reload-all |
Reload all plugins | ✅ Root |
| DELETE | /api/plugin/remove/{name} |
Xóa plugin folder | ✅ Root |
| GET | /api/plugin/folders |
List plugin folders | ❌ Public |
| GET | /api/plugin/list |
List loaded plugins | ❌ Public |
| GET | /api/plugin/commands |
Get all commands | ❌ Public |
| GET | /api/plugin/commands/{name}/schema |
Get command schema | ❌ Public |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| POST | /api/plugin/execute |
Execute command | ❌ Public (command-level auth) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/plugin/sdk/download |
Download SDK ZIP (DLL + docs) |
| GET | /api/plugin/sdk/info |
Get SDK version info |
| GET | /api/plugin/template/download |
Download sample plugin template |
| GET | /api/plugin/services |
Discover available DI services |
| GET | /api/plugin/services/{name} |
Get service detail với code examples |
Xem chi tiết tại:
DiscoveryHostApi.csproj:<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<ProjectReference Include="..\MyPlugin\MyPlugin.csproj" />
</ItemGroup>
dotnet build -c Debug
cd DiscoveryHostApi
dotnet run
Set breakpoints trong plugin code → F5 trong Visual Studio
Execute command qua Swagger UI → Breakpoint hit! ✅
# Download template
curl -O http://localhost:5000/api/plugin/template/download
unzip DemoPluginTemplate.zip
# Explore code
cd DemoCalculator
cat Commands/AddCommand.cs
cat plugin.json
# Build
dotnet build -c Release
# Upload
cd bin/Release/net8.0
curl -X POST http://localhost:5000/api/plugin/upload \
-F "file=@DemoCalculator.dll"
# Load
curl -X POST http://localhost:5000/api/plugin/load \
-H "Content-Type: application/json" \
-d '{"pluginPath": "DemoCalculator"}'
# Execute
curl -X POST http://localhost:5000/api/plugin/execute \
-H "Content-Type: application/json" \
-d '{
"command": "DemoCalculator.add",
"payload": {"a": 10, "b": 5}
}'
| Command | Description | Example Payload |
|---|---|---|
add |
Cộng hai số | {"a": 10, "b": 5} |
subtract |
Trừ hai số | {"a": 10, "b": 3} |
multiply |
Nhân hai số | {"a": 4, "b": 5} |
divide |
Chia hai số | {"a": 20, "b": 4} |
power |
Lũy thừa | {"base": 2, "exponent": 3} |
sqrt |
Căn bậc hai | {"value": 16} |
factorial |
Giai thừa | {"n": 5} |
git checkout -b feature/my-featuregit commit -am 'Add my feature'git push origin feature/my-featureProprietary License - Copyright © 2025 Thần Nông
Sử dụng thư viện này yêu cầu giấy phép từ Thần Nông.
DiscoveryHostApi/README.mdDocs/PLUGIN_DEVELOPMENT.mdDocs/SCHEMA_AUTO_GENERATION.mdDocs/AUTHORIZATION_QUICK_EXAMPLE.mdHappy Plugin Development! 🚀
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.1 | 496 | 12/8/2025 |
Version 1.0.1:
- Plugin lifecycle management (Load/Unload/Reload)
- JsonLogic-based authorization
- Auto schema generation from method signatures
- Version-based DLL naming support
- Hot reload without app restart
- Isolated AssemblyLoadContext per plugin
- Debug mode with breakpoint support