This commit is contained in:
jimmy committed 2025-11-14 16:59:06 +08:00
1 parent ab3721912d
commit a3827a17f2
17 files changed
+319 -31

No files matched your search

+13
View File
@@ -7,5 +7,18 @@
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Wpf\Wpf.csproj" />
<ProjectReference Include="..\Config\Config.csproj" />
<ProjectReference Include="..\NetClient\NetClient.csproj" />
<ProjectReference Include="..\NetServer\NetServer.csproj" />
<ProjectReference Include="..\Log\Log.csproj" />
</ItemGroup>
</Project>
+31 -4
View File
@@ -1,12 +1,39 @@
<Window x:Class="Client.MainWindow"
<Window x:Class="Client.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:Client"
xmlns:b="clr-namespace:Sharp.Wpf.Behaviors;assembly=Sharp.Wpf"
xmlns:w="clr-namespace:Sharp.Wpf.Windowing;assembly=Sharp.Wpf"
xmlns:conv="clr-namespace:Sharp.Wpf.Converters;assembly=Sharp.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
Title="MainWindow" Height="450" Width="800"
w:WindowPlacement.RememberPlacement="True"
w:WindowPlacement.PlacementId="MainWindow">
<Window.Resources>
<conv:BoolToVisibilityConverter x:Key="Bool2Vis" />
</Window.Resources>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Sharp.Wpf 集成示例" FontSize="18" FontWeight="Bold"/>
<StackPanel Orientation="Horizontal" Grid.Row="1" Margin="0,8,0,0">
<Button Content="点击弹窗"
Margin="0,0,8,0"
b:EventToCommand.EventName="Click"
b:EventToCommand.Command="{Binding ShowDialogCommand}" />
<Button Content="触发异常"
b:EventToCommand.EventName="Click"
b:EventToCommand.Command="{Binding ThrowCommand}" />
</StackPanel>
<TextBlock Grid.Row="2" Text="正在处理..." Foreground="DarkOrange"
Visibility="{Binding IsBusy, Converter={StaticResource Bool2Vis}}"/>
<TextBlock Grid.Row="3" Text="{Binding Greeting}" VerticalAlignment="Top"/>
</Grid>
</Window>
+4 -10
View File
@@ -1,13 +1,5 @@
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Client.ViewModels;
namespace Client
{
@@ -19,6 +11,8 @@ namespace Client
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
}
}
+15 -1
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
@@ -6,4 +6,18 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="Serilog" Version="4.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Enrichers.Process" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
</ItemGroup>
</Project>
@@ -1,6 +1,7 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Common.Results;
namespace NetClient.Abstractions;
@@ -18,5 +19,14 @@ public interface IHttpClient
Task DeleteAsync(string path, object? query = null, CancellationToken cancellationToken = default);
Task<TResponse?> PostMultipartAsync<TResponse>(string path, System.Action<System.Net.Http.MultipartFormDataContent> buildContent, CancellationToken cancellationToken = default);
}
// Result-based convenience methods (non-throwing)
Task<Result<T>> GetResultAsync<T>(string path, object? query = null, CancellationToken cancellationToken = default);
Task<Result<string>> GetStringResultAsync(string path, object? query = null, CancellationToken cancellationToken = default);
Task<Result<byte[]>> GetBytesResultAsync(string path, object? query = null, CancellationToken cancellationToken = default);
Task<Result<TResponse>> PostResultAsync<TRequest, TResponse>(string path, TRequest body, CancellationToken cancellationToken = default);
Task<Result<TResponse>> PutResultAsync<TRequest, TResponse>(string path, TRequest body, CancellationToken cancellationToken = default);
Task<Result<TResponse>> PatchResultAsync<TRequest, TResponse>(string path, TRequest body, CancellationToken cancellationToken = default);
Task<Result> DeleteResultAsync(string path, object? query = null, CancellationToken cancellationToken = default);
Task<Result<TResponse>> PostMultipartResultAsync<TResponse>(string path, System.Action<System.Net.Http.MultipartFormDataContent> buildContent, CancellationToken cancellationToken = default);
}
@@ -21,7 +21,7 @@ public static class ServiceCollectionExtensions
{
services.TryAddTransient<LoggingHandler>();
services.TryAddTransient<BearerTokenAuthenticationHandler>();
services.TryAddSingleton<IHttpContentSerializer>(sp => new SystemTextJsonContentSerializer(new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web)));
services.TryAddSingleton<IHttpContentSerializer>(sp => new SystemTextJsonContentSerializer(Common.Utils.JsonHelper.CamelCaseOptions));
services.TryAddSingleton<INetClientFactory, NetClientFactory>();
return services;
}
@@ -108,4 +108,3 @@ public static class ServiceCollectionExtensions
return policyBuilder.WaitAndRetryAsync(delay);
}
}
@@ -6,6 +6,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Common.Results;
using NetClient.Abstractions;
using NetClient.Exceptions;
using NetClient.Internals.Url;
@@ -85,6 +86,150 @@ public class DefaultNetClient : IHttpClient
return await _serializer.DeserializeAsync<TResponse>(stream, cancellationToken).ConfigureAwait(false);
}
public async Task<Result<T>> GetResultAsync<T>(string path, object? query = null, CancellationToken cancellationToken = default)
{
try
{
var value = await GetAsync<T>(path, query, cancellationToken).ConfigureAwait(false);
return Result.Success(value!);
}
catch (ApiException ex)
{
return ToFailureResult<T>(ex);
}
catch (Exception ex)
{
return Result.Failure<T>(ex);
}
}
public async Task<Result<string>> GetStringResultAsync(string path, object? query = null, CancellationToken cancellationToken = default)
{
try
{
var value = await GetStringAsync(path, query, cancellationToken).ConfigureAwait(false);
return Result.Success(value);
}
catch (ApiException ex)
{
return ToFailureResult<string>(ex);
}
catch (Exception ex)
{
return Result.Failure<string>(ex);
}
}
public async Task<Result<byte[]>> GetBytesResultAsync(string path, object? query = null, CancellationToken cancellationToken = default)
{
try
{
var value = await GetBytesAsync(path, query, cancellationToken).ConfigureAwait(false);
return Result.Success(value);
}
catch (ApiException ex)
{
return ToFailureResult<byte[]>(ex);
}
catch (Exception ex)
{
return Result.Failure<byte[]>(ex);
}
}
public async Task<Result<TResponse>> PostResultAsync<TRequest, TResponse>(string path, TRequest body, CancellationToken cancellationToken = default)
{
try
{
var value = await PostAsync<TRequest, TResponse>(path, body, cancellationToken).ConfigureAwait(false);
return Result.Success(value!);
}
catch (ApiException ex)
{
return ToFailureResult<TResponse>(ex);
}
catch (Exception ex)
{
return Result.Failure<TResponse>(ex);
}
}
public async Task<Result<TResponse>> PutResultAsync<TRequest, TResponse>(string path, TRequest body, CancellationToken cancellationToken = default)
{
try
{
var value = await PutAsync<TRequest, TResponse>(path, body, cancellationToken).ConfigureAwait(false);
return Result.Success(value!);
}
catch (ApiException ex)
{
return ToFailureResult<TResponse>(ex);
}
catch (Exception ex)
{
return Result.Failure<TResponse>(ex);
}
}
public async Task<Result<TResponse>> PatchResultAsync<TRequest, TResponse>(string path, TRequest body, CancellationToken cancellationToken = default)
{
try
{
var value = await PatchAsync<TRequest, TResponse>(path, body, cancellationToken).ConfigureAwait(false);
return Result.Success(value!);
}
catch (ApiException ex)
{
return ToFailureResult<TResponse>(ex);
}
catch (Exception ex)
{
return Result.Failure<TResponse>(ex);
}
}
public async Task<Result> DeleteResultAsync(string path, object? query = null, CancellationToken cancellationToken = default)
{
try
{
await DeleteAsync(path, query, cancellationToken).ConfigureAwait(false);
return Result.Success();
}
catch (ApiException ex)
{
return ToFailureResult<object>(ex).Ignore();
}
catch (Exception ex)
{
return Result.Failure(ex);
}
}
public async Task<Result<TResponse>> PostMultipartResultAsync<TResponse>(string path, Action<MultipartFormDataContent> buildContent, CancellationToken cancellationToken = default)
{
try
{
var value = await PostMultipartAsync<TResponse>(path, buildContent, cancellationToken).ConfigureAwait(false);
return Result.Success(value!);
}
catch (ApiException ex)
{
return ToFailureResult<TResponse>(ex);
}
catch (Exception ex)
{
return Result.Failure<TResponse>(ex);
}
}
private static Result<T> ToFailureResult<T>(ApiException ex)
{
var message = string.IsNullOrWhiteSpace(ex.ResponseContent)
? $"HTTP {(int)ex.StatusCode}"
: ex.ResponseContent!;
return Result.Failure<T>((long)ex.StatusCode, message, ex);
}
private string BuildUri(string path, object? query)
{
var q = QueryStringHelper.Build(query);
@@ -123,4 +268,3 @@ public class DefaultNetClient : IHttpClient
return await _serializer.DeserializeAsync<TResponse>(stream, cancellationToken).ConfigureAwait(false);
}
}
+12
View File
@@ -6,4 +6,16 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.*" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.*" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.*" />
<PackageReference Include="Polly.Contrib.WaitAndRetry" Version="1.*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\Config\Config.csproj" />
</ItemGroup>
</Project>
@@ -17,10 +17,5 @@ public class NetClientOptions
public RetryOptions Retry { get; set; } = new();
public JsonSerializerOptions JsonSerializerOptions { get; set; } = new(JsonSerializerDefaults.Web)
{
PropertyNameCaseInsensitive = true,
WriteIndented = false
};
public JsonSerializerOptions JsonSerializerOptions { get; set; } = Common.Utils.JsonHelper.CamelCaseOptions;
}
+14
View File
@@ -0,0 +1,14 @@
using System.Configuration;
using System.Data;
using System.Windows;
namespace Repository
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}
+24
View File
@@ -0,0 +1,24 @@
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Repository
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
+11
View File
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
</PropertyGroup>
</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>
@@ -1,10 +1,9 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
// This code was generated by a tool.
//
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
@@ -14,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Sharp.Wpf")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+237cbd8788e3abea9c8e3f9fdb46798cde43452b")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+ab3721912dc64444eabd750973d21817cb24fbac")]
[assembly: System.Reflection.AssemblyProductAttribute("Sharp.Wpf")]
[assembly: System.Reflection.AssemblyTitleAttribute("Sharp.Wpf")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
@@ -1 +1 @@
2f73c8423764d0eb8aeceea01d1feea6277f92837fb5fffa27cece3438cfc6e5
9b4de542688aa50112519013d6cf33dd03c8f285c691dd96ec4b3acd75e126bd
+27
View File
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/>
<PackageReference Include="MSTest" Version="3.6.4"/>
</ItemGroup>
<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Sharp\Log\Log.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="8.0.10" />
</ItemGroup>
</Project>
+1
View File
@@ -0,0 +1 @@
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]