Code Copied

非典型WCF回调

业务场景

上一篇中,我们构建了一个WCF的回调程序,我将其称之为“传统的WCF回调”,它属于WCF的内置功能。
实际的一些应用场景中,我们构建的WCF服务需要给外部系统调用,而外部系统可能是没有办法实现Service端的回调契约的,请见下图。

image

①. To-be内部系统:由于企业业务升级了,原先的内部系统需要置换,需要另外开发一套新的系统来支撑最新的业务。

②. 外部系统-客户端:外部系统的客户端是第3方的系统。但客户端保留了原有的通信接口和规则,这也就意味着客户端是不会做任何升级或更新的。

③. IIS-WCF服务:为了适应客户端的调用接口,我们提供一套WCF服务。这一套WCF服务仍然要使用原来的请求操作名称,并保持SOAP请求和SOAP响应的数据格式。例如:客户端请求操作名称为http://dms.mz.com/DataService/PGM001,新构建的WCF服务要保持这个操作名称。

④. Batch-PGM:PGM001_01、PGM001_02是一系列的Batch程序(dll程序)。程序编号总共8位,前5位是业务编号,表示一种业务(例如PGM001、PGM002);最后两位表示表示业务的分支(例如01、02)。客户端每一次请求都会处理一种业务,每种业务涉及到的程序会按照分支编号依次调用。

⑤回调:每一次请求时,WCF都先处理分支编号为01的程序(例如:PGM001_01)。
当响应成功后,再依照分支编号回调PGM001_02、PGM001_03…等程序,PGM001_01在PMG001系列程序中相当于启动程序。
PGM001_02、PGM001_03…等程序和PGM001_01使用相同的参数和参数值,这表明PGM001_01的使用过的参数要在后续的回调程序中重用。

这里有一个问题:客户端的程序并不是我们创建的,我们无法在客户端中实现服务端的回调契约,“传统的WCF回调”在这里无法伸出拳脚了。那么如何通过技术手段实现图中的“回调场景”呢?

如上描述的回调方式为非传统的回调方式,我们可以称之为“非典型WCF回调”

创建WCF Service

1. 创建工程

在Visual Studio中创建一个AtypicalWCFCallbackSample的解决方案,然后分别创建3个工程:

  1. Service (WCF服务应用):声明DataService契约
  2. PGM001_01 (类库):包含PGM001的第1分支程序
  3. PGM001_02(类库):包含PGM001的第2分支程序

image

2. SOAP消息格式

下面的XML是客户端发送SOAP请求和接收SOAP响应的消息格式。

SOAP请求消息格式:

<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://dms.mz.com/DataService/PGM001</a:Action>
    <a:MessageID>urn:uuid:5cf13aa8-3bf3-4cbd-b06c-96df805f323d</a:MessageID>
    <a:ReplyTo>
      <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
    </a:ReplyTo>
  </s:Header>
  <s:Body>
    <RequestMessageOfContractRequest xmlns="http://dms.mz.com">
      <Common xmlns:d4p1="http://schemas.datacontract.org/2004/07/Service.Messages" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <d4p1:PgmId>PGM001</d4p1:PgmId>
        <d4p1:Sender>
          <d4p1:SystemId>EDI</d4p1:SystemId>
          <d4p1:ApplicationId>a01</d4p1:ApplicationId>
          <d4p1:SendDate>2015-03-21T09:49:00</d4p1:SendDate>
        </d4p1:Sender>
        <d4p1:Target i:nil="true" />
        <d4p1:Return i:nil="true" />
      </Common>
      <ApplicationData xmlns:d4p1="http://schemas.datacontract.org/2004/07/Service.Messages" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <d4p1:DealerCode>D001</d4p1:DealerCode>
        <d4p1:KeyCode>00001111</d4p1:KeyCode>
      </ApplicationData>
    </RequestMessageOfContractRequest>
  </s:Body>
</s:Envelope>

SOAP响应消息格式:

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://dms.mz.com/DataService/PGM001Response</a:Action>
    <a:RelatesTo>urn:uuid:5c481369-7666-40f1-b125-d467732d9550</a:RelatesTo>
  </s:Header>
  <s:Body>
    <ResponseMessageOfContractResponse xmlns="http://dms.mz.com">
      <Common xmlns:b="http://schemas.datacontract.org/2004/07/Service.Messages" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <b:PgmId>PGM001</b:PgmId>
        <b:Sender>
          <b:SystemId>EDI</b:SystemId>
          <b:ApplicationId>a01</b:ApplicationId>
          <b:SendDate>2015-03-21T09:56:00</b:SendDate>
        </b:Sender>
        <b:Target>
          <b:SystemId>DMS</b:SystemId>
          <b:ApplicationId>xxx</b:ApplicationId>
          <b:SendDate>2015-03-21T10:00:40.221932+08:00</b:SendDate>
        </b:Target>
        <b:Return>
          <b:ReturnCode>200</b:ReturnCode>
          <b:ReturnMessage>执行成功!</b:ReturnMessage>
        </b:Return>
      </Common>
      <ApplicationData xmlns:b="http://schemas.datacontract.org/2004/07/Service.Messages" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <b:Data>Some data...</b:Data>
      </ApplicationData>
    </ResponseMessageOfContractResponse>
  </s:Body>
</s:Envelope>

SOAP请求和响应的消息格式拥有类似的结构,它们的Common节点结构是完全一样的。

3. 定义MessageContract

由于要维持SOAP的请求和响应消息格式,所以使用MessageContract(消息契约),消息契约可以自定义消息格式。

这里没有使用DataContract(数据契约),而使用MessageContract(消息契约),因为MessageContract可以控制SOAP的MessageHeader(消息头)和MessageBody(消息体)结构。MessageContract特性用于数据类型上,MessageHeader和MessageBodyMember特性用于属性上。

根据上面的消息格式,我们要定义2个MessageContract:RequestMessageOfContractRequest和ResponseMessageOfContractResponse。以及一个DataContract:Common。

image

Common.cs

using System;
using System.Runtime.Serialization;

namespace Service.Messages
{
    /// <summary>
    /// 消息体的共通部分
    /// </summary>
    [DataContract]
    public class Common
    {
        /// <summary>
        /// 程序ID
        /// </summary>
        [DataMember(Order = 1)]
        public string PgmId { get; set; }

        /// <summary>
        /// 发送系统信息
        /// </summary>
        [DataMember(Order = 2)]
        public Sender Sender { get; set; }

        /// <summary>
        /// 目标系统信息
        /// </summary>
        [DataMember(Order = 3)]
        public Target Target { get; set; }

        /// <summary>
        /// 返回信息
        /// </summary>
        [DataMember(Order = 4)]
        public ReturnInfo Return { get; set; }
    }

    /// <summary>
    /// 发送请求的系统信息
    /// </summary>
    [DataContract]
    public class Sender
    {
        [DataMember(Order = 1)]
        public string SystemId { get; set; }

        [DataMember(Order = 2)]
        public string ApplicationId { get; set; }

        [DataMember(Order = 3)]
        public DateTime SendDate { get; set; }
    }

    /// <summary>
    /// 接收请求的系统信息
    /// </summary>
    [DataContract]
    public class Target
    {
        [DataMember(Order = 1)]
        public string SystemId { get; set; }

        [DataMember(Order = 2)]
        public string ApplicationId { get; set; }

        [DataMember(Order = 3)]
        public DateTime SendDate { get; set; }
    }

    /// <summary>
    /// 返回信息
    /// </summary>
    [DataContract]
    public class ReturnInfo
    {
        /// <summary>
        /// 返回代码
        /// </summary>
        [DataMember(Order = 1)]
        public int ReturnCode { get; set; }

        /// <summary>
        /// 返回信息
        /// </summary>
        [DataMember(Order = 2)]
        public string ReturnMessage { get; set; }
    }
}

RequestMessageOfContractRequest.cs

using System.Runtime.Serialization;
using System.ServiceModel;

namespace Service.Messages
{
    /// <summary>
    /// 请求消息结构
    /// </summary>
    [MessageContract]
    public class RequestMessageOfContractRequest
    {

        [MessageBodyMember(Order = 1)]
        public Common Common { get; set; }

        [MessageBodyMember(Order = 2)]
        public RequestData ApplicationData { get; set; }
    }

    /// <summary>
    /// 消息体的应用数据
    /// </summary>
    [DataContract]
    public class RequestData
    {
        /// <summary>
        /// 代理商代码
        /// </summary>
        [DataMember(Order = 1)]
        public string DealerCode { get; set; }

        /// <summary>
        /// 关键代码
        /// </summary>
        [DataMember(Order = 2)]
        public string KeyCode { get; set; }
    }

    
}

ResponseMessageOfContractResponse.cs

using System.Runtime.Serialization;
using System.ServiceModel;

namespace Service.Messages
{
    /// <summary>
    /// 响应消息结构
    /// </summary>
    [MessageContract]
    public class ResponseMessageOfContractResponse
    {
        [MessageBodyMember(Order = 1)]
        public Common Common { get; set; }

        [MessageBodyMember(Order = 2)]
        public ResponseData ApplicationData { get; set; }
    }

    /// <summary>
    /// 响应数据
    /// </summary>
    [DataContract]
    public class ResponseData
    {
        /// <summary>
        /// 数据
        /// </summary>
        [DataMember(Order = 1)]
        public string Data { get; set; }
    }
}

 

4. 定义服务契约

请分别注意SOAP请求中的Action和RequestMesageOfContractRequest节点的namespace

SOAP请求的Action:http://dms.mz.com/DataService/PGM001可以拆解为3个部分:

①http://dms.mz.com/ 是服务器的域

②DataService是公开的服务契约名称

③PGM001是公开的操作名称

   第①部分在WCF服务部署时指定域名,现在我们可以不管;第②部分和第③部分可以在定义服务契约时实现。

  • ServiceContract特性中的Name属性可以指定服务器契约名称,将Name属性指定为DataService
  • 将操作契约的名称指定为PGM001

SOAP请求RequestMesageOfContractRequest节点的namespace:http://dms.mz.com

  • ServiceContract特性中的Namespace属性可以指定消息结构的命名空间,将NameSpace属性指定为http://dms.mz.com

IDataService.cs

using System.ServiceModel;
using Service.Messages;

namespace Service
{
    [ServiceContract(Name = "DataService", Namespace = "http://dms.mz.com")]
    public interface IDataService
    {
        [OperationContract]
        ResponseMessageOfContractResponse PGM001(RequestMessageOfContractRequest request);
    }
}

5. 动态调用PGM

本文开头的结构图中,WCF服务是寄宿在IIS中的,Batch中的PGM程序是放在服务器其他位置的。由于WCF服务不会直接引用这些PGM类库,所以WCF服务调用PGM是动态的,每个PGM最终都会被编译成一个dll文件。

使用.NET的反射功能可以动态调用dll,我们将使用反射的2个功能:

  1. 动态创建实例
  2. 动态调用方法

请看PgmFactory.cs,您不必关注这个类中的所有细节

  • GetPgmObjectById方法:动态加载PGM程序的dll,并创建dll中的PGM实例
  • InvokeMethod方法:动态调用PGM中的方法,PGM程序中的方法名称都是“Run”
  • BatchScriptPath:它是存储PGM程序dll的路径配置

PgmFactory.cs

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Reflection;

namespace Service.Factory
{
    public class PgmFactory
    {

        /// <summary>
        /// PGM信息
        /// </summary>
        internal class PgmInfo
        {
            public string DealerCode { get; set; }
            public string PgmId { get; set; }
            public string Namespace { get; set; }
            public string ClassName { get; set; }
        }


        #region 常量和字段信息

        /// <summary>
        /// 存放PGM程序dll的路径配置
        /// </summary>
        private const string BatchPathConfig = "BatchPath";

        /// <summary>
        /// PGM程序中的被调用方法名称
        /// </summary>
        private const string MethodName = "Run";

        // 为了演示,使用PgmInfo列表存储动态调用的程序信息,这样的信息应该存储在数据库中
        private readonly IList<PgmInfo> _pgmInfos = new List<PgmInfo>
        {
            new PgmInfo
            {
                DealerCode = "D001",
                PgmId = "PGM001",
                Namespace = "Com.DMS.Integration.PGM001",
                ClassName = "PGM001_01"
            },
            new PgmInfo
            {
                DealerCode = "D001",
                PgmId = "PGM002",
                Namespace = "Com.DMS.Integration.PGM002",
                ClassName = "DMS_PGM002_01"
            }
        };

        #endregion 

        #region 公共方法

        /// <summary>
        /// 获取PGM对象
        /// </summary>
        public object GetPgmObjectById(string pgmId, string dealerCode)
        {
            string[] info = GetPgmInfo(dealerCode, pgmId);
            // Create intance dynamically
            if (info != null)
                return CreateInstance(info[0], info[1]);
            return null;
        }

        /// <summary>
        /// 动态调用PGM对象的方法
        /// </summary>
        public object InvokeMethod(Object obj, object[] parameters)
        {
            Type objType = obj.GetType();
            MethodInfo method = objType.GetMethod(MethodName);
            return method.Invoke(obj, parameters);
        }

        #endregion

        #region 私有方法

        /// <summary>
        /// 获取Pgm的dll路径和Class的全称
        /// </summary>
        private string[] GetPgmInfo(string dealerCode, string pgmId)
        {
            string batchPath = ConfigurationManager.AppSettings[BatchPathConfig];

            PgmInfo pgmInfo = _pgmInfos.FirstOrDefault(p => p.DealerCode == dealerCode && p.PgmId == pgmId);

            if (pgmInfo != null)
            {
                // 业务设计时规定了PGM程序的AssemblyName和ClassName一致
                string dllPath = batchPath + pgmInfo.ClassName + ".dll";
                string fullName = pgmInfo.Namespace + "." + pgmInfo.ClassName;
                return new[] {dllPath, fullName};
            }
            return null;
        }

        /// <summary>
        /// 使用反射动态创建实例
        /// </summary>
        private object CreateInstance(string path, string fullName)
        {
            try
            {
                object obj = Assembly.LoadFile(path).CreateInstance(fullName);
                return obj;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        #endregion
    }
}

6. 实现服务契约

操作契约的处理逻辑如下:

  1. 从SOAP请求消息中获取关键信息
  2. 动态调用PGM对象,然后再动态调用PGM对象的方法
  3. 组织SOAP响应的消息内容

DataService.cs

using System;
using Service.Factory;
using Service.Messages;

namespace Service
{

    public class DataService : IDataService
    {
        public ResponseMessageOfContractResponse PGM001(RequestMessageOfContractRequest request)
        {
            // 从SOAP请求消息中获取关键信息
            string pgmId = request.Common.PgmId;
            string dealerCode = request.ApplicationData.DealerCode;
            string keyCode = request.ApplicationData.KeyCode;

            // 动态调用PGM对象,然后再动态调用PGM对象的方法
            PgmFactory factory = new PgmFactory();
            object pgm = factory.GetPgmObjectById(pgmId, dealerCode);
            object result =factory.InvokeMethod(pgm, new[] { dealerCode, keyCode });

            // 组织SOAP响应的消息内容
            ResponseMessageOfContractResponse response = new ResponseMessageOfContractResponse
            {
                Common = new Common()
                {
                    PgmId = request.Common.PgmId,
                    Sender = request.Common.Sender,
                    Target = new Target {SystemId = "DMS", ApplicationId = "xxx", SendDate = DateTime.Now},
                    Return = new ReturnInfo {ReturnCode = 200, ReturnMessage = "PGM001_01执行成功!"}
                },
                ApplicationData = new ResponseData()
                {
                    Data = result.ToString()
                }
            };

            return response;
        }
    }
}

在组织SOAP响应的消息内容代码段,为了演示我偷了懒——Target和Return信息使用了hard-code,实际上我们应该通过一些逻辑处理来获取Target和Return的信息。

7. 配置web.config

  • 配置PGM程序dll的存储路径,将项目PGM001_01和PGM001_02编译好的dll放到目录F:\Programs\BatchPath\
  • 配置Service的ABC(address, binding, contract),绑定配置使用wsHttpBinding

image

<?xml version="1.0"?>
<configuration>

  <appSettings>
    <add key="BatchPath" value="F:\Programs\BatchPath\"/>
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5"/>
  </system.web>
  <system.serviceModel>
    
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    
    <bindings>
      <wsHttpBinding>
        <binding name="wsBinding">
          <security mode="None" />
        </binding>
      </wsHttpBinding>
    </bindings>
    
    <services>
      <service name="Service.DataService">
        <endpoint address="" binding="wsHttpBinding" contract="Service.IDataService" 
                  bindingConfiguration="wsBinding" />
      </service>
    </services>
       
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <!--
        To browse web app root directory during debugging, set the value below to true.
        Set to false before deployment to avoid disclosing web app folder information.
      -->
    <directoryBrowse enabled="true"/>
  </system.webServer>

</configuration>

8. 测试调用

将Service项目设置为启动项目,DataService.svc设置为起始页,按下F5启动WCF Test Client工具。

输入请求信息

在Formatted Tab页输入RequestMessageOfContractRequest的属性值

image

在XML Tab页可以查看RequestMessageOfContractRequest对应的SOAP消息结构

image

输出响应信息

在Formatted Tab页中,点击Invoke按钮后可以查看Response结果

image

点击XML Tab页可以查看ResponseMessageOfContractResponse对应的消息结构

image

回调实现

现在我们已经实现了一种业务第一步的SOAP请求和响应(PGM001_01),并且消息格式也满足客户端的需求了,那么该如何实现后续步骤的回调呢(PGM001_02、PGM001_03…)?

是否执行后续回调的关键是第一步的响应是否成功。
幸运的是,ASP.NET的HttpResponse对象的StatusCode属性让我们可以获知响应是否成功。

但是现在我们仍然面临两个问题:

  1. 调用PGM001_01时的请求参数RequestMessageOfContractRequest如何传给程序PGM001_02?
    客户端仅发起了一次请求,在调用完PGM01操作后,参数RequestMessageOfContractRequest就被释放了。
  2. 在哪里使用HttpResonse对象?
    以IIS作为宿主的WCF服务程序不同于一般的ASP.NET Web Forms应用程序,在一般的ASP.NET Web Forms应用程序中,通过页面的回发我们可以捕获到HttpResponse对象及其StatusCode。

下面我们来分别解决这两个问题。

在WCF中使用Session

Session可以用于用户回话周期内的对象存储,所以在每一次请求过程中,可以将RequestMessageOfContractRequest对象存储在Session中。
这样只要在回话周期内,其它的PGM程序都能够通过Session取到RequestMessageOfContractRequest对象了。
WCF提供了ASP.NET兼容模式的支持,并且我们的WCF服务程序将以IIS作为宿主,这使我们能够像ASP.NET一样使用Session对象。

设置ASP.NET兼容模式支持

需要在2个地方进行ASP.NET兼容模式支持,才能让WCF支持ASP.NET的HttpContext。

  1. 在契约实现类上使用AspNetCompatibilityRequirements特性
    image
  2. 在web.config中配置<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    image

修改后的DataService.cs:

using System;
using System.ServiceModel.Activation;
using System.Web;
using Service.Factory;
using Service.Messages;

namespace Service
{

    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class DataService : IDataService
    {
        public ResponseMessageOfContractResponse PGM001(RequestMessageOfContractRequest request)
        {
            // 将request对象存储在Session中,以供后续的回调PGM程序使用
            HttpContext.Current.Session["ContractRequest"] = request;

            // 从SOAP请求消息中获取关键信息
            string pgmId = request.Common.PgmId;
            string dealerCode = request.ApplicationData.DealerCode;
            string keyCode = request.ApplicationData.KeyCode;

            // 动态调用PGM对象,然后再动态调用PGM对象的方法
            PgmFactory factory = new PgmFactory();
            object pgm = factory.GetPgmObjectById(pgmId, dealerCode);
            object result =factory.InvokeMethod(pgm, new[] { dealerCode, keyCode });

            // 组织SOAP响应的消息内容
            ResponseMessageOfContractResponse response = new ResponseMessageOfContractResponse
            {
                Common = new Common()
                {
                    PgmId = request.Common.PgmId,
                    Sender = request.Common.Sender,
                    Target = new Target {SystemId = "DMS", ApplicationId = "xxx", SendDate = DateTime.Now},
                    Return = new ReturnInfo {ReturnCode = 200, ReturnMessage = "PGM001_01执行成功!"}
                },
                ApplicationData = new ResponseData()
                {
                    Data = result.ToString()
                }
            };

            return response;
        }
    }
}

在WCF中使用HttpApplication

HttpAplication对象可以监听客户端的请求,它提供了一系列的事件来跟踪请求和响应的过程。
HttpApplication对象中包含Response属性,Response属性的类型是HttpResonse。
全局应用程序类(Global Application Class)即我们所熟知的Global.asax文件,它派生自HttpApplication类,故我们可以在全局应用程序类中使用HttpApplication对象的属性。

既然要监听HttpResponse的状态(StatusCode),那就要先了解HttpApplication对象从请求开始到响应结束的事件生命周期。
HttpApplication的一次请求开始到结束,共经历19个事件,这些事件按以下顺序触发:

  1. BeginRequest

  2. AuthenticateRequest

  3. PostAuthenticateRequest

  4. AuthorizeRequest

  5. PostAuthorizeRequest

  6. ResolveRequestCache

  7. PostResolveRequestCache

    After the PostResolveRequestCache event and before the PostMapRequestHandler event, an event handler (which is a page that corresponds to the request URL) is created. When a server is running IIS 7.0 in Integrated mode and at least the .NET Framework version 3.0, the MapRequestHandler event is raised. When a server is running IIS 7.0 in Classic mode or an earlier version of IIS, this event cannot be handled.

