A:增加Server端使用示例
This commit is contained in:
1 parent
989747a2ae
commit
b14721d79e
23 files changed
+1411
-37
No files matched your search
@@ -1,19 +0,0 @@
|
|||||||
using System.Windows;
|
|
||||||
using Sharp.Wpf.Exceptions;
|
|
||||||
|
|
||||||
namespace Client;
|
|
||||||
|
|
||||||
public partial class App : Application
|
|
||||||
{
|
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnStartup(e);
|
|
||||||
var handler = new UiExceptionHandler();
|
|
||||||
handler.Register(new ExceptionHandlingOptions
|
|
||||||
{
|
|
||||||
ShowDialog = true,
|
|
||||||
DialogTitle = "应用程序错误"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Config;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 描述默认配置构建时的约定。
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ConfigDefaultsOptions
|
||||||
|
{
|
||||||
|
public ConfigDefaultsOptions()
|
||||||
|
{
|
||||||
|
BasePath = AppContext.BaseDirectory;
|
||||||
|
EnvironmentName = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")
|
||||||
|
?? Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主配置文件(默认 appsettings.json)
|
||||||
|
/// </summary>
|
||||||
|
public string MainFile { get; set; } = "appsettings.json";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主配置文件是否可选,默认 false 保证文件缺失即时暴露。
|
||||||
|
/// </summary>
|
||||||
|
public bool MainFileOptional { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 要使用的环境名称(默认 DOTNET_ENVIRONMENT / ASPNETCORE_ENVIRONMENT)
|
||||||
|
/// </summary>
|
||||||
|
public string? EnvironmentName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否自动尝试加载 appsettings.{Environment}.json(默认 true)
|
||||||
|
/// </summary>
|
||||||
|
public bool IncludeEnvironmentSpecificFile { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基础路径,默认为 AppContext.BaseDirectory
|
||||||
|
/// </summary>
|
||||||
|
public string? BasePath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否包含环境变量(默认 true)
|
||||||
|
/// </summary>
|
||||||
|
public bool IncludeEnvironmentVariables { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 环境变量前缀,null 表示不过滤
|
||||||
|
/// </summary>
|
||||||
|
public string? EnvironmentPrefix { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 额外要加载的文件,默认全部按 Optional=true 处理
|
||||||
|
/// </summary>
|
||||||
|
public IList<string> AdditionalFiles { get; } = new List<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 额外的配置源描述,便于手动指定 Optional 或自定义格式。
|
||||||
|
/// </summary>
|
||||||
|
public IList<ConfigSource> AdditionalSources { get; } = new List<ConfigSource>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 提供“约定优于配置”的默认构建方式。
|
||||||
|
/// </summary>
|
||||||
|
public static class ConfigDefaults
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构建一个默认的 ConfigManager:自动加载 appsettings.json(+环境变体) + 环境变量。
|
||||||
|
/// </summary>
|
||||||
|
public static ConfigManager BuildDefault(
|
||||||
|
Action<ConfigDefaultsOptions>? defaults = null,
|
||||||
|
Action<ConfigOptions>? configure = null)
|
||||||
|
{
|
||||||
|
return ConfigBuilder.Build(options =>
|
||||||
|
{
|
||||||
|
var defaultOptions = new ConfigDefaultsOptions();
|
||||||
|
defaults?.Invoke(defaultOptions);
|
||||||
|
|
||||||
|
ApplyDefaults(options, defaultOptions);
|
||||||
|
configure?.Invoke(options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 直接构建并获取强类型配置,默认启用数据注解验证。
|
||||||
|
/// </summary>
|
||||||
|
public static T Load<T>(
|
||||||
|
bool validate = true,
|
||||||
|
string? section = null,
|
||||||
|
Action<ConfigDefaultsOptions>? defaults = null,
|
||||||
|
Action<ConfigOptions>? configure = null) where T : new()
|
||||||
|
{
|
||||||
|
var manager = BuildDefault(defaults, configure);
|
||||||
|
return manager.Get<T>(section, validate);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ApplyDefaults(ConfigOptions target, ConfigDefaultsOptions defaults)
|
||||||
|
{
|
||||||
|
if (defaults.BasePath is not null)
|
||||||
|
{
|
||||||
|
target.BasePath = defaults.BasePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.IncludeEnvironmentVariables = defaults.IncludeEnvironmentVariables;
|
||||||
|
target.EnvironmentPrefix = defaults.EnvironmentPrefix;
|
||||||
|
|
||||||
|
target.Sources.Add(CreateSource(defaults.MainFile, defaults.MainFileOptional));
|
||||||
|
|
||||||
|
if (defaults.IncludeEnvironmentSpecificFile && !string.IsNullOrWhiteSpace(defaults.EnvironmentName))
|
||||||
|
{
|
||||||
|
var envFile = BuildEnvironmentSpecificFile(defaults.MainFile, defaults.EnvironmentName!);
|
||||||
|
target.Sources.Add(CreateSource(envFile, optional: true));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var file in defaults.AdditionalFiles)
|
||||||
|
{
|
||||||
|
target.Sources.Add(CreateSource(file, optional: true));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var source in defaults.AdditionalSources)
|
||||||
|
{
|
||||||
|
target.Sources.Add(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConfigSource CreateSource(string path, bool optional)
|
||||||
|
{
|
||||||
|
if (!ConfigSource.TryGuess(path, out var guessed))
|
||||||
|
{
|
||||||
|
throw new NotSupportedException($"Unsupported config extension: {path}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ConfigSource
|
||||||
|
{
|
||||||
|
Path = guessed.Path,
|
||||||
|
Format = guessed.Format,
|
||||||
|
Optional = optional
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildEnvironmentSpecificFile(string mainFile, string environment)
|
||||||
|
{
|
||||||
|
var ext = Path.GetExtension(mainFile);
|
||||||
|
var name = Path.GetFileNameWithoutExtension(mainFile);
|
||||||
|
var dir = Path.GetDirectoryName(mainFile);
|
||||||
|
|
||||||
|
var envFile = $"{name}.{environment}{ext}";
|
||||||
|
return string.IsNullOrEmpty(dir) ? envFile : Path.Combine(dir, envFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,43 @@
|
|||||||
|
using System;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
|
public interface IConfigAccessor<out T>
|
||||||
|
{
|
||||||
|
T Value { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class ConfigAccessor<T> : IConfigAccessor<T>, IDisposable where T : class, new()
|
||||||
|
{
|
||||||
|
private readonly ConfigManager _manager;
|
||||||
|
private readonly IDisposable _subscription;
|
||||||
|
private readonly string? _section;
|
||||||
|
private readonly bool _validate;
|
||||||
|
private readonly Action<T>? _onReload;
|
||||||
|
private T _current;
|
||||||
|
|
||||||
|
public ConfigAccessor(ConfigManager manager, string? section, bool validate, Action<T>? onReload)
|
||||||
|
{
|
||||||
|
_manager = manager;
|
||||||
|
_section = section;
|
||||||
|
_validate = validate;
|
||||||
|
_onReload = onReload;
|
||||||
|
_current = manager.Get<T>(_section, _validate);
|
||||||
|
_subscription = manager.OnReload(() =>
|
||||||
|
{
|
||||||
|
var next = _manager.Get<T>(_section, _validate);
|
||||||
|
_current = next;
|
||||||
|
_onReload?.Invoke(next);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Value => _current;
|
||||||
|
|
||||||
|
public void Dispose() => _subscription.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
public static class ConfigServiceCollectionExtensions
|
public static class ConfigServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -17,5 +53,52 @@ public static class ConfigServiceCollectionExtensions
|
|||||||
});
|
});
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 ConfigDefaults 的约定快速注册 ConfigManager。
|
||||||
|
/// </summary>
|
||||||
|
public static IServiceCollection AddDefaultConfig(this IServiceCollection services,
|
||||||
|
Action<ConfigDefaultsOptions>? defaults = null,
|
||||||
|
Action<ConfigOptions>? configure = null)
|
||||||
|
{
|
||||||
|
return services.AddConfig(options =>
|
||||||
|
{
|
||||||
|
var defaultOptions = new ConfigDefaultsOptions();
|
||||||
|
defaults?.Invoke(defaultOptions);
|
||||||
|
ConfigDefaults.ApplyDefaults(options, defaultOptions);
|
||||||
|
configure?.Invoke(options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将强类型配置绑定注册为单例,并在配置热更新时自动刷新。
|
||||||
|
/// </summary>
|
||||||
|
public static IServiceCollection AddConfigBinding<T>(this IServiceCollection services,
|
||||||
|
string? section = null,
|
||||||
|
bool validate = true,
|
||||||
|
Action<T>? onReload = null) where T : class, new()
|
||||||
|
{
|
||||||
|
services.TryAddSingleton<IConfigAccessor<T>>(sp =>
|
||||||
|
{
|
||||||
|
var manager = sp.GetRequiredService<ConfigManager>();
|
||||||
|
return new ConfigAccessor<T>(manager, section, validate, onReload);
|
||||||
|
});
|
||||||
|
services.TryAddSingleton(sp => sp.GetRequiredService<IConfigAccessor<T>>().Value);
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 同时注册默认配置管线以及强类型绑定。
|
||||||
|
/// </summary>
|
||||||
|
public static IServiceCollection AddDefaultConfigBinding<T>(this IServiceCollection services,
|
||||||
|
Action<ConfigDefaultsOptions>? defaults = null,
|
||||||
|
Action<ConfigOptions>? configure = null,
|
||||||
|
string? section = null,
|
||||||
|
bool validate = true,
|
||||||
|
Action<T>? onReload = null) where T : class, new()
|
||||||
|
{
|
||||||
|
services.AddDefaultConfig(defaults, configure);
|
||||||
|
services.AddConfigBinding(section, validate, onReload);
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,30 @@
|
|||||||
|
|
||||||
## 使用方式
|
## 使用方式
|
||||||
|
|
||||||
|
### 0) 约定优于配置的快速接入
|
||||||
|
|
||||||
|
```
|
||||||
|
// 1 行代码拿到强类型配置,并做数据注解验证
|
||||||
|
var settings = ConfigDefaults.Load<AppSettings>();
|
||||||
|
|
||||||
|
// 或者先构建 ConfigManager,方便后续使用 IConfiguration
|
||||||
|
var manager = ConfigDefaults.BuildDefault(opt =>
|
||||||
|
{
|
||||||
|
opt.EnvironmentPrefix = "APP"; // 仅读取 APP__ 前缀的环境变量
|
||||||
|
opt.AdditionalFiles.Add("extra.ini"); // 按需附加其它文件,默认 optional
|
||||||
|
});
|
||||||
|
var settings = manager.Get<AppSettings>(validate: true);
|
||||||
|
```
|
||||||
|
|
||||||
|
依赖注入中也可以一步完成“构建+绑定+热更新”:
|
||||||
|
|
||||||
|
```
|
||||||
|
services.AddDefaultConfigBinding<AppSettings>(defaults =>
|
||||||
|
{
|
||||||
|
defaults.EnvironmentName = "Production"; // 手动指定环境
|
||||||
|
}, section: "App", onReload: s => logger.LogInformation("配置已更新"));
|
||||||
|
```
|
||||||
|
|
||||||
### 1) 从文件构建(常见)
|
### 1) 从文件构建(常见)
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -183,4 +207,3 @@ var cm = provider.GetRequiredService<ConfigManager>();
|
|||||||
- 提供选项开关以切换 INI 映射策略(是否复制根键到各节)
|
- 提供选项开关以切换 INI 映射策略(是否复制根键到各节)
|
||||||
- 为 INI/TOML 增加自定义 `FileConfigurationProvider`,支持 `ReloadOnChange`
|
- 为 INI/TOML 增加自定义 `FileConfigurationProvider`,支持 `ReloadOnChange`
|
||||||
- 增强序列化器的集合/字典类型支持(当前对 INI 做了简化)
|
- 增强序列化器的集合/字典类型支持(当前对 INI 做了简化)
|
||||||
|
|
||||||
+14
-10
@@ -5,17 +5,25 @@
|
|||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
```
|
```
|
||||||
using Config;
|
// 直接按照约定加载 appsettings.json (+ 环境变体)
|
||||||
|
var settings = ConfigDefaults.Load<AppSettings>();
|
||||||
|
|
||||||
var cm = ConfigBuilder.BuildFromFiles(
|
// 如果需要继续访问 IConfiguration,可先拿到 ConfigManager
|
||||||
|
var cm = ConfigDefaults.BuildDefault(defaults =>
|
||||||
|
{
|
||||||
|
defaults.AdditionalFiles.Add("extra.ini");
|
||||||
|
defaults.EnvironmentPrefix = "APP";
|
||||||
|
});
|
||||||
|
var settings2 = cm.Get<AppSettings>(validate: true);
|
||||||
|
|
||||||
|
// 传统 BuildFromFiles 仍然可用
|
||||||
|
var cmManual = ConfigBuilder.BuildFromFiles(
|
||||||
new[] { "appsettings.json", "settings.ini", "extra.xml", "feature.toml" },
|
new[] { "appsettings.json", "settings.ini", "extra.xml", "feature.toml" },
|
||||||
reloadOnChange: true,
|
reloadOnChange: true,
|
||||||
includeEnvironment: true,
|
includeEnvironment: true,
|
||||||
environmentPrefix: "APP",
|
environmentPrefix: "APP",
|
||||||
basePath: AppContext.BaseDirectory
|
basePath: AppContext.BaseDirectory
|
||||||
);
|
);
|
||||||
|
|
||||||
var settings = cm.Get<AppSettings>(validate: true);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -95,13 +103,10 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
using Config;
|
using Config;
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddConfig(opt =>
|
services.AddDefaultConfigBinding<AppSettings>(section: "App");
|
||||||
{
|
|
||||||
opt.BasePath = AppContext.BaseDirectory;
|
|
||||||
opt.Sources.Add(new ConfigSource { Path = "appsettings.json", Format = ConfigFormat.Json });
|
|
||||||
});
|
|
||||||
|
|
||||||
var sp = services.BuildServiceProvider();
|
var sp = services.BuildServiceProvider();
|
||||||
|
var settings = sp.GetRequiredService<AppSettings>();
|
||||||
var cm = sp.GetRequiredService<ConfigManager>();
|
var cm = sp.GetRequiredService<ConfigManager>();
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -111,4 +116,3 @@ var cm = sp.GetRequiredService<ConfigManager>();
|
|||||||
- INI 映射默认会将根键复制到一级节(若该节没定义同名键),有助于简化配置;如不符合你的期望,可在后续版本通过选项开关关闭
|
- INI 映射默认会将根键复制到一级节(若该节没定义同名键),有助于简化配置;如不符合你的期望,可在后续版本通过选项开关关闭
|
||||||
- TOML 暂不直接 `LoadFromFile<T>`,推荐通过配置管线 + 绑定
|
- TOML 暂不直接 `LoadFromFile<T>`,推荐通过配置管线 + 绑定
|
||||||
- JSON/XML 支持 `ReloadOnChange`;INI/TOML 默认不启用文件级热更新
|
- JSON/XML 支持 `ReloadOnChange`;INI/TOML 默认不启用文件级热更新
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<Application x:Class="Server.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:Server"
|
||||||
|
StartupUri="MainWindow.xaml">
|
||||||
|
<Application.Resources>
|
||||||
|
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Windows;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Config;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Sharp.Log;
|
||||||
|
using Server.Config;
|
||||||
|
using Server.Services;
|
||||||
|
|
||||||
|
namespace Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
public ConfigManager? ConfigManager { get; private set; }
|
||||||
|
public AppConfig? AppConfig { get; private set; }
|
||||||
|
public ILoggerFactory? LoggerFactory { get; private set; }
|
||||||
|
public ServerService? ServerService { get; private set; }
|
||||||
|
|
||||||
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnStartup(e);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. 初始化配置
|
||||||
|
InitializeConfiguration();
|
||||||
|
|
||||||
|
// 2. 初始化日志系统
|
||||||
|
InitializeLogging();
|
||||||
|
|
||||||
|
// 3. 初始化服务器服务
|
||||||
|
InitializeServerService();
|
||||||
|
|
||||||
|
var logger = LoggerFactory?.CreateLogger<App>();
|
||||||
|
logger?.LogInformation("应用程序启动成功");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
$"应用程序初始化失败: {ex.Message}\n\n{ex.StackTrace}",
|
||||||
|
"启动错误",
|
||||||
|
MessageBoxButton.OK,
|
||||||
|
MessageBoxImage.Error);
|
||||||
|
|
||||||
|
Shutdown(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeConfiguration()
|
||||||
|
{
|
||||||
|
ConfigManager = ConfigDefaults.BuildDefault();
|
||||||
|
AppConfig = ConfigManager.Get<AppConfig>(validate: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeLogging()
|
||||||
|
{
|
||||||
|
if (AppConfig == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("配置未初始化");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保日志目录存在
|
||||||
|
var logDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppConfig.Logging.LogDirectory);
|
||||||
|
if (!Directory.Exists(logDirectory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(logDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用标准的Microsoft.Extensions.Logging配置
|
||||||
|
var services = new Microsoft.Extensions.DependencyInjection.ServiceCollection();
|
||||||
|
|
||||||
|
services.AddLogging(logging =>
|
||||||
|
{
|
||||||
|
logging.SetMinimumLevel(ParseLogLevel(AppConfig.Logging.LogLevel));
|
||||||
|
|
||||||
|
if (AppConfig.Logging.LogToConsole)
|
||||||
|
{
|
||||||
|
logging.AddConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果需要文件日志,可以添加其他日志提供程序
|
||||||
|
// 这里使用Sharp.Log的扩展方法
|
||||||
|
// logging.AddSharpLog();
|
||||||
|
});
|
||||||
|
|
||||||
|
var serviceProvider = services.BuildServiceProvider();
|
||||||
|
LoggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeServerService()
|
||||||
|
{
|
||||||
|
if (AppConfig == null || LoggerFactory == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("配置或日志系统未初始化");
|
||||||
|
}
|
||||||
|
|
||||||
|
var logger = LoggerFactory.CreateLogger<ServerService>();
|
||||||
|
ServerService = new ServerService(AppConfig, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Microsoft.Extensions.Logging.LogLevel ParseLogLevel(string level)
|
||||||
|
{
|
||||||
|
return level.ToLower() switch
|
||||||
|
{
|
||||||
|
"trace" => Microsoft.Extensions.Logging.LogLevel.Trace,
|
||||||
|
"debug" => Microsoft.Extensions.Logging.LogLevel.Debug,
|
||||||
|
"information" => Microsoft.Extensions.Logging.LogLevel.Information,
|
||||||
|
"warning" => Microsoft.Extensions.Logging.LogLevel.Warning,
|
||||||
|
"error" => Microsoft.Extensions.Logging.LogLevel.Error,
|
||||||
|
"critical" => Microsoft.Extensions.Logging.LogLevel.Critical,
|
||||||
|
"none" => Microsoft.Extensions.Logging.LogLevel.None,
|
||||||
|
_ => Microsoft.Extensions.Logging.LogLevel.Information
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnExit(ExitEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 停止服务器
|
||||||
|
ServerService?.Dispose();
|
||||||
|
|
||||||
|
// 释放日志资源
|
||||||
|
LoggerFactory?.Dispose();
|
||||||
|
|
||||||
|
var logger = LoggerFactory?.CreateLogger<App>();
|
||||||
|
logger?.LogInformation("应用程序正常退出");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 忽略退出时的异常
|
||||||
|
Console.WriteLine($"退出时出错: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnExit(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Server.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 服务器配置
|
||||||
|
/// </summary>
|
||||||
|
public class ServerConfig
|
||||||
|
{
|
||||||
|
[Range(1, 65535)]
|
||||||
|
public int Port { get; set; } = 8080;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string Host { get; set; } = "localhost";
|
||||||
|
|
||||||
|
[Range(1, 10000)]
|
||||||
|
public int MaxConcurrentRequests { get; set; } = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志配置
|
||||||
|
/// </summary>
|
||||||
|
public class LoggingConfig
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string LogLevel { get; set; } = "Information";
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string LogDirectory { get; set; } = "logs";
|
||||||
|
|
||||||
|
[Range(1, 365)]
|
||||||
|
public int MaxLogFiles { get; set; } = 30;
|
||||||
|
|
||||||
|
public bool LogToConsole { get; set; } = true;
|
||||||
|
|
||||||
|
public bool LogToFile { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用配置(根配置)
|
||||||
|
/// </summary>
|
||||||
|
public class AppConfig
|
||||||
|
{
|
||||||
|
public ServerConfig Server { get; set; } = new();
|
||||||
|
|
||||||
|
public LoggingConfig Logging { get; set; } = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
using Sharp.NetServer.Attributes;
|
||||||
|
using Sharp.NetServer.Controllers;
|
||||||
|
using Sharp.NetServer.Extensions;
|
||||||
|
using Sharp.NetServer.Models;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace Server.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 数据操作控制器(演示CRUD操作)
|
||||||
|
/// </summary>
|
||||||
|
[Route("/api/data")]
|
||||||
|
public class DataController : BaseController
|
||||||
|
{
|
||||||
|
// 内存存储(演示用)
|
||||||
|
private static readonly ConcurrentDictionary<string, DataItem> _dataStore = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取所有数据
|
||||||
|
/// GET /api/data
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet]
|
||||||
|
public async Task GetAll(HttpContext context)
|
||||||
|
{
|
||||||
|
var items = _dataStore.Values.ToList();
|
||||||
|
var result = ResultExtensions.HttpSuccess(new
|
||||||
|
{
|
||||||
|
Count = items.Count,
|
||||||
|
Items = items
|
||||||
|
});
|
||||||
|
|
||||||
|
await WriteSuccessResponseAsync(context, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定ID的数据
|
||||||
|
/// GET /api/data/123
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task GetById(HttpContext context)
|
||||||
|
{
|
||||||
|
var id = context.Request.Url.Segments.LastOrDefault()?.TrimEnd('/');
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(id))
|
||||||
|
{
|
||||||
|
await BadRequestAsync(context, "Invalid ID");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_dataStore.TryGetValue(id, out var item))
|
||||||
|
{
|
||||||
|
var result = ResultExtensions.HttpSuccess(item);
|
||||||
|
await WriteSuccessResponseAsync(context, result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var result = ResultExtensions.HttpFailure<object>($"Item with id '{id}' not found", 404);
|
||||||
|
await NotFoundAsync(context, result.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建新数据
|
||||||
|
/// POST /api/data
|
||||||
|
/// Body: { "name": "test", "value": "data" }
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost]
|
||||||
|
public async Task Create(HttpContext context)
|
||||||
|
{
|
||||||
|
var request = await ReadJsonBodyAsync<CreateDataRequest>(context);
|
||||||
|
|
||||||
|
if (request == null || string.IsNullOrWhiteSpace(request.Name))
|
||||||
|
{
|
||||||
|
await BadRequestAsync(context, "Name is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = Guid.NewGuid().ToString();
|
||||||
|
var item = new DataItem
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Name = request.Name,
|
||||||
|
Value = request.Value,
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
UpdatedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
_dataStore[id] = item;
|
||||||
|
|
||||||
|
var result = ResultExtensions.HttpSuccess(item, "Data created successfully");
|
||||||
|
await WriteSuccessResponseAsync(context, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新数据
|
||||||
|
/// PUT /api/data/123
|
||||||
|
/// Body: { "name": "updated", "value": "new data" }
|
||||||
|
/// </summary>
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task Update(HttpContext context)
|
||||||
|
{
|
||||||
|
var id = context.Request.Url.Segments.LastOrDefault()?.TrimEnd('/');
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(id))
|
||||||
|
{
|
||||||
|
await BadRequestAsync(context, "Invalid ID");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_dataStore.TryGetValue(id, out var item))
|
||||||
|
{
|
||||||
|
await NotFoundAsync(context, $"Item with id '{id}' not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = await ReadJsonBodyAsync<UpdateDataRequest>(context);
|
||||||
|
if (request != null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.Name))
|
||||||
|
{
|
||||||
|
item.Name = request.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.Value != null)
|
||||||
|
{
|
||||||
|
item.Value = request.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.UpdatedAt = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = ResultExtensions.HttpSuccess(item, "Data updated successfully");
|
||||||
|
await WriteSuccessResponseAsync(context, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除数据
|
||||||
|
/// DELETE /api/data/123
|
||||||
|
/// </summary>
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task Delete(HttpContext context)
|
||||||
|
{
|
||||||
|
var id = context.Request.Url.Segments.LastOrDefault()?.TrimEnd('/');
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(id))
|
||||||
|
{
|
||||||
|
await BadRequestAsync(context, "Invalid ID");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_dataStore.TryRemove(id, out var item))
|
||||||
|
{
|
||||||
|
var result = ResultExtensions.HttpSuccess(item, "Data deleted successfully");
|
||||||
|
await WriteSuccessResponseAsync(context, result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await NotFoundAsync(context, $"Item with id '{id}' not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清空所有数据
|
||||||
|
/// DELETE /api/data
|
||||||
|
/// </summary>
|
||||||
|
[HttpDelete]
|
||||||
|
public async Task Clear(HttpContext context)
|
||||||
|
{
|
||||||
|
var count = _dataStore.Count;
|
||||||
|
_dataStore.Clear();
|
||||||
|
|
||||||
|
var result = ResultExtensions.HttpSuccess(new { DeletedCount = count }, "All data cleared");
|
||||||
|
await WriteSuccessResponseAsync(context, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Request/Response Models
|
||||||
|
|
||||||
|
public class CreateDataRequest
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string? Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UpdateDataRequest
|
||||||
|
{
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DataItem
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string? Value { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public DateTime UpdatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using Sharp.NetServer.Attributes;
|
||||||
|
using Sharp.NetServer.Controllers;
|
||||||
|
using Sharp.NetServer.Extensions;
|
||||||
|
using Sharp.NetServer.Models;
|
||||||
|
|
||||||
|
namespace Server.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 健康检查控制器
|
||||||
|
/// </summary>
|
||||||
|
[Route("/api/health")]
|
||||||
|
public class HealthController : BaseController
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 健康检查端点
|
||||||
|
/// GET /api/health
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet]
|
||||||
|
public async Task Check(HttpContext context)
|
||||||
|
{
|
||||||
|
var result = ResultExtensions.HttpSuccess(new
|
||||||
|
{
|
||||||
|
Status = "Healthy",
|
||||||
|
Timestamp = DateTime.UtcNow,
|
||||||
|
Uptime = DateTime.UtcNow - Process.GetCurrentProcess().StartTime
|
||||||
|
});
|
||||||
|
|
||||||
|
await WriteSuccessResponseAsync(context, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 详细健康检查
|
||||||
|
/// GET /api/health/detailed
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("/detailed")]
|
||||||
|
public async Task DetailedCheck(HttpContext context)
|
||||||
|
{
|
||||||
|
var process = Process.GetCurrentProcess();
|
||||||
|
var result = ResultExtensions.HttpSuccess(new
|
||||||
|
{
|
||||||
|
Status = "Healthy",
|
||||||
|
Timestamp = DateTime.UtcNow,
|
||||||
|
System = new
|
||||||
|
{
|
||||||
|
MachineName = Environment.MachineName,
|
||||||
|
OSVersion = Environment.OSVersion.ToString(),
|
||||||
|
ProcessorCount = Environment.ProcessorCount,
|
||||||
|
Is64BitOS = Environment.Is64BitOperatingSystem
|
||||||
|
},
|
||||||
|
Process = new
|
||||||
|
{
|
||||||
|
Id = process.Id,
|
||||||
|
Name = process.ProcessName,
|
||||||
|
StartTime = process.StartTime,
|
||||||
|
Memory = new
|
||||||
|
{
|
||||||
|
WorkingSet = $"{process.WorkingSet64 / 1024 / 1024} MB",
|
||||||
|
PrivateMemory = $"{process.PrivateMemorySize64 / 1024 / 1024} MB"
|
||||||
|
},
|
||||||
|
Threads = process.Threads.Count
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await WriteSuccessResponseAsync(context, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
using Sharp.NetServer.Attributes;
|
||||||
|
using Sharp.NetServer.Controllers;
|
||||||
|
using Sharp.NetServer.Extensions;
|
||||||
|
using Sharp.NetServer.Models;
|
||||||
|
|
||||||
|
namespace Server.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 系统信息控制器
|
||||||
|
/// </summary>
|
||||||
|
[Route("/api/system")]
|
||||||
|
public class SystemController : BaseController
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取系统信息
|
||||||
|
/// GET /api/system/info
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("/info")]
|
||||||
|
public async Task GetSystemInfo(HttpContext context)
|
||||||
|
{
|
||||||
|
var result = ResultExtensions.HttpSuccess(new
|
||||||
|
{
|
||||||
|
MachineName = Environment.MachineName,
|
||||||
|
UserName = Environment.UserName,
|
||||||
|
OSVersion = Environment.OSVersion.ToString(),
|
||||||
|
ProcessorCount = Environment.ProcessorCount,
|
||||||
|
Is64BitOS = Environment.Is64BitOperatingSystem,
|
||||||
|
Is64BitProcess = Environment.Is64BitProcess,
|
||||||
|
SystemDirectory = Environment.SystemDirectory,
|
||||||
|
CurrentDirectory = Environment.CurrentDirectory,
|
||||||
|
TickCount = Environment.TickCount64,
|
||||||
|
Version = Environment.Version.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
await WriteSuccessResponseAsync(context, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取环境变量
|
||||||
|
/// GET /api/system/env?name=PATH
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("/env")]
|
||||||
|
public async Task GetEnvironmentVariable(HttpContext context)
|
||||||
|
{
|
||||||
|
var name = GetQueryParam(context, "name", "");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
var result = ResultExtensions.HttpFailure<object>("Environment variable name is required", 400);
|
||||||
|
await WriteErrorResponseAsync(context, result.Message, 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = Environment.GetEnvironmentVariable(name);
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
var result = ResultExtensions.HttpFailure<object>($"Environment variable '{name}' not found", 404);
|
||||||
|
await NotFoundAsync(context, result.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var successResult = ResultExtensions.HttpSuccess(new { Name = name, Value = value });
|
||||||
|
await WriteSuccessResponseAsync(context, successResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前时间
|
||||||
|
/// GET /api/system/time
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("/time")]
|
||||||
|
public async Task GetCurrentTime(HttpContext context)
|
||||||
|
{
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var utcNow = DateTime.UtcNow;
|
||||||
|
|
||||||
|
var result = ResultExtensions.HttpSuccess(new
|
||||||
|
{
|
||||||
|
Local = new
|
||||||
|
{
|
||||||
|
DateTime = now,
|
||||||
|
Date = now.Date,
|
||||||
|
Time = now.TimeOfDay,
|
||||||
|
DayOfWeek = now.DayOfWeek.ToString()
|
||||||
|
},
|
||||||
|
UTC = new
|
||||||
|
{
|
||||||
|
DateTime = utcNow,
|
||||||
|
Date = utcNow.Date,
|
||||||
|
Time = utcNow.TimeOfDay,
|
||||||
|
UnixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await WriteSuccessResponseAsync(context, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
<Window x:Class="Server.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:Server"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="Sharp HTTP Server" Height="600" Width="900"
|
||||||
|
WindowStartupLocation="CenterScreen">
|
||||||
|
<Grid Margin="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 标题栏 -->
|
||||||
|
<Border Grid.Row="0" Background="#2196F3" Padding="15" CornerRadius="5" Margin="0,0,0,10">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="Sharp HTTP Server" FontSize="24" FontWeight="Bold" Foreground="White"/>
|
||||||
|
<TextBlock Text="基于 NetServer 框架的 HTTP 服务器演示" FontSize="12" Foreground="White" Opacity="0.9"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 服务器控制面板 -->
|
||||||
|
<Border Grid.Row="1" BorderBrush="#E0E0E0" BorderThickness="1" CornerRadius="5" Padding="15" Margin="0,0,0,10">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- 服务器信息 -->
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="0" Text="服务器地址:" VerticalAlignment="Center" Margin="0,0,10,5"/>
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="1" x:Name="ServerUrlTextBlock" Text="http://localhost:8080"
|
||||||
|
FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,0,5"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="0" Text="服务器状态:" VerticalAlignment="Center" Margin="0,0,10,5"/>
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="1" x:Name="StatusTextBlock" Text="未启动"
|
||||||
|
Foreground="Gray" FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,0,5"/>
|
||||||
|
|
||||||
|
<!-- 控制按钮 -->
|
||||||
|
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="0,10,0,0">
|
||||||
|
<Button x:Name="StartButton" Content="启动服务器" Width="100" Height="32" Margin="0,0,10,0" Click="StartButton_Click"
|
||||||
|
Background="#4CAF50" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand"/>
|
||||||
|
<Button x:Name="StopButton" Content="停止服务器" Width="100" Height="32" Margin="0,0,10,0" Click="StopButton_Click"
|
||||||
|
Background="#F44336" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand" IsEnabled="False"/>
|
||||||
|
<Button x:Name="RestartButton" Content="重启服务器" Width="100" Height="32" Margin="0,0,10,0" Click="RestartButton_Click"
|
||||||
|
Background="#FF9800" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand" IsEnabled="False"/>
|
||||||
|
<Button x:Name="ClearLogButton" Content="清空日志" Width="100" Height="32" Click="ClearLogButton_Click"
|
||||||
|
Background="#9E9E9E" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 日志显示区域 -->
|
||||||
|
<Border Grid.Row="2" BorderBrush="#E0E0E0" BorderThickness="1" CornerRadius="5" Padding="10">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0" Text="日志信息" FontSize="14" FontWeight="Bold" Margin="0,0,0,10"/>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
|
||||||
|
<TextBox x:Name="LogTextBox"
|
||||||
|
IsReadOnly="True"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Background="#F5F5F5"
|
||||||
|
BorderThickness="0"
|
||||||
|
FontFamily="Consolas"
|
||||||
|
FontSize="11"
|
||||||
|
Padding="5"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- API 端点提示 -->
|
||||||
|
<Border Grid.Row="3" Background="#E3F2FD" Padding="10" CornerRadius="5" Margin="0,10,0,0">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="可用的 API 端点:" FontWeight="Bold" Margin="0,0,0,5"/>
|
||||||
|
<TextBlock FontSize="11">
|
||||||
|
<Run Text="• GET /api/health - 健康检查"/>
|
||||||
|
<LineBreak/>
|
||||||
|
<Run Text="• GET /api/health/detailed - 详细健康检查"/>
|
||||||
|
<LineBreak/>
|
||||||
|
<Run Text="• GET /api/system/info - 系统信息"/>
|
||||||
|
<LineBreak/>
|
||||||
|
<Run Text="• GET /api/system/time - 当前时间"/>
|
||||||
|
<LineBreak/>
|
||||||
|
<Run Text="• GET /api/data - 获取所有数据"/>
|
||||||
|
<LineBreak/>
|
||||||
|
<Run Text="• POST /api/data - 创建数据 (Body: {"name":"test","value":"data"})"/>
|
||||||
|
<LineBreak/>
|
||||||
|
<Run Text="• GET /api/data/{id} - 获取指定数据"/>
|
||||||
|
<LineBreak/>
|
||||||
|
<Run Text="• PUT /api/data/{id} - 更新数据"/>
|
||||||
|
<LineBreak/>
|
||||||
|
<Run Text="• DELETE /api/data/{id} - 删除数据"/>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
|
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using Server.Services;
|
||||||
|
using Server.Config;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
private ServerService? _serverService;
|
||||||
|
private AppConfig? _config;
|
||||||
|
private ILogger<MainWindow>? _logger;
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
Loaded += MainWindow_Loaded;
|
||||||
|
Closing += MainWindow_Closing;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 从App获取依赖
|
||||||
|
var app = (App)Application.Current;
|
||||||
|
_config = app.AppConfig;
|
||||||
|
_logger = app.LoggerFactory?.CreateLogger<MainWindow>();
|
||||||
|
_serverService = app.ServerService;
|
||||||
|
|
||||||
|
if (_config != null && _serverService != null)
|
||||||
|
{
|
||||||
|
// 更新UI显示服务器地址
|
||||||
|
ServerUrlTextBlock.Text = $"http://{_config.Server.Host}:{_config.Server.Port}";
|
||||||
|
|
||||||
|
// 订阅服务器状态变化事件
|
||||||
|
_serverService.StatusChanged += OnServerStatusChanged;
|
||||||
|
|
||||||
|
AppendLog("应用程序已启动");
|
||||||
|
AppendLog($"配置已加载: {_config.Server.Host}:{_config.Server.Port}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AppendLog("错误: 无法初始化服务器服务");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.LogError(ex, "MainWindow初始化失败");
|
||||||
|
AppendLog($"初始化失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainWindow_Closing(object? sender, System.ComponentModel.CancelEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_serverService?.IsRunning == true)
|
||||||
|
{
|
||||||
|
var result = MessageBox.Show(
|
||||||
|
"服务器正在运行,是否停止服务器并退出?",
|
||||||
|
"确认退出",
|
||||||
|
MessageBoxButton.YesNo,
|
||||||
|
MessageBoxImage.Question);
|
||||||
|
|
||||||
|
if (result == MessageBoxResult.Yes)
|
||||||
|
{
|
||||||
|
_serverService.Stop();
|
||||||
|
AppendLog("服务器已停止");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.LogError(ex, "关闭窗口时出错");
|
||||||
|
AppendLog($"关闭出错: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_serverService?.Start();
|
||||||
|
UpdateButtonStates(isRunning: true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.LogError(ex, "启动服务器失败");
|
||||||
|
AppendLog($"启动失败: {ex.Message}");
|
||||||
|
MessageBox.Show($"启动服务器失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_serverService?.Stop();
|
||||||
|
UpdateButtonStates(isRunning: false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.LogError(ex, "停止服务器失败");
|
||||||
|
AppendLog($"停止失败: {ex.Message}");
|
||||||
|
MessageBox.Show($"停止服务器失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestartButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_serverService?.Restart();
|
||||||
|
UpdateButtonStates(isRunning: true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.LogError(ex, "重启服务器失败");
|
||||||
|
AppendLog($"重启失败: {ex.Message}");
|
||||||
|
MessageBox.Show($"重启服务器失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearLogButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
LogTextBox.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnServerStatusChanged(object? sender, string status)
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
StatusTextBlock.Text = status;
|
||||||
|
|
||||||
|
// 根据状态更新颜色
|
||||||
|
if (status.Contains("已启动"))
|
||||||
|
{
|
||||||
|
StatusTextBlock.Foreground = System.Windows.Media.Brushes.Green;
|
||||||
|
}
|
||||||
|
else if (status.Contains("已停止"))
|
||||||
|
{
|
||||||
|
StatusTextBlock.Foreground = System.Windows.Media.Brushes.Gray;
|
||||||
|
}
|
||||||
|
else if (status.Contains("失败") || status.Contains("错误"))
|
||||||
|
{
|
||||||
|
StatusTextBlock.Foreground = System.Windows.Media.Brushes.Red;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StatusTextBlock.Foreground = System.Windows.Media.Brushes.Orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendLog(status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateButtonStates(bool isRunning)
|
||||||
|
{
|
||||||
|
StartButton.IsEnabled = !isRunning;
|
||||||
|
StopButton.IsEnabled = isRunning;
|
||||||
|
RestartButton.IsEnabled = isRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendLog(string message)
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
|
LogTextBox.AppendText($"[{timestamp}] {message}\n");
|
||||||
|
LogTextBox.ScrollToEnd();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\NetServer\NetServer.csproj" />
|
||||||
|
<ProjectReference Include="..\Log\Log.csproj" />
|
||||||
|
<ProjectReference Include="..\Config\Config.csproj" />
|
||||||
|
<ProjectReference Include="..\Wpf\Wpf.csproj" />
|
||||||
|
<ProjectReference Include="..\Common\Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup />
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Sharp.NetServer;
|
||||||
|
using Sharp.NetServer.Server;
|
||||||
|
using Server.Config;
|
||||||
|
using Server.Controllers;
|
||||||
|
|
||||||
|
namespace Server.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// HTTP服务器服务,管理服务器生命周期
|
||||||
|
/// </summary>
|
||||||
|
public class ServerService
|
||||||
|
{
|
||||||
|
private HttpServer? _httpServer;
|
||||||
|
private Task? _serverTask;
|
||||||
|
private CancellationTokenSource? _cts;
|
||||||
|
private readonly AppConfig _config;
|
||||||
|
private readonly ILogger<ServerService> _logger;
|
||||||
|
|
||||||
|
public bool IsRunning { get; private set; }
|
||||||
|
|
||||||
|
public event EventHandler<string>? StatusChanged;
|
||||||
|
|
||||||
|
public ServerService(AppConfig config, ILogger<ServerService> logger)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动服务器
|
||||||
|
/// </summary>
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (IsRunning)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Server is already running");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Starting HTTP server on {Host}:{Port}", _config.Server.Host, _config.Server.Port);
|
||||||
|
OnStatusChanged($"正在启动服务器 {_config.Server.Host}:{_config.Server.Port}...");
|
||||||
|
|
||||||
|
var builder = new HttpServerBuilder()
|
||||||
|
.UseUrls($"http://{_config.Server.Host}:{_config.Server.Port}/")
|
||||||
|
.ConfigureLogging(services =>
|
||||||
|
{
|
||||||
|
services.AddLogging(logging =>
|
||||||
|
{
|
||||||
|
// 使用应用程序的日志配置
|
||||||
|
logging.SetMinimumLevel(ParseLogLevel(_config.Logging.LogLevel));
|
||||||
|
|
||||||
|
if (_config.Logging.LogToConsole)
|
||||||
|
{
|
||||||
|
logging.AddConsole();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
// 注册配置
|
||||||
|
services.AddSingleton(_config);
|
||||||
|
services.AddSingleton(_config.Server);
|
||||||
|
services.AddSingleton(_config.Logging);
|
||||||
|
})
|
||||||
|
// 注册控制器(从当前程序集)
|
||||||
|
.AddControllers<ServerService>();
|
||||||
|
|
||||||
|
_httpServer = builder.Build();
|
||||||
|
|
||||||
|
// 在后台启动服务器
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
|
_serverTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _httpServer.StartAsync(_cts.Token);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Server task error");
|
||||||
|
OnStatusChanged($"服务器运行错误: {ex.Message}");
|
||||||
|
IsRunning = false;
|
||||||
|
}
|
||||||
|
}, _cts.Token);
|
||||||
|
|
||||||
|
// 等待服务器启动
|
||||||
|
Thread.Sleep(500);
|
||||||
|
|
||||||
|
IsRunning = true;
|
||||||
|
_logger.LogInformation("HTTP server started successfully on {Host}:{Port}", _config.Server.Host, _config.Server.Port);
|
||||||
|
OnStatusChanged($"服务器已启动: http://{_config.Server.Host}:{_config.Server.Port}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
IsRunning = false;
|
||||||
|
_logger.LogError(ex, "Failed to start HTTP server");
|
||||||
|
OnStatusChanged($"启动失败: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 停止服务器
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!IsRunning)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Server is not running");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Stopping HTTP server");
|
||||||
|
OnStatusChanged("正在停止服务器...");
|
||||||
|
|
||||||
|
_cts?.Cancel();
|
||||||
|
_httpServer?.Stop();
|
||||||
|
_serverTask?.Wait(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
|
IsRunning = false;
|
||||||
|
_logger.LogInformation("HTTP server stopped successfully");
|
||||||
|
OnStatusChanged("服务器已停止");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to stop HTTP server");
|
||||||
|
OnStatusChanged($"停止失败: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重启服务器
|
||||||
|
/// </summary>
|
||||||
|
public void Restart()
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Restarting HTTP server");
|
||||||
|
OnStatusChanged("正在重启服务器...");
|
||||||
|
|
||||||
|
Stop();
|
||||||
|
Thread.Sleep(500); // 等待端口释放
|
||||||
|
Start();
|
||||||
|
|
||||||
|
OnStatusChanged("服务器已重启");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析日志级别
|
||||||
|
/// </summary>
|
||||||
|
private LogLevel ParseLogLevel(string level)
|
||||||
|
{
|
||||||
|
return level.ToLower() switch
|
||||||
|
{
|
||||||
|
"trace" => LogLevel.Trace,
|
||||||
|
"debug" => LogLevel.Debug,
|
||||||
|
"information" => LogLevel.Information,
|
||||||
|
"warning" => LogLevel.Warning,
|
||||||
|
"error" => LogLevel.Error,
|
||||||
|
"critical" => LogLevel.Critical,
|
||||||
|
"none" => LogLevel.None,
|
||||||
|
_ => LogLevel.Information
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStatusChanged(string status)
|
||||||
|
{
|
||||||
|
StatusChanged?.Invoke(this, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (IsRunning)
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
_cts?.Dispose();
|
||||||
|
_serverTask?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"Server": {
|
||||||
|
"Port": 8080,
|
||||||
|
"Host": "localhost",
|
||||||
|
"MaxConcurrentRequests": 100
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": "Information",
|
||||||
|
"LogDirectory": "logs",
|
||||||
|
"MaxLogFiles": 30,
|
||||||
|
"LogToConsole": true,
|
||||||
|
"LogToFile": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Log.Test", "..\..\test\Log.
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetServer.Test", "..\..\test\NetServer.Test\NetServer.Test.csproj", "{020997C0-FB5F-4222-8620-818840EF4447}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetServer.Test", "..\..\test\NetServer.Test\NetServer.Test.csproj", "{020997C0-FB5F-4222-8620-818840EF4447}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{C2505AD1-D775-4881-A56F-42BDB7CDE58D}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -99,6 +101,10 @@ Global
|
|||||||
{020997C0-FB5F-4222-8620-818840EF4447}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{020997C0-FB5F-4222-8620-818840EF4447}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{020997C0-FB5F-4222-8620-818840EF4447}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{020997C0-FB5F-4222-8620-818840EF4447}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{020997C0-FB5F-4222-8620-818840EF4447}.Release|Any CPU.Build.0 = Release|Any CPU
|
{020997C0-FB5F-4222-8620-818840EF4447}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C2505AD1-D775-4881-A56F-42BDB7CDE58D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C2505AD1-D775-4881-A56F-42BDB7CDE58D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C2505AD1-D775-4881-A56F-42BDB7CDE58D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C2505AD1-D775-4881-A56F-42BDB7CDE58D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// This code was generated by a tool.
|
// 此代码由工具生成。
|
||||||
|
// 运行时版本:4.0.30319.42000
|
||||||
//
|
//
|
||||||
// Changes to this file may cause incorrect behavior and will be lost if
|
// 对此文件的更改可能会导致不正确的行为,并且如果
|
||||||
// the code is regenerated.
|
// 重新生成代码,这些更改将会丢失。
|
||||||
// </auto-generated>
|
// </auto-generated>
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Sharp.Wpf")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("Sharp.Wpf")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+ab3721912dc64444eabd750973d21817cb24fbac")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+989747a2ae8392dd304ec136a20c73d2714a43b9")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("Sharp.Wpf")]
|
[assembly: System.Reflection.AssemblyProductAttribute("Sharp.Wpf")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("Sharp.Wpf")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("Sharp.Wpf")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
9b4de542688aa50112519013d6cf33dd03c8f285c691dd96ec4b3acd75e126bd
|
6c5c7e08ff66373e9981bab4e0ba90b429207193706551d9719bf8bb6534127e
|
||||||
@@ -1 +1 @@
|
|||||||
17631078795867788
|
17633435623876895
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Configuration;
|
||||||
|
using System.Data;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Client
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in new issue
Block a user