Using T4 To Generate WCF Client Proxy

Posted on December 29, 2011 14:29 by Ahmed Al Amir

Windows Communication Foundation (WCF) is Microsoft’s unified programming model for building service-oriented applications. It enables developers to build secure, reliable, transacted solutions that integrate across platforms and interoperate with existing investments.

In order to communicate with a WCF service you will need a client proxy which can be generated automatically using Visual Studio by “Add Service Reference” but I don’t prefer this way because it generates too much code, extra configuration and the service must publish its metadata, besides that every time the service updated you will need to update the service reference in order to reflect the new changes.

To perfectly loose-couple your application layers you will need to separate service contracts into their own assemblies so they can be shared and consumed by different applications and layers but this approach leaves a lot of manual work for the clients consuming our services as each client has to implement proxy for the corresponding contract.

Through this talk I will show you a simple way to eliminate manual work during proxy development. I will use T4 to dynamically create proxy class for WCF service contract in the same assembly with the contract in order to simplify consumption of our WCF service. Finally in the talk I will show you how to create Visual Studio template to generate WCF proxy classes.

Before digging into WCF proxy generation, let’s have a look to how T4 works. T4 text template is a mixture of text blocks and control logic that can generate a text file. The control logic is written as fragments of program code in Visual C# or Visual Basic. The generated file can be text of any kind, such as a Web page, or a resource file, or program source code in any language.

The first thing we would do is to add T4 file to our solution, To do this, in Solution Explorer, right-click your project and point to Add, and then click New Item. In the Add New Item dialog box select Text Template from the Templates pane. Name the file NewTemplate.tt. Notice that the Custom Tool property of the file is TextTemplatingFileGenerator.

Open the added file and change “hostspecific” to true, “debug” to true, “language” to C# and the output extension to “cs” like below:

<#@ template language="C#" hostspecific="True" debug="True" #>

<#@ output extension="cs" #>


This means that our generated file will be in C# code and we enabled “hostspecific” in order to allow our T4 to access the text template host engine. Make sure that “System.ServiceModel” DLL is referenced. In T4 add the below directives:

<#@ Assembly Name="EnvDTE" #>

<#@ Assembly Name="EnvDTE80" #>

<#@ import namespace="EnvDTE" #>

<#@ Import Namespace="EnvDTE80" #>

<#@ Import Namespace="Microsoft.VisualStudio.TextTemplating" #>


The above directives allow us to reference and access to visual studio environment along with text template generation engine. I have created a utility helper class (WCFProxyGenerator) that will provide the necessary helper functions to create proxy, these functions are summarized below:

1- RootNamespace: Gets the root namespace of the target project.
2- DefaultNamespace: Gets the default namespace for the target project.
3- GetServiceInterface(): Gets service interface type object for the specified file (searches the file for WCF interfaces).
4- GenerateParameters(): Gets a string that contains WCF method parameters.
5- GenerateCodeImports(): Gets the namespaces that need to be imported by the proxy.

Let’s now write some real T4 code, the first thing we need here is the name of the WCF service contract file, so I will declare T4 variable for it, also we can give our user the chance to choose the name of the proxy, if the user does not provide a certain name for the proxy then we will use the same name of the T4 file: 

<#
 
   string WCFServiceInterfaceFile = "wcfservicecontract.cs";
    string ProxyName = "";
   
    WCFProxyGenerator Context = new WCFProxyGenerator(this.Host);
    CodeInterface2 ServiceContract = Context.GetServiceInterface(WCFServiceInterfaceFile);
       

    if (ServiceContract != null)
    {
        if (string.IsNullOrEmpty(ProxyName))
            ProxyName = System.IO.Path.GetFileNameWithoutExtension(this.Host.TemplateFile);
        WriteLine(Context.GenerateCodeImports(ServiceContract));
    }
    else
        return null;

#>

The above T4 code will create object of our utility class, then it will use that object to search for specified file in solution, if the contract is found then we will use utility object to import the required namespaces for our proxy “using statements”.

The next step is to write the T4 script that will generate WCF proxy class:

namespace <#=Context.DefaultNamespace #>

{      

       public partial class <#=ProxyName #> : ClientBase<<#=ServiceContract.Name #>>, <#=ServiceContract.Name #>

       {

       <#

       foreach (var itm in ServiceContract.Members)
    {
        if (itm is CodeFunction2)
        {
            CodeFunction2 method = (CodeFunction2)itm;
            if (Context.IsServiceMethod(method) && method.Access == vsCMAccess.vsCMAccessPublic)
            {             

       #>

       public <#=method.Type.AsString #> <#=method.Name #>(<#=Context.GenerateParameters(method.Parameters)#>)

              {

                     <#=method.Type.TypeKind == vsCMTypeRef.vsCMTypeRefVoid ? "" : "return" #> this.Channel.<#=method.Name #>(<#=Context.GenerateParameters(method.Parameters, false) #>);

              }

 

       <#

            }                  }
    }#>   

       }

}

In the above script, we first declare the namespace of our class then we generate partial class for the proxy that implements “ClientBase<>” along with our WCF service interface. I used a partial class in order to give other developer space for extending proxy implementation.

The T4 script then loops through each method in the WCF service contract and checks if the method is WCF operation method and if so it generates the method signature by using our utility class. After generating the method signature we fill the method body by using the channel factory that we have from our inherited base class.

I created a Visual Studio item template in order to simplify our proxy generation process. You can download the item template here (WCFProxyGenerator.zip (208.85 kb)) then drop this zip file as is in the appropriate location to be used by visual studio, the default location for this file should be “%USERPROFILE%\Documents\Visual Studio 2010.\Templates\ItemTemplates\”, restart visual studio and create a new class library project then add a new item to the project (you should see our item template there):

Add the WCF proxy generator template file, create any WCF service contract and open the T4 file and pass the service contract file name to T4 file then save the T4 file and you will get your WCF proxy class generated.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Microsoft Lync Server 2010 communications software offers instant messaging (IM), presence, conferencing, and telephony solutions that can support enterprise-level collaboration requirements. Recently I was working in an integration project that links Lync server with Microsoft exchange server and I found the best way to integrate these two entities is though EWS.

Exchange Web Services (EWS) provides the functionality to enable client applications to communicate with the Exchange server. EWS provides access to much of the same data that is made available through Microsoft Office Outlook. EWS clients can integrate Outlook data into Line-of-Business (LOB) applications. SOAP provides the messaging framework for messages sent between the client application and the Exchange server.

Though this talk I’m going to discuss EWS integration with .net client applications, in later articles I will discuss the integration with Lync Server 2010. To get started with EWS you will need to reference EWS to your solution, you can do this with the Visual Studio generated proxy classes for Exchange Web Services but I’m not going to use Microsoft managed APIs to interact with EWS that can be downloaded here.

The Microsoft Exchange Web Services (EWS) Managed API 1.1 provides a managed interface for developing client applications that use Exchange Web Services. The EWS Managed API simplifies the implementation of applications that communicate with Microsoft Exchange Server 2007 Service Pack 1 (SP1) and later versions of Microsoft Exchange. Built on the Exchange Web Services SOAP protocol and Autodiscover, the EWS Managed API provides a .NET interface to EWS that is easy to learn, use, and maintain.

The Exchange Web Services (EWS) Managed API consists of a single assembly, Microsoft.Exchange.WebServices.dll, from your Microsoft Visual Studio project, add a reference to Microsoft.Exchange.WebServices.dll, and add a using clause for the Microsoft.Exchange.WebServices.Data namespace.

The first thing we would do is to construct "ExchangeService" object that handles the communication with the exchange server:

public ExchangeService CreateExchangeService()
{
     ExchangeService service = new ExchangeService(Microsoft.Exchange.WebServices.Data.ExchangeVersion.Exchange2010_SP1);
     service.Credentials = new WebCredentials("UserName", "Password", "Domain");
     service.Url = new Uri("https://domain.com/ews/Exchange.asmx");
     return service;
}

The diagram below shows EWS object hierarchy:

 

Now we are ready to explore EWS features:

E-Mail

  • Sending Email:

 

ExchangeService EWS = this.CreateExchangeService();
EmailMessage message = new EmailMessage(EWS);
message.Subject = "Hello EWS!";
message.Body = "Testing EWS.";
message.ToRecipients.Add("aalamir@intellecting.net");
message.SendAndSaveCopy();

  • Replying Email:

In order to reply for an email, we should first search for the message we want to reply for as follows:

FindItemsResults<Item> Result = EWS.FindItems(WellKnownFolderName.Inbox,
   new SearchFilter.ContainsSubstring(ItemSchema.Subject, "Hello EWS!"),
   new ItemView(int.MaxValue) { PropertySet = new PropertySet(ItemSchema.Subject)});

 
 foreach (var itm in Result)
  
{
      
EmailMessage message = itm as EmailMessage;
      
message.Reply("EWS Reply Test!!!", true);
  
}

  • Forwarding Email:
    foreach (var itm in Result)
   
{
       
EmailMessage message = itm as EmailMessage;
       
message.Forward("EWS Forward Test!!!", "aalamir@intellecting.net");
    }
  • Moving Email:
   foreach (var itm in Result)
  
{
      
EmailMessage message = itm as EmailMessage;
      
message.Move(WellKnownFolderName.JunkEmail);
  
}
  • Deleting Email:
   foreach (var itm in Result)
   
{
      
EmailMessage message = itm as EmailMessage;
      
message.Delete(DeleteMode.MoveToDeletedItems);
   
}

 

An important thing to mention here is if you try to access any property of EmailMessage object you will get an exception because during search operation EWS retrieves only Item class properties so you will need to call the [load()] method after casting the object in order to retrieve the properties.

 

Contacts

Contact contact = new Contact(EWS);
contact.CompanyName = "Intellecting";
contact.GivenName = "Ahmed Al Amir";
contact.DisplayName = "Al Amir";
contact.NickName = "Marp";
contact.JobTitle = "EWS test...";
contact.PhoneNumbers[PhoneNumberKey.MobilePhone] = "0123456789";

contact.Save();

Tasks

  • Adding new Task:

 

ExchangeService EWS = this.CreateExchangeService();
Task task = new Task(EWS) { Subject = "EWS Task Test", Body = "Task Body goes here !!!" };
task.Save();

 

  • Updating Existing Task:

 

FindItemsResults<Item> Result = EWS.FindItems(WellKnownFolderName.Tasks,
                new SearchFilter.ContainsSubstring(ItemSchema.Subject, "EWS Task Test"),
                new ItemView(int.MaxValue) { PropertySet = new PropertySet(ItemSchema.Subject) });

 

foreach (var itm in Result)
{
    Task tsk = itm as Task;

    tsk.Status = TaskStatus.InProgress;

    tsk.StartDate = DateTime.Now.AddDays(-1);
    tsk.DueDate = DateTime.Now.AddDays(+1);

    tsk.PercentComplete = 60;

    tsk.Update(ConflictResolutionMode.AutoResolve);
}

 

