Click here to Skip to main content
Click here to Skip to main content

Using the Web Services and COM+ Event System in the .Net Application.

, 11 Oct 2001 CPOL
Rate this:
Please Sign up or sign in to vote.
This article describes how to subscribe the Web Service into the COM+ Event System using the C# language.

Introduction

The Web Services are a url-address driven resources to perform a specific application service. In the logical business model the Web services represent reusable components - processors to obtain or change the distributed business state. The application business behavior can be notified using a loosely coupled event (LCE) system supported by COM+ services. It's based on the Publisher/Subscriber design pattern. This article describes how to integrate the Web Services into the COM+ Event System in the .Net Application using the C# language. I am using a simple example of the Web Services in the Publisher/Subscriber scenario, which it will publish the Log Message to the subscriber.

Concept and Design.

The concept of the LCE Web Service is based on using the Web Service proxy as a subscriber to the Event System. The LCE system during the run-time is driven by the abstract (meta class) of the Event Class and data of the Subscriptions (which it represents a logical connectivity) stored in the Event System. From the application model viewpoint they are handled by the Publisher respectively Subscriber component. The Web Services are integral part of the .Net Framework with well-defined interfaces (contracts) that describe services provided. In this sample I am using two Web Services:

  • Publisher to fire an event
  • Subscriber to receive the event calls
The following picture shows the position of the Web Services in the LCE System solution:

This scenario of the "fire & forget" event is very straightforward and full transparently between the Web Services and their consumers. In the .Net Application this "plumbing" is done using the Web Service Proxy on the consumer side. The Web Method is mapped back and forth between them using the SOAP technologies on the http communication channel. In the about design we can see two Web Service consumers:

  • The application component (A) is a consumer to the Publisher Web Service, which it will initiate the Event Class and and then "fire event".
  • The Event System is a consumer to the Subscriber Web Service to invoke the Web Method in the callback manner.
The Subscriber side needs more explanations. The Web-to-Web Service process can be handled in the synch or asynch manner. In this event notification sample, the Publisher and Subscriber are running in the asynch design pattern, which it will make a full process isolation and concurrency on the Subscriber side (using the queueable object).
The other note is about the subscription type. There are two kinds of types - transient and persistent subscription. In the Web notification design, the persistent subscription is the better choice, where each event call will initiate a new instance of the stateless subscriber (using the poolable object). The other advantage of the persistent subscription is its lifetime in the Event System store. It will guarantee its persistence after restarting a machine.
Note the Event Class and Subscriber represent also "glue - gateway" between the managed and unmanaged code, that's why they are located in the COM+ application. The .Net Framework handles all interoperability transparently based on the interfaces contract and Reflection design.

Implementation

The implementation of the LCE system using Web services is based on the loosely coupled design pattern contracted by the Event System interfaces and Event Meta class. This abstract is built into the common assembly and installed into the Global Assembly Cache. The solution has the following assemblies:

  • EventClassLogMsg, common abstract definitions (interfaces and meta class)
  • WebServiceFireEvent, publisher web service
  • SubscriberLogMsg, subscriber web service proxy
  • WebServiceLogMsg, subscriber web service

Event Class and Interfaces.

This assembly contains all abstract definitions useful for publisher and subscriber components. Note that assembly is used also to create a COM+ application included the Event Class wrapper - component to interoperate with unmanaged code, that's why the class is derived from the ServicedComponent and attributed with EventClassAttribute. It's a very elegant solution given by the .Net Framework architecture to access the Event Class from either an unmanaged or managed code.

using System;
using System.Diagnostics;
using System.EnterpriseServices;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Security.Permissions;
using System.Threading;
//

using COMPlusEventSystem;
    
  [assembly: ApplicationName("EventClassLogMsg")]
  [assembly: ApplicationActivation(ActivationOption.Server)]
  [assembly: ApplicationAccessControl(Value = false, 
             Authentication  = AuthenticationOption.None)]

#region COM+ Event System Interfaces
namespace COMPlusEventSystem
{
  //--------------------------< EventSystem  >---------------------------------

  //

