当前位置: 首页 > news >正文

C# 基于DI和JSON序列化的界面配置参数方案

基于DI和JSON序列化的界面配置参数方案

如题,该功能的实现是基于Microsoft.Extensions.DependencyInjectionNewtonsoft.Json实现WPF界面参数的保存和加载。具体的DI依赖注入)工具可以有很多选择,同样数据序列化和反序列化工具也有很多。

  • DI
    • Ninject
    • Unity
    • Autofac
  • 对象序列化和反序列化
    • System.Text.Json Json文件
    • System.Xml.Serialization Xml文件

本文基于Microsoft.Extensions.DependencyInjectionNewtonsoft.Json进行界面参数(ViewModel)的保存和加载。

MVVM中的DI说明

详情参考mvvm.toolkit依赖注入说明:
learn.microsoft.com/zh-cn/dotnet/communitytoolkit/mvvm/ioc
ViewModel注册

public sealed partial class App : Application
{public App(){Services = ConfigureServices();this.InitializeComponent();}/// <summary>/// Gets the current <see cref="App"/> instance in use/// </summary>public new static App Current => (App)Application.Current;/// <summary>/// Gets the <see cref="IServiceProvider"/> instance to resolve application services./// </summary>public IServiceProvider Services { get; }/// <summary>/// Configures the services for the application./// </summary>private static IServiceProvider ConfigureServices(){var services = new ServiceCollection();services.AddSingleton<IFilesService, FilesService>();services.AddSingleton<MainViewModel>();return services.BuildServiceProvider();}
}

ViewModel获取

MainViewModel mainVm = App.Current.Services.GetService<MainViewModel>();

参数保存

  /// <summary>/// 保存配置文件/// 将当前DI容器中的实例序列化保存到配置文件中  /// 注意:不需要保存的属性请添加<property: JsonIgnore>特性/// </summary>/// <typeparam name="T">需要保存的ViewModel类型,例如<see cref="ViewModels.Main.BenchParamViewModel"/></typeparam>public static void Save<T>() where T : class{if (!Directory.Exists(CONFIG_UI)){Directory.CreateDirectory(CONFIG_UI);}Type type = typeof(T);var appRunModel = App.Current.Services.GetService<T>();var contect = JsonConvert.SerializeObject(appRunModel);var fileNameFull = System.IO.Path.Combine(CONFIG_UI, type.Name + ".json");File.WriteAllText(fileNameFull, contect);}

保存原理:

  • App.Current.Services.GetService<T>()获取到当前的实例对象
  • 然后将对象通过JsonConvert.SerializeObject(appRunModel)序列化为Json文本。

为了简化保存的信息内容,在ViewModel中对部分属性进行忽略操作。主要的方式是在属性上添加[JsonIgnore]特性说明。值得注意的是,如果ViewModel中采用[ObservableProperty]定义通知属性时,为了将[JsonIgnore]特性传递到公共的属性上,需要添加property字段属性传递说明,如下所示:

        [ObservableProperty][property: JsonIgnore]private int blowingTime = 55;

参数加载

        /// <summary>/// 根据类型加载配置文件/// 自动赋值修改DI容器中的实例/// </summary>/// <typeparam name="T">需要加载ViewModel类型,例如<see cref="ViewModels.Main.BenchParamViewModel"/></typeparam>/// <returns></returns>public static T? Load<T>() where T : class{Type type = typeof(T);var fileNameFull = System.IO.Path.Combine(CONFIG_UI, type.Name + ".json");if (!File.Exists(fileNameFull)) // 当前配置不存在 {// 加载默认配置文件fileNameFull = System.IO.Path.Combine(CONFIG_UI_DEFAULT, type.Name + ".json");if (!File.Exists(fileNameFull)) // 默认配置也不存在return null;}var content = File.ReadAllText(fileNameFull);var jsonFileModel = JsonConvert.DeserializeObject<T>(content);if (jsonFileModel == null) return null;var appRunModel = App.Current.Services.GetService<T>();SetProperties(appRunModel, jsonFileModel);return appRunModel;}

参数加载的原理:

  • 读取Json文件内容,直接反序列化为对象JsonConvert.DeserializeObject<T>(content)
  • 然后从DI App.Current.Services.GetService<T>()获取当前软件正在运行对象。最后把Json反序列化对象赋值给正在运行的对象

