A:整体并简化代码

This commit is contained in:
jimmy committed 2025-12-22 19:50:09 +08:00
1 parent 2c83fc72e4
commit 2563288afe
26 files changed
+889 -566

No files matched your search

+7 -4
View File
@@ -54,19 +54,20 @@ dotnet clean Sharp.sln
```
src/Sharp/ # All source projects
├── Common/ # Core foundation (Result<T>, Global container, utilities)
├── Common/ # Core foundation (Result<T>, Global container, utilities, SystemConfig)
├── Config/ # Multi-format configuration with hot-reload
├── Sharp.Infrastructure/ # Infrastructure abstractions
├── Wpf/ # WPF UI toolkit (MVVM, dialogs, converters)
├── Data/ # EF Core wrapper with repository pattern
├── Repository/ # Repository implementations
├── Log/ # Serilog-based structured logging
├── Cache/ # Memory cache (planned)
├── EventBus/ # Event pub/sub (planned)
├── NetClient/ # HTTP client with Polly resilience
├── NetServer/ # Lightweight HTTP server with middleware
├── Ports/ # Serial port abstraction (RS232/RS485)
├── Plc/ # PLC abstraction interface
├── Plc.Siemens/ # Siemens S7 PLC implementation (S7.NET+)
├── EventBus/ # Event pub/sub (planned)
├── Process/ # Process execution
├── Python/ # Python script execution
├── Client/ # WPF demo application
└── Server/ # WPF server example
@@ -77,6 +78,8 @@ test/ # MSTest test projects
├── Log.Test/
├── NetClient.Test/
└── NetServer.Test/
appsettings.template.json # 完整的配置模板文件
```
### Key Architectural Patterns
+486
View File
@@ -0,0 +1,486 @@
# Sharp-Basic 配置指南
本文档说明 Sharp-Basic 框架的配置约定和最佳实践。
## 目录
- [配置文件模板](#配置文件模板)
- [约定配置节名称](#约定配置节名称)
- [环境变量覆盖](#环境变量覆盖)
- [模块配置详解](#模块配置详解)
- [最佳实践](#最佳实践)
---
## 配置文件模板
Sharp-Basic 提供了完整的配置文件模板:**`appsettings.template.json`**
### 使用方法
1. **复制模板文件**
```bash
cp appsettings.template.json appsettings.json
```
2. **根据项目需要选择性保留配置**
- 只使用数据库?保留 `Database` 节
- 需要PLC通信?保留 `Plc:Siemens` 节
- 不需要的配置可以删除
3. **修改配置值**
```json
{
"Database": {
"Provider": "SqlServer",
"ConnectionString": "你的数据库连接字符串"
},
"Plc": {
"Siemens": {
"IpAddress": "你的PLC IP地址"
}
}
}
```
---
## 约定配置节名称
Sharp-Basic 遵循 **"约定优于配置"** 原则,每个模块都有固定的配置节名称:
### 核心模块
| 模块 | 配置节名称 | 说明 | 必填字段 |
|------|------------|------|----------|
| **System** | `System` | 系统级HTTP配置 | `HTTPIp`, `HttpPort` |
| **Database** | `Database` | 数据库连接配置 | `Provider`, `ConnectionString` |
| **Logging** | `Logging` | 日志级别配置 | `LogLevel` |
| **SharpLog** | `SharpLog` | Sharp日志扩展 | 无(全部可选) |
### 网络模块
| 模块 | 配置节名称 | 说明 | 必填字段 |
|------|------------|------|----------|
| **NetClient** | `NetClient` | HTTP客户端配置 | 无(可使用System默认值) |
| **NetServer** | `NetServer` | HTTP服务端配置 | 无(可使用System默认值) |
### IoT模块
| 模块 | 配置节名称 | 说明 | 必填字段 |
|------|------------|------|----------|
| **Plc.Siemens** | `Plc:Siemens` | 西门子PLC配置 | `IpAddress`, `CpuType` |
| **SerialPort** | `SerialPort` | 串口通信配置 | `PortName`, `BaudRate` |
### 工具模块
| 模块 | 配置节名称 | 说明 | 必填字段 |
|------|------------|------|----------|
| **Process** | `Process` | 进程执行配置 | 无(全部可选) |
| **Python** | `Python` | Python脚本配置 | 无(全部可选) |
| **Cache** | `Cache` | 缓存配置 | 无(全部可选) |
| **EventBus** | `EventBus` | 事件总线配置 | 无(全部可选) |
---
## 环境变量覆盖
所有配置都可以通过环境变量覆盖,格式:`SHARP__{节名}__{键名}`
### 覆盖规则
- 使用双下划线 `__` 分隔层级
- 所有字母必须大写(Windows不区分大小写,Linux区分)
- 冒号 `:` 用双下划线 `__` 替代
### 示例
#### 覆盖数据库配置
```bash
# Windows (PowerShell)
$env:SHARP__Database__Provider="Sqlite"
$env:SHARP__Database__ConnectionString="Data Source=test.db"
# Linux/macOS
export SHARP__Database__Provider=Sqlite
export SHARP__Database__ConnectionString="Data Source=test.db"
```
#### 覆盖PLC配置
```bash
# Windows (PowerShell)
$env:SHARP__Plc__Siemens__IpAddress="192.168.1.200"
$env:SHARP__Plc__Siemens__CpuType="S71500"
# Linux/macOS
export SHARP__Plc__Siemens__IpAddress=192.168.1.200
export SHARP__Plc__Siemens__CpuType=S71500
```
#### 覆盖System配置
```bash
# Windows (PowerShell)
$env:SHARP__System__HttpPort="9090"
# Linux/macOS
export SHARP__System__HttpPort=9090
```
### 优先级
配置优先级从高到低:
1. **环境变量** - 最高优先级
2. **appsettings.{Environment}.json** - 环境特定配置
3. **appsettings.json** - 默认配置
---
## 模块配置详解
### 1. 数据库配置 (Database)
#### 最简配置
```json
{
"Database": {
"Provider": "Sqlite",
"ConnectionString": "Data Source=app.db"
}
}
```
#### 代码中使用
```csharp
var config = ConfigBuilder.CreateDefault().Configuration;
services.AddSharpDatabase<AppDbContext>(config); // 自动读取 Database 节
```
#### 支持的数据库提供程序
- `SqlServer` - Microsoft SQL Server
- `Sqlite` - SQLite
- `PostgreSQL` - PostgreSQL
- `MySql` - MySQL/MariaDB
- `InMemory` - 内存数据库(仅用于测试)
---
### 2. PLC配置 (Plc:Siemens)
#### 最简配置
```json
{
"Plc": {
"Siemens": {
"IpAddress": "192.168.1.100",
"CpuType": "S71200"
}
}
}
```
#### 代码中使用
```csharp
var config = ConfigBuilder.CreateDefault().Configuration;
services.AddSiemensPlc(config.GetSection("Plc:Siemens"));
```
#### 支持的CPU类型
- `S71200` - S7-1200 系列
- `S71500` - S7-1500 系列
- `S7300` - S7-300 系列
- `S7400` - S7-400 系列
- `S7200` - S7-200 系列
- `S7200Smart` - S7-200 Smart 系列
---
### 3. 日志配置 (Logging & SharpLog)
#### 最简配置
```json
{
"Logging": {
"LogLevel": {
"Default": "Information"
}
},
"SharpLog": {
"LogFilePath": "logs/app-.log",
"RollingInterval": "Day"
}
}
```
#### 代码中使用
```csharp
services.AddLogging(b => b.AddSharpLog()); // 自动读取 SharpLog 节
```
---
### 4. HTTP配置 (System, NetClient, NetServer)
#### System配置(共享默认值)
```json
{
"System": {
"HTTPIp": "127.0.0.1",
"HttpPort": 8080,
"TimeoutSeconds": 30,
"Scheme": "http"
}
}
```
#### NetClient配置(可选,留空则使用System默认值)
```json
{
"NetClient": {
"BaseAddress": "http://api.example.com",
"Timeout": "00:00:30",
"EnableLogging": true
}
}
```
#### NetServer配置(可选,留空则使用System默认值)
```json
{
"NetServer": {
"ListenUrls": [
"http://localhost:8080/"
]
}
}
```
---
## 最佳实践
### 1. 按需配置
**只保留需要的配置节**,不要保留所有模板内容:
```json
{
// ❌ 不好:保留了不使用的配置
"Database": { ... },
"Plc": { ... },
"SerialPort": { ... },
"Python": { ... }
}
{
// ✅ 好:只保留需要的配置
"Database": { ... },
"Logging": { ... }
}
```
### 2. 使用环境特定配置
为不同环境创建不同的配置文件:
```
appsettings.json # 默认配置
appsettings.Development.json # 开发环境
appsettings.Production.json # 生产环境
appsettings.Testing.json # 测试环境
```
### 3. 敏感信息使用环境变量
**不要在配置文件中存储敏感信息**(密码、密钥等):
```bash
# ✅ 好:使用环境变量
export SHARP__Database__ConnectionString="Server=prod;User=sa;Password=SecretPass123"
export SHARP__Plc__Siemens__IpAddress="192.168.1.100"
```
```json
// ❌ 不好:敏感信息写在配置文件
{
"Database": {
"ConnectionString": "Server=prod;User=sa;Password=SecretPass123"
}
}
```
### 4. 添加注释说明
在配置文件中添加注释(JSON5格式,或删除注释后部署):
```json
{
// 数据库配置 - 生产环境使用SQL Server
"Database": {
"Provider": "SqlServer",
"ConnectionString": "Server=.;Database=MyApp;..."
},
// PLC配置 - 车间1号PLC
"Plc": {
"Siemens": {
"IpAddress": "192.168.1.100", // 车间1号PLC
"CpuType": "S71200"
}
}
}
```
### 5. 配置验证
Sharp-Basic 会在启动时验证必填配置项:
```csharp
// ✅ 好:配置缺失时会抛出清晰的异常
services.AddSharpDatabase<AppDbContext>(config);
// 异常:未找到数据库连接字符串配置: Database:ConnectionString
services.AddSiemensPlc(config.GetSection("Plc:Siemens"));
// 异常:未配置PLC IP地址
```
---
## 快速参考
### 典型WPF应用配置
```json
{
"System": {
"HTTPIp": "127.0.0.1",
"HttpPort": 8080
},
"Database": {
"Provider": "Sqlite",
"ConnectionString": "Data Source=app.db"
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
},
"SharpLog": {
"LogFilePath": "logs/app-.log",
"RollingInterval": "Day"
}
}
```
### 工业IoT应用配置
```json
{
"System": {
"HTTPIp": "0.0.0.0",
"HttpPort": 8080
},
"Database": {
"Provider": "SqlServer",
"ConnectionString": "Server=.;Database=ScadaDB;Trusted_Connection=True;"
},
"Plc": {
"Siemens": {
"IpAddress": "192.168.1.100",
"CpuType": "S71200",
"MonitorInterval": 500,
"AutoReconnect": true
}
},
"SerialPort": {
"PortName": "COM1",
"BaudRate": 9600
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Plc.Siemens": "Debug"
}
}
}
```
### Web服务应用配置
```json
{
"System": {
"HTTPIp": "0.0.0.0",
"HttpPort": 5000,
"Scheme": "https"
},
"Database": {
"Provider": "PostgreSQL",
"ConnectionString": "Host=localhost;Database=myapi;Username=postgres;Password=***"
},
"NetServer": {
"ListenUrls": [
"https://0.0.0.0:5000/",
"http://0.0.0.0:5001/"
],
"ShowDetailedErrors": false
},
"Cache": {
"DefaultExpirationSeconds": 300,
"SizeLimit": 10000
}
}
```
---
## 相关文档
- [appsettings.template.json](appsettings.template.json) - 完整配置模板
- [Config模块README](src/Sharp/Config/README.md) - 配置管理详解
- [Data模块README](src/Sharp/Data/README.md) - 数据库配置详解
- [Plc.Siemens模块README](src/Sharp/Plc.Siemens/README.md) - PLC配置详解
---
## 故障排查
### 问题:找不到配置节
**错误信息**:
```
未找到数据库连接字符串配置: Database:ConnectionString
```
**解决方法**:
1. 检查配置文件名是否为 `appsettings.json`
2. 检查配置节名称是否正确(区分大小写)
3. 检查配置文件是否在项目根目录
4. 检查配置文件的 "复制到输出目录" 设置
### 问题:环境变量不生效
**原因**:环境变量格式错误
**正确格式**:
```bash
# ✅ 正确:双下划线分隔
SHARP__Database__Provider=Sqlite
# ❌ 错误:单下划线
SHARP_Database_Provider=Sqlite
# ❌ 错误:使用冒号
SHARP:Database:Provider=Sqlite
```
### 问题:配置被覆盖
**检查优先级**:
1. 先检查环境变量
2. 再检查 appsettings.{Environment}.json
3. 最后检查 appsettings.json
---
**版本**: 1.0.0
**更新日期**: 2025-12-22
+361
View File
@@ -0,0 +1,361 @@
{
// ========================================
// Sharp-Basic 框架配置模板
//
// 这是一个完整的配置文件模板,包含了所有Sharp模块的配置示例
// 使用时可以根据项目需要选择性保留相关配置节
// ========================================
// ========================================
// 系统配置 (System)
// 约定配置节名称:System
// 用于:NetClient、NetServer 的默认HTTP配置
// ========================================
"System": {
// HTTP服务监听IP地址
// 支持:IP地址、localhost、域名
"HTTPIp": "127.0.0.1",
// HTTP服务监听端口
// 范围:1-65535
"HttpPort": 8080,
// HTTP请求超时时间(秒)
// 范围:1-600
"TimeoutSeconds": 30,
// HTTP协议
// 可选值:http、https
"Scheme": "http"
},
// ========================================
// 数据库配置 (Database)
// 约定配置节名称:Database
// 用于:Data 模块 - AddSharpDatabase<TContext>(configuration)
// ========================================
"Database": {
// 数据库提供程序
// 可选值:SqlServer、Sqlite、PostgreSQL、MySql、InMemory
"Provider": "SqlServer",
// 数据库连接字符串
// SqlServer示例:Server=.;Database=MyApp;Trusted_Connection=True;TrustServerCertificate=True;
// Sqlite示例:Data Source=app.db
// PostgreSQL示例:Host=localhost;Database=myapp;Username=postgres;Password=123456
// MySql示例:server=localhost;database=myapp;user=root;password=123456
"ConnectionString": "Server=.;Database=MyApp;Trusted_Connection=True;TrustServerCertificate=True;"
},
// ========================================
// 日志配置 (Logging)
// 约定配置节名称:Logging
// 用于:Log 模块 - AddLogging(b => b.AddSharpLog())
// ========================================
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
// Sharp日志扩展配置
"SharpLog": {
// 日志文件路径(相对或绝对路径)
"LogFilePath": "logs/app-.log",
// 日志滚动间隔
// 可选值:Infinite、Year、Month、Day、Hour、Minute
"RollingInterval": "Day",
// 单个日志文件最大大小(字节)
// null 表示不限制
"FileSizeLimitBytes": 10485760,
// 保留的日志文件数量
// null 表示不限制
"RetainedFileCountLimit": 31,
// 是否输出到控制台
"EnableConsole": true,
// 是否输出到文件
"EnableFile": true,
// 是否启用结构化日志(JSON格式)
"EnableStructuredLog": false
},
// ========================================
// HTTP客户端配置 (NetClient)
// 约定配置节名称:NetClient
// 用于:NetClient 模块 - AddNetClient(configuration.GetSection("NetClient"))
// ========================================
"NetClient": {
// 基础地址(留空则使用System配置)
"BaseAddress": "http://localhost:8080",
// 请求超时时间
"Timeout": "00:00:30",
// 是否启用日志
"EnableLogging": true,
// 是否包含关联ID
"IncludeCorrelationId": true,
// 关联ID请求头名称
"CorrelationIdHeaderName": "X-Correlation-ID",
// 是否使用Bearer Token认证
"UseBearerToken": false,
// 重试配置
"Retry": {
// 是否启用重试
"Enabled": true,
// 最大重试次数
"MaxRetries": 3,
// 重试延迟(秒)
"DelaySeconds": 2
},
// 默认请求头
"DefaultRequestHeaders": {
"User-Agent": "Sharp-NetClient/1.0",
"Accept": "application/json"
}
},
// ========================================
// HTTP服务端配置 (NetServer)
// 约定配置节名称:NetServer
// 用于:NetServer 模块 - AddNetServer(configManager)
// ========================================
"NetServer": {
// 监听URL列表(留空则使用System配置)
"ListenUrls": [
"http://localhost:8080/",
"http://0.0.0.0:8080/"
],
// 是否启用详细错误信息
"ShowDetailedErrors": true,
// 请求正文最大大小(字节)
"MaxRequestBodySize": 30000000,
// 请求超时时间
"RequestTimeout": "00:05:00"
},
// ========================================
// PLC配置 - 西门子 (Plc:Siemens)
// 约定配置节名称:Plc:Siemens
// 用于:Plc.Siemens 模块 - AddSiemensPlc(configuration.GetSection("Plc:Siemens"))
// ========================================
"Plc": {
"Siemens": {
// PLC IP地址
"IpAddress": "192.168.1.100",
// CPU类型
// 可选值:S71200、S71500、S7300、S7400、S7200、S7200Smart
"CpuType": "S71200",
// 机架号(S7-300/400)
"Rack": 0,
// 插槽号(S7-300/400)
"Slot": 1,
// 端口号
"Port": 102,
// 连接超时时间(毫秒)
"ConnectTimeout": 5000,
// 读写超时时间(毫秒)
"ReadWriteTimeout": 3000,
// 监控轮询间隔(毫秒)
"MonitorInterval": 500,
// 是否自动重连
"AutoReconnect": true,
// 重连间隔(毫秒)
"ReconnectInterval": 5000
}
},
// ========================================
// 串口配置 (SerialPort)
// 约定配置节名称:SerialPort
// 用于:Ports 模块 - AddSerialPortService()
// ========================================
"SerialPort": {
// 端口名称(如 COM1、/dev/ttyUSB0)
"PortName": "COM1",
// 波特率
// 常用值:9600、19200、38400、57600、115200
"BaudRate": 9600,
// 数据位
// 可选值:5、6、7、8
"DataBits": 8,
// 停止位
// 可选值:None、One、Two、OnePointFive
"StopBits": "One",
// 校验位
// 可选值:None、Odd、Even、Mark、Space
"Parity": "None",
// 读取超时时间(毫秒)
// -1 表示无限等待
"ReadTimeout": 1000,
// 写入超时时间(毫秒)
// -1 表示无限等待
"WriteTimeout": 1000,
// 接收缓冲区大小(字节)
"ReadBufferSize": 4096,
// 发送缓冲区大小(字节)
"WriteBufferSize": 2048
},
// ========================================
// 进程执行配置 (Process)
// 约定配置节名称:Process
// 用于:Process 模块 - AddSharpProcess()
// ========================================
"Process": {
// 默认工作目录
"WorkingDirectory": "",
// 默认超时时间(毫秒)
// -1 表示无限等待
"DefaultTimeout": 30000,
// 是否重定向标准输出
"RedirectStandardOutput": true,
// 是否重定向标准错误
"RedirectStandardError": true,
// 是否使用Shell执行
"UseShellExecute": false,
// 是否创建窗口
"CreateNoWindow": true
},
// ========================================
// Python配置 (Python)
// 约定配置节名称:Python
// 用于:Python 模块 - AddSharpPython()
// ========================================
"Python": {
// Python解释器路径
// 留空则自动查找系统PATH中的python
"PythonPath": "",
// Python脚本根目录
"ScriptDirectory": "scripts",
// 默认超时时间(毫秒)
"DefaultTimeout": 60000,
// 是否启用虚拟环境
"UseVirtualEnv": false,
// 虚拟环境路径
"VirtualEnvPath": ""
},
// ========================================
// 缓存配置 (Cache)
// 约定配置节名称:Cache
// 用于:Cache 模块 - AddSharpCache()
// ========================================
"Cache": {
// 默认过期时间(秒)
"DefaultExpirationSeconds": 600,
// 缓存大小限制(条目数)
// null 表示不限制
"SizeLimit": null,
// 内存压缩比例(0.0-1.0)
"CompactionPercentage": 0.25,
// 过期扫描频率(秒)
"ExpirationScanFrequency": 60
},
// ========================================
// 事件总线配置 (EventBus)
// 约定配置节名称:EventBus
// 用于:EventBus 模块 - AddEventBus()
// ========================================
"EventBus": {
// 是否启用异步处理
"EnableAsync": true,
// 异步事件队列容量
"QueueCapacity": 1000,
// 是否在异常时继续执行其他订阅者
"ContinueOnError": true,
// 是否启用死信队列
"EnableDeadLetterQueue": false,
// 死信队列容量
"DeadLetterQueueCapacity": 100
},
// ========================================
// WPF配置 (Wpf)
// 约定配置节名称:Wpf
// 用于:Wpf 模块 - AddSharpWpf()
// ========================================
"Wpf": {
// 默认对话框标题
"DefaultDialogTitle": "提示",
// 是否启用全局异常处理
"EnableGlobalExceptionHandler": true,
// 主题
// 可选值:Light、Dark
"Theme": "Light",
// 语言
// 可选值:zh-CN、en-US
"Language": "zh-CN"
},
// ========================================
// 环境变量覆盖说明
// ========================================
// 所有配置都可以通过环境变量覆盖,格式:
// SHARP__{节名}__{键名}
//
// 示例:
// SHARP__Database__Provider=Sqlite
// SHARP__Database__ConnectionString=Data Source=test.db
// SHARP__Plc__Siemens__IpAddress=192.168.1.200
// SHARP__System__HttpPort=9090
//
// 注意:双下划线 __ 用于分隔层级
// ========================================
}
+5 -3
View File
@@ -75,13 +75,11 @@ dotnet clean Sharp.sln
```
src/Sharp/ # All source projects
├── Common/ # Core foundation (Result<T>, Global container, utilities)
├── Common/ # Core foundation (Result<T>, Global container, utilities, SystemConfig)
├── Config/ # Multi-format configuration with hot-reload
├── Sharp.Infrastructure/ # Infrastructure abstractions
├── Wpf/ # WPF UI toolkit (MVVM, dialogs, converters) - NO UI library dependency
├── Wpf.HandyControl/ # HandyControl UI library integration (optional)
├── Data/ # EF Core wrapper with repository pattern
├── Repository/ # Repository implementations
├── Log/ # Serilog-based structured logging
├── Cache/ # Memory cache (planned)
├── EventBus/ # Event pub/sub (planned)
@@ -90,6 +88,8 @@ src/Sharp/ # All source projects
├── Ports/ # Serial port abstraction (RS232/RS485)
├── Plc/ # PLC abstraction interface
├── Plc.Siemens/ # Siemens S7 PLC implementation (S7.NET+)
├── Process/ # Process execution
├── Python/ # Python script execution
├── Client/ # WPF demo application
└── Server/ # WPF server example
@@ -100,6 +100,8 @@ test/ # MSTest test projects
├── Log.Test/
├── NetClient.Test/
└── NetServer.Test/
../../appsettings.template.json # 完整的配置模板文件(约定配置节名称)
```
### Key Architectural Patterns
+3 -5
View File
@@ -5,9 +5,9 @@ using Client.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Common;
using Common.Configuration;
using Config;
using NetClient.DependencyInjection;
using Sharp.Infrastructure.Configuration;
using Sharp.Log.Extensions;
using Sharp.NetServer.DependencyInjection;
using Sharp.Wpf.Services;
@@ -87,11 +87,10 @@ namespace Client
// 2) Build DI container
var services = new ServiceCollection();
// ConfigManager and typed SystemConfig accessor
// ConfigManager and IConfiguration
services.AddSingleton(cm);
// Register standard IConfiguration so that .BindConfiguration() works
services.AddSingleton<IConfiguration>(cm.Configuration);
services.AddSystemConfig(cm);
services.AddSingleton<IConfiguration>(cm.Configuration);
// Logging (binds from section "SharpLog")
services.AddLogging(b => b.AddSharpLog());
@@ -136,7 +135,6 @@ namespace Client
// 3) Register global facades for convenience
Global.RegisterSingleton<IServiceProvider>(_serviceProvider);
Global.RegisterSingleton(cm);
Global.RegisterSingleton(_serviceProvider.GetRequiredService<ISystemConfigAccessor>());
// Also register IDispatcher in Global if it's needed by static contexts (though injection is preferred)
Global.RegisterSingleton(_serviceProvider.GetRequiredService<IDispatcher>());
-1
View File
@@ -20,7 +20,6 @@
<ProjectReference Include="..\NetClient\NetClient.csproj" />
<ProjectReference Include="..\NetServer\NetServer.csproj" />
<ProjectReference Include="..\Log\Log.csproj" />
<ProjectReference Include="..\Sharp.Infrastructure\Sharp.Infrastructure.csproj" />
<ProjectReference Include="..\Plc.Siemens\Plc.Siemens.csproj" />
<ProjectReference Include="..\Ports\Ports.csproj" />
<ProjectReference Include="..\Process\Process.csproj" />
@@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Common.Configuration;
/// <summary>
/// 系统级通用配置
/// 包含 HTTP 访问所需的基础字段,可被 NetClient/NetServer 等模块复用
/// 约定配置节名称:System
/// </summary>
public sealed class SystemConfig
{
[Required]
[RegularExpression(@"^([0-9]{1,3}\.){3}[0-9]{1,3}$|^localhost$|^[a-zA-Z0-9_.-]+$")]
public string HTTPIp { get; set; } = "127.0.0.1";
[Range(1, 65535)]
public int HttpPort { get; set; } = 8080;
[Range(1, 600)]
public int TimeoutSeconds { get; set; } = 30;
public string Scheme { get; set; } = "http";
}
@@ -2,7 +2,7 @@ using System;
using Microsoft.Extensions.DependencyInjection;
using Config;
using NetClient.Options;
using Sharp.Infrastructure.Configuration;
using Common.Configuration;
namespace NetClient.DependencyInjection;
-1
View File
@@ -16,7 +16,6 @@
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\Config\Config.csproj" />
<ProjectReference Include="..\Sharp.Infrastructure\Sharp.Infrastructure.csproj" />
</ItemGroup>
</Project>
@@ -1,7 +1,7 @@
using Config;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Sharp.Infrastructure.Configuration;
using Common.Configuration;
using Sharp.NetServer.Server;
namespace Sharp.NetServer.DependencyInjection;
-1
View File
@@ -10,7 +10,6 @@
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\Config\Config.csproj" />
<ProjectReference Include="..\Sharp.Infrastructure\Sharp.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
@@ -1,77 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using Repository.Options;
namespace Repository;
internal static class DatabaseConfigurator
{
public static void Configure(DbContextOptionsBuilder builder, DatabaseConnectionOptions options)
{
if (string.IsNullOrWhiteSpace(options.ConnectionString))
{
throw new InvalidOperationException(
$"Database connection '{options.ConnectionName ?? "Default"}' has an empty connection string.");
}
builder.EnableDetailedErrors(options.EnableDetailedErrors);
builder.EnableSensitiveDataLogging(options.EnableSensitiveLogging);
switch (options.Provider)
{
case DatabaseProvider.SqlServer:
builder.UseSqlServer(options.ConnectionString, sql =>
{
if (!string.IsNullOrWhiteSpace(options.MigrationsAssembly))
{
sql.MigrationsAssembly(options.MigrationsAssembly);
}
if (options.EnableRetryOnFailure)
{
sql.EnableRetryOnFailure(options.RetryCount, options.RetryDelay, errorNumbersToAdd: null);
}
});
break;
case DatabaseProvider.PostgreSql:
builder.UseNpgsql(options.ConnectionString, npgsql =>
{
if (!string.IsNullOrWhiteSpace(options.MigrationsAssembly))
{
npgsql.MigrationsAssembly(options.MigrationsAssembly);
}
if (options.EnableRetryOnFailure)
{
npgsql.EnableRetryOnFailure(options.RetryCount, options.RetryDelay, null);
}
});
break;
case DatabaseProvider.MySql:
var serverVersion = string.IsNullOrWhiteSpace(options.ServerVersion)
? ServerVersion.AutoDetect(options.ConnectionString)
: ServerVersion.Parse(options.ServerVersion);
builder.UseMySql(options.ConnectionString, serverVersion, mysql =>
{
if (!string.IsNullOrWhiteSpace(options.MigrationsAssembly))
{
mysql.MigrationsAssembly(options.MigrationsAssembly);
}
if (options.EnableRetryOnFailure)
{
mysql.EnableRetryOnFailure(options.RetryCount, options.RetryDelay, null);
}
});
break;
case DatabaseProvider.Sqlite:
builder.UseSqlite(options.ConnectionString, sqlite =>
{
if (!string.IsNullOrWhiteSpace(options.MigrationsAssembly))
{
sqlite.MigrationsAssembly(options.MigrationsAssembly);
}
});
break;
default:
throw new NotSupportedException($"Unsupported database provider '{options.Provider}'.");
}
}
}
@@ -1,60 +0,0 @@
using Config;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Repository.Initialization;
using Repository.Options;
namespace Repository.DependencyInjection;
public static class RepositoryServiceCollectionExtensions
{
/// <summary>
/// 依据配置中心 (ConfigManager) 注册 DbContext,自动选择数据库 Provider 并可选执行迁移。
/// </summary>
public static IServiceCollection AddSharpDbContext<TContext>(this IServiceCollection services,
ConfigManager configManager,
string section = "Database",
string? connectionName = null,
bool initializeOnStartup = true,
Action<DbContextOptionsBuilder>? configureBuilder = null,
ServiceLifetime contextLifetime = ServiceLifetime.Scoped,
ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)
where TContext : DbContext
{
var databaseOptions = configManager.Get<DatabaseOptions>(section, validate: false) ?? new DatabaseOptions();
var connection = databaseOptions.Resolve(connectionName);
return services.AddSharpDbContext<TContext>(connection, initializeOnStartup, configureBuilder, contextLifetime, optionsLifetime);
}
/// <summary>
/// 直接基于手动提供的 DatabaseConnectionOptions 注册 DbContext。
/// </summary>
public static IServiceCollection AddSharpDbContext<TContext>(this IServiceCollection services,
DatabaseConnectionOptions connection,
bool initializeOnStartup = true,
Action<DbContextOptionsBuilder>? configureBuilder = null,
ServiceLifetime contextLifetime = ServiceLifetime.Scoped,
ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)
where TContext : DbContext
{
var resolved = connection.Clone();
services.AddDbContext<TContext>((sp, builder) =>
{
DatabaseConfigurator.Configure(builder, resolved);
configureBuilder?.Invoke(builder);
}, contextLifetime, optionsLifetime);
services.TryAddSingleton<IDatabaseInitializer<TContext>>(sp =>
new DatabaseInitializer<TContext>(sp, resolved, initializeOnStartup));
if (initializeOnStartup)
{
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService>(sp =>
(IHostedService)sp.GetRequiredService<IDatabaseInitializer<TContext>>()));
}
return services;
}
}
@@ -1,48 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Repository.Options;
namespace Repository.Initialization;
internal sealed class DatabaseInitializer<TContext> : IDatabaseInitializer<TContext>, IHostedService
where TContext : DbContext
{
private readonly IServiceProvider _serviceProvider;
private readonly DatabaseConnectionOptions _options;
private readonly bool _runOnStartup;
public DatabaseInitializer(IServiceProvider serviceProvider, DatabaseConnectionOptions options, bool runOnStartup)
{
_serviceProvider = serviceProvider;
_options = options;
_runOnStartup = runOnStartup;
}
public async Task InitializeAsync(CancellationToken cancellationToken = default)
{
using var scope = _serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<TContext>();
if (_options.AutoMigrate)
{
await context.Database.MigrateAsync(cancellationToken);
}
else
{
await context.Database.EnsureCreatedAsync(cancellationToken);
}
}
public Task StartAsync(CancellationToken cancellationToken)
{
if (!_runOnStartup)
{
return Task.CompletedTask;
}
return InitializeAsync(cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
@@ -1,11 +0,0 @@
using Microsoft.EntityFrameworkCore;
namespace Repository.Initialization;
/// <summary>
/// 提供数据库初始化(建库/迁移)能力。
/// </summary>
public interface IDatabaseInitializer<in TContext> where TContext : DbContext
{
Task InitializeAsync(CancellationToken cancellationToken = default);
}
@@ -1,71 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace Repository.Options;
/// <summary>
/// 单个数据库连接的配置。
/// </summary>
public sealed class DatabaseConnectionOptions
{
[Required]
public string ConnectionString { get; set; } = string.Empty;
public DatabaseProvider Provider { get; set; } = DatabaseProvider.Sqlite;
/// <summary>
/// 针对 MySQL/Pomelo 的服务器版本。留空则自动检测。
/// </summary>
public string? ServerVersion { get; set; }
/// <summary>
/// 指定迁移程序集;留空则使用 DbContext 所在程序集。
/// </summary>
public string? MigrationsAssembly { get; set; }
/// <summary>
/// 是否在启动时自动执行迁移(默认为 true)。
/// </summary>
public bool AutoMigrate { get; set; } = true;
/// <summary>
/// 启用失败重试。
/// </summary>
public bool EnableRetryOnFailure { get; set; } = true;
public int RetryCount { get; set; } = 5;
public TimeSpan RetryDelay { get; set; } = TimeSpan.FromSeconds(5);
/// <summary>
/// 打印敏感日志(调试阶段可启用)。
/// </summary>
public bool EnableSensitiveLogging { get; set; }
public bool EnableDetailedErrors { get; set; }
/// <summary>
/// 供内部追踪当前连接名称。
/// </summary>
public string? ConnectionName { get; set; }
/// <summary>
/// 克隆当前选项,避免多处引用同一实例。
/// </summary>
public DatabaseConnectionOptions Clone()
{
return new DatabaseConnectionOptions
{
ConnectionString = ConnectionString,
Provider = Provider,
ServerVersion = ServerVersion,
MigrationsAssembly = MigrationsAssembly,
AutoMigrate = AutoMigrate,
EnableRetryOnFailure = EnableRetryOnFailure,
RetryCount = RetryCount,
RetryDelay = RetryDelay,
EnableSensitiveLogging = EnableSensitiveLogging,
EnableDetailedErrors = EnableDetailedErrors,
ConnectionName = ConnectionName
};
}
}
@@ -1,32 +0,0 @@
namespace Repository.Options;
/// <summary>
/// 数据库配置根节点,支持多命名连接。
/// </summary>
public sealed class DatabaseOptions
{
public string DefaultConnection { get; set; } = "Default";
public IDictionary<string, DatabaseConnectionOptions> Connections { get; set; } =
new Dictionary<string, DatabaseConnectionOptions>(StringComparer.OrdinalIgnoreCase)
{
["Default"] = new DatabaseConnectionOptions
{
Provider = DatabaseProvider.Sqlite,
ConnectionString = "Data Source=app.db"
}
};
public DatabaseConnectionOptions Resolve(string? connectionName = null)
{
var name = string.IsNullOrWhiteSpace(connectionName) ? DefaultConnection : connectionName;
if (!Connections.TryGetValue(name, out var options))
{
throw new InvalidOperationException($"Connection '{name}' is not configured.");
}
var clone = options.Clone();
clone.ConnectionName = name;
return clone;
}
}
@@ -1,12 +0,0 @@
namespace Repository.Options;
/// <summary>
/// 支持的数据库提供器。
/// </summary>
public enum DatabaseProvider
{
SqlServer,
PostgreSql,
MySql,
Sqlite
}
-90
View File
@@ -1,90 +0,0 @@
# Sharp.Repository
面向 Sharp 套件的数据库集成模块,围绕 EF Core 封装“约定优于配置”的默认体验:通过一个配置段即可在 MySQL / SQL Server / PostgreSQL / Sqlite 之间切换,自动注册 `DbContext` 并在启动时执行 `Migration`/`EnsureCreated`。
## 功能
- **多 Provider**:内置 SQL Server、PostgreSQL、MySQL (Pomelo)、Sqlite,按配置自动选择 `UseXXX`.
- **多命名连接**:`Database.Connections` 支持任意命名,默认 `Default`.
- **自动迁移**:默认在应用启动(或手动调用)时执行 `context.Database.MigrateAsync()`。
- **DI 集成**:`services.AddSharpDbContext<TContext>(ConfigManager …)` 一行搞定配置绑定 + DbContext 注册 + 初始化器。
- **可扩展**:可选择禁用自动迁移、开启敏感日志/详细错误、指定迁移程序集、配置 MySQL ServerVersion。
## 配置示例
```json
{
"Database": {
"DefaultConnection": "Main",
"Connections": {
"Main": {
"Provider": "Sqlite",
"ConnectionString": "Data Source=app.db"
},
"Reporting": {
"Provider": "SqlServer",
"ConnectionString": "Server=.;Database=Sharp;Trusted_Connection=True;",
"EnableRetryOnFailure": true,
"MigrationsAssembly": "Server"
},
"Analytics": {
"Provider": "PostgreSql",
"ConnectionString": "Host=localhost;Database=analytics;Username=postgres;Password=123456"
}
}
}
}
```
## 快速接入
```csharp
using Config;
using Microsoft.Extensions.DependencyInjection;
using Repository.DependencyInjection;
var cm = ConfigDefaults.BuildDefault();
services.AddSharpDbContext<AppDbContext>(cm, section: "Database", connectionName: "Main");
// 可选:如果没有 Host,可在启动时手动执行迁移
var initializer = serviceProvider.GetRequiredService<IDatabaseInitializer<AppDbContext>>();
await initializer.InitializeAsync();
```
默认会在 Host 启动时自动调用 `InitializeAsync`(借助 `IHostedService`)。若不想自动迁移,可在配置里设置 `AutoMigrate=false`,或者调用 `services.AddSharpDbContext(... initializeOnStartup: false)`。
## 手动選擇 Provider
若想在没有 Config 模块的场景手动注册:
```csharp
var connection = new DatabaseConnectionOptions
{
Provider = DatabaseProvider.MySql,
ConnectionString = "server=localhost;database=app;user=root;password=123456",
ServerVersion = "8.0.35",
AutoMigrate = true
};
services.AddSharpDbContext<AppDbContext>(connection);
```
## DbContext 模板
```csharp
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<User> Users => Set<User>();
}
public class User
{
public Guid Id { get; set; }
public string UserName { get; set; } = string.Empty;
}
```
将 `Repository` 模块与 `Config` 结合后,使用者只需在 `appsettings.json` 填写连接串,即可获得可用的 `DbContext`,更复杂的配置(如连接池、命名策略)仍可沿用 EF Core 原本的扩展方法。
-22
View File
@@ -1,22 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Config\Config.csproj" />
</ItemGroup>
</Project>
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>
-7
View File
@@ -1,7 +0,0 @@
namespace Sharp.Infrastructure
{
public class Class1
{
}
}
@@ -1,69 +0,0 @@
using System.ComponentModel.DataAnnotations;
using Config;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Sharp.Infrastructure.Configuration;
/// <summary>
/// 官方示例:系统级通用配置。可由项目复制或修改,Config 模块本身不再内置业务模型。
/// 包含 HTTP 访问所需的基础字段,可被 NetClient/NetServer 等模块复用。
/// </summary>
public sealed class SystemConfig
{
[Required]
[RegularExpression(@"^([0-9]{1,3}\.){3}[0-9]{1,3}$|^localhost$|^[a-zA-Z0-9_.-]+$")]
public string HTTPIp { get; set; } = "127.0.0.1";
[Range(1, 65535)]
public int HttpPort { get; set; } = 8080;
[Range(1, 600)]
public int TimeoutSeconds { get; set; } = 30;
public string Scheme { get; set; } = "http";
}
public interface ISystemConfigAccessor
{
SystemConfig Value { get; }
}
internal sealed class SystemConfigAccessor : ISystemConfigAccessor, IDisposable
{
private readonly ConfigManager _configManager;
private readonly IDisposable _subscription;
private readonly string _section;
private readonly bool _validate;
private SystemConfig _current;
public SystemConfigAccessor(ConfigManager configManager, string section, bool validate)
{
_configManager = configManager;
_section = section;
_validate = validate;
_current = configManager.Get<SystemConfig>(_section, _validate);
_subscription = configManager.OnReload(() =>
{
_current = _configManager.Get<SystemConfig>(_section, _validate);
});
}
public SystemConfig Value => _current;
public void Dispose() => _subscription.Dispose();
}
public static class SystemConfigServiceCollectionExtensions
{
/// <summary>
/// Registers SystemConfig accessor backed by ConfigManager under the specified section (default "System").
/// </summary>
public static IServiceCollection AddSystemConfig(this IServiceCollection services, ConfigManager configManager, string section = "System", bool validate = false)
{
services.TryAddSingleton(configManager);
services.TryAddSingleton<ISystemConfigAccessor>(_ => new SystemConfigAccessor(configManager, section, validate));
services.TryAddSingleton(sp => sp.GetRequiredService<ISystemConfigAccessor>().Value);
return services;
}
}
-5
View File
@@ -1,5 +0,0 @@
# Sharp.Infrastructure
基础设施层项目,通常用于存放解决方案级别的横切关注点或胶水代码,目前包含部分遗留配置或共享类型。
> **注意**:随着模块化重构的进行,本项目的许多职责已迁移至 `Common`、`Config` 等专门模块。当前主要作为兼容性保留或特定业务的基础设施依赖。
@@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Config\Config.csproj" />
</ItemGroup>
</Project>
-28
View File
@@ -19,12 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetClient", "NetClient\NetC
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetServer", "NetServer\NetServer.csproj", "{8223A4A8-DE17-4E26-A3EB-674E58B9C446}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "Repository\Repository.csproj", "{74D45E9E-A513-453F-B51D-5541D2857AB8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Config.Test", "..\..\test\Config.Test\Config.Test.csproj", "{F48331AE-8662-411E-B789-A0158A4A1F21}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sharp.Infrastructure", "Sharp.Infrastructure\Sharp.Infrastructure.csproj", "{10DDCB28-9DF5-4BC7-8A3E-AA92275BBB40}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wpf", "Wpf\Wpf.csproj", "{FA8C6432-F42B-4A47-B0C1-60E1773E9CF7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetClient.Test", "..\..\test\NetClient.Test\NetClient.Test.csproj", "{DE83D16E-72D4-4E54-B4D2-17D9CF4E09DD}"
@@ -159,18 +155,6 @@ Global
{8223A4A8-DE17-4E26-A3EB-674E58B9C446}.Release|x64.Build.0 = Release|Any CPU
{8223A4A8-DE17-4E26-A3EB-674E58B9C446}.Release|x86.ActiveCfg = Release|Any CPU
{8223A4A8-DE17-4E26-A3EB-674E58B9C446}.Release|x86.Build.0 = Release|Any CPU
{74D45E9E-A513-453F-B51D-5541D2857AB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{74D45E9E-A513-453F-B51D-5541D2857AB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{74D45E9E-A513-453F-B51D-5541D2857AB8}.Debug|x64.ActiveCfg = Debug|Any CPU
{74D45E9E-A513-453F-B51D-5541D2857AB8}.Debug|x64.Build.0 = Debug|Any CPU
{74D45E9E-A513-453F-B51D-5541D2857AB8}.Debug|x86.ActiveCfg = Debug|Any CPU
{74D45E9E-A513-453F-B51D-5541D2857AB8}.Debug|x86.Build.0 = Debug|Any CPU
{74D45E9E-A513-453F-B51D-5541D2857AB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{74D45E9E-A513-453F-B51D-5541D2857AB8}.Release|Any CPU.Build.0 = Release|Any CPU
{74D45E9E-A513-453F-B51D-5541D2857AB8}.Release|x64.ActiveCfg = Release|Any CPU
{74D45E9E-A513-453F-B51D-5541D2857AB8}.Release|x64.Build.0 = Release|Any CPU
{74D45E9E-A513-453F-B51D-5541D2857AB8}.Release|x86.ActiveCfg = Release|Any CPU
{74D45E9E-A513-453F-B51D-5541D2857AB8}.Release|x86.Build.0 = Release|Any CPU
{F48331AE-8662-411E-B789-A0158A4A1F21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F48331AE-8662-411E-B789-A0158A4A1F21}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F48331AE-8662-411E-B789-A0158A4A1F21}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -183,18 +167,6 @@ Global
{F48331AE-8662-411E-B789-A0158A4A1F21}.Release|x64.Build.0 = Release|Any CPU
{F48331AE-8662-411E-B789-A0158A4A1F21}.Release|x86.ActiveCfg = Release|Any CPU
{F48331AE-8662-411E-B789-A0158A4A1F21}.Release|x86.Build.0 = Release|Any CPU
{10DDCB28-9DF5-4BC7-8A3E-AA92275BBB40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{10DDCB28-9DF5-4BC7-8A3E-AA92275BBB40}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10DDCB28-9DF5-4BC7-8A3E-AA92275BBB40}.Debug|x64.ActiveCfg = Debug|Any CPU
{10DDCB28-9DF5-4BC7-8A3E-AA92275BBB40}.Debug|x64.Build.0 = Debug|Any CPU
{10DDCB28-9DF5-4BC7-8A3E-AA92275BBB40}.Debug|x86.ActiveCfg = Debug|Any CPU
{10DDCB28-9DF5-4BC7-8A3E-AA92275BBB40}.Debug|x86.Build.0 = Debug|Any CPU
{10DDCB28-9DF5-4BC7-8A3E-AA92275BBB40}.Release|Any CPU.ActiveCfg = Release|Any CPU
{10DDCB28-9DF5-4BC7-8A3E-AA92275BBB40}.Release|Any CPU.Build.0 = Release|Any CPU
{10DDCB28-9DF5-4BC7-8A3E-AA92275BBB40}.Release|x64.ActiveCfg = Release|Any CPU
{10DDCB28-9DF5-4BC7-8A3E-AA92275BBB40}.Release|x64.Build.0 = Release|Any CPU
{10DDCB28-9DF5-4BC7-8A3E-AA92275BBB40}.Release|x86.ActiveCfg = Release|Any CPU
{10DDCB28-9DF5-4BC7-8A3E-AA92275BBB40}.Release|x86.Build.0 = Release|Any CPU
{FA8C6432-F42B-4A47-B0C1-60E1773E9CF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA8C6432-F42B-4A47-B0C1-60E1773E9CF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA8C6432-F42B-4A47-B0C1-60E1773E9CF7}.Debug|x64.ActiveCfg = Debug|Any CPU