  [ComImport, Guid("4E14FBA2-2E22-11D1-9964-00C04FBBB345")]
  public class EventSystem {}
  [InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
   Guid("4E14FB9F-2E22-11D1-9964-00C04FBBB345")]
  public interface IEventSystem
  {
    [PreserveSig]
    object Query([In, MarshalAs(UnmanagedType.BStr)] string progId, 
      [In, MarshalAs(UnmanagedType.BStr)] string queryCriteria, 
      [Out] out Int32 errorIndex);
    [PreserveSig]
    void Store([In, MarshalAs(UnmanagedType.BStr)] string progId, 
      [In, MarshalAs(UnmanagedType.Interface)] object pInterface);
    [PreserveSig]
    void Remove([In, MarshalAs(UnmanagedType.BStr)] string progId, 
      [In, MarshalAs(UnmanagedType.BStr)] string queryCriteria, 
      [Out] out Int32 errorIndex);
     [PreserveSig]
    string get_EventObjectChangeEventClassID();  
    [PreserveSig]
    object QueryS([In, MarshalAs(UnmanagedType.BStr)] string progId, 
      [Out] out Int32 errorIndex);
    [PreserveSig]
    void RemoveS([In, MarshalAs(UnmanagedType.BStr)] string progId, 
      [Out] out Int32 errorIndex);
  }
  //

    
  [ComImport, Guid("7542E960-79C7-11D1-88F9-0080C7D771BF")]
  public class EventSubcription {}
  [InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
   Guid("4A6B0E15-2E38-11D1-9965-00C04FBBB345")]
  public interface IEventSubcription
  {
    string SubscriptionID { get; set; }
    string SubscriptionName { get; set; }
    string PublisherID { get; set; }
    string EventClassID { get; set; }
    string MethodName { get; set; }
    string SubscriberCLSID { get; set; }
    object SubscriberInterface { get; set; }
    bool   PerUser { get; set; }
    string OwnerSID { get; set; }
    bool   Enabled { get; set; }
    string Description { get; set; }
    string MachineName { get; set; }

    [PreserveSig]
    object GetPublisherProperty([In, MarshalAs(UnmanagedType.BStr)] string PropertyName);
    [PreserveSig]
    void   PutPublisherProperty([In, MarshalAs(UnmanagedType.BStr)] string PropertyName,
                                 ref object propertyValue);
    [PreserveSig]
    void   RemovePublisherProperty([In, MarshalAs(UnmanagedType.BStr)] string PropertyName);
    [PreserveSig]
    object GetPublisherPropertyCollection();
    [PreserveSig]
    object GetSubscriberProperty([In, MarshalAs(UnmanagedType.BStr)] string PropertyName);
    [PreserveSig]
    void   PutSubscriberProperty([In, MarshalAs(UnmanagedType.BStr)] string PropertyName, 
                                  ref object propertyValue);
    [PreserveSig]
    void   RemoveSubscriberProperty([In, MarshalAs(UnmanagedType.BStr)] string PropertyName);
    [PreserveSig]
    object GetSubscriberPropertyCollection();
    string InterfaceID { get; set; }
  }

  [InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
                 Guid("4A6B0E16-2E38-11D1-9965-00C04FBBB345")]
  public interface IEventSubcription2 : IEventSubcription                            
  {
    string FilterCriteria { get; set; }
    string SubscriberMoniker { get; set; }
  }
}    
#endregion

namespace EventClassLogMsg
{
  [Guid("32F5CB45-F0CB-41e2-919E-54D884905F13")]
  public interface ILogMsg
  {
    void WriteLogMsg(string ticket, string msg);
  }

  // meta class

  [Guid("5AA8C91D-EA0F-405a-8840-95344038AF5A")]
  [EventClass]
  [Transaction(TransactionOption.Disabled)]
  [ObjectPooling(Enabled=true, MinPoolSize=10, MaxPoolSize=60)]
  [EventTrackingEnabled]
  public class EventClassLogMsg : ServicedComponent, ILogMsg
  {
    public void WriteLogMsg(string ticket, string msg){}
  }
}

Publisher

