在c#中实现用于生成特定领域XML的DSL

本文关键字:XML DSL 实现 用于 | 更新日期: 2023-09-27 18:07:24

我有一个遗留的HTTP/XML服务,我需要与它交互以实现应用程序中的各种功能。

我必须为服务创建广泛的请求消息,因此为了避免在代码中散布大量神奇的字符串,我决定创建xml XElement片段来创建基本的DSL。

例如

而不是……

new XElement("root", 
  new XElement("request",
    new XElement("messageData", ...)));

我打算使用:

Root( Request( MessageData(...) ) );

将Root、Request和MessageData(当然,这些只是为了便于说明)定义为静态方法,它们的作用类似于:

private static XElement Root(params object[] content) 
{
    return new XElement("root", content);
}

这给了我一个伪函数组合样式,我喜欢这种类型的任务。

我的最后一个问题是关于理智/最佳实践的,所以这可能太主观了,但是无论如何我都很感激有机会得到一些反馈。

  1. 我打算将这些私有方法转移到公共静态类中,以便任何想要为服务编写消息的类都可以轻松访问它们。

  2. 我还打算让服务的不同功能通过特定的消息构建类创建它们的消息,以提高可维护性。

这是实现这个简单DSL的好方法吗,还是我缺少了一些能让我做得更好的特殊调味品?

让我怀疑的是,一旦我将这些方法移动到另一个类,我就增加了这些方法调用的长度(当然,我仍然保留了删除大容量魔术字符串的初始目标)。我应该更关心DSL语言类的大小(loc),而不是语法简洁性吗?

警告

请注意,在这个实例中,远程服务实现得很差,并且不符合任何通用的消息传递标准,例如WSDL、SOAP、XML/RPC、WCF等。

在这些情况下,创建手工构建的消息显然是不明智的。

在极少数情况下,您必须处理像这里所讨论的那样的服务,并且由于某种原因无法重新设计它,下面的答案提供了一些处理这种情况的可能方法。

在c#中实现用于生成特定领域XML的DSL

您是否注意到所有的System.Linq.Xml类都是而不是密封的?

public class Root : XElement
{
    public Request Request { get { return this.Element("Request") as Request; } }
    public Response Response { get { return this.Element("Response") as Response; } }
    public bool IsRequest { get { return Request != null; } }
    /// <summary>
    /// Initializes a new instance of the <see cref="Root"/> class.
    /// </summary>
    public Root(RootChild child) : base("Root", child) { }
}
public abstract class RootChild : XElement { }
public class Request : RootChild { }
public class Response : RootChild { }
var doc = new Root(new Request());

请记住,这不适用于"读取"场景,您将只能从应用程序通过代码创建的XML中获得强类型图形。

手动操作xml是应该自动化的事情之一,如果可能的话。

实现这一点的方法之一是从端点抓取消息传递XSD定义,并使用XSD .exe工具使用它们生成c#类型。

然后您可以创建一个类型并使用XmlSerializer序列化它,它将为您输出xml消息。

我注意到这篇文章用c# 4.0构造任意XML,这非常棒。

库的源代码在这里- https://github.com/mmonteleone/DynamicBuilder/tree/master/src/DynamicBuilder

此时,有一个明显的缺陷,不支持xml名称空间。希望这个问题能得到解决。

作为一个简单的例子,下面是如何完成的。

dynamic x = new Xml();
x.hello("world");

产生:

<hello>world</hello>

下面是从文章中摘录的另一个快速示例。

dynamic x = new Xml();
// passing an anonymous delegate creates a nested context
x.user(Xml.Fragment(u => {
    u.firstname("John");
    u.lastname("Doe");
    u.email("jdoe@example.org");
    u.phone(new { type="cell" }, "(985) 555-1234");
}));

收益率:

<user>
    <firstname>John</firstname>
    <lastname>Doe</lastname>
    <email>jdoe@example.org</email>
    <phone type="cell">(985) 555-1234</phone>
</user>

使用Ruby库Builder后,这种创建任意XML的方法同样简洁,到了"有趣"的地步!

我把它标记为答案,因为,尽管它没有直接谈到"使用DSL创建任意XML",但由于语法的极端简洁和动态特性,它倾向于消除这种需求。

我个人认为这是在c#中创建任意XML的最好方法,如果你有v4.0编译器并且必须手工处理它,当然有更好的方法来自动生成XML序列化。

用c#写这个看起来工作量很大。将DSL设计为XML词汇表,然后将其编译到XSLT中,在XSLT中编写编译器(翻译器)。我做过很多次了