Appointments

Appointment app = new Appointment(EWS);

app.Subject = "EWS Appointment Test";

app.Start = DateTime.Now.AddDays(+1);

app.Location = "Meeting Room [1]";

app.End = DateTime.Now.AddDays(+1).AddMinutes(90);

app.Save();

 

Meetings

Appointment meeting = new Appointment(EWS);

meeting.Subject = "EWS Meeting Test!!!";

meeting.Body = "Body Goes here.";

meeting.Start = DateTime.Now.AddDays(+1);

meeting.End = meeting.Start.AddMinutes(90);

meeting.Location = "Meeting Room[1]";

meeting.RequiredAttendees.Add("user1@domain.com");

meeting.RequiredAttendees.Add("user2@domain.com");

meeting.OptionalAttendees.Add("user3@domain.com");
meeting.Save(SendInvitationsMode.SendToAllAndSaveCopy);

 

Now after exploring the main basic features of EWS, let’s dig through EWS notifications:

Notifications

EWS allows client applications to subscribe to event notifications that make it possible to determine what events occurred on a specific folder since a specific point in time (for example, what items were created, modified, moved, or deleted).

There are two types of subscriptions: pull subscriptions and push subscriptions. With pull subscription, the client application has to poll the server regularly to retrieve the list of events that occurred since the last time the server was polled. With push subscription, Exchange directly notifies the client application when an event occurs.

Using pull notifications with the EWS Managed API:

The following is an example that shows how to subscribe to pull notifications and how to retrieve the latest events.

PullSubscription subscription = service.SubscribeToPullNotifications(
       new FolderId[] { WellKnownFolderName.Inbox },5,null,
       EventType.NewMail, EventType.Created, EventType.Deleted);
 GetEventsResults events = subscription.GetEvents();
 

foreach (ItemEvent itemEvent in events.ItemEvents)
{
    switch (itemEvent.EventType)
    {
        case EventType.NewMail:
            EmailMessage message = EmailMessage.Bind(service, itemEvent.ItemId);
            break;
        case EventType.Created:
            Item item = Item.Bind(service, itemEvent.ItemId);
            break;
        case EventType.Deleted:
            Console.WriteLine("Item deleted: " + itemEvent.ItemId.UniqueId);
            break;
    }
}

Using push notifications with the EWS Managed API:

The EWS Managed API does not provide a built-in push notifications listener. It is the responsibility of the client application to implement such a listener.
I will not discuss the details of creating EWS notification listener as it is not the scope of this talk but I attached a full working solution that contains a simple WCF notification listener.

First,  decide which EWS items  you need to subscribe for, below we will subscribe for Email and calendar items:

List<FolderId> folderIds = new List<FolderId>()
{
     WellKnownFolderName.Inbox,
     WellKnownFolderName.Calendar
};


 

Second, choose the event type you need, for example (New Mail, Deleted items, …):

List<EventType> eventTypes = new List<EventType>();
eventTypes.Add(EventType.NewMail);
eventTypes.Add(EventType.Deleted);
eventTypes.Add(EventType.Moved);
eventTypes.Add(EventType.Created);
eventTypes.Add(EventType.Modified);

 

The last thing is to subscribe for push notification:

PushSubscription pushSubscription = 
          service.SubscribeToPushNotifications(folderIds,
          new Uri(listenerEndpoint),1, null, eventTypes.ToArray());

 

you can find a full working solution of all the examples discussed above here EWS.zip (1.60 mb).

Currently rated 4.0 by 2 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Pipelines are a component of Microsoft BizTalk Server that provides an implementation of the Pipes and Filters integration pattern. During the receiving and sending of messages, there are business reasons to perform transformations on messages to prepare them to enter or leave BizTalk Server. Pipelines enable the developer to define a series of transformations that will be performed on a message as it is being received or sent.

A common example is that you may need to compress your data before sending them to target subscriber in order to save server resources such as disk space or bandwidth, through this talk I'm going to discuss how to develop a general custom pipeline component to compress/decompress data. There are two types of pipelines, send and receive, and these match the ports in which they execute. Send pipelines are executed in send ports and in the response portion of a request/response receive port, while receive pipelines are executed in receive locations, and in the response portion of a solicit/response send port. Essentially, receive pipelines are intended to be used to transform messages that are being published to the MessageBox database, while send pipelines are intended to be used on messages which have been subscribed to and are being sent out of BizTalk Server.

 

Receive Pipeline Stages Send Pipeline Stages
   
  1. Decode: Decrypts or decodes the message data.
  2. Disassemble: Disassembles an interchange into smaller messages and parses message contents.
  3. Validate: Validates the message data, generally against a schema.
  4. Resolve Party: Identifies the BizTalk Server party associated with some security token in the message or message context.
  1. Pre-assemble: Performs any message processing necessary before assembling the message.
  2. Assemble: Assembles the message and prepares it to be transmitted by taking steps such as adding envelopes, converting XML to flat files, or other tasks complementary to the disassemble stage in a receive pipeline.
  3. Encode: Encodes or encrypts the message before delivery.

 

A general pipeline component gets one message from the BizTalk Messaging Engine, processes it, and returns it to the BizTalk Server engine, it implements the following interfaces:

  • IBaseComponent Interface: All pipeline components need to implement this interface to provide basic information about the component.
  • IComponent Interface: All pipeline components except assemblers and disassemblers implement this interface to get messages from the BizTalk Server engine for processing and to pass processed messages back to the engine.
  • IComponentUI Interface: Defines methods that enable pipeline components to be used within the Pipeline Designer environment.
  • IPersistPropertyBag Interface: Defines the methods to prepare for, load, and save the properties of pipeline components.


Now let's start developing our compression/decompression custom pipeline component:

  1. Open visual studio 2010 and create a new class library project then add a reference of BizTalk pipeline " Microsoft.BizTalk.Pipeline.dll" normally found under the "c:\Program Files\Microsoft BizTalk Server 2010\Microsoft.BizTalk.Pipeline.dll".
  2. Add new class to the project, name it "CompressionPipeline" that implements all the basic interfaces mentioned earlier, additional three class attributes will be added as follows:


                 [ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
         
           [System.Runtime.InteropServices.Guid("AAD4E8D2-3900-4542-88A7-43B6D6FE9080")]
                
    [ComponentCategory(CategoryTypes.CATID_Encoder)]
             
        public class CompressionPipeline : Microsoft.BizTalk.Component.Interop.IComponent,
              
            IBaseComponent, IPersistPropertyBag, IComponentUI

    •  The "ComponentCategory" associates our component with a specific pipeline design category in the pipeline designer which tells the designer that this class represents a pipeline component.
    • The "Guid" attribute supplies an explicit unique identifier to our class for COM registery and uniquely identify our component. Use visual studio tools to generate GUID by choosing "Tools" à "Create GUID".
    • The third attribute is used to tell the pipeline designer that this component is encoding component as we are developing a send pipeline.

  3. Before implementing each interface we will add a resource file that separates resources "Strings, Pictures, Icons, …" from our actual implementation code, add a new resource file, I will add some strings to the resource file that help us implementing our interfaces as can be observed from the below screenshot:

     



     
  4. First, I will implement IBaseComponent interface, which allows other developers and biztalk administrators to get information about our compression pipeline component, I will use the resources created in the previous step as follows:

    #region IBaseComponent Members
            
    public string Description
    {
          get { return CompressionPipelineResources.CompressionPipelineDescription; }
    }
            
    public string Name
    {
         get { return CompressionPipelineResources.CompressionPipelineName; }
    }
            
    public string Version
    {
         get { return CompressionPipelineResources.CompressionPipelineVersion; }
    }
            
    #endregion

  5. We will need an icon for our component so open the resource file again and choose "Images" or "Icons" and copy the desired image from your local disk drive to the resource file. This will drive us to implement IComponentUI as observed from the following code snippet:

    #region IComponentUI Members        
    public IntPtr Icon
           
    {
               
         get { return CompressionPipelineResources.ZIP.GetHicon(); }
           
    }
            
    public System.Collections.IEnumerator Validate(object projectSystem)
           
    {
               
         
    return null;
           
    }
             
    #endregion

    As you can see from the above code that I omitted the "validate" method which verifies that all of the configuration properties are set correctly. So I configured validation to null.

  6. .Net introduced compression capabilities since version 2.0 as a part of the data streaming framework, there are several frameworks available that give better performance than the one provided by .Net but I prefer to use compression algorithms provided by .Net in order to eliminate dependencies and to simplify deployments. .Net provides two algorithms GZip and Deflate so we want our BizTalk administrators to be able to select the compression algorithm, our custom pipeline contains a string property that holds the definition of the compression algorithm. BizTalk supports the persistence of the user configuration by implementing IPersistPropertyBag interface as follows:


     #region -- Properties --       
    [Browsable(true)]
           
    [Description("Controls the compression algorithm used to encode the output stream.")]
           
    [DefaultValue("GZip")]
           
    public string CompressionType
           
    {
               
          
    get;
           set;       
    }
           
    #endregion
     
    #region IPersistPropertyBag Members              
    public
    void GetClassID(out Guid classID)
                 
    {
                      
          
    classID = new System.Guid("AAD4E8D2-3900-4542-88A7-43B6D6FE9080");
                 
    }
                  
    public void InitNew()
                 
    {
                             
    }
                  
    public
    void Load(IPropertyBag propertyBag, int errorLog)
                 
    {
                      
          
    object val=null;
                      
          
    propertyBag.Read("CompressionAlgorithm", out val,0);
                      
          
    if
    (val != null) this.CompressionType = val.ToString();
                 
    }
                  
    public void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)
                 
    {
                     
          
    object val = this.CompressionType;
                     
          
    propertyBag.Write("CompressionAlgorithm",ref val);
                 
    }
                  
    #endregion

    As you can see that "Load" and "Save" methods are used to persist our compression algorithm type, using string is not the best practice for this functionality as the BizTalk administrator or our users could mistype or misspell the algorithm type. The best way is to use enumeration and limit the user to choose from a dropdown list to avoid any mistyping, doing this is out of my scope though this talk but you can refer to Saravana Kumar's talk " Understanding Design-Time Properties for Custom Pipeline Components.doc (1.22 mb)" also you can download the full resource from here.

  7. The last step for our pipeline is to  implement IComponent interface that does the actual stream compression work:

    #region IComponent Members               
    public Microsoft.BizTalk.Message.Interop.IBaseMessage Execute(
              Microsoft.BizTalk.Component.Interop.IPipelineContext pc, Microsoft.BizTalk.Message.Interop.IBaseMessage inmsg)
           
    {
               
          if (inmsg.BodyPart != null && inmsg.BodyPart.Data != null)
                
          
    {
                   
               
    try
                   
               
    {
                       
                     
    MemoryStream ms = new MemoryStream();
                      Stream inStr = inmsg.BodyPart.GetOriginalDataStream();
                      switch (this.CompressionType)
                      {
                            case "GZip":
                                using (GZipStream zipped = new GZipStream(ms, CompressionMode.Compress, true))
                                {
                                    inStr.Seek(0, SeekOrigin.Begin);
                                    inStr.CopyTo(zipped);
                                }
                                break;
                            case "Deflate":
                                using (DeflateStream zipped = new DeflateStream(ms, CompressionMode.Compress, true))
                                {
                                    inStr.Seek(0, SeekOrigin.Begin);
                                    inStr.CopyTo(zipped);
                                }
                                break;
                        }
                        inmsg.BodyPart.Data = ms;
                        inmsg.BodyPart.Data.Seek(0, SeekOrigin.Begin);
                        return inmsg;
                    }
                    catch (Exception ex)
                    {
                        throw new BTSException(string.Format(CompressionPipelineResources.CompressionExcpetion, ex.Message));
                    }
                }
                return inmsg;
            }        
    #endregion


    One important thing here is never to close the message input stream that's why we set the value of "LeaveOpen" of compression stream to "true" in order to leave the stream open after compression.

  8. The last step is to sign our custom class library "dll" then copy it to BizTalk pipeline components folder which is normally found under the path " C:\Program Files\Microsoft BizTalk Server 2010\Pipeline Components \".

Now our custom compression pipeline component is ready to be used by pipeline designer. The next step is to create a new BizTalk project to use our custom pipeline:

  1. Create a new BizTalk project then add a new send pipeline item, the pipeline designer will be opened. From the toolbox right click then click "Choose Item", from the tab control choose "BizTalk Pipeline Components", you will find our compression custom pipeline component, choose it and drag the component from the toolbox to the "Encode" area as below:
  2.  





  3. The second step is to deploy our custom compression pipeline to our BizTalk server to be ready for use, right click on the BizTalk project and click properties then choose "Deployment" from property page and setup your target server as well as your target BizTalk application as can be seen from the below screenshot:





  4. You will have to sign the project, click "Signing" and create a new key then save your settings and close the properties, then right click on the project and choose "Deploy".
  5. Now our pipeline component can be used from BizTalk server, open BizTalk administration console then explore "BizTalk Application 1" you will find our custom compression component exists in pipelines artifacts. Create a new send port and configure it to use our compression pipeline component as below:



We can test our pipeline component by using visual studio in case of BizTalk unavailability by following the below steps:

  1. Load the custom pipeline project solution into Visual Studio.
  2. Change the output path for your solution to <Installation Folder>\Pipeline Components. In Solution Explorer, right-click your project, click the Build tab, and then change the Output Path by clicking the Browse button and selecting the <Installation Folder>\Pipeline Components directory.
  3. Change the start action for your solution. In Solution Explorer, right-click your project, click the Debug tab, click Start external program, then click … and navigate to <Installation Folder>\SDK\Utilities\PipelineTools and choose Pipeline.exe. Under Start Options, enter the command line arguments appropriate for your component for example [<Path>\YourPipeline.btp -d <Path>\YourTestFile.txt -c]
  4. Set your breakpoints if you want to debug the pipeline component.
  5. Press Ctrl + F5 to start testing our just F5 to begin debugging.

Now we have finished our compression pipeline component, you can follow the same steps to create decompression for a receiving pipeline also there is a great tool for creating custom pipeline component which adds a new pipeline project template for Visual Studio, you can find it on codeplex "BizTalk Server Pipeline Component Wizard" .
I have attached the full solution that contains both compression/decompression pipelines here [Intellecting.CustomPipelines.zip (161.46 kb)].

Currently rated 4.5 by 2 people

  • Currently 4.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

It has been a long time since my last talk about using service broker for notification of data changes, let's continue what we have started. The first thing we need to do here is how to receive a message from service broker. SQL server has a special command for retrieving a message from service broker:

Declare @FetchSize int = 1          -- Number of messages to get in a single fetch process
Declare @TimeToWait int = 5000      -- Time to wait for messages to be received (for example, 5 Seconds)

WAITFOR ( RECEIVE TOP(@FetchSize) * FROM NotificationQueue), TIMEOUT @TimeToWait

As you observe from the above query that you can control the number of messages to fetch and the blocking timeout for a message to be received, if you have multiple messages submitted through one conversation handle then you can do batch receive but if one message is submitted then you can only receive one message at a time.
Now after we got our message from service broker, we want to prepare it to be audited. We supposed to transform the message body to XML format and use SQL server XML capabilities to transform the XML into SQL data.
I will create audit trace table with the following structure:

create table AuditTrace
(
   AuditTraceID bigint primary key identity(1,1),
   [User] nvarchar(200),
   [Process] int,
   [HostName] nvarchar(200),
   [Time] datetime,
   [TargetTable] nvarchar(200),
   [Action] varchar(10),
   [OldData] xml,
   [NewData] xml
)

 Here are the steps to fill the above table from the parsed message received from SQL server service broker:

  1. First of all we will we cast the message body to XML format as follows:

    DECLARE @PayLoad xmlWAITFOR (RECEIVE TOP(1) @PayLoad = message_body FROM NotificationQueue), TIMEOUT 5000

  2. Then we prepare the message payload by letting SQL server parse the message and convert it to parsed XML document ready for consumption:

    DECLARE @idoc intEXEC sp_xml_preparedocument @idoc OUTPUT, @PayLoad

  3. Now I will use OPENXML to open the document and start processing it by parsing the message header into SQL data format and insert the data to our audit trace table as follows:

    insert into  AuditTrace ([User],[Process],[HostName],[Time],[TargetTable],[Action]) SELECT * FROM OPENXML(@idoc, N'//NotificationMessage/Header') 
    with ([User] nvarchar(200),Process int,HostName nvarchar(200),Time datetime,TargetTable nvarchar(200),MessageType varchar(10))

  4. By using XQuery I will update our previously inserted row in the audit trace to amend the old and the new changed data:

    Declare @Key bigint = SCOPE_IDENTITY()
    Update audittrace set NewData = @PayLoad.query(N'//NotificationMessage/New'), olddata = @PayLoad.query(N'//NotificationMessage/Old')
    where AuditTraceid = @Key

  5. The last thing to do here is to clean up the memory to close the previously opened XML document:

    EXEC sp_xml_removedocument @idoc


 By completing these tasks we will have a audit data in our audit trace data table like screen-shot below :

 

I want to consider another situation where we want to audit the changed data to another table in our database that has the same schema as the production table but is suffixed with (Audit), I could do this by using complex XML with some SQL server temp tables and some loops, instead I will use SQL CLR to achieve my needs, The common language runtime (CLR) integration feature is off by default, and must be enabled in order to use objects that are implemented using CLR integration. To enable CLR integration, use the clr enabled option of the sp_configure stored procedure:

sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
GO
sp_configure 'clr enabled', 1;
GO
RECONFIGURE;
GO