对ViewModel进行Json序列化时,需要注意:ViewModel中大部分情况下,默认构造函数会存在很多对信息的初始化的代码,因此如果序列化时,采用冲突的默认构造函数,会导致很多参数信息的多次构造,导致与加载的Json文件的内容不一致。
解决办法需要在ViewModel中指明Json序列化时的使用的构造函数,如下所示:

    partial class InformationInputViewModel : ObservableObject{public InformationInputViewModel(){WeakReferenceMessenger.Default.Register<string>(this, (r, m) =>{if (m == "DbUpdate"){Gcus = PetSetDbBll.SqliteDb.Queryable<Can>().Where(o => o.Temp == "GCU").ToList();Ecus = PetSetDbBll.SqliteDb.Queryable<Can>().Where(o => o.Temp == "ECU").ToList();Rules = PetSetDbBll.SqliteDb.Queryable<RunRule>().ToList();if (gcu != null)Gcu = gcus.Where(t => t.Name == gcu.Name).FirstOrDefault();if (ecu != null)Ecu = ecus.Where(t => t.Name == ecu.Name).FirstOrDefault();if (Rule != null)Rule = rules.Where(t => t.NameVer == rule.NameVer).FirstOrDefault();}});}[JsonConstructor]public InformationInputViewModel(string json) { }}

由于正在运行的对象是由DI进行管理的,所以赋值只能修改值。为了赋值的通用性,采用反射进行属性的赋值。如果有一些赋值的特殊处理,都可以在赋值进行筛选。

/// <summary>/// 设置对象属性值/// 让target = source /// </summary>/// <param name="target">需要设置的目标,被修改的对象</param>/// <param name="source">数据源,被修改对象的修改信息获取的数据源</param>private static void SetProperties(object target, object source){var targetType = target.GetType();var sourceType = source.GetType();var properties = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty);foreach (var prop in properties){var jsonIgnoreAttribute = prop.GetCustomAttribute<JsonIgnoreAttribute>();if (jsonIgnoreAttribute != null){continue; // 跳过带有JsonIgnore特性的属性}if (!prop.CanWrite) continue;var sourceProp = sourceType.GetProperty(prop.Name);if (sourceProp == null) continue;var sourceValue = sourceProp.GetValue(source);// 如果属性值不为 null,赋值给目标对象if (sourceValue != null){// 特殊处理 ObservableCollection 类型的属性if (prop.PropertyType.IsGenericType && (prop.PropertyType.GetGenericTypeDefinition() == typeof(ObservableCollection<>) || prop.PropertyType.GetGenericTypeDefinition() == typeof(List<>))){var collection = prop.GetValue(target) as System.Collections.IList;if (collection != null){collection.Clear(); // 清空原有集合foreach (var item in (System.Collections.IEnumerable)sourceValue){collection.Add(item); // 添加新元素}}}else if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string) && prop.PropertyType != typeof(Brush)){// 如果属性是类类型(非字符串),递归处理var targetValue = prop.GetValue(target);if (targetValue != null){SetProperties(targetValue, sourceValue);}}else{if (prop.CanWrite)prop.SetValue(target, sourceValue);}}}}

总结

以上就是基于DIJSON序列化反射实现的ViewModel参数配置参数保存和加载功能。可以普遍的应用到各个项目中,需要轻微改造:

  • ViewModel中明确哪些不需要保存,添加[JsonIgnore]特性
  • ViewModel中明确序列化和反序列时的构造函数[JsonConstructor]
  • SetProperties反射赋值函数可能需要面相项目进行稍微修改

其他未采用方案

方案1(使用IMemeryParam接口)

配置定义

定义IMemeryParam接口,封装保存、加载和配置文件名接口内容,如果需要进行参数保存的ViewModel继承这个接口,并实现对应的函数接口和配置文件名。
每个ViewModel的配置参数独立的存储到一个文件中。
本地config_ui文件夹中存储每一个需要保存的参数,一类参数存储到一个独立的文件中,以ViewModel进行分类。
本地config_ui\default文件夹中备份了默认参数,系统发布的版本中,默认情况下会有当前版本的最新默认配置,该配置用于恢复配置时使用。

配置应用

外部定义一个静态类,用于管理参数保存接口。
配置记忆的加载:在ViewModel的构造函数中,自动调用Load函数,实现配置记忆获取。

默认配置说明

