786 lines
18 KiB
Markdown
786 lines
18 KiB
Markdown
# RobotNet10.CustomConfiguration
|
|
|
|
Thư viện quản lý cấu hình động cho ứng dụng RobotNet10, cho phép import/export, chỉnh sửa và quản lý các file cấu hình dạng JSON với hỗ trợ nhiều kiểu dữ liệu.
|
|
|
|
## 📋 Mục lục
|
|
|
|
- [Tính năng](#tính-năng)
|
|
- [Cấu trúc Project](#cấu-trúc-project)
|
|
- [Cài đặt và Cấu hình](#cài-đặt-và-cấu-hình)
|
|
- [Hướng dẫn sử dụng Backend](#hướng-dẫn-sử-dụng-backend)
|
|
- [Hướng dẫn sử dụng Frontend](#hướng-dẫn-sử-dụng-frontend)
|
|
- [Format JSON Config](#format-json-config)
|
|
- [API Endpoints](#api-endpoints)
|
|
- [Ví dụ sử dụng](#ví-dụ-sử-dụng)
|
|
- [Troubleshooting](#troubleshooting)
|
|
|
|
## ✨ Tính năng
|
|
|
|
- ✅ **Import/Export Config**: Import và export các file cấu hình dạng JSON
|
|
- ✅ **Quản lý Config Files**: Tạo, đọc, cập nhật, xóa các file cấu hình
|
|
- ✅ **Quản lý Variables**: Thêm, sửa, xóa các biến trong config
|
|
- ✅ **Hỗ trợ nhiều kiểu dữ liệu**: String, Int, Double, Bool, Object, Array, Enum
|
|
- ✅ **Validation**: Kiểm tra tính hợp lệ của dữ liệu theo type và constraints
|
|
- ✅ **Tìm kiếm**: Tìm kiếm config theo tên, loại
|
|
- ✅ **UI Component**: Component Blazor sẵn có để quản lý config qua giao diện
|
|
- ✅ **Storage Manager**: Tích hợp với RobotNet10.StorageManager (Local hoặc MinIO)
|
|
|
|
## 📁 Cấu trúc Project
|
|
|
|
```
|
|
RobotNet10.CustomConfiguration/
|
|
├── Controllers/
|
|
│ └── ConfigController.cs # REST API Controller
|
|
├── DTOs/
|
|
│ ├── ConfigFileDto.cs
|
|
│ ├── ConfigFileMetadataDto.cs
|
|
│ ├── ConfigVariableDto.cs
|
|
│ └── Requests/
|
|
│ ├── CreateConfigRequest.cs
|
|
│ ├── UpdateConfigRequest.cs
|
|
│ └── UpdateVariableRequest.cs
|
|
├── Extensions/
|
|
│ └── ServiceCollectionExtensions.cs # DI Extension methods
|
|
├── Helpers/
|
|
│ ├── JsonConfigParser.cs # Parse/Serialize JSON
|
|
│ └── VariableTypeConverter.cs # Convert variable types
|
|
├── Models/
|
|
│ ├── ConfigFile.cs
|
|
│ ├── ConfigFileMetadata.cs
|
|
│ ├── ConfigVariable.cs
|
|
│ └── ConfigVariableType.cs
|
|
├── Services/
|
|
│ ├── IConfigService.cs
|
|
│ ├── ConfigService.cs
|
|
│ ├── ConfigService.Implementation.cs
|
|
│ └── ConfigService.Metadata.cs
|
|
└── Validators/
|
|
└── ConfigValidator.cs # Validation logic
|
|
```
|
|
|
|
## 🚀 Cài đặt và Cấu hình
|
|
|
|
### 1. Thêm Project Reference
|
|
|
|
Thêm reference vào project của bạn:
|
|
|
|
```xml
|
|
<ItemGroup>
|
|
<ProjectReference Include="..\Commons\RobotNet10.CustomConfiguration\RobotNet10.CustomConfiguration.csproj" />
|
|
</ItemGroup>
|
|
```
|
|
|
|
### 2. Cấu hình Backend (ASP.NET Core)
|
|
|
|
#### Bước 1: Thêm using trong `Program.cs`
|
|
|
|
```csharp
|
|
using RobotNet10.CustomConfiguration.Extensions;
|
|
using RobotNet10.StorageManager;
|
|
```
|
|
|
|
#### Bước 2: Đăng ký Services
|
|
|
|
**Cách 1: Từ appsettings.json (Khuyến nghị)**
|
|
|
|
```csharp
|
|
// Trong Program.cs
|
|
builder.Services.AddCustomConfiguration(builder.Configuration, "StorageConfig");
|
|
```
|
|
|
|
Thêm vào `appsettings.json`:
|
|
|
|
```json
|
|
{
|
|
"StorageConfig": {
|
|
"UsingLocal": true,
|
|
"LocalFolder": "Configs",
|
|
"Bucket": "",
|
|
"RetryCount": 3,
|
|
"MinioConfig": {
|
|
"Endpoint": "localhost:9000",
|
|
"User": "minioadmin",
|
|
"Password": "minioadmin",
|
|
"EnableSSL": false
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Cách 2: Trực tiếp trong code**
|
|
|
|
```csharp
|
|
var storageConfig = new StorageConfig
|
|
{
|
|
UsingLocal = true,
|
|
LocalFolder = "Configs",
|
|
Bucket = "",
|
|
RetryCount = 3
|
|
};
|
|
|
|
builder.Services.AddCustomConfiguration(storageConfig);
|
|
```
|
|
|
|
#### Bước 3: Đảm bảo đã map Controllers
|
|
|
|
```csharp
|
|
var app = builder.Build();
|
|
|
|
// ... middleware ...
|
|
|
|
app.MapControllers(); // Đảm bảo có dòng này
|
|
|
|
app.Run();
|
|
```
|
|
|
|
### 3. Cấu hình Frontend (Blazor)
|
|
|
|
#### Bước 1: Thêm Project Reference
|
|
|
|
```xml
|
|
<ItemGroup>
|
|
<ProjectReference Include="..\Components\RobotNet10.CustomConfigurationEditor\RobotNet10.CustomConfigurationEditor.csproj" />
|
|
</ItemGroup>
|
|
```
|
|
|
|
#### Bước 2: Đăng ký Services trong `Program.cs` hoặc `Client/Program.cs`
|
|
|
|
```csharp
|
|
using RobotNet10.CustomConfigurationEditor.Services.API;
|
|
using RobotNet10.CustomConfigurationEditor.Services.State;
|
|
|
|
// HttpClient (nếu chưa có)
|
|
builder.Services.AddScoped(sp => new HttpClient
|
|
{
|
|
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
|
|
});
|
|
|
|
// MudBlazor (nếu chưa có)
|
|
builder.Services.AddMudServices();
|
|
|
|
// CustomConfiguration Services
|
|
builder.Services.AddScoped<ConfigApiService>();
|
|
builder.Services.AddScoped<ConfigManagerState>();
|
|
```
|
|
|
|
#### Bước 3: Copy JavaScript file
|
|
|
|
Copy file `downloadFile.js` từ `RobotNet10.CustomConfigurationEditor/wwwroot/js/downloadFile.js` vào `wwwroot/js/` của project frontend.
|
|
|
|
Thêm vào `index.html` hoặc `App.razor`:
|
|
|
|
```html
|
|
<script src="js/downloadFile.js"></script>
|
|
```
|
|
|
|
#### Bước 4: Thêm using trong `_Imports.razor`
|
|
|
|
```razor
|
|
@using RobotNet10.CustomConfigurationEditor.Components.ConfigManager
|
|
```
|
|
|
|
## 📖 Hướng dẫn sử dụng Backend
|
|
|
|
### Sử dụng IConfigService
|
|
|
|
Inject `IConfigService` vào service hoặc controller của bạn:
|
|
|
|
```csharp
|
|
public class MyService
|
|
{
|
|
private readonly IConfigService _configService;
|
|
|
|
public MyService(IConfigService configService)
|
|
{
|
|
_configService = configService;
|
|
}
|
|
|
|
public async Task<ConfigFile> GetMqttConfigAsync()
|
|
{
|
|
return await _configService.GetConfigByTypeAsync("MQTTBrokerConfig");
|
|
}
|
|
}
|
|
```
|
|
|
|
### Các phương thức chính
|
|
|
|
```csharp
|
|
// Lấy tất cả configs (metadata)
|
|
var configs = await _configService.GetAllConfigsAsync();
|
|
|
|
// Lấy config theo ID
|
|
var config = await _configService.GetConfigByIdAsync(id);
|
|
|
|
// Lấy config theo ConfigType
|
|
var mqttConfig = await _configService.GetConfigByTypeAsync("MQTTBrokerConfig");
|
|
|
|
// Tạo config mới
|
|
var newConfig = await _configService.CreateConfigAsync(
|
|
configType: "MyConfig",
|
|
variables: variables,
|
|
description: "My configuration"
|
|
);
|
|
|
|
// Cập nhật config
|
|
await _configService.UpdateConfigAsync(id, variables, description);
|
|
|
|
// Xóa config
|
|
await _configService.DeleteConfigAsync(id);
|
|
|
|
// Import từ file
|
|
using var stream = File.OpenRead("config.json");
|
|
var imported = await _configService.ImportConfigAsync(stream, "config.json", "MyConfig");
|
|
|
|
// Export ra file
|
|
var exportStream = await _configService.ExportConfigAsync(id);
|
|
|
|
// Cập nhật variable
|
|
await _configService.UpdateVariableAsync(id, "port", 8080);
|
|
|
|
// Thêm variable
|
|
await _configService.AddVariableAsync(id, newVariable);
|
|
|
|
// Xóa variable
|
|
await _configService.RemoveVariableAsync(id, "variableName");
|
|
```
|
|
|
|
## 🎨 Hướng dẫn sử dụng Frontend
|
|
|
|
### Sử dụng Component
|
|
|
|
Tạo page mới hoặc thêm vào page hiện có:
|
|
|
|
```razor
|
|
@page "/config-manager"
|
|
@using RobotNet10.CustomConfigurationEditor.Components.ConfigManager
|
|
|
|
<ConfigManagerComponent />
|
|
```
|
|
|
|
### Sử dụng ConfigManagerState
|
|
|
|
Inject `ConfigManagerState` vào component của bạn:
|
|
|
|
```razor
|
|
@inject ConfigManagerState State
|
|
|
|
<MudButton OnClick="LoadConfigs">Load Configs</MudButton>
|
|
|
|
@code {
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await State.LoadConfigsAsync();
|
|
}
|
|
|
|
private async Task LoadConfigs()
|
|
{
|
|
await State.LoadConfigsAsync();
|
|
}
|
|
}
|
|
```
|
|
|
|
### Các phương thức State
|
|
|
|
```csharp
|
|
// Load tất cả configs
|
|
await State.LoadConfigsAsync();
|
|
|
|
// Load với search query
|
|
await State.LoadConfigsAsync("MQTT");
|
|
|
|
// Load config theo ID
|
|
await State.LoadConfigByIdAsync(id);
|
|
|
|
// Load config theo ConfigType
|
|
await State.LoadConfigByTypeAsync("MQTTBrokerConfig");
|
|
|
|
// Select config
|
|
await State.SelectConfigAsync(configMetadata);
|
|
|
|
// Tạo config mới
|
|
var config = await State.CreateConfigAsync(
|
|
configType: "MyConfig",
|
|
variables: variables,
|
|
description: "My config"
|
|
);
|
|
|
|
// Cập nhật config
|
|
await State.UpdateConfigAsync(variables, description);
|
|
|
|
// Xóa config
|
|
await State.DeleteConfigAsync(id);
|
|
|
|
// Import config
|
|
using var stream = file.OpenReadStream();
|
|
var imported = await State.ImportConfigAsync(stream, file.Name, "MyConfig");
|
|
|
|
// Export config
|
|
var stream = await State.ExportConfigAsync(id);
|
|
|
|
// Cập nhật variable
|
|
await State.UpdateVariableAsync("port", 8080);
|
|
|
|
// Thêm variable
|
|
await State.AddVariableAsync(newVariable);
|
|
|
|
// Xóa variable
|
|
await State.RemoveVariableAsync("variableName");
|
|
```
|
|
|
|
## 📄 Format JSON Config
|
|
|
|
### Cấu trúc cơ bản
|
|
|
|
File config là một mảng JSON chứa các variable:
|
|
|
|
```json
|
|
[
|
|
{
|
|
"name": "port",
|
|
"type": "int",
|
|
"value": 8080,
|
|
"Min": 0,
|
|
"Max": 65535,
|
|
"Roles": ""
|
|
},
|
|
{
|
|
"name": "host",
|
|
"type": "string",
|
|
"value": "localhost",
|
|
"Roles": ""
|
|
},
|
|
{
|
|
"name": "enableSSL",
|
|
"type": "bool",
|
|
"value": true,
|
|
"Roles": ""
|
|
}
|
|
]
|
|
```
|
|
|
|
### Các kiểu dữ liệu hỗ trợ
|
|
|
|
#### 1. String
|
|
|
|
```json
|
|
{
|
|
"name": "host",
|
|
"type": "string",
|
|
"value": "localhost",
|
|
"Roles": ""
|
|
}
|
|
```
|
|
|
|
#### 2. Int
|
|
|
|
```json
|
|
{
|
|
"name": "port",
|
|
"type": "int",
|
|
"value": 8080,
|
|
"Min": 0,
|
|
"Max": 65535,
|
|
"Roles": ""
|
|
}
|
|
```
|
|
|
|
#### 3. Double
|
|
|
|
```json
|
|
{
|
|
"name": "timeout",
|
|
"type": "double",
|
|
"value": 30.5,
|
|
"Min": 0.0,
|
|
"Max": 100.0,
|
|
"Roles": ""
|
|
}
|
|
```
|
|
|
|
#### 4. Bool
|
|
|
|
```json
|
|
{
|
|
"name": "enableSSL",
|
|
"type": "bool",
|
|
"value": true,
|
|
"Roles": ""
|
|
}
|
|
```
|
|
|
|
#### 5. Enum
|
|
|
|
```json
|
|
{
|
|
"name": "logLevel",
|
|
"type": "enum",
|
|
"value": "Info",
|
|
"EnumValues": ["Debug", "Info", "Warning", "Error"],
|
|
"Roles": ""
|
|
}
|
|
```
|
|
|
|
#### 6. Object
|
|
|
|
```json
|
|
{
|
|
"name": "database",
|
|
"type": "object",
|
|
"value": {
|
|
"host": "localhost",
|
|
"port": 5432,
|
|
"name": "mydb"
|
|
},
|
|
"Roles": ""
|
|
}
|
|
```
|
|
|
|
#### 7. Array
|
|
|
|
```json
|
|
{
|
|
"name": "allowedIPs",
|
|
"type": "array",
|
|
"value": ["192.168.1.1", "192.168.1.2", "10.0.0.1"],
|
|
"Roles": ""
|
|
}
|
|
```
|
|
|
|
### Các thuộc tính
|
|
|
|
| Thuộc tính | Bắt buộc | Mô tả |
|
|
|-----------|----------|-------|
|
|
| `name` | ✅ | Tên của variable (duy nhất trong config) |
|
|
| `type` | ✅ | Kiểu dữ liệu: `string`, `int`, `double`, `bool`, `enum`, `object`, `array` |
|
|
| `value` | ✅ | Giá trị của variable |
|
|
| `Min` | ❌ | Giá trị tối thiểu (cho `int` và `double`) |
|
|
| `Max` | ❌ | Giá trị tối đa (cho `int` và `double`) |
|
|
| `EnumValues` | ❌ | Danh sách giá trị cho phép (cho `enum`) |
|
|
| `Roles` | ❌ | Roles string (có thể để trống) |
|
|
|
|
## 🔌 API Endpoints
|
|
|
|
### Config File Management
|
|
|
|
#### GET `/api/configs`
|
|
Lấy tất cả configs (metadata only)
|
|
|
|
**Query Parameters:**
|
|
- `search` (optional): Tìm kiếm theo tên hoặc ConfigType
|
|
|
|
**Response:** `200 OK`
|
|
```json
|
|
[
|
|
{
|
|
"id": "guid",
|
|
"configType": "MQTTBrokerConfig",
|
|
"createdAt": "2024-01-01T00:00:00Z",
|
|
"updatedAt": "2024-01-01T00:00:00Z",
|
|
"description": "MQTT Broker Configuration"
|
|
}
|
|
]
|
|
```
|
|
|
|
#### GET `/api/configs/{id}`
|
|
Lấy config theo ID
|
|
|
|
**Response:** `200 OK`
|
|
```json
|
|
{
|
|
"id": "guid",
|
|
"configType": "MQTTBrokerConfig",
|
|
"variables": [...],
|
|
"createdAt": "2024-01-01T00:00:00Z",
|
|
"updatedAt": "2024-01-01T00:00:00Z",
|
|
"description": "MQTT Broker Configuration"
|
|
}
|
|
```
|
|
|
|
#### GET `/api/configs/by-type/{configType}`
|
|
Lấy config theo ConfigType
|
|
|
|
**Response:** `200 OK` (same as GET by ID)
|
|
|
|
#### GET `/api/configs/exists/{configType}`
|
|
Kiểm tra ConfigType có tồn tại không
|
|
|
|
**Response:** `200 OK`
|
|
```json
|
|
true
|
|
```
|
|
|
|
#### POST `/api/configs`
|
|
Tạo config mới
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"configType": "MyConfig",
|
|
"variables": [
|
|
{
|
|
"name": "port",
|
|
"type": "int",
|
|
"value": 8080
|
|
}
|
|
],
|
|
"description": "My configuration"
|
|
}
|
|
```
|
|
|
|
**Response:** `201 Created`
|
|
|
|
#### PUT `/api/configs/{id}`
|
|
Cập nhật config
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"variables": [...],
|
|
"description": "Updated description"
|
|
}
|
|
```
|
|
|
|
**Response:** `200 OK`
|
|
|
|
#### DELETE `/api/configs/{id}`
|
|
Xóa config
|
|
|
|
**Response:** `204 No Content`
|
|
|
|
### Import/Export
|
|
|
|
#### POST `/api/configs/import`
|
|
Import config từ JSON file
|
|
|
|
**Request:** `multipart/form-data`
|
|
- `file`: JSON file
|
|
- `configType`: ConfigType name
|
|
|
|
**Response:** `200 OK`
|
|
|
|
#### GET `/api/configs/{id}/export`
|
|
Export config ra JSON file
|
|
|
|
**Response:** `200 OK` (application/json)
|
|
|
|
### Variable Management
|
|
|
|
#### PUT `/api/configs/{id}/variables/{variableName}`
|
|
Cập nhật giá trị variable
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"variableName": "port",
|
|
"value": 8080
|
|
}
|
|
```
|
|
|
|
**Response:** `200 OK`
|
|
|
|
#### POST `/api/configs/{id}/variables`
|
|
Thêm variable mới
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"name": "newVariable",
|
|
"type": "string",
|
|
"value": "value"
|
|
}
|
|
```
|
|
|
|
**Response:** `200 OK`
|
|
|
|
#### DELETE `/api/configs/{id}/variables/{variableName}`
|
|
Xóa variable
|
|
|
|
**Response:** `200 OK`
|
|
|
|
## 💡 Ví dụ sử dụng
|
|
|
|
### Ví dụ 1: Tạo MQTT Broker Config
|
|
|
|
```csharp
|
|
var variables = new List<ConfigVariable>
|
|
{
|
|
new ConfigVariable
|
|
{
|
|
Name = "host",
|
|
Type = ConfigVariableType.String,
|
|
Value = "localhost"
|
|
},
|
|
new ConfigVariable
|
|
{
|
|
Name = "port",
|
|
Type = ConfigVariableType.Int,
|
|
Value = 1883,
|
|
Min = 0,
|
|
Max = 65535
|
|
},
|
|
new ConfigVariable
|
|
{
|
|
Name = "enableSSL",
|
|
Type = ConfigVariableType.Bool,
|
|
Value = false
|
|
}
|
|
};
|
|
|
|
var config = await _configService.CreateConfigAsync(
|
|
configType: "MQTTBrokerConfig",
|
|
variables: variables,
|
|
description: "MQTT Broker Configuration"
|
|
);
|
|
```
|
|
|
|
### Ví dụ 2: Import Config từ File
|
|
|
|
```csharp
|
|
using var stream = File.OpenRead("mqtt-config.json");
|
|
var config = await _configService.ImportConfigAsync(
|
|
stream: stream,
|
|
fileName: "mqtt-config.json",
|
|
configType: "MQTTBrokerConfig"
|
|
);
|
|
```
|
|
|
|
### Ví dụ 3: Sử dụng Config trong Service
|
|
|
|
```csharp
|
|
public class MqttService
|
|
{
|
|
private readonly IConfigService _configService;
|
|
|
|
public MqttService(IConfigService configService)
|
|
{
|
|
_configService = configService;
|
|
}
|
|
|
|
public async Task ConnectAsync()
|
|
{
|
|
var config = await _configService.GetConfigByTypeAsync("MQTTBrokerConfig");
|
|
|
|
var host = config.Variables.First(v => v.Name == "host").Value?.ToString();
|
|
var port = (int)config.Variables.First(v => v.Name == "port").Value!;
|
|
|
|
// Connect to MQTT broker using host and port
|
|
}
|
|
}
|
|
```
|
|
|
|
### Ví dụ 4: Cập nhật Variable qua API
|
|
|
|
```csharp
|
|
// C# HttpClient
|
|
var client = new HttpClient();
|
|
var request = new
|
|
{
|
|
variableName = "port",
|
|
value = 8883
|
|
};
|
|
|
|
var response = await client.PutAsJsonAsync(
|
|
"https://api.example.com/api/configs/{id}/variables/port",
|
|
request
|
|
);
|
|
```
|
|
|
|
### Ví dụ 5: Frontend - Sử dụng Component
|
|
|
|
```razor
|
|
@page "/settings/config"
|
|
@using RobotNet10.CustomConfigurationEditor.Components.ConfigManager
|
|
|
|
<PageTitle>Configuration Manager</PageTitle>
|
|
|
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
|
<ConfigManagerComponent />
|
|
</MudContainer>
|
|
```
|
|
|
|
## 🔧 Troubleshooting
|
|
|
|
### Lỗi: "ConfigType already exists"
|
|
|
|
**Nguyên nhân:** ConfigType đã tồn tại trong hệ thống.
|
|
|
|
**Giải pháp:**
|
|
- Sử dụng ConfigType khác
|
|
- Xóa config cũ trước khi tạo mới
|
|
- Kiểm tra bằng `ConfigTypeExistsAsync()` trước khi tạo
|
|
|
|
### Lỗi: "Invalid variable type"
|
|
|
|
**Nguyên nhân:** Type của variable không hợp lệ.
|
|
|
|
**Giải pháp:**
|
|
- Kiểm tra type là một trong: `string`, `int`, `double`, `bool`, `enum`, `object`, `array`
|
|
- Đảm bảo value phù hợp với type
|
|
|
|
### Lỗi: "Value out of range"
|
|
|
|
**Nguyên nhân:** Giá trị vượt quá Min/Max.
|
|
|
|
**Giải pháp:**
|
|
- Kiểm tra giá trị nằm trong khoảng Min và Max
|
|
- Cập nhật Min/Max nếu cần
|
|
|
|
### Lỗi: "Invalid JSON format"
|
|
|
|
**Nguyên nhân:** File JSON không đúng format.
|
|
|
|
**Giải pháp:**
|
|
- Kiểm tra file là mảng JSON hợp lệ
|
|
- Đảm bảo mỗi variable có `name`, `type`, `value`
|
|
- Validate JSON trước khi import
|
|
|
|
### Lỗi: "StorageManager not initialized"
|
|
|
|
**Nguyên nhân:** StorageConfig chưa được cấu hình.
|
|
|
|
**Giải pháp:**
|
|
- Đảm bảo đã gọi `AddCustomConfiguration()` trong `Program.cs`
|
|
- Kiểm tra `StorageConfig` trong `appsettings.json`
|
|
|
|
### Frontend: Component không hiển thị
|
|
|
|
**Nguyên nhân:** Services chưa được đăng ký.
|
|
|
|
**Giải pháp:**
|
|
- Kiểm tra đã đăng ký `ConfigApiService` và `ConfigManagerState`
|
|
- Đảm bảo có `HttpClient` với `BaseAddress`
|
|
- Kiểm tra đã có `MudBlazor` services
|
|
|
|
### Frontend: Export không hoạt động
|
|
|
|
**Nguyên nhân:** JavaScript file chưa được thêm.
|
|
|
|
**Giải pháp:**
|
|
- Copy `downloadFile.js` vào `wwwroot/js/`
|
|
- Thêm script tag vào `index.html` hoặc `App.razor`
|
|
|
|
## 📝 Lưu ý
|
|
|
|
1. **ConfigType là duy nhất**: Mỗi ConfigType chỉ có thể tồn tại một lần trong hệ thống
|
|
2. **Validation**: Tất cả dữ liệu đều được validate trước khi lưu
|
|
3. **Storage**: Config files được lưu trong `configs/{ConfigType}.json` trong StorageManager
|
|
4. **Metadata**: Metadata được lưu trong `configs/_metadata.json`
|
|
5. **Thread Safety**: Services được đăng ký là `Scoped`, phù hợp cho web applications
|
|
|
|
## 📚 Tài liệu tham khảo
|
|
|
|
- [RobotNet10.StorageManager Documentation](../RobotNet10.StorageManager/README.md)
|
|
- [MudBlazor Documentation](https://mudblazor.com/)
|
|
- [ASP.NET Core Documentation](https://docs.microsoft.com/aspnet/core)
|
|
|
|
## 🤝 Đóng góp
|
|
|
|
Nếu bạn phát hiện lỗi hoặc có đề xuất cải thiện, vui lòng tạo issue hoặc pull request.
|
|
|
|
## 📄 License
|
|
|
|
[Thêm thông tin license nếu có]
|
|
|
|
|
|
|