上集回顾
上集中已经实现了XCF的基础,但是不难发现这样的实现没有多少实用意义。
本集的重点就是讨论怎么把XCF实用化。
准备Xsd
想一下如果要定义一个xml来描述,那么需要哪些元素。
首先是一个模板,这个模板描述了请求的总体结构。
其次是变量,这些变量描述了请求中的变化值。
然后是需要一个机制,将模板和变量融合起来,变成一个真正的请求实例。
最后是对响应的处理,如果不关心响应的话,这部分可以省略。
所以,可以简单的定义出这样的Schema:
<?xml version="1.0" encoding="utf-8"?> <xs:schema id="WcfFantasia" targetNamespace="http://www.cnblogs.com/vwxyzh/WcfFantasia/" elementFormDefault="qualified" attributeFormDefault="qualified" xmlns:f="http://www.cnblogs.com/vwxyzh/WcfFantasia/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xs:complexType name="Request"> <xs:sequence> <xs:element name="template" type="xs:anyType"/> <xs:element name="transform" type="xs:anyType" minOccurs="0"/> </xs:sequence> </xs:complexType> <xs:complexType name="Response"> <xs:sequence minOccurs="0" maxOccurs="unbounded"> <xs:element name="item" type="f:ResponseItem"/> </xs:sequence> </xs:complexType> <xs:complexType name="ResponseItem"> <xs:attribute name="name" type="xs:string" use="required"/> <xs:attribute name="path" type="xs:string" use="required"/> </xs:complexType> <xs:complexType name="RequestResponse"> <xs:sequence> <xs:element name="request" type="f:Request"/> <xs:element name="response" type="f:Response"/> </xs:sequence> <xs:attribute name="method" type="xs:string" use="required"/> <xs:attribute name="address" type="xs:string" use="required"/> </xs:complexType> <xs:element name="root" type="f:RequestResponse"/> <xs:element name="bind"> <xs:complexType> <xs:attribute name="value" type="xs:string" use="required"/> </xs:complexType> </xs:element> </xs:schema>
其中,request中的template节允许承载任何内容,用于记录模板,而transform节主要承载一个Xslt,但是由于无法在Xsd中指定Xslt,就暂时用任意内容代替。
XCF的请求/响应Xml实例
看了xsd还在云里雾里?不妨直接看Xml实例吧,服务继续用上次的服务:
<?xml version="1.0" encoding="utf-8"?> <f:root f:method="Echo" f:address="http://localhost:12345/EchoService/" xmlns:f="http://www.cnblogs.com/vwxyzh/WcfFantasia/"> <f:request> <f:template> <Echo xmlns="urn:test"> <p> <Age> <f:bind value="age"/> </Age> <Name> <f:bind value="name"/> </Name> </p> </Echo> </f:template> <f:transform> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f="http://www.cnblogs.com/vwxyzh/WcfFantasia/"> <xsl:output omit-xml-declaration="yes" method="xml" encoding="utf-8"/> <xsl:template match="f:bind"> <xsl:variable name="name" select="@value"/> <xsl:value-of select="f:GetContextValue($name)"/> </xsl:template> <xsl:template match="/|@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet> </f:transform> </f:request> <f:response> <f:item f:name="name" f:path="/a:EchoResponse/a:EchoResult/a:Name" xmlns:a="urn:test"/> <f:item f:name="age" f:path="/a:EchoResponse/a:EchoResult/a:Age" xmlns:a="urn:test"/> </f:response> </f:root>
乍看上去是不是有点多?
别急,接下来,一步一步的分析一下。
首先,看一下template的内容:
<Echo xmlns="urn:test"> <p> <Age> <f:bind value="age"/> </Age> <Name> <f:bind value="name"/> </Name> </p> </Echo>
是不是有点熟悉,去掉bind节,换成产量的话,这个就是上集中的请求内容。
那么bind节是干什么的?就把它当成一个变量占位符,在后面的Xslt中会对这一节做运算,并替换内容。
接着,看一下transform节的内容:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f="http://www.cnblogs.com/vwxyzh/WcfFantasia/"> <xsl:output omit-xml-declaration="yes" method="xml" encoding="utf-8"/> <xsl:template match="f:bind"> <xsl:variable name="name" select="@value"/> <xsl:value-of select="f:GetContextValue($name)"/> </xsl:template> <xsl:template match="/|@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
这里使用了一个通用的转换方式,当然这里也可以使用特定转换。
至于response节,暂时不作为重点介绍。
实现XcfEngine
简单的实现一下:
using System; using System.Collections.Generic; using System.IO; using System.Xml; using System.Xml.XPath; using System.Xml.Xsl; namespace WcfFantasia { public class XcfEngine { public const string XcfNamespace = "http://www.cnblogs.com/vwxyzh/WcfFantasia/"; private readonly XcfChannelFactory m_factory; public XcfEngine(XcfChannelFactory factory) { m_factory = factory; } public Dictionary<string, object> Call(XmlReader reader, IDictionary<string, object> contexts) { XPathDocument doc = new XPathDocument(reader); XmlNameTable nameTable = reader.NameTable; XmlNamespaceManager namespaceManager = new XmlNamespaceManager(nameTable); namespaceManager.AddNamespace("f", XcfNamespace); var root = doc.CreateNavigator().SelectSingleNode("f:root", namespaceManager); using (var stream = new MemoryStream()) { CreateRequest(contexts, root, stream, namespaceManager); stream.Seek(0L, SeekOrigin.Begin); var address = root.SelectSingleNode("@f:address", namespaceManager).Value; var method = root.SelectSingleNode("@f:method", namespaceManager).Value; using (var c = m_factory.Create(new Uri(address))) using (var requestReader = XmlReader.Create(stream)) using (var resp = c.Request(method, requestReader)) using (var respBody = resp.GetBody()) return ProcessResponse(root, namespaceManager, respBody); } } private void CreateRequest(IDictionary<string, object> contexts, XPathNavigator root, MemoryStream stream, XmlNamespaceManager namespaceManager) { var template = root.SelectSingleNode("f:request/f:template", namespaceManager).SelectChildren(XPathNodeType.Element); template.MoveNext(); var transform = root.SelectSingleNode("f:request/f:transform", namespaceManager).SelectChildren(XPathNodeType.Element); transform.MoveNext(); using (var input = template.Current.ReadSubtree()) using (var trans = transform.Current.ReadSubtree()) Transform(trans, input, stream, contexts); } private void Transform(XmlReader transform, XmlReader input, Stream result, IDictionary<string, object> dict) { var f = new XslCompiledTransform(); f.Load(transform); XsltArgumentList xal = new XsltArgumentList(); xal.AddExtensionObject(XcfNamespace, new XcfContext(dict)); f.Transform(input, xal, result); } private static Dictionary<string, object> ProcessResponse(XPathNavigator root, XmlNamespaceManager namespaceManager, XmlReader respBody) { Dictionary<string, object> result = new Dictionary<string, object>(); XPathDocument respDoc = new XPathDocument(respBody); foreach (XPathNavigator item in root.Select("f:response/f:item", namespaceManager)) { foreach (var nspair in item.GetNamespacesInScope(XmlNamespaceScope.Local)) namespaceManager.AddNamespace(nspair.Key, nspair.Value); string xpath = item.GetAttribute("path", XcfNamespace); string name = item.GetAttribute("name", XcfNamespace); var itemNode = respDoc.CreateNavigator().SelectSingleNode(xpath, namespaceManager); if (itemNode != null) result[name] = itemNode.Value; else result[name] = null; } return result; } } }
public class XcfContext { private Dictionary<string, object> m_dict; public XcfContext(IDictionary<string, object> dict) { m_dict = new Dictionary<string, object>(dict); } public object GetContextValue(string key) { object result; m_dict.TryGetValue(key, out result); return result ?? string.Empty; } }
整个XcfEngine就完成了,来看看怎么用吧:
using (var f = new XcfChannelFactory(new WSHttpBinding())) { XcfEngine engine = new XcfEngine(f); using (var reader = XmlReader.Create("sample.xml")) { var result = engine.Call(reader, new Dictionary<string, object> { { "age", 1 }, { "name", "xcf" }, }); foreach (var item in result) Console.WriteLine("Name={0}, Value={1}", item.Key, item.Value); } }
这样就把整个xcf调用起来了,当然变量部分目前直接使用Dictionary,当然可以使用更好的方式,毕竟这个只是初步的实现。
XCF展望
说了三篇的XCF,那么定位是什么哪?
无契约类型的优势是什么?或者说把契约还原成xml的优势是什么?
首先,xml而不是类型就不依赖编译,也就是当对方契约变化的时候,可以直接更新,这个特性看起来不怎么重要,但是在特定的场合下非常重要。
其次,xml可以有更多的存储/发布方式,例如数据库或web/wcf等各种方式。
再次,xml本身是一整套解决方案,可以用各种Xml的应用组合出更强大的xml应用。
最后,可以利用xml调用那些编译程序时完全未知的web/wcf服务。