更新框架

This commit is contained in:
chfei committed 2026-10-27 14:35:03 +08:00
1 parent ef50fa0620
commit 84b28f9672
72 files changed
+2222 -282

No files matched your search

+19 -6
View File
@@ -1,9 +1,22 @@
<mf:FrameworkApplication
<mf:FrameworkApplication
x:Class="Store.Management.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Store.Management"
xmlns:mf="Store.Management.Framework"
StartupUri="MainWindow.xaml">
<mf:FrameworkApplication.Resources />
</mf:FrameworkApplication>
xmlns:mf="clr-namespace:Store.Management.Framework;assembly=Store.Management.Framework">
<Application.Resources>
<ResourceDictionary>
<SolidColorBrush x:Key="PrimaryBrush" Color="#3267C8" />
<SolidColorBrush x:Key="TextIconBrush" Color="#FFFFFF" />
<SolidColorBrush x:Key="BackgroundBrush" Color="#FFFFFF" />
<Style x:Key="DialogWindowStyle" TargetType="Window">
<Setter Property="ResizeMode" Value="NoResize" />
</Style>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Store.Management.Framework;component/Resources/HandyControlBasic.xaml" />
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" />
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</mf:FrameworkApplication>
+25 -25
View File
@@ -1,29 +1,29 @@
using Prism.Ioc;
using Prism.Ioc;
using MAX.Notification;
using Store.Management.Notifications;
using Store.Management.Framework;
using System.Configuration;
using System.Data;
using Store.Management.Services;
using Store.Management.ViewModels;
using Store.Management.Views;
using System.Windows;
namespace Store.Management
namespace Store.Management;
public partial class App : FrameworkApplication
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : FrameworkApplication
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
}
protected override Window CreateShell()
{
throw new NotImplementedException();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
protected override Window CreateShell() => Container.Resolve<MainWindow>();
protected override void RegisterTypes(IContainerRegistry registry)
{
registry.RegisterSingleton<SingletonProbe>();
registry.Register<TransientProbe>();
registry.RegisterSingleton<NotificationExampleState>();
registry.Register<IPublisher>(provider => new NotificationPublisher(
new PrismNotificationServiceProvider(provider)));
// 显式提供处理器集合,展示一条通知分发给多个业务模块。
registry.Register<IEnumerable<INotificationHandler<ProductSavedNotification>>>(provider =>
new INotificationHandler<ProductSavedNotification>[]
{
new ProductCacheHandler(provider.Resolve<NotificationExampleState>()),
new ProductAuditHandler(provider.Resolve<NotificationExampleState>())
});
registry.RegisterDialog<FrameworkTestDialog, FrameworkTestDialogViewModel>();
}
}
+13 -12
View File
@@ -1,12 +1,13 @@
<Window x:Class="Store.Management.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:Store.Management"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
</Grid>
</Window>
<Window
x:Class="Store.Management.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:Store.Management.Views"
Title="Store.Management · 框架能力测试"
Width="960"
Height="720"
MinWidth="720"
MinHeight="560"
WindowStartupLocation="CenterScreen">
<views:FrameworkTestPage />
</Window>
+4 -22
View File
@@ -1,24 +1,6 @@
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 Store.Management
namespace Store.Management;
public partial class MainWindow : Window
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
public MainWindow() => InitializeComponent();
}
+57
View File
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<nlog
xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
autoReload="true"
throwExceptions="false"
internalLogLevel="Off">
<!-- optional, add some variables
https://github.com/nlog/NLog/wiki/Configuration-file#variables
-->
<variable name="logdir" value="${gdc:item=LoggingFolder}" />
<targets async="true">
<!--Type输出类型:
Console 输出到控制台
Debugger 输出到VS输出窗口
File 输出到文件
Mail 输出为邮件发送
Network 输出到网络地址
Database 输出到数据库-->
<target xsi:type="File" name="Fatal"
fileName="${logdir}/${date:format=yyyyMM}/Fatal/${shortdate}.log"
layout="${longdate}|${level}|${logger}|${callsite}|${message}|${exception:format=tostring}"
/>
<target xsi:type="File" name="Error"
fileName="${logdir}/${date:format=yyyyMM}/Error/${shortdate}.log"
layout="${longdate}|${level}|${logger}|${callsite}|${message}|${exception:format=tostring}"
/>
<target xsi:type="File" name="Info" fileName="${logdir}/${date:format=yyyyMM}/Info/${shortdate}.log"/>
<target xsi:type="File" name="Debug" fileName="${logdir}/${date:format=yyyyMM}/Debug/${shortdate}.log"/>
</targets>
<rules>
<!--NLog支持如下几种记录等级:
1.Trace - 最常见的记录信息,一般用于普通输出
2.Debug - 同样是记录信息,不过出现的频率要比Trace少一些,一般用来调试程序
3.Info - 信息类型的消息
4.Warn - 警告信息,一般用于比较重要的场合
5.Error - 错误信息
6.Fatal - 致命异常信息。一般来讲,发生致命异常之后程序将无法继续执行。-->
<logger name="*" level="Fatal" writeTo="Fatal" />
<logger name="*" level="Error" writeTo="Error" />
<logger name="*" level="Warn" writeTo="Error" />
<logger name="*" level="Info" writeTo="Info" />
<logger name="*" level="Debug" writeTo="Debug" />
<!--<logger name="*" level="Trace" writeTo="Trace" />-->
<!--将所有记录的信息输出至控制台-->
<!--<logger name="*" writeTo="Debugger" />-->
<!--<logger name="*" minlevel="Debug" writeTo="Debugger" />
此处增加了一个minlevel="Warn",规则就变成了将所有记录等级大于等于Warn的等级信息输出至控制台-->
<!--<logger name="*" minlevel="Debug" writeTo="csv" />-->
</rules>
</nlog>
@@ -0,0 +1,54 @@
using MAX.Notification;
using Prism.Ioc;
namespace Store.Management.Notifications;
/// <summary>保存示例处理结果,由发布方等待完成后读取。</summary>
public sealed class NotificationExampleState
{
private readonly object gate = new();
private readonly List<string> entries = [];
public void Record(string message)
{
lock (gate)
{
entries.Add(message);
if (entries.Count > 10) entries.RemoveAt(0);
}
}
public string GetHistory()
{
lock (gate) return string.Join(Environment.NewLine, entries);
}
}
/// <summary>模拟模块一:商品保存后刷新缓存。</summary>
public sealed class ProductCacheHandler(NotificationExampleState state)
: INotificationHandler<ProductSavedNotification>
{
public Task Handle(ProductSavedNotification notification, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
state.Record($"缓存模块:已刷新商品「{notification.ProductName}」。");
return Task.CompletedTask;
}
}
/// <summary>模拟模块二:异步记录操作日志。</summary>
public sealed class ProductAuditHandler(NotificationExampleState state)
: INotificationHandler<ProductSavedNotification>
{
public async Task Handle(ProductSavedNotification notification, CancellationToken cancellationToken)
{
await Task.Delay(200, cancellationToken).ConfigureAwait(false);
state.Record($"审计模块:已记录商品「{notification.ProductName}」的保存操作。");
}
}
/// <summary>将标准服务解析转交给当前 Prism 容器。</summary>
internal sealed class PrismNotificationServiceProvider(IContainerProvider container) : IServiceProvider
{
public object? GetService(Type serviceType) => container.Resolve(serviceType);
}
@@ -0,0 +1,6 @@
using MAX.Notification;
namespace Store.Management.Notifications;
/// <summary>商品保存后发布的业务通知;不持有页面或控件实例。</summary>
public sealed record ProductSavedNotification(string ProductName) : INotification;
@@ -0,0 +1,4 @@
namespace Store.Management.Services;
/// <summary>通过实例标识观察容器生命周期。</summary>
public sealed class SingletonProbe { public Guid InstanceId { get; } = Guid.NewGuid(); }
public sealed class TransientProbe { public Guid InstanceId { get; } = Guid.NewGuid(); }
@@ -2,7 +2,6 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
@@ -10,6 +9,13 @@
<ItemGroup>
<ProjectReference Include="..\Store.Management.Framework\Store.Management.Framework.csproj" />
<ProjectReference Include="..\Store.Management.Api\Store.Management.Api.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="NLog.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,23 @@
using MAX.Framework.Controls;
using Prism.Services.Dialogs;
namespace Store.Management.ViewModels;
public sealed class FrameworkTestDialogViewModel : ConfirmDialogViewModel
{
private string message = "";
public FrameworkTestDialogViewModel() => Title = "框架弹窗测试";
public string Message { get => message; set => SetProperty(ref message, value); }
public override void OnDialogOpened(IDialogParameters parameters)
{
Message = parameters.GetValue<string>("message") ?? "";
ErrorMsg = "";
}
protected override DialogResult Confirm()
{
if (string.IsNullOrWhiteSpace(Message))
{
ErrorMsg = "返回内容不能为空。";
return null!; // 基类约定:null 表示保持弹窗打开。
}
return new DialogResult(ButtonResult.OK, new DialogParameters { { "message", Message.Trim() } });
}
}
@@ -0,0 +1,97 @@
using Prism.Commands;
using MAX.Notification;
using Store.Management.Notifications;
using Prism.Ioc;
using Prism.Mvvm;
using Prism.Services.Dialogs;
using Store.Management.Services;
using Store.Management.Views;
namespace Store.Management.ViewModels;
public sealed class FrameworkTestPageViewModel : BindableBase
{
private readonly IContainerProvider container;
private readonly SingletonProbe singleton;
private readonly TransientProbe transient;
private readonly IDialogService dialogs;
private readonly IPublisher publisher;
private readonly NotificationExampleState notificationState;
private string productName = "示例商品";
private string notificationResult = "输入商品名称,发布后查看两个模块的处理结果。";
private string iocResult = "点击按钮查看实例 ID 与测试结果。";
private string dialogInput = "来自主页面的测试参数";
private string dialogResult = "等待弹窗返回。";
public FrameworkTestPageViewModel(IContainerProvider container, SingletonProbe singleton, TransientProbe transient, IDialogService dialogs,
IPublisher publisher, NotificationExampleState notificationState)
{
this.container = container;
this.singleton = singleton;
this.transient = transient;
this.dialogs = dialogs;
this.publisher = publisher;
this.notificationState = notificationState;
PublishNotificationCommand = new AsyncDelegateCommand(PublishNotificationAsync);
TestIocCommand = new DelegateCommand(TestIoc);
ShowModalCommand = new DelegateCommand(() => OpenDialog(true));
ShowModelessCommand = new DelegateCommand(() => OpenDialog(false));
}
public DelegateCommand TestIocCommand { get; }
public AsyncDelegateCommand PublishNotificationCommand { get; }
public string ProductName { get => productName; set => SetProperty(ref productName, value); }
public string NotificationResult { get => notificationResult; private set => SetProperty(ref notificationResult, value); }
private async Task PublishNotificationAsync()
{
if (string.IsNullOrWhiteSpace(ProductName))
{
NotificationResult = "请输入商品名称。";
return;
}
try
{
NotificationResult = "正在等待所有通知处理器完成……";
await publisher.Publish(new ProductSavedNotification(ProductName.Trim()));
// 回到页面的 UI 上下文后更新绑定;业务处理器不访问 ViewModel。
NotificationResult = $"通知处理完成(显示最近 10 条记录):\n{notificationState.GetHistory()}";
}
catch (Exception ex)
{
NotificationResult = $"通知处理失败:{ex.GetBaseException().Message}";
}
}
public DelegateCommand ShowModalCommand { get; }
public DelegateCommand ShowModelessCommand { get; }
public string IocResult { get => iocResult; private set => SetProperty(ref iocResult, value); }
public string DialogInput { get => dialogInput; set => SetProperty(ref dialogInput, value); }
public string DialogResult { get => dialogResult; private set => SetProperty(ref dialogResult, value); }
private void TestIoc()
{
try
{
var resolved = container.Resolve<SingletonProbe>();
var first = container.Resolve<TransientProbe>();
var second = container.Resolve<TransientProbe>();
bool passed = ReferenceEquals(singleton, resolved) && !ReferenceEquals(transient, first) && !ReferenceEquals(first, second);
IocResult = $"{(passed ? "通过" : "失败")}\n构造注入单例:{singleton.InstanceId}\n再次解析单例:{resolved.InstanceId}\n构造注入瞬态:{transient.InstanceId}\n解析瞬态 A:{first.InstanceId}\n解析瞬态 B:{second.InstanceId}";
}
catch (Exception ex) { IocResult = $"IoC 测试失败:{ex.Message}"; }
}
private void OpenDialog(bool modal)
{
if (string.IsNullOrWhiteSpace(DialogInput))
{
DialogResult = "请输入测试参数后再打开弹窗。";
return;
}
try
{
var parameters = new DialogParameters { { "message", DialogInput.Trim() } };
DialogResult = "弹窗已打开,等待操作。";
void OnClosed(IDialogResult result) => DialogResult = result.Result == ButtonResult.OK
? $"确认:{result.Parameters.GetValue<string>("message")}" : $"弹窗已关闭,结果:{result.Result}";
if (modal) dialogs.ShowDialog(nameof(FrameworkTestDialog), parameters, OnClosed);
else dialogs.Show(nameof(FrameworkTestDialog), parameters, OnClosed);
}
catch (Exception ex) { DialogResult = $"打开弹窗失败:{ex.GetBaseException().Message}"; }
}
}
@@ -0,0 +1,17 @@
<f:UserControlBase x:Class="Store.Management.Views.FrameworkTestDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:f="clr-namespace:Store.Management.Framework.Controls;assembly=Store.Management.Framework" Width="520" Height="300">
<Grid Margin="24">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="编辑返回内容" FontSize="20" FontWeight="SemiBold" />
<TextBox Height="36" Grid.Row="1" Text="{Binding Message, UpdateSourceTrigger=PropertyChanged}" MaxLength="200" Margin="0,16,0,12" />
<TextBlock Grid.Row="2" Text="{Binding ErrorMsg}" Foreground="Firebrick" TextWrapping="Wrap" />
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Height="36" Content="取消(Esc)" Command="{Binding CancelCommand}" Padding="12,6" Margin="0,0,12,0" />
<Button Height="36" Content="确认(Enter)" Command="{Binding ConfirmCommand}" Padding="12,6" />
</StackPanel>
</Grid>
</f:UserControlBase>
@@ -0,0 +1,6 @@
using Store.Management.Framework.Controls;
namespace Store.Management.Views;
public partial class FrameworkTestDialog : UserControlBase
{
public FrameworkTestDialog() => InitializeComponent();
}
@@ -0,0 +1,61 @@
<f:UserControlBase
x:Class="Store.Management.Views.FrameworkTestPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:di="clr-namespace:Store.Management.Framework.Extensions;assembly=Store.Management.Framework"
xmlns:f="clr-namespace:Store.Management.Framework.Controls;assembly=Store.Management.Framework"
xmlns:s="clr-namespace:Store.Management.Services">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="28">
<TextBlock
FontSize="26"
FontWeight="SemiBold"
Text="框架能力测试" />
<TextBlock Margin="0,8,0,20" Text="验证 Prism / Autofac 注入、DISource 扩展及框架弹窗。" />
<GroupBox Padding="16" Header="IoC · 生命周期与注入">
<StackPanel>
<TextBlock Text="构造函数注入与重复解析:单例应一致,瞬态应不同。" TextWrapping="Wrap" />
<Button
HorizontalAlignment="Left"
Command="{Binding TestIocCommand}"
Content="运行 IoC 测试" />
<TextBlock Text="{Binding IocResult}" TextWrapping="Wrap" />
<TextBlock Margin="0,16,0,4" Text="XAML DISource 注入单例 ID(应与上方一致):" />
<TextBlock DataContext="{di:DISource Type={x:Type s:SingletonProbe}}" Text="{Binding InstanceId}" />
</StackPanel>
</GroupBox>
<GroupBox
Margin="0,20,0,0"
Padding="16"
Header="弹出框 · 参数与返回结果">
<StackPanel>
<TextBlock Text="输入传入弹窗的内容,编辑后确认并观察返回结果。Enter 确认,Esc 取消。" TextWrapping="Wrap" />
<TextBox
Height="36"
Margin="0,12"
MaxLength="200"
Text="{Binding DialogInput, UpdateSourceTrigger=PropertyChanged}" />
<WrapPanel>
<Button
Command="{Binding ShowModalCommand}"
Content="打开模态弹窗"
Style="{StaticResource ButtonDefault}" />
<Button Command="{Binding ShowModelessCommand}" Content="打开非模态弹窗" />
</WrapPanel>
<TextBlock
Margin="0,8,0,0"
Text="{Binding DialogResult}"
TextWrapping="Wrap" />
</StackPanel>
</GroupBox>
<GroupBox Margin="0,20,0,0" Padding="16" Header="通知 · 一个发布方与两个处理器">
<StackPanel>
<TextBlock Text="模拟保存商品,依次通知缓存模块和审计模块;等待全部完成后显示结果,无须页面订阅事件。" TextWrapping="Wrap" />
<TextBox Margin="0,12" MaxLength="100" Text="{Binding ProductName, UpdateSourceTrigger=PropertyChanged}" />
<Button HorizontalAlignment="Left" Command="{Binding PublishNotificationCommand}" Content="发布商品保存通知" />
<TextBlock Margin="0,8,0,0" Text="{Binding NotificationResult}" TextWrapping="Wrap" />
</StackPanel>
</GroupBox>
</StackPanel>
</ScrollViewer>
</f:UserControlBase>
@@ -0,0 +1,6 @@
using Store.Management.Framework.Controls;
namespace Store.Management.Views;
public partial class FrameworkTestPage : UserControlBase
{
public FrameworkTestPage() => InitializeComponent();
}