The Publisher is the Web Service created by the ASP.Net Web Service template. There is only one simple Web Method to fire the Event Class' method.
The method can be tested using the http://localhost/WebServiceFireEvent/Service.asmx?op=FireEvent page.

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;
using System.Runtime.InteropServices;
using System.Reflection;
using System.EnterpriseServices;
//

using EventClassLogMsg;


namespace WebServiceFireEvent
{
  /// <span class="code-SummaryComment"><summary></span>

  /// Summary description for Service.

  /// <span class="code-SummaryComment"></summary></span>

  public class Service : System.Web.Services.WebService
  {
    public Service()
    {
      //CODEGEN: This call is required by the ASP.NET Web Services Designer

      InitializeComponent();
    }

    #region Component Designer generated code
    /// <span class="code-SummaryComment"><summary></span>

    /// Required method for Designer support - do not modify

    /// the contents of this method with the code editor.

    /// <span class="code-SummaryComment"></summary></span>

    private void InitializeComponent()
    {
    }
    #endregion

    /// <span class="code-SummaryComment"><summary></span>

    /// Clean up any resources being used.

    /// <span class="code-SummaryComment"></summary></span>

    protected override void Dispose( bool disposing )
    {
    }
    
    [WebMethod]
    public string FireEvent(string ticket, string msg)
    {
      int tsStart = Environment.TickCount;
      using (EventClassLogMsg.EventClassLogMsg ec = new EventClassLogMsg.EventClassLogMsg()) 
      {
        ec.WriteLogMsg(ticket, msg);
      }
      long tsEnd = Environment.TickCount - tsStart;
      Trace.WriteLine(string.Format("The LogMsg [{0}] has been published [{1}ms]", 
                      ticket, tsEnd));
      return ticket;
    }
  }    
}

Subscriber

The Subscriber side is divided into two parts: Web Service a Web Service Proxy.

Web Service:

The Web Service is created by the ASP.Net Web Service template with the same way as the about Publisher. There is only one simple Web Method - WriteLogMsg, which has to have the same method signature as the Event Class (it simplified the SOAP formatting). We don't have any business for this service, just only to notify this action. In real product, this service might have an implementation of the business such as log messages into database, monitoring messages, tuning application knowledge base, etc. The method can be tested using the http://localhost/WebServiceLogMsg/Service.asmx?op=WriteLogMsg page.

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;
using System.Threading;

namespace WebServiceLogMsg
{
  /// <span class="code-SummaryComment"><summary></span>

  /// Summary description for Service1.

  /// <span class="code-SummaryComment"></summary></span>

  public class Service : System.Web.Services.WebService
  {
    public Service()
    {
      //CODEGEN: This call is required by the ASP.NET Web Services Designer

      InitializeComponent();
    }

    #region Component Designer generated code
    /// <span class="code-SummaryComment"><summary></span>

    /// Required method for Designer support - do not modify

    /// the contents of this method with the code editor.

    /// <span class="code-SummaryComment"></summary></span>

    private void InitializeComponent()
    {
    }
    #endregion

    /// <span class="code-SummaryComment"><summary></span>

    /// Clean up any resources being used.

    /// <span class="code-SummaryComment"></summary></span>

    protected override void Dispose( bool disposing )
    {
    }
    [WebMethod]
    public void WriteLogMsg(string ticket, string msg)
    {
      // to do:

      Trace.WriteLine(string.Format("[{0}] WebServiceLogMsg.WriteLogMsg({1}, {2})", 
                      this.GetHashCode(), ticket, msg));
    }
  }
}

Web Service Proxy:

The Web Service Proxy is the key part of this solution. Its implementation covers the following features:

  • The Proxy is running as the COM+ application (interop with unmanaged code)
  • The Proxy class is derived from the SoapHttpClientProtocol (wrapper to the Web Service)
  • The Proxy class inherits the Event interface ILogMsg and interfaces of the poolable object design pattern such as IObjectControl and IObjectConstruct.
  • The Event interface method WriteLogMsg has the same signature as the requested Web Method
  • Creating the Subscription and its registration/unregistration with the Event System store. Note that subscription is a persistent type using the queue moniker string
    "queue:/new:RKiss.SubscriberLogMsg.Service" to initiate this proxy class.
  • Subscriber is calling the Proxy WebMethod based on the features of the QC component (recovering messages, retrying messages, etc.)
