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服务。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值