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

csharp通过对象和模板字符串解析模板

通过模板字符串和一个匿名对象来解析字符串,这种需求比较常见,对于比较简单的模板字符串,我们可以直接替换,但对于比较复杂的模板,我们可以通过模板工具来实现。

Scriban简介

Scriban是一个适用于.Net的一款轻量级的脚本引擎。

通过包管理进行安装:
在这里插入图片描述
官方案例:

var template = Template.Parse("Hello {{name}}!");
var result = template.Render(new { Name = "World" }); // => "Hello World!" 

对于这种简单的模板可以直接使用,但针对嵌套的会出现问题,如:

var obj = new
{Today = new { time = DateTime.Now, luckyNumber = 3.1415926525 },
};var temp = Template.Parse("当前时间:{{Today.time}}");var test = temp.Render(obj); // Cannot get the member Today.time for a null object

针对有嵌套的对象,需要单独对传入的对象进行处理,以下是对该功能的封装

TemplateUtil模板渲染工具

public class TemplateUtil
{/// <summary>/// 渲染模板/// </summary>/// <param name="template">模板内容</param>/// <param name="anonymousObject">匿名对象</param>public static string RenderTemplate(string template, object anonymousObject){if (string.IsNullOrEmpty(template) || anonymousObject == null)return template;try{// 通过对象构建脚本对象示例var scriptObject = (ScriptObject)ToScriptObject(anonymousObject);// 解析模板var parsedTemplate = Template.Parse(template);// 创建模板引擎的执行上下文var context = new TemplateContext();// 将ScriptObject添加到模板的全局作用域中context.PushGlobal(scriptObject);// 解析错误就直接返回templateif (parsedTemplate.HasErrors){// 错误信息:parsedTemplate.Messagesreturn template;}// 获取解析后的字符串var result = parsedTemplate.Render(context);return result;}catch{return template;}}/// <summary>/// 将匿名对象转换为Scriban可识别的ScriptObject【解决嵌套对象中非字符字段解析问题】/// </summary>/// <param name="anonymousObject">要转换的匿名对象</param>/// <returns>转换后的ScriptObject</returns>public static object ToScriptObject(object anonymousObject){// 空值检查if (anonymousObject == null) return null;// 检查是否为JObjectif (anonymousObject is JObject jObject){return ConvertJObjectToScriptObject(jObject);}// 检查是否为JToken(处理JValue等)if (anonymousObject is JToken jToken){return ConvertJTokenToObject(jToken);}// 创建新的 ScriptObject 来存储转换后的数据var scriptObject = new ScriptObject();// 获取匿名对象的实际类型var type = anonymousObject.GetType();// 通过反射获取对象的所有属性foreach (var property in type.GetProperties()){   // 获取属性值var value = property.GetValue(anonymousObject);// 保留模板中的原始属性,并将转换后的属性值添加到ScriptObjectscriptObject[property.Name] = ConvertValue(value);}return scriptObject;}/// <summary>/// 将JObject转换为ScriptObject/// </summary>/// <param name="jObject">要转换的JObject</param>/// <returns>转换后的ScriptObject</returns>private static ScriptObject ConvertJObjectToScriptObject(JObject jObject){var scriptObject = new ScriptObject();foreach (var property in jObject.Properties()){scriptObject[property.Name] = ConvertJTokenToObject(property.Value);}return scriptObject;}/// <summary>/// 将JToken转换object/// </summary>/// <param name="token">要转换的JToken</param>/// <returns>转换后的对象</returns>private static object ConvertJTokenToObject(JToken token){if (token == null) return null;return token.Type switch{JTokenType.Object => ConvertJObjectToScriptObject((JObject)token),JTokenType.Array => ConvertJArrayToList((JArray)token),JTokenType.Integer => token.Value<long>(),JTokenType.Float => token.Value<double>(),JTokenType.String => token.Value<string>(),JTokenType.Boolean => token.Value<bool>(),JTokenType.Null => null,JTokenType.Date => token.Value<DateTime>(),JTokenType.Guid => token.Value<Guid>(),_ => token.ToString(),};}/// <summary>/// 将JArray转换为List/// </summary>/// <param name="jArray">要转换的JArray</param>/// <returns>转换后的列表</returns>private static List<object> ConvertJArrayToList(JArray jArray){var list = new List<object>();foreach (var item in jArray){list.Add(ConvertJTokenToObject(item));}return list;}/// <summary>/// 递归转换属性值/// 处理各种类型的值:基本类型、匿名对象、集合等/// </summary>/// <param name="value">要转换的值</param>/// <returns>转换后的值</returns>private static object ConvertValue(object value){// 空值检查if (value == null) return null;// 获取值类型var valueType = value.GetType();// 检查是否为JObjectif (value is JObject jObject){return ConvertJObjectToScriptObject(jObject);}// 检查是否为JTokenif (value is JToken jToken){return ConvertJTokenToObject(jToken);}// 如果是匿名类型,递归转换if (valueType.IsClass && valueType.Name.Contains("AnonymousType")){return ToScriptObject(value);}// 如果是数组或集合,就单独处理if (IsCollection(valueType)){return ConvertEnumerable(value);}// 直接返回值return value;}/// <summary>/// 判断是否为集合或数组/// </summary>private static bool IsCollection(Type type){// 排除字符串(虽然字符串实现了 IEnumerable<char>,但通常不作为集合处理)if (type == typeof(string)) return false;// 1. 检查是否为数组if (type.IsArray){return true;}// 2. 检查是否实现了 IEnumerable<> 泛型接口if (type.IsGenericType){var interfaces = type.GetInterfaces();if (interfaces.Any(i =>i.IsGenericType &&i.GetGenericTypeDefinition() == typeof(IEnumerable<>))){return true;}}// 3. 检查是否实现了非泛型 IEnumerable 接口if (typeof(IEnumerable).IsAssignableFrom(type)){return true;}return false;}/// <summary>/// 转换可枚举对象/// </summary>/// <param name="enumerable">要转换的可枚举对象</param>/// <returns>转换后的列表</returns>private static object ConvertEnumerable(object enumerable){// 创建列表来存储转换后的元素var list = new List<object>();// 遍历原始集合中的每个元素foreach (var item in (IEnumerable)enumerable){// 递归转换每个元素并添加到列表list.Add(ConvertValue(item));}return list;}
}

