在 C# 中实现 DSL 以生成特定于域的 XML

发布于 2024-12-02 16:55:21 字数 1131 浏览 2 评论 0原文

我有一个旧版 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 等。

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

在极少数情况下,您确实必须处理类似此处所讨论的服务,并且无论出于何种原因都无法对其进行重新设计,下面的答案提供了一些处理这种情况的可能方法。

I have a legacy HTTP/XML service that I need to interact with for various features in my application.

I have to create a wide range of request messages for the service, so to avoid a lot of magic strings littered around the code, I've decided to create xml XElement fragments to create a rudimentary DSL.

For example.

Instead of...

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

I'm intended to use:

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

With Root, Request and MessageData (of course, these are for illustrative purposes) defined as static methods which all do something similar to:

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

This gives me a pseudo functional composition style, which I like for this sort of task.

My ultimate question is really one of sanity / best practices, so it's probably too subjective, however I'd appreciate the opportunity to get some feedback regardless.

  1. I'm intending to move these private methods over to public static class, so that they are easily accessible for any class that wants to compose a message for the service.

  2. I'm also intending to have different features of the service have their messages created by specific message building classes, for improved maintainability.

Is this a good way to implement this simple DSL, or am I missing some special sauce that will let me do this better?

The thing that leads me to doubt, is the fact that as soon as I move these methods to another class I increase the length of these method calls (of course I do still retain the initial goal of removing the large volume magic strings.) Should I be more concerned about the size (loc) of the DSL language class, than I am about syntax brevity?

Caveats

Note that in this instance the remote service poorly implemented, and doesn't conform to any general messaging standards, e.g. WSDL, SOAP, XML/RPC, WCF etc.

In those cases, it would obviously not be wise to create hand built messages.

In the rare cases where you do have to deal with a service like the one in question here, and it cannot be re-engineered for whatever reason, the answers below provide some possible ways of dealing with the situation.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

浪菊怪哟 2024-12-09 16:55:22

我注意到这篇文章用于使用 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("[email protected]");
    u.phone(new { type="cell" }, "(985) 555-1234");
}));

结果是:

<user>
    <firstname>John</firstname>
    <lastname>Doe</lastname>
    <email>[email protected]</email>
    <phone type="cell">(985) 555-1234</phone>
</user>

使用 Ruby 库 Builder 后,这种创建任意 XML 的方法同样简洁,甚至接近“有趣”!

我将其标记为答案,因为尽管它没有直接说明“使用 DSL 创建任意 XML”,但由于语法的极其简洁和动态特性,它往往会消除这种需求。

我个人认为,如果您有 v4.0 编译器并且必须手动启动它,那么这是在 C# 中创建任意 XML 的最佳方法,当然还有更好的方法通过序列化自动生成 XML。将此保留给 XML,它必须采用仅适用于遗留系统的特定形式。

I noticed this article for constructing arbitrary XML with C#4.0 which is great.

The source for the library is here - https://github.com/mmonteleone/DynamicBuilder/tree/master/src/DynamicBuilder

At this time, there is a notable deficiency, no xml namespace support. Hopefully that will get fixed though.

As a quick example, here's how it's done.

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

Which yields:

<hello>world</hello>

Here's another quick example yanked from the article.

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("[email protected]");
    u.phone(new { type="cell" }, "(985) 555-1234");
}));

Which yields:

<user>
    <firstname>John</firstname>
    <lastname>Doe</lastname>
    <email>[email protected]</email>
    <phone type="cell">(985) 555-1234</phone>
</user>

Having used the Ruby library Builder this method of creating arbitrary XML is similarly terse, to the point that it verges on "fun"!

I've marked this as the answer, because, even though it doesn't directly speak to "using a DSL to create arbitrary XML" it tends to remove the need due to the extremely terse and dynamic nature of the syntax.

Personally I think this is the best way to create arbitrary XML in C# if you have the v4.0 compiler and have to crank it by hand, there are of course much better ways to generate XML automatically with serialization. Reserve this for XML which must be in a specific form for legacy systems only.

地狱即天堂 2024-12-09 16:55:22

用 C# 编写此代码似乎需要大量工作。将您的 DSL 设计为 XML 词汇表,然后将其编译为 XSLT,用 XSLT 编写编译器(翻译器)。我已经做过很多次了。

Writing this in C# seems an awful lot of work. Design your DSL as an XML vocabulary, and then compile it into XSLT, writing the compiler (translator) in XSLT. I've done this many times.

同展鸳鸯锦 2024-12-09 16:55:21

您是否注意到所有 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 中的强类型图。

Have you noticed that all the System.Linq.Xml classes are not sealed?

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());

Remember this won't work for 'reading' scenarios, you will only have the strong-typed graph from the XML that your application creates via code.

睡美人的小仙女 2024-12-09 16:55:21

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

执行此操作的方法之一是从端点获取消息传递 XSD 定义,并使用 xsd.exe 工具使用它们生成 C# 类型。

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

Hand-cranking xml is one of the things which should be automated if possible.

One of the ways of doing this is to grab the messaging XSD definitions off your endpoint and use them to generate C# types using the xsd.exe tool.

Then you can create a type and serialize it using the XmlSerializer, which will pump out your xml message for you.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文