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

+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