使用示例:

/// <summary>
/// 使用样例(仅支持匿名对象和JObject的渲染)
/// </summary>
public static void Test()
{// 匿名对象var obj = new{Name = "时分",Address = new { Province = "四川", City = "成都" },Today = new { time = DateTime.Now, luckyNumber = 3.1415926525 },Scores = new[] { 80, 85, 90 },爱好 = new{音乐 = "燕归巢",游戏 = "原神"}};// 文本处理var textTemplate = "你好,我是{{ Name }}。我目前居住在{{ Address['Province'] }}{{ Address.City }},我喜欢听{{ 爱好.音乐 }}这首歌,偶尔玩玩{{ 爱好.游戏 }}";var text = TemplateUtil.RenderTemplate(textTemplate, obj);Console.WriteLine(text); // 你好,我是时分。我目前居住在四川成都,我喜欢听燕归巢这首歌,偶尔玩玩原神// 日期处理 时间参数不能是date,方法date.to_string冲突var dateTemplate = "当前时间: {{ Today.time | date.to_string '%Y-%m-%d' }}"; // Y(年)、m(月)、d(日)、H(时)、M(分)、S(秒)var date = TemplateUtil.RenderTemplate(dateTemplate, obj);Console.WriteLine(date); // 当前时间: 2025-11-13 17:15:06// 数字处理var number1 = TemplateUtil.RenderTemplate("{{ Today.luckyNumber | math.round 3 }}", obj); // 四舍五入并指定小数位数Console.WriteLine(number1); // 3.142var number2 = TemplateUtil.RenderTemplate("{{ Today.luckyNumber | math.format 'p2'}}", obj); // 百分数 P(n)表示保留n位小数Console.WriteLine(number2);  // 314.16 %// 循环处理var scoreTemplate = "分数列表:{{ for score in Scores }} {{ score }} 分 {{ end }}";var score = TemplateUtil.RenderTemplate(scoreTemplate, obj);Console.WriteLine(score); // 分数列表: 80 分  85 分  90 分 // JObject对象var jobj = new JObject{["Name"] = "时秒",["Address"] = new JObject{["Province"] = "四川",["City"] = "成都",},["座右铭"] = new JArray{new JObject{["Sentence"] = "长风破浪会有时,直挂云帆济沧海",["Author"] = "李白"},new JObject{["Sentence"] = "书中自有颜如玉,书中自有黄金屋",["Author"] = "赵恒"}}};var textTemplate2 = "你好,我是{{ Name }}。我目前居住在{{ Address['Province'] }}{{ Address.City }}。" +"我特别喜欢{{ for poem in 座右铭 }} {{ poem.Author }}的{{ poem.Sentence }} {{ end }}";var text2 = TemplateUtil.RenderTemplate(textTemplate2, jobj); Console.WriteLine(text2); // 你好,我是时秒。我目前居住在四川成都。我特别喜欢 李白的长风破浪会有时,直挂云帆济沧海  赵恒的书中自有颜如玉,书中自有黄金屋 
}
http://www.dtcms.com/a/609218.html

相关文章:

  • MYSQL结构操作DDL指令1.数据库操作
  • 为什么会有免费制作网站wordpress建站腾讯云
  • 仓颉迁移实战:将 Node.js 微服务移植到 Cangjie 的工程化评测
  • Redis(六)——哨兵
  • 网站错敏词整改报告,如何整改后如何定期自查自检
  • 网站验收时项目建设总结报告网站建设与维护本科教材
  • 【Java】使用国密2,3,4.仿照https 统一请求响应加解密
  • 华为对象存储:nginx代理临时访问地址后访问报错:Authentication Failed
  • 【2025-11-13】软件供应链安全日报:最新漏洞预警与投毒预警情报汇总
  • 【玩转多核异构】T153核心板RISC-V核的实时性应用解析
  • 单周期Risc-V指令拆分与datapath绘制
  • Java+EasyExcel 打造学习平台视频学习时长统计系统
  • 【PHP】使用buildsql构造子查询
  • 防火墙主要有哪些类型?如何保护网络安全?
  • 在线商城网站制作如东住房和城乡建设局网站
  • Java 与 PHP 开发核心良好习惯笔记(含通用+语言特有)
  • AI 电影制作迈入新阶段:谷歌云Veo 3.1模型发布,实现音频全覆盖与精细化创意剪辑
  • C++函数式策略模式中配置修改
  • [MCP][]快速入门MCP开发
  • 为食堂写个网站建设免费毕业设计的网站建设
  • 云原生数据平台(cloudeon)--核心服务组件扩展
  • 字典或者列表常用方法介绍
  • 计算机网络中的地址体系全解析(包含 A/B/C 类地址 + 私有地址 + CIDR)
  • SpringBoot教程(三十四)| SpringBoot集成本地缓存Caffeine
  • 专业摄影网站推荐专业做卖菜的网站
  • Hadess V1.2.5版本发布,新增推送规则、制品扫描等,有效保障制品质量与安全
  • 华清远见25072班单片机高级学习day1
  • Apache Flink运行环境搭建
  • Node.js(v16.13.2版本)安装及环境配置教程
  • Flutter 每日库: device_info_plus获取设备详细信息