using System.Diagnostics;
using System.Xml.Serialization;
using System;
using System.Web.Services.Protocols;
using System.Web.Services;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
using System.Reflection;

using COMPlusEventSystem;
using RKiss.SubscriberLogMsgInterfaces;

namespace RKiss.SubscriberLogMsgInterfaces 
{
  #region Interfaces: ILogMsg, IObjectControl, IObjectConstruct, IObjectConstructString 

  // The Interface of the EventClass

  [Guid("32F5CB45-F0CB-41e2-919E-54D884905F13")]
  [InterfaceQueuing]
  public interface ILogMsg
  {
    void WriteLogMsg(string ticket, string msg);
  }

  [InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
  Guid("51372aec-cae7-11cf-be81-00aa00a2fa25")]
  public interface IObjectControl
  {
    [PreserveSig]
    void Activate();
    [PreserveSig]
    void Deactivate();
    [PreserveSig]
    bool CanBePooled();
  }

  [InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
  Guid("41C4F8B3-7439-11D2-98CB-00C04F8EE1C4")]
  public interface IObjectConstruct
  {
    [PreserveSig]
    void Construct([In, MarshalAs(UnmanagedType.IDispatch)] object pCtorObj);
  }

  [InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
  Guid("41C4F8B2-7439-11D2-98CB-00C04F8EE1C4")]
  public interface IObjectConstructString
  {
    string ConstructString { get; }    
  }
  #endregion
}  

namespace RKiss.SubscriberLogMsg {
        
  [WebServiceBinding(Name="ServiceSoap", Namespace="http://tempuri.org/")]
  public class Service : SoapHttpClientProtocol, ILogMsg, IObjectControl, IObjectConstruct
  {
    // Subscription ID 

    static string guidSub = "{0150BA01-1CF4-4431-A775-A30353E7BD20}"; 

    [DebuggerStepThrough()]
    public Service() 
    {
      this.Url = "http://localhost/WebServiceLogMsg/Service.asmx";
    }
    [ComRegisterFunction]
    public static void RegisterSubscription(Type t)
    { 
      IEventSystem es = new EventSystem() as IEventSystem;
      IEventSubcription2 sub = new EventSubcription() as IEventSubcription2;
      sub.Description = "Subscription for WebServiceLogMsg";
      sub.SubscriptionName = "WebServiceLogMsg";
      sub.SubscriptionID = guidSub;
      sub.Enabled = true;
      sub.EventClassID = "{" + 
            Type.GetTypeFromProgID("EventClassLogMsg.EventClassLogMsg").GUID.ToString() + "}";
      sub.MethodName = "WriteLogMsg";
      sub.SubscriberMoniker = @"queue:/new:RKiss.SubscriberLogMsg.Service";
      es.Store("EventSystem.EventSubscription",  sub);
      Trace.WriteLine(string.Format("Subscription {0} has been registered", guidSub));
    }
    [ComUnregisterFunction]
    public static void UnregisterSubscription(Type t)
    {
      int errorIndex = 0;
      IEventSystem es = new EventSystem() as IEventSystem;
      string strCriteria = "SubscriptionID=" + guidSub;
      es.Remove("EventSystem.EventSubscription", strCriteria, out errorIndex);
      Trace.WriteLine(string.Format("Subscription {0} has been unregistered", guidSub));
    }
    //IEventLog