如果Load时,配置记忆信息为空,首先从default中查找默认配置,并加载。
如果default中不存在默认配置,则使用程序预定义参数信息作为默认配置。

配置文件格式说明

文件的格式采用json,使用Newtonsoft.Json的序列化和反序列化,可以很方便的实现文本内容与实例对象的转换。

示例

PETDomain

    public static class PETDomain{public static string BaseDirectory => AppDomain.CurrentDomain.BaseDirectory;public static string CONFIG_UI => System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config_ui");public static string CONFIG_UI_DEFAULT => System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config_ui", "default");public static void Save(){App.Current.Services.GetService<BenchParamViewModel>().Save();}}

BenchParamViewModel 示例

    partial class BenchParamViewModel : ObservableObject, IMemeryParam{[ObservableProperty]string header = string.Empty;[ObservableProperty]private ParamDisplayGridModel paramDisplayGridModel = new ParamDisplayGridModel(){Title = "台架参数",GridModelColl = new ObservableCollection<ParamDisplayModel>{new ParamDisplayModel{Title = "进水水压Kpa",Value =null,ValueSource="进水水压"},new ParamDisplayModel{Title ="汽油压力kPa",Value =null,ValueSource= "汽油压力"},new ParamDisplayModel{Title ="电瓶电压V", Value =null,ValueSource="电瓶电压"},new ParamDisplayModel{Title = "水箱温度℃",Value =null,ValueSource="水箱温度"},new ParamDisplayModel{Title ="进水温度℃",Value =null,ValueSource="进水温度"},new ParamDisplayModel{Title ="出水温度℃", Value =null,ValueSource="出水温度"}}};//[ObservableProperty]//private ParamDisplayGridModel paramDisplayGridModel = ParamSaving.Load(ParamType.台架参数);#region IMemeryParampublic void Save(string path = null){if (path == null) path = System.IO.Path.Combine(PETDomain.CONFIG_UI, ParamName);FileInfo file = new FileInfo(path);if (!Directory.Exists(file.Directory.FullName)){Directory.CreateDirectory(file.Directory.FullName);}var contect = JsonConvert.SerializeObject(paramDisplayGridModel);var fileNameFull = System.IO.Path.Combine(path);File.WriteAllText(fileNameFull, contect);}public bool Load(string path = null){if (path == null) path = System.IO.Path.Combine(PETDomain.CONFIG_UI, ParamName);// 当前配置if (!File.Exists(path)) // 当前配置不存在 {path = System.IO.Path.Combine(PETDomain.CONFIG_UI_DEFAULT, ParamName); // 默认配置if (!File.Exists(path)) // 默认配置也不存在{return false;}}var content = File.ReadAllText(path);ParamDisplayGridModel = JsonConvert.DeserializeObject<ParamDisplayGridModel>(content);return true;}public string ParamName => "台架参数.json";#endregionpublic BenchParamViewModel(){// 注册一个消息处理程序,监听 DataSourceModel<double?> 类型的消息WeakReferenceMessenger.Default.Register<DataSourceModel<double?>>(this, (r, m) =>{// 当接收到消息时,调用 UpdataValue 方法更新参数值UpdataValue(m);});// 加载保存参数Load();}// UpdataValue 方法用于更新参数值private void UpdataValue(DataSourceModel<double?> dataSource){// 从 ParamDisplayGridModel 的 GridModelColl 中筛选出与 dataSource.paraname 匹配的 ParamDisplayModel 对象var models = ParamDisplayGridModel.GridModelColl.Where(o => o.ValueSource == dataSource.paraname).ToList();// 遍历筛选出的 ParamDisplayModel 对象foreach (var displayModel in models){// 如果当前模型的值与数据源的值不同,则更新当前模型的值if (displayModel.Value != dataSource.value){displayModel.Value = dataSource.value;}}}}
}

方案2(使用ParamSaving公共管理配置)

配置定义

ParamSaving提供Load()Save()函数,这两个函数可以集中的管理整个项目所有的需要保存的参数信息。
Load()
Load()函数需要传递一个
ParamType
的参数,用于标识加载的参数类型,该参数类型唯一对应一个配置文件,获取后的内容转换成对应的参数内容返回对象。
在ViewModel中需要记忆功能的参数需要调用Load函数获取配置记忆的对象作为当前程序启动的配置信息。

