You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#(.NET 5)中使用动态绑定表达式格式化字符串?

在.NET 5中实现类似AngularJS的字符串模板绑定(支持属性与方法调用)

需求概述

需要实现类似AngularJS的字符串模板功能:定义包含占位符的格式字符串,通过访问动态模型的属性或调用其方法,生成最终的格式化字符串。

模型示例:

{
    "firstName": "John",
    "lastName": "Doe",
    "random": () => Random.Next(),
    "formatDate": (fmt, value) => new DateTime(value).ToString(fmt)
}

目标格式字符串示例(AngularJS风格):

Hello {firstName} {lastName}. Today is {formatDate('d', new Date())}.

已尝试方案及问题

  • WPF/XAML:引入大量不必要的UI相关开销,仅需纯字符串格式化功能,不符合需求
  • Roslyn:要求格式表达式必须是合法C#代码,实现复杂度高、工作量大
  • Formattable字符串/ICustomFormatter:不支持动态模型的属性访问与方法调用

调用代码示例

var formatString = person.Tenant.WelcomeMessageFormat;
var model = new
{
    FirstName = person.FirstName,
    LastName = person.LastName,
    Random = () => Random.Next(),
    FormatDate = (fmt, value) => new DateTime(value).ToString(fmt)
};
var message = ParseFormatString(formatString, model);
SendMessage(message);

// 需要实现的核心方法
public string ParseFormatString(string format, object model)
{
    // 待实现逻辑
}

解决方案:轻量级自定义解析器

下面是一个无额外依赖的实现,通过正则匹配占位符,结合反射解析属性和方法调用,满足基础需求:

using System;
using System.Text.RegularExpressions;
using System.Reflection;

public string ParseFormatString(string format, object model)
{
    // 匹配所有{...}格式的占位符
    var placeholderRegex = new Regex(@"\{([^{}]+)\}", RegexOptions.Compiled);
    return placeholderRegex.Replace(format, match =>
    {
        var expression = match.Groups[1].Value.Trim();
        try
        {
            return EvaluateExpression(expression, model)?.ToString() ?? string.Empty;
        }
        catch
        {
            // 解析失败时返回原占位符,避免破坏整体格式
            return match.Value;
        }
    });
}

private object EvaluateExpression(string expression, object model)
{
    // 判断是否为方法调用
    if (expression.Contains("("))
    {
        var methodName = expression.Substring(0, expression.IndexOf('(')).Trim();
        var paramSection = expression.Substring(expression.IndexOf('(') + 1, 
            expression.LastIndexOf(')') - expression.IndexOf('(') - 1).Trim();
        var parameters = ParseMethodParameters(paramSection, model);

        // 查找匹配的方法
        var method = model.GetType().GetMethod(methodName, 
            parameters.Select(p => p.GetType()).ToArray());
        if (method == null)
            throw new InvalidOperationException($"未找到方法:{methodName}");

        return method.Invoke(model, parameters);
    }
    // 属性访问
    else
    {
        var property = model.GetType().GetProperty(expression);
        if (property == null)
            throw new InvalidOperationException($"未找到属性:{expression}");

        return property.GetValue(model);
    }
}

private object[] ParseMethodParameters(string paramSection, object model)
{
    if (string.IsNullOrWhiteSpace(paramSection))
        return Array.Empty<object>();

    var paramStrings = paramSection.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
    var parameters = new object[paramStrings.Length];

    for (int i = 0; i < paramStrings.Length; i++)
    {
        var param = paramStrings[i].Trim();
        // 处理单引号包裹的字符串字面量
        if (param.StartsWith("'") && param.EndsWith("'"))
        {
            parameters[i] = param.Substring(1, param.Length - 2);
        }
        // 处理new Date()的特殊情况(替换为当前时间)
        else if (param.Equals("new Date()", StringComparison.OrdinalIgnoreCase))
        {
            parameters[i] = DateTime.Now;
        }
        // 尝试解析为模型属性
        else
        {
            var property = model.GetType().GetProperty(param);
            if (property != null)
            {
                parameters[i] = property.GetValue(model);
            }
            // 尝试转换为基础类型
            else if (int.TryParse(param, out int intVal))
            {
                parameters[i] = intVal;
            }
            else if (DateTime.TryParse(param, out DateTime dateVal))
            {
                parameters[i] = dateVal;
            }
            // 无法解析时保留原字符串
            else
            {
                parameters[i] = param;
            }
        }
    }

    return parameters;
}

说明

这个实现是轻量级的,仅依赖.NET基础库,支持:

  • 访问模型的公共属性
  • 调用模型的公共方法(支持字面量参数、模型属性参数、基础类型参数)
  • 处理new Date()这类特殊表达式(可根据需求扩展)

如果需要支持更复杂的表达式(比如嵌套属性、运算符、Lambda等),可以在此基础上扩展解析逻辑。

内容的提问来源于stack exchange,提问作者just.another.programmer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 23:20:27