    [DebuggerStepThrough()]
    [SoapDocumentMethod("http://tempuri.org/WriteLogMsg", 
      Use=System.Web.Services.Description.SoapBindingUse.Literal, 
      ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
    public void WriteLogMsg(string ticket, string msg)
    {
      try 
      {
        this.Invoke("WriteLogMsg", new object[] { ticket, msg});
      }
      catch(Exception ex)
      {
        Trace.WriteLine(string.Format("[{0}] SubscriberLogMsg.WriteLogMsg catch Err = {1}", 
                        this.GetHashCode(), ex.Message));
        throw ex;
      }
    }
    //IObjectConstruct

    public void Construct(object pCtorObj) 
    {
      string constr = (pCtorObj as IObjectConstructString).ConstructString;
      this.Url = constr;
      Trace.WriteLine(string.Format("[{0}] SubscriberLogMsg.Construct = {1}", this.GetHashCode(),
                       constr));
    }
    //IObjectControl

    public void Activate() 
    {
      Trace.WriteLine(string.Format("[{0}] SubscriberLogMsg.Activate", this.GetHashCode()));
    }
    public void Deactivate()
    {
      Trace.WriteLine(string.Format("[{0}] SubscriberLogMsg.Deactive", this.GetHashCode()));
    }
    public bool CanBePooled()
    {
      Trace.WriteLine(string.Format("[{0}] SubscriberLogMsg.CanBePooled", this.GetHashCode()));
      return true;
    }
  }
}

The proxy class can be derived only from a single class SoapHttpClientProtocol and there is no way to attribute the class to perform its registry in the COM+ catalog (it requested to use the ServiceComponent class), so we have to do it manually using the following steps:

  • Create a new COM + server/queueable application named as WebServiceSubscriber.
  • Use the regasm.exe tool to registry proxy as a COM component and generating its typelib file: c:\regasm SubscriberLogMsg.dll /tlb:SubscriberLogMsg.tlb. Note that the Event Class component has to be installed in prior (see the proxy static method RegisterSubscription implementation).
  • Add the SubsciberLogMsg.tlb into the WebServiceSubscriber COM+ application
  • Enable properties for object pooling and set the min pool size = 10
  • If you want to use another an url address of the Web Service than the http://localhost/WebServiceLogMsg/Service.asmx then follow the next step otherwise skip it.
  • Enable properties for object construction and set the construction string for the url address of the Web Service Subscriber.
  • Check the Queueing properties in the ILogMsg interface

Test

The LCE Web Services can be simple tested using the Publisher Web Service test page and DebugView utility from http://www.sysinternals.com. Before this test be sure that:

  • The Event Class has been registered
  • The Subscriber Proxy has been installed in the COM+ catalog
  • The both Web Services are located in the http://localhost and web server has been refreshed
Then start these applications and check if the number in the Pooled column of these components is 10. This means, that components have been constructed properly and they are ready to work. Now, we can perform the test as is shown in the following picture:

Each time when you click the Invoke button (with some parameters) on the Publisher page you can see the Trace messages on the DebugView screen. You can play more with the Publisher and Subscriber to verify capability of the loosely coupled design pattern and asynch processing in the local, over Internet and mix environments.

Conclusion.

In this example is shown how simple can be the Web Services integrated into the Event System using the LCE design pattern. The .Net Framework makes an easy implementation of the Web Services - Publisher/Subscriber skeleton. Using the LCE Web Services in the distributed event driven architecture is full transparent to the application components, which they can be developed incrementally and independent.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Roman Kiss
Software Developer (Senior)
United States United States
No Biography provided

Comments and Discussions

 
Generali need your help!!! Pinmemberductv060321-Nov-07 7:57 
QuestionWhat does "http://tempuri.org/WriteLogMsg" mean? Pinmemberhaughtycool17-Mar-06 17:00 
Generali am not able to add SubscriberLogMsg.tlb Pinmemberhaughtycool16-Mar-06 17:54 
Generalhttp://airline-ticket-to-india.tangoing.info/ Pinsusshttp://airline-ticket-to-india.tangoing.info/4-Dec-07 17:32 
GeneralUnable to add SubscriberLogMsg.tlb in COM+ Services Pinmembershamohamed5-Jul-04 21:04 
Firstly - Excellent Article.
this article was a great help in getting started with webservices and LCEs.
i am not able to add SubscriberLogMsg.tlb in to the WebServiceSubscriber COM+ Application(the NEXT button on COM+ Application is disabled).
can you help me on this.
 
thanks
shamohamed

Generaluse transaction both in webservice and .net remoting Pinmemberkevinasp5-Jan-04 22:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 11 Oct 2001
Article Copyright 2001 by Roman Kiss
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid