|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThe 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:
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 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. ImplementationThe 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:
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
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.
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
{
/// <summary>
/// Summary description for Service.
/// </summary>
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
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
}
#endregion
/// <summary>
/// Clean up any resources being used.
/// </summary>
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;
}
}
}
SubscriberThe 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 -
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
{
/// <summary>
/// Summary description for Service1.
/// </summary>
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
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
}
#endregion
/// <summary>
/// Clean up any resources being used.
/// </summary>
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:
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
TestThe 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:
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 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.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||