To create our CLR stored procedure, open Visual Studio 2010 and create new database project, choose your preferred language (I will take C#) as you can see below:

 

After creating the project you will need to choose your database in which deployment will take place, then add a new stored procedure which will contain our parsing and auditing code as follows:

[Microsoft.SqlServer.Server.SqlProcedure]
public static void ParseAuditMessage(string XMLBody)
{
        string MESSAGE_HEADER_NAME= "Header";
        string MESSAGE_NEW_NAME = "New";
        MemoryStream st = new MemoryStream();StreamWriter wr = new StreamWriter(st);
        wr.Write(XMLBody);
        wr.Flush();
        st.Seek(0, SeekOrigin.Begin);
        DataSet ds = new DataSet();
        ds.ReadXml(st);
        string TargetTable = ds.Tables[MESSAGE_HEADER_NAME].Rows[0]["TargetTable"].ToString();
        foreach (DataRow DataRowTmp in ds.Tables[MESSAGE_NEW_NAME].Rows)
        {
            
using (SqlConnection connection = new SqlConnection("context connection=true"))
            {
                StringBuilder CommandString = new StringBuilder();
                StringBuilder TargetsString = new StringBuilder();
                StringBuilder ParametersString = new StringBuilder();
                SqlCommand tmpCommand = new SqlCommand("",connection);
               
CommandString.AppendFormat("INSERT INTO {0}audit ", TargetTable);
                foreach (DataColumn col in DataRowTmp.Table.Columns)
                {
                    TargetsString.AppendFormat("{0},", col.ColumnName);
                    ParametersString.AppendFormat("@{0},", col.ColumnName);
                    tmpCommand.Parameters.AddWithValue(string.Format("@{0}", col.ColumnName), DataRowTmp[col]);
                }
                if (TargetsString.Length > 0)
                    TargetsString = TargetsString.Remove(TargetsString.Length - 1, 1);
                if (ParametersString.Length > 0)
                    ParametersString = ParametersString.Remove(ParametersString.Length - 1, 1);
                CommandString.AppendFormat(" ({0}) VALUES ({1}) ", TargetsString.ToString(), ParametersString.ToString());
                tmpCommand.CommandText = CommandString.ToString();
                connection.Open();
                tmpCommand.ExecuteNonQuery();
            }
        }
    } 

 

Build your project then click deploy which will deploy you assembly and creates a new stored procedure to your target database chosen earlier. Now let's move to SQL server, first we will need to create a new stored procedure that will receive notification message from server broker and passes it to our CLR procedure for auditing:

Create PROCEDURE [dbo].[NotificationQueueActiviation]
AS
Declare @FetchSize int = 1
Declare @TimeToWait int = 5000
Declare @MsgBody nvarchar(max)
BEGIN
BEGIN TRANSACTION;
    WAITFOR ( RECEIVE TOP(@FetchSize) @MsgBody = message_body FROM NotificationQueue), TIMEOUT @TimeToWait
    
IF (@@ROWCOUNT = 0)
    BEGIN
      ROLLBACK TRANSACTION;
    
END
    EXECUTE ParseAuditMessage @MsgBody 
COMMIT TRANSACTION;
END

The last thing we will need to do is to alter our service broker queue to use internal activation as follows:

ALTER QUEUE NotificationQueue
    WITH ACTIVATION
    ( STATUS = ON,
      PROCEDURE_NAME = NotificationQueueActiviation,
      MAX_QUEUE_READERS = 20,
      EXECUTE AS SELF
    );
GO

 

Now if we run any CRUD operation against our database then we will have our log as you can see below:

You can find the SQL server CLR project which contains a simple parsing and auditing mechanism here. AuditProc.zip (25.18 kb) 

Currently rated 4.8 by 4 people

  • Currently 4.75/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Service Broker helps developers build asynchronous, loosely coupled applications in which independent components work together to accomplish a task. These application components exchange messages that contain the information that is required to complete the task. Service Broker's features provide a number of significant benefits to database applications. These features and benefits include:

  • Conversations: Service broker is all about messaging, two entities can exchanges messages without caring about the low level communication infrastructure; conversation is a reliable, persistent communication channel that guarantees message delivery.
  • Message ordering and coordination: Service Broker queues are integrated into the database which means regular database maintenance and administration also included and handles the most difficult tasks involved in writing messaging applications. These difficult tasks include message coordination, reliable message delivery, locking, and starting queue readers.
  • Transactional asynchronous programming: Message delivery between applications is transactional and asynchronous; if your application transaction rolls back, all Service Broker operations in the transaction roll back. In asynchronous delivery, the Database Engine handles delivery while the application continues to run.
  • Support for loosely coupled applications: SOA is about messaging and service broker is built using messaging infrastructure so it supports loosely coupled applications. Your applications do not need to run at the same time and do not have to know the physical location or the implementation of the other participants in the conversation.

The following illustration presents a high-level view of Service Broker network communication between two SQL Server instances:

Notice that the conversation is a persistent, logical connection. The conversation can occur over any period of time, and during that period of time, the conversation can use any number of network connections.

Network connections occur between two Service Broker endpoints. These connections use TCP/IP. If the connection is inactive for a short time, SQL Server closes the network connection. To deliver a message, Service Broker holds the message in the transmission queue for the database that sent the message. The recipient delivers the message directly to the queue for the destination service. The queue for the sending service is not involved in the operation.

Let's now but service broker into practical example which is  a generic database CRUD auditing. The first thing you will need to do is to enable service broker for your database and this could be done by using the following SQL command:

alter database [DataBaseName] set enable_broker

This option activates Service Broker message delivery, preserving the existing Service Broker identifier for the database. if you have an old database that uses service broker then you will need to generate a new identifier for your service broker:

alter database [DataBaseName] set new_broker

Now it's time to create our service broker objects, primary I will create a template message type to hold the actual data payload for exchange during a certain conversation. A message type defines the name of a message and the validation that Service Broker performs on messages that have that name:

CREATE MESSAGE TYPE [//intellecting.net/Notification] VALIDATION = NONE ;

Note that the message type specifies a validation type of NONE because the message will contain data that is not well-formed XML or it might binary data.

Then I will create service broker contract which defines the name of a specific business task and list the message types used in that task. Service Broker contracts define two different service roles: the initiator and the target. The initiator of a conversation begins the conversation by sending a message to the target. The contract that the conversation uses defines which service role can send messages of a given message type:

CREATE CONTRACT [//intellecting.net/INotification] ([//intellecting.net/Notification] SENT BY INITIATOR);

The above SQL command indicates that only the initiator of the conversation can send messages of the specified message type which means that we defined a one way contract.

Now I will create a service broker Queue which stores messages, when Service Broker receives a message for a service, Service Broker inserts the message into the queue for that service. Service Broker manages queues and presents a view of a queue that is similar to a table:

CREATE QUEUE NotificationQueue WITH STATUS = ON;

A queue may be associated with a stored procedure. In this case, SQL Server activates the stored procedure when there are messages in the queue to be processed. SQL Server can start more than one instance of the stored procedure up to a configured maximum as shown below:

CREATE QUEUE AuditQueue WITH STATUS = ON,
      ACTIVATION (
            PROCEDURE_NAME = AuditMessage,
            MAX_QUEUE_READERS = 20,
            EXECUTE AS SELF )

As you can notice from the above SQL code that the activation stored procedure is defined with a maximum of 20 instance and will execute as the current user.

If your auditing database will lay on the same server with the OLTP database then it is preferable to use  internal activation with stored procedures as it is a common way to design Service Broker applications and of course your stored procedure will contain the routine needed to audit your data changes.

Finally we will need to create a service which represents a business process as a distinct set of tasks, each contract within the service represents a specific task. Each service uses a queue to store messages, messages sent to the service are delivered to the queue:

CREATE SERVICE NotificationService
       ON QUEUE NotificationQueue
      ([//intellecting.net/INotification])

If your auditing database will be on another SQL server then you will need to configure your service broker instance to use message routing, initially we will create service broker endpoint that exposes our service capabilities to the outside world:

CREATE ENDPOINT AuditBrokerEndpoint
    STATE = STARTED
    AS TCP ( LISTENER_PORT = 5000 )
    FOR SERVICE_BROKER ( AUTHENTICATION = WINDOWS ) ;

After configuring the endpoint, you must set up security between the distributed Service Broker services. By default, Service Broker doesn’t allow two Service Broker services to communicate without configured security. For transport security I will use Windows Authentication especially if the two service broker instances are registered in the same domain and  It would even work if you have a trusted relationship between two Windows domains.

You need to run your SQL server instance with a certain domain account, for example intellecting\ahmedamir, you can do this easily through Services MMC snap-in. you now have to create SQL Server logins for the SQL Server that communicates with you. Note that this login represents the service account of the remote SQL Server machine, not the service account of the instance where you’re creating the Service Broker endpoint:

CREATE LOGIN [intellecting\AuditAccount] FROM WINDOWS;

You will need to grant connect and send permission for your service as follows:

GRANT CONNECT ON ENDPOINT::AuditBrokerEndpoint TO [intellecting\AuditAccount];
GRANT SEND ON SERVICE::[NotificationService] TO PUBLIC;
 

After configuring the endpoint and service security, we need to instruct Service Broker to route our sender messages to a certain SQL server instance, so on the sender service broker instance we will configure our routing as follows:

CREATE ROUTE AuditBrokerService WITH
      SERVICE_NAME = 'AuditBrokerService',
     
ADDRESS = 'TCP://targetserver:5000'

By creating all service broker objects; our messaging infrastructure is ready for message exchange through a single SQL server instance our by using multiple SQL server instances. Now let us try to send message to our service broker, I will create the service initiator; the following stored procedure opens a conversation to our service and submit the XML message to the service queue:

CREATE PROCEDURE [dbo].[AduitMessage] @Message xml
AS
BEGIN
      BEGIN TRY
            BEGIN TRANSACTION
                  DECLARE @ch UNIQUEIDENTIFIER
              
    BEGIN DIALOG CONVERSATION @ch
                  FROM SERVICE NotificationService
                  TO SERVICE 'NotificationService'
                  ON CONTRACT [//intellecting.net/INotification] WITH ENCRYPTION = OFF;
               
   SEND ON CONVERSATION @ch MESSAGE TYPE [//intellecting.net/Notification] (@Message);
            COMMIT;
      END TRY
      BEGIN CATCH
            ROLLBACK TRANSACTION

      END CATCH
END

As can be observed from the above code that no message encryption is used and the procedure sends a message to a local service queue, if you want to send a message to another SQL server instance then you will just use the name of the service in the Service Broker Route defined earlier.

The question now is how to format your audit message with the hooked data and how to use the above stored procedure to send your audit messages to service broker. In order to hook any data changes you will need to create SQL AFTER trigger to capture data changes, you can create three trigger; for insert, Update and delete but I will consolidate them to one trigger as follows:

CREATE TRIGGER [dbo].[tr_Orders_Audit] ON [dbo].[Orders]
FOR INSERT,UPDATE,DELETE
AS
BEGIN
  DECLARE @Message XML
  
DECLARE @TableName varchar(MAX) = 'Orders'
  
DECLARE @ColumnsUpdated varbinary(MAX) = COLUMNS_UPDATED()
  
DECLARE @InsertedCount int = (SELECT COUNT(1) FROM INSERTED)
  
DECLARE @DeletedCount int = (SELECT COUNT(1) FROM DELETED)
  
DECLARE @ChangedCloumns XML = (SELECT COLUMN_NAME AS Name,DATA_TYPE as Type,
                                
sys.fn_IsBitSetInBitmask(@ColumnsUpdated,COLUMNPROPERTY(
                                
OBJECT_ID(TABLE_SCHEMA + '.' + TABLE_NAME),COLUMN_NAME, 'ColumnID')) as IsUpdate
                                 FROM INFORMATION_SCHEMA.COLUMNS Field WHERE TABLE_NAME = @TableName FOR XML RAW('Column'))

 
 IF @InsertedCount > 0 AND  @DeletedCount <= 0 -- SQL Insert
 
BEGIN
      DECLARE @MessageType varchar(MAX) = 'INSERT'
      DECLARE @NewData XML = (Select * from INSERTED FOR XML RAW('New'))
 
END

 
 IF @InsertedCount > 0 AND  @DeletedCount > 0 -- SQL Update
 
BEGIN
      SET @MessageType = 'UPDATE'     
     
SET @NewData =  (Select * from INSERTED FOR XML RAW('New'))
      DECLARE @OldData XML  = (SELECT * from DELETED FOR XML Raw('Old'))
 
END
 
 
 IF @InsertedCount <= 0 AND  @DeletedCount > 0 -- SQL Delete
 
BEGIN
      SET @MessageType = 'DELETE'
      SET @ChangedCloumns = (select Column_Name AS Name,Data_Type as Type,0  as  IsUpdate 
                             from
 INFORMATION_SCHEMA.COLUMNS where table_name=@TableName FOR XML RAW('Column'))
      SET @OldData = (SELECT * from DELETED FOR XML RAW('Old'))
 
END

 
 DECLARE @Header XML = (Select SUSER_SNAME() AS [User],@@SPID as Process, HOST_NAME() AS [HostName],GETDATE() AS [Time],
                        
@TableName AS TargetTable,@MessageType as MessageType FOR XML RAW('Header'))
  
SET @Message = (Select @Header,@ChangedCloumns,@NewData,@OldData for XML RAW('NotificationMessage'))
 
exec AduitMessage @Message
END


As can be noticed from the above code that our trigger is divided into multiple parts:

  1. In the first part, we dynamically detect the changed columns of target table by calling SQL server trigger function 'COLUMNS_UPDATED()' which in turn returns a bitmask of the changed columns according to column order, then we pass this bit mask to system function to get the names of the changed columns and finally we format the result by using XML.
  2. In the second part, we detect the trigger action [Insert, Update or Delete] by selecting the number of rows in the INSERTED & DELETED tables and according to the trigger type we format the data changes by using XML.
  3. In the third part, we collect information about the user and actions to be our message header and then we combine all the formatted data generated by the first two steps to produce the final message to be audited.
  4. In the final part, we just submit the formatted XML message to our audit procedure which in turn sends the message to service broker infrastructure.


Now I can say that we finished the first part of creating our auditing system, the remaining is how to get your XML messages from service broker (in case of distributed brokers) and how to parse them to produce our audit data. I will introduce message receiving and parsing in my next talk soon.

I attached the SQL server database that contains all the code discussed here. DATA.zip (264.13 kb)

Currently rated 4.7 by 3 people

  • Currently 4.666667/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5