  8. PostMapRequestHandler

  9. AcquireRequestState

  10. PostAcquireRequestState

  11. PreRequestHandlerExecute

    The event handler is executed.

  12. PostRequestHandlerExecute

  13. ReleaseRequestState

  14. PostReleaseRequestState

    After the PostReleaseRequestState event is raised, any existing response filters will filter the output.

  15. UpdateRequestCache

  16. PostUpdateRequestCache

  17. LogRequest.

    This event is supported in IIS 7.0 Integrated mode and at least the .NET Framework 3.0

  18. PostLogRequest

    This event is supported IIS 7.0 Integrated mode and at least the .NET Framework 3.0

  19. EndRequest

在这一系列事件中,从PostRequestHandlerExecute事件开始,到EndRequest事件结束,我们都能够使用HttpResponse对象的StatusCode。
但是从ReleaseRequestState事件开始,ASP.NET的Session对象就不能使用了,所以我们选择在PostRequestHandlerExecute对象中使用HttpResponse的StatusCode来判定响应是否成功。

在WCF工程下添加一个全局应用程序类文件Global.asax。

image

修改PgmFactory.cs,添加一个GetNextPgmObjectById方法,用于动态获取后续PGM程序中的对象。

/// <summary>
/// 获取后续PGM对象
/// </summary>
/// <param name="stepNo">步骤编号</param>
/// <param name="pgmId">程序ID</param>
/// <param name="dealerCode">代理店代码</param>
/// <returns></returns>
public object GetNextPgmObjectById(string stepNo, string pgmId, string dealerCode)
{
    string[] info = GetPgmInfo(dealerCode, pgmId);
    if (info != null)
    {
        info[0] = info[0].Replace("_01", "_" + stepNo);
        info[1] = info[1].Replace("_01", "_" + stepNo);
        return CreateInstance(info[0], info[1]);
    }
    return null;
}

在Global.asax中添加如下代码:

using System;
using System.Diagnostics;
using System.Web;
using Service.Factory;
using Service.Messages;

namespace Service
{
    public class Global : HttpApplication
    {

        void Application_PostRequestHandlerExecute(object sender, EventArgs e)
        {
            string str = HttpContext.Current.Session.SessionID;

            // 响应成功时
            if (this.Response.StatusCode == 200)
            {
                
                if (HttpContext.Current != null && HttpContext.Current.Session != null)
                {
                    // 使用Session获取RequestMessageOfContractRequest对象的值
                    if (HttpContext.Current.Session["ContractRequest"] != null)
                    {
                        var request =
                            HttpContext.Current.Session["ContractRequest"] as RequestMessageOfContractRequest;

                        // 执行后续的步骤
                        ExecuteNextSteps(request);
                    }
                }
            }
        }

        private void ExecuteNextSteps(RequestMessageOfContractRequest request)
        {
            string pgmId = request.Common.PgmId;
            string dealerCode = request.ApplicationData.DealerCode;
            string keyCode = request.ApplicationData.KeyCode;

            // 当前步骤编号
            int currentStep = 2;
            // 最大步骤编号
            int maxStep = 2;

            PgmFactory factory = new PgmFactory();

            //循环执行后续的PGM程序
            while (currentStep <= maxStep)
            {
                
                string stepNo = currentStep.ToString("00");

                // 动态创建后续PGM对象,并动态调用方法
                object obj = factory.GetNextPgmObjectById(stepNo, pgmId, dealerCode);
                string result = factory.InvokeMethod(obj, new[] {dealerCode, keyCode}).ToString();

                // 输出结果
                Trace.WriteLine(string.Format("PGM:{0}, result:{1}.", pgmId + "_" + stepNo, result));
                currentStep++;
            }

        }
    }
}

打开Output窗口并切换到Debug选项,再次运行WCF Test Client工具,输入RequestMessageOfContractRequest的信息并点击Invoke按钮。
在Output窗口可以看到PGM001_01.dll和PGM001_02.dll都是动态加载的,PGM001_02也成功执行了。
image

参考和下载

参考链接

HttpApplication事件:https://msdn.microsoft.com/en-us/library/vstudio/System.Web.HttpApplication(v=vs.110).aspx

源码下载

http://blog.64cm.com/source/WCF/AtypicalWCFCallbackSample.zip