Save()
Save()函数需要传入需要保存的参数对象和参数类型ParamType,直接将参数对象转换成Json文本直接存储到ParamType对应的配置文件即可。

同时为了对外方便,提供一个无参的*Save()*函数,用于集中存储所有参数内容。

配置文件格式说明

文件的格式采用json,使用Newtonsoft.Json的序列化和反序列化,可以很方便的实现文本内容与实例对象的转换。

示例

public static class ParamSaving{static string basePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config_ui");/// <summary>/// 根据文件名加载参数信息/// </summary>/// <param name="fileName"></param>/// <returns></returns>public static T? Load<T>(ParamType fileName) where T : class{var fileNameFull = System.IO.Path.Combine(basePath, fileName + ".json");var content = File.ReadAllText(fileNameFull);return JsonConvert.DeserializeObject<T>(content);}public static void ClearDirectory(string directoryPath){// 检查目录是否存在if (Directory.Exists(directoryPath)){// 获取目录中的所有文件并删除string[] files = Directory.GetFiles(directoryPath);foreach (string file in files){File.Delete(file);}// 如果需要删除子目录中的文件,可以递归调用string[] subDirectories = Directory.GetDirectories(directoryPath);foreach (string subDirectory in subDirectories){ClearDirectory(subDirectory);}}else{Console.WriteLine("指定的目录不存在!");}}/// <summary>/// 对外统一管理所有参数存储接口/// </summary>public static void Save(){Save(App.Current.Services.GetService<BenchParamViewModel>().ParamDisplayGridModel, ParamType.台架参数);Save(App.Current.Services.GetService<BenchParamViewModel>().ParamDisplayGridModel, ParamType.其他啊啊参数);}/// <summary>/// 将参数信息保存到指定文件/// </summary>/// <param name="model"></param>/// <param name="fileName"></param>public static void Save(ParamDisplayGridModel model, ParamType fileName){if (!Directory.Exists(basePath)){Directory.CreateDirectory(basePath);}ClearDirectory(basePath);var contect = JsonConvert.SerializeObject(model);var fileNameFull = System.IO.Path.Combine(basePath, fileName + ".json");File.WriteAllText(fileNameFull, contect);}}

ViewModel中加载参数

        [ObservableProperty]private ParamDisplayGridModel paramDisplayGridModel = ParamSaving.Load<ParamDisplayGridModel>(ParamType.台架参数);
http://www.dtcms.com/a/453581.html

相关文章:

  • 零遁nas做网站微信小程序怎么做店铺免费
  • 2025 AI 伦理治理破局:从制度设计到实践落地的中国探索
  • 堆排序原理与实现详解
  • 网页设计与网站建设过程wordpress淘宝客主题破解版
  • 不关网站备案wordpress安装完成后
  • 分割回文串(dfs)
  • 第二十二章:记忆封存,时光回溯——Memento的备忘录艺术
  • Spring Framework源码解析——ApplicationContextAware
  • 30个做设计的网站wordpress远程图片下载
  • 建网站权威机构西安专业网站建设服务
  • Express+Vue表格数据分页联调:模拟数据与真实接口的无缝切换
  • Qt 多线程与并发编程详解
  • 第五个实验——动态nat地址转换操作
  • 排查 TCP 连接中 TIME_WAIT 状态异常
  • 《C++ 实际应用系列》第二部分:内存管理与性能优化实战
  • 登建设厅锁子的是哪个网站祥云平台网站管理系统
  • 浙江省建设厅网站在哪里手机网站制作招聘
  • nat server 概念及题目
  • 试看30秒做受小视频网站深圳外贸网站制作
  • 网站营销推广怎么做网络营销推广网站建设关于公司怎么写
  • 【AI】专访 Braintrust CEO Ankur Goyal:为什么 AI 评测是产品 prototype 走向生产的唯一桥梁?
  • 大模型文生图和语音转换的调用以及向量和向量数据库RedisStack.
  • 做代练去什么网站安全合肥网站seo整站优化
  • 网站案例展示怎么做桂电做网站的毕设容易过嘛
  • QT-常用控件(一)
  • 网站开发选asp还是hph网站域名解析步骤
  • AI行业应用深度解析:从理论到实践的跨越
  • DeepMind 和罗光记团队 推出“帧链”概念:视频模型或将实现全面视觉理解
  • 外贸圈阿里巴巴微信seo什么意思
  • 【专业词汇】元认知