XCF之实用篇

上集回顾

    上集中已经实现了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服务。

你可能感兴趣的:(c)