MSMQ – Basic Console application testing some basic functionalities

Posted by admin on July 25th, 2010

Currently I'm doing some r&d on MSMQ and how to use it in an asp.net application. I started with these two links which explain the basics of MSMQ:

I created a small .NET Console Application to test out some of the examples myself. It's basically a lot the same code (some of it is exactly the same), but it made me easily change some settings to check how they worked together. Especially transactions interested me because I'll need them for my final code in our product.

This is the source project: MSMQ Basic - TryOut

Next is the code. I added explanations in the code itself. But you really should read the MSDN documentation as it is very clear. I haven't tried out journaling yet because you need two computers to test that, so I'll do that later on @work. Next up on r&d is MSMQ support in Spring.NET and after that checking a JMS (Java Message Service) implementation (and it's integration in Spring.NET).

C#:
  1. using System;
  2. using System.Messaging;
  3. using System.Transactions;
  4.  
  5. namespace MSMQ
  6. {
  7.     /// <summary>
  8.     /// @Author: Lieven Cardoen
  9.     /// This little program tests some of the functionalities of MSMQ.
  10.     /// Sending messages synchronously and asynchronously are covered.
  11.     /// Transactions are also covered, internally by MSMQ and externally by DTC.
  12.     /// A next step would be to use some threads, but this was just some minor R&D
  13.     /// that I had to do for our product Edumatic Exam.
  14.     /// Documentation:
  15.     /// http://msdn.microsoft.com/en-us/library/ms978425.aspx
  16.     /// http://msdn.microsoft.com/en-us/library/ms978430.aspx
  17.     /// </summary>
  18.     public class MsmqTryOut
  19.     {
  20.         protected MessageQueue MessageQueue;
  21.         private const string NonTransMessageQueueName = ".\\private$\\finishedSessionsNonTrans";
  22.         private const string IntTransMessageQueueName = ".\\private$\\finishedSessionsIntTrans";
  23.         private const string ExtTransMessageQueueName = ".\\private$\\finishedSessionsExtTrans";
  24.         private const string AckQueueName = ".\\private$\\Ack";
  25.         private const string OrdersQueueName = ".\\private$\\Orders";
  26.  
  27.         /// <summary>
  28.         /// Main entry point
  29.         /// </summary>
  30.         static void Main()
  31.         {
  32.             var program = new MsmqTryOut();
  33.            
  34.         //NON TRANSACTIONAL QUEUE --> SYNC AND ASYNC MESSAGES   
  35.             program.SetUpQueue(NonTransMessageQueueName, false);
  36.             program.SendMessage(String.Format("Message sent at {0}", DateTime.Now)
  37.                 , "Non-Transactional sync message (first message)", MessageQueueTransactionType.None);
  38.             program.SendMessage(String.Format("Message sent at {0}", DateTime.Now)
  39.                 , "Non-Transactional async message (second message)", MessageQueueTransactionType.None);
  40.             /**
  41.              * Program would hang until a message is delivered (if no messages are in the queue
  42.              * and none are added anymore, program would hang forever).
  43.              * Here first message will be received
  44.              */
  45.             program.ReceiveSyncMessage(MessageQueueTransactionType.None);
  46.             /**
  47.              * Program wouldn't hang forever. Handler would be triggered when new message in queue.
  48.              * Here second message will be received. If second message is received, the queue will be deleted
  49.              * in the async handler.
  50.              */
  51.             program.ReceiveAsyncMessage();
  52.            
  53.            
  54.         //INTERNAL TRANSACTIONS (MSMQ)
  55.             program.SetUpQueue(IntTransMessageQueueName, true);
  56.             //Start the internal msmq transaction
  57.             var msgTx = new MessageQueueTransaction();
  58.             msgTx.Begin();
  59.             program.SendMessage(String.Format("Message sent at {0}", DateTime.Now), "Internal MSMQ Transactional sync Message", msgTx);
  60.             program.SendMessage(String.Format("Message sent at {0}", DateTime.Now), "Internal MSMQ Transactional sync Message", msgTx);
  61.             msgTx.Commit();
  62.  
  63.             //Start new transaction
  64.             msgTx.Begin();
  65.             program.ReceiveSyncMessage(msgTx);
  66.             program.ReceiveSyncMessage(msgTx);
  67.             Console.WriteLine("----------------------------------------------------------------------------------");
  68.             Console.WriteLine("Dispose transaction");
  69.             msgTx.Dispose();
  70.             //Two messages should still be available because previous receives were disposed
  71.             program.ReceiveSyncMessage(msgTx);
  72.             program.ReceiveAsyncMessage(); //queue will be deleted in the async handler
  73.            
  74.  
  75.         //TRANSACTIONAL QUEUE WITH EXTERNAL TRANSACTIONS (DTC)
  76.             program.SetUpQueue(ExtTransMessageQueueName, true);
  77.             //If it's a transactional queue, then Single needs to be used if the message
  78.             //isn't send in a transaction
  79.             program.SendMessage(String.Format("Message sent at {0}", DateTime.Now), "DTC Transactional sync Message", MessageQueueTransactionType.Single);
  80.             program.SendMessage(String.Format("Message sent at {0}", DateTime.Now), "DTC Transactional sync Message", MessageQueueTransactionType.Single);
  81.             using (var scope = new TransactionScope())
  82.             {
  83.                 //If a transaction is used and the queue is externally transactional (DTC),
  84.                 //then Automatic needs to be used
  85.                 program.ReceiveSyncMessage(MessageQueueTransactionType.Automatic);
  86.                 program.ReceiveSyncMessage(MessageQueueTransactionType.Automatic);
  87.                 //ReceiveAsync();   //!!! Apparently this won't be disposed. + error is thrown when async
  88.                                     //handler is triggered because the MessageQueue is already deleted.
  89.                 Console.WriteLine("----------------------------------------------------------------------------------");
  90.                 Console.WriteLine("Dispose transaction");
  91.                 scope.Dispose();
  92.             }
  93.             //Two messages should still be available
  94.             program.ReceiveSyncMessage(MessageQueueTransactionType.Single);
  95.             program.ReceiveAsyncMessage(); //queue will be deleted in the async handler
  96.  
  97.         //ACKNOWLEDGEMENTS
  98.             ReceiveAcknowledgementMessages();
  99.             DeleteQueue(OrdersQueueName);
  100.             DeleteQueue(AckQueueName);
  101.            
  102.  
  103.             //Pause the program
  104.             Console.ReadKey();
  105.         }
  106.  
  107.         /// <summary>
  108.         /// Tests acknowledgements
  109.         /// </summary>
  110.         private static void ReceiveAcknowledgementMessages()
  111.         {
  112.             Console.WriteLine("----------------------------------------------------------------------------------");
  113.             if (!MessageQueue.Exists(OrdersQueueName)) MessageQueue.Create(OrdersQueueName);
  114.             if(!MessageQueue.Exists(AckQueueName)) MessageQueue.Create(AckQueueName);
  115.             //Send Message
  116.             var msgQ = new MessageQueue(OrdersQueueName)
  117.                                     {
  118.                                         DefaultPropertiesToSend =
  119.                                             {
  120.                                                 AcknowledgeType = AcknowledgeTypes.FullReachQueue |
  121.                                                                   AcknowledgeTypes.FullReceive,
  122.                                                 AdministrationQueue = new MessageQueue(AckQueueName)
  123.                                             }
  124.                                     };
  125.             msgQ.Send("Sample");
  126.  
  127.             //Receive Message
  128.             var msg = msgQ.Receive(new TimeSpan(0), MessageQueueTransactionType.Single);
  129.             if (msg != null) Console.WriteLine("Message Received {0}", msg.Id);
  130.  
  131.             //Receive Acknowledgement Messages
  132.             var adminQ = new MessageQueue(AckQueueName)
  133.                              {
  134.                                  MessageReadPropertyFilter = {CorrelationId = true}
  135.                              };
  136.             do
  137.             {
  138.                try
  139.                {
  140.                   msg = adminQ.Receive(new TimeSpan(0,0,5));
  141.                   if (msg != null && msg.MessageType == MessageType.Acknowledgment)
  142.                      switch(msg.Acknowledgment)
  143.                      {
  144.                         case Acknowledgment.ReachQueue:
  145.                            Console.WriteLine("Message Reached Queue {0}", msg.CorrelationId);
  146.                            break;
  147.                         case Acknowledgment.Receive:
  148.                            Console.WriteLine("Message Received {0}", msg.CorrelationId);
  149.                            break;
  150.                      }
  151.                }
  152.                catch(Exception e)
  153.                {
  154.                   Console.WriteLine(e.Message);
  155.                   msg = null;
  156.                }
  157.             }
  158.             while (msg != null);
  159.         }
  160.  
  161.         /// <summary>
  162.         /// Get a message out of the queue synchronously
  163.         /// </summary>
  164.         /// <param name="messageQueueTransactionType">Type of transaction to be used</param>
  165.         private void ReceiveSyncMessage(MessageQueueTransactionType messageQueueTransactionType)
  166.         {
  167.             Console.WriteLine("----------------------------------------------------------------------------------");
  168.             var message = MessageQueue.Receive(messageQueueTransactionType);
  169.             if (message != null)
  170.             {
  171.                 message.Formatter = new XmlMessageFormatter(new[] {typeof (String)});
  172.                 var text = message.Body as String;
  173.                 var label = message.Label;
  174.                 Console.WriteLine("Received sync message (type:" + messageQueueTransactionType + ")");
  175.                 Console.WriteLine("Text:" + text);
  176.                 Console.WriteLine("Label:" + label);
  177.             }
  178.             else
  179.             {
  180.                 Console.WriteLine("ERROR: Message was null");
  181.             }
  182.         }
  183.  
  184.         /// <summary>
  185.         /// Get a message out of the queue synchronously
  186.         /// </summary>
  187.         /// <param name="mgsTx">The MessageQueueTransaction to be used</param>
  188.         private void ReceiveSyncMessage(MessageQueueTransaction mgsTx)
  189.         {
  190.             Console.WriteLine("----------------------------------------------------------------------------------");
  191.             var message = MessageQueue.Receive(mgsTx);
  192.             if (message != null)
  193.             {
  194.                 //We'll use the XmlMessageFormatter, but you could also use the binary formatter
  195.                 message.Formatter = new XmlMessageFormatter(new[] { typeof(String) });
  196.                 var text = message.Body as String;
  197.                 var label = message.Label;
  198.                 Console.WriteLine("Received sync message (type:" + mgsTx + ")");
  199.                 Console.WriteLine("Text:" + text);
  200.                 Console.WriteLine("Label:" + label);
  201.             }
  202.             else
  203.             {
  204.                 Console.WriteLine("ERROR: Message was null");
  205.             }
  206.         }
  207.  
  208.         /// <summary>
  209.         /// Get a message out of the queue asynchronously.
  210.         /// If messages are very large and network is slow for instance, then it
  211.         /// take some time to get the message. If async is used, program will not
  212.         /// hang.
  213.         /// </summary>
  214.         private void ReceiveAsyncMessage()
  215.         {
  216.             MessageQueue.ReceiveCompleted += ReceiveCompletedHandler;
  217.             MessageQueue.BeginReceive();
  218.         }
  219.  
  220.         /// <summary>
  221.         /// Asynchronous message handler
  222.         /// </summary>
  223.         /// <param name="sender">The MessageQueue</param>
  224.         /// <param name="args">Arguments (message can be accessed through the arguments</param>
  225.         private static void ReceiveCompletedHandler(Object sender, ReceiveCompletedEventArgs args)
  226.         {
  227.             Console.WriteLine("----------------------------------------------------------------------------------");
  228.             var message = args.Message;
  229.             //We'll use the XmlMessageFormatter, but you could also use the binary formatter
  230.             message.Formatter = new XmlMessageFormatter(new[] { typeof(String) });
  231.             var text = message.Body as String;
  232.             var label = message.Label;
  233.             Console.WriteLine("Received async message");
  234.             Console.WriteLine("Can be received after queue is deleted.");
  235.             Console.WriteLine("Text:" + text);
  236.             Console.WriteLine("Label:" + label);
  237.             var messageQeueue = sender as MessageQueue;
  238.  
  239.             //We delete the queue when the async message is received
  240.             //This is weird, but it makes things simple
  241.             if(messageQeueue != null) DeleteQueue(messageQeueue.Path);
  242.             else Console.WriteLine("ERROR: messageQueue was null");
  243.         }
  244.  
  245.         /// <summary>
  246.         /// Sets up the queue. If queue doesn't exist, it is created.
  247.         /// </summary>
  248.         /// <param name="messageQueueName">Name of the queue</param>
  249.         /// <param name="transactional">Boolean whether queue should be transactional or not</param>
  250.         private void SetUpQueue(string messageQueueName, bool transactional)
  251.         {
  252.             Console.WriteLine("----------------------------------------------------------------------------------");
  253.             Console.WriteLine("SetUpQueue(" + messageQueueName + Environment.NewLine + ", "
  254.                 + (transactional ? "Transactional" : "Non-Transactional") + ")");
  255.             if (!MessageQueue.Exists(messageQueueName))
  256.             {
  257.                 try
  258.                 {
  259.                     MessageQueue = MessageQueue.Create(messageQueueName, transactional);
  260.                 }
  261.                 catch (Exception createException)
  262.                 {
  263.                     //Error could occur creating queue if the code does
  264.                     //not have sufficient permissions to create queues.
  265.                     throw new Exception("Error Creating Queue", createException);   
  266.                 }
  267.             }
  268.             else
  269.             {
  270.                 try
  271.                 {
  272.                     //Warning: If existing queue has a transactional property that
  273.                     //is not equal to the transaction argument of the funtion
  274.                     //the queue should actually be recreated.
  275.                     MessageQueue = new MessageQueue(messageQueueName);
  276.                 }
  277.                 catch(Exception getException)
  278.                 {
  279.                     throw new Exception("Error Getting Queue", getException);
  280.                 }
  281.             }
  282.             //Gets or sets a value that indicates whether the message is guaranteed
  283.             //to be delivered in the event of a computer failure or network problem.
  284.             MessageQueue.DefaultPropertiesToSend.Recoverable = true;
  285.         }
  286.  
  287.         /// <summary>
  288.         /// Deletes a queue
  289.         /// </summary>
  290.         /// <param name="messageQueueName">Name of the queue</param>
  291.         private static void DeleteQueue(string messageQueueName)
  292.         {
  293.             Console.WriteLine("----------------------------------------------------------------------------------");
  294.             Console.WriteLine("DeleteQueue(" + messageQueueName + ")");
  295.             try
  296.             {
  297.                 MessageQueue.Delete(messageQueueName);
  298.             }
  299.             catch (Exception e)
  300.             {
  301.                 throw new Exception("Deletion Failed", e);
  302.             }
  303.         }
  304.  
  305.         /// <summary>
  306.         /// Sends a message to the queue
  307.         /// </summary>
  308.         /// <param name="textMessage">Text message</param>
  309.         /// <param name="label">Label of the message</param>
  310.         /// <param name="type">Type of transaction</param>
  311.         private void SendMessage(string textMessage, string label, MessageQueueTransactionType type)
  312.         {
  313.             Console.WriteLine("----------------------------------------------------------------------------------");
  314.             Console.WriteLine("SendMessage(" + textMessage + Environment.NewLine + ", " + label + ", " + type);
  315.             try
  316.             {
  317.                 var message = new Message {Body = textMessage, Recoverable = true};
  318.                 MessageQueue.Send(message, label, type);
  319.             }
  320.             catch (Exception e)
  321.             {
  322.                 throw new Exception("Error sending message to Queueu", e);
  323.             }
  324.         }
  325.  
  326.         /// <summary>
  327.         /// Sends a message to the queue
  328.         /// </summary>
  329.         /// <param name="textMessage">Text message</param>
  330.         /// <param name="label">Label of the message</param>
  331.         /// <param name="msgTx">The MessageQueueTransaction to be used</param>
  332.         private void SendMessage(string textMessage, string label, MessageQueueTransaction msgTx)
  333.         {
  334.             Console.WriteLine("----------------------------------------------------------------------------------");
  335.             Console.WriteLine("SendMessage(" + textMessage + Environment.NewLine + ", " + label + ", " + msgTx);
  336.             try
  337.             {
  338.                 var message = new Message { Body = textMessage, Recoverable = true };
  339.                 MessageQueue.Send(message, label, msgTx);
  340.             }
  341.             catch (Exception e)
  342.             {
  343.                 throw new Exception("Error sending message to Queueu", e);
  344.             }
  345.         }
  346.     }
  347. }

<

Flex Project in IntelliJ with Charts

Posted by admin on May 30th, 2010

This year, my brother graduates as Civil Engineer. For his thesis, he needed a program that calculated the different criteria for building roads. Charts were ideal to show why certain materials were better and how they compared to each other. Starting from an Excel file, we first created the model, then implemented all the calculations and finished with creating tables and charts. Thanks to the help of my brother, we were able to create this application on short notice.

Normally I work with FlexBuilder, but for this project I wanted to experiment with IntelliJ. I already had experimented with BlazeDs in IntelliJ, but I had never really created a project. IntelliJ is great, but it's really hard to compare it with FlexBuilder because the project I'm doing in FlexBuilder is way bigger. I would need to check FlashBuilder to be able to compare the features and import the big project in IntelliJ to see how compiling compares to each other.

The new Spark architecture was strange in the beginning, especially because now you have all these namespaces mixing together. Mx, s, fx, ... I really should check some online resources and videos because now I just experimented while doing the project. Not a lot of time to go and search for things, best practices, ...

No backend was used, but setting BlazeDs (or GraniteDs) up in IntelliJ is really a piece of cake. Coming from .NET world, it does feel strange and it's a totally new world.

The project can be found here, but I think not a lot of people will understand it. There are three states: in the first general parameters need to be entered, in the second some specific parameters need to be entered and in the third all tables and charts are shown.

I still have one big problem and that's loading the application. Apparently a couple of swf files are downloaded before the application is shown. I guess that these swf files should be included when compiling, but I haven't managed to do this. If one of you knows how to resolve this, please let me know. Now the application is loading toooo slow. Maybe it's a configuration in IntelliJ? I have searched online but haven't found anything, so I guess it's not a bug in Flex SDK 4.

<

Flash game Eggie (WebORB, PHP and MySql)

Posted by admin on May 30th, 2010

Together with a colleague Piet Santy (aka Ploem), who did the artwork, we created a Flash Game for a friend of ours. Elections are up in Belguim and Dominique Dehaene needs votes! One way to get some attention and publicity is off course the internet. It was great to work with CS5, but I did have some problems with code completion and Flash Builder. Also, in Flash Builder, lots of classes weren't found. I probably haven't searched enough to find out the solutions of these problems.
I really like creating once in a while such a small game. No design patterns are involved, no architecture, just programming and quickly seeing result! With WebORB for PHP together with MySql I created a TOP 10 highscores. When the game finishes, your score is stored in the database and top 10 is fetched and displayed. Setting up WebORB for PHP was very easy. I just had some problems with a function that I called Echo, which is a reserved word in PHP and off course not knowing the language PHP. But everything was created very fast.

The result can be found here.

<

WebBrowser Control – Flash DropDown Fix (Framework 2.0)

Posted by admin on May 4th, 2010

For a customer of ours we had to create a standalone browser that could load assessments for candidates. It couldn't be loaded in a browser because the user would have access to the computer. Anyway, the .NET WebBrowser component loads a html page with the swf that starts the assessment.
The problem now was that Flex/Flash dropdowns didn't seem to work. A candidate could click on the dropdown, the items were shown but when an item was clicked, the dropdown closed and the item was not selected. On my laptop I couldn't reproduce the bug.

After searching someone pointed me in the right direction on StackOverflow and with the help of this question on a vbdotnetforum, I finally managed to find the problem and two solutions.

Problem

It seems that when clicking the dropdown, a Flash layer is displayed on top of the dropdown with the items. At that moment, the WebBrowser seems to take over the focus. When clicking on an item, you are actually clicking on the whole Flash Application, giving back focus to the swf, but not selecting an item. Because Flash gets back the focus, the dropdown closes automatically.

Solution 1

The problem only occurs when the client pc has .NET Framework 2.0 installed and nothing higher. When installing .NET Framework 2.0 SP1 or 3.5, the problem is resolved.

Solution 2

By extending the WebBrowser component and overriding one function, you can resolve the problem without upgrading .NET Framework. Use this Class as your WebBrowser and everything should work fine (the VB implementation can be found at the link above):

C#:
  1. using System.Windows.Forms;
  2.  
  3. namespace SaltoExplorer
  4. {
  5.     /// <summary>
  6.     /// When clients have .NET Framework 2.0 installed and
  7.     /// nothing higher, then there's a problem that not item
  8.     /// can be selected in Flex DropDowns.
  9.     /// It's a problem with focus. From the moment
  10.     /// .NET Framework 2.0 SP1 or  higher (like 3.5) is installed,
  11.     /// the problem is resolved.
  12.     /// Extending the WebBrowser component can fix this problem,
  13.     /// but it should only be done if the problem occurs
  14.     /// (with .NET Framework 2.0)
  15.     /// See
  16.     /// http://www.vbdotnetforums.com/vb-net-general-discussion/18563-webbrowser-control-2-0-framework-flash-fix.html
  17.     /// http://stackoverflow.com/questions/2205924/problems-flashflex-in-net-webbrowser-component
  18.     /// </summary>
  19.     public class Edumatic3WebBrowser : WebBrowser
  20.     {
  21.         private const int WmLbuttondown = 0x201;
  22.         private const int WmRbuttondown = 0x204;
  23.         private const int WmMbuttondown = 0x207;
  24.         private const int WmMouseactivate = 0x21;
  25.  
  26.         private ContainerControl FindContainerControl()
  27.         {
  28.             ContainerControl cc = null;
  29.             Control ctl = this;
  30.             while(ctl != null)
  31.             {
  32.                 var tempCc = ctl as ContainerControl;
  33.                 if(tempCc != null)
  34.                 {
  35.                     cc = tempCc;
  36.                     break;
  37.                 }
  38.                 ctl = ctl.Parent;
  39.             }
  40.             return cc;
  41.         }
  42.  
  43.         protected override void WndProc(ref Message m)
  44.         {
  45.             switch (m.Msg)
  46.             {
  47.                 case WmLbuttondown:
  48.                 case WmRbuttondown:
  49.                 case WmMbuttondown:
  50.                 case WmMouseactivate:
  51.                     if(!DesignMode)
  52.                     {
  53.                         var cc = FindContainerControl();
  54.                         if(cc != null && cc.ActiveControl != this)
  55.                         {
  56.                             //Probably giving focus to the Swf
  57.                             cc.Focus();
  58.                         }
  59.                     }
  60.                     base.DefWndProc(ref m);
  61.                     break;
  62.                 default:
  63.                     base.DefWndProc(ref m);
  64.                     break;
  65.             }
  66.         }
  67.     }
  68. }

<

Configure WebORB with Log4Net

Posted by admin on January 17th, 2010

This will be a quick one because time is not on my side these days... If you've never worked with Log4Net, I suggest to go and read some documentation on this an try to set it up.

A while ago I was searching how I could merge the logging from WebORB with my Log4Net configuration. As it happens, I had two log files, one from WebORB and one from Log4Net. Having an overview of the logging, was difficult like this. After some initial help from Mark Piller (WebORB), I managed to merge the two loggings. However, in the beginning, some logs were still written to the WebORB log file. After some weeks I got another email from Mark explaining how it could be done so that all logging would go to the Log4Net logging.

So here's how you do it:

In the Weborb.config file, add next lines in the logging tag section:

XML:
  1. <!--<currentPolicy>Date Based Logging</currentPolicy>-->
  2. <currentPolicy>Log4NetPolicy</currentPolicy>
  3.  
  4. <loggingPolicy>
  5.   <policyName>Log4NetPolicy</policyName>
  6.   <className>Edu3.Weborb.Logging.Log4NetPolicy</className>
  7. </loggingPolicy>

The Date Based Logging is the default policy used by WebORB which I commented out. We'll implement a Log4NetPolicy (you can take another name if you wish so).

In the loggingPolicy tag you can map the policy to a className which we'll implement. This class needs to implement the interface Weborb.Util.Logging.ILoggingPolicy, which is an interface in the weborb dll.

Important is to set all the log enables to true. It's Log4Net that will handle levels. If you set, for instance, the WebORB INFO level to enable=false, then our custom Log4Net logger will not receive any INFO loggings from WebORB. Even if you then set INFO level to be logged in Log4Net, the WebORB INFO levels won't be logged.

XML:
  1. <log enable="yes">WEBORB INFO</log>
  2. <log enable="yes">WEBORB DEBUG</log>
  3. <log enable="yes">WEBORB ERROR</log>
  4. <log enable="yes">WEBORB SERIALIZATION</log>
  5. <log enable="yes">WEBORB EXCEPTION</log>
  6. <log enable="yes">WEBORB INSTRUMENTATION</log>
  7. <log enable="yes">WEBORB SECURITY</log>
  8. <log enable="yes">WEBORB MESSAGE SERVER</log>
  9. <log enable="yes">WEBORB THREADING</log>

So here's my Edu3.Weborb.Logging.Log4NetPolicy class:

C#:
  1. using Weborb.Util.Logging;
  2. using System.Collections;
  3.  
  4. namespace Edu3.Weborb.Logging
  5. {
  6.     class Log4NetPolicy : ILoggingPolicy
  7.     {
  8.         private readonly Hashtable _hashtable;
  9.         private const string PolicyName = "Log4NetPolicy";
  10.  
  11.         public Log4NetPolicy(Hashtable hashTable)
  12.         {
  13.             _hashtable = hashTable;
  14.         }
  15.  
  16.         public ILogger getLogger()
  17.         {
  18.             return new Log4NetLogger();
  19.         }
  20.  
  21.         public string getPolicyName()
  22.         {
  23.             return PolicyName;
  24.         }
  25.  
  26.         public Hashtable getPolicyParameters()
  27.         {
  28.             return _hashtable;
  29.         }
  30.     }
  31. }

getLogger() should return the logger object which will be used by WebORB
getPolicyName() should return the name of the policy. Make sure it is the same as you specified in the config file
getPolicyParameters() should return the same hashtable which was passed into your policy constructor

The Log4NetLogger is also a custom written class that looks like this:

C#:
  1. using System;
  2. using log4net;
  3. using Weborb.Util.Logging;
  4. using log4net.Core;
  5.  
  6. namespace Edu3.Weborb.Logging
  7. {
  8.     public class Log4NetLogger : AbstractLogger
  9.     {
  10.         private static readonly ILog Log = LogManager.GetLogger("WebORB");
  11.        
  12.         public override void fireEvent(string category, object eventObject
  13.             , DateTime timestamp)
  14.         {
  15.             Level activityLevel;
  16.             switch (category)
  17.             {
  18.                 case LoggingConstants.WEBORB_INFO:
  19.                     activityLevel =
  20.                         Log.Logger.Repository.LevelMap["WEBORB_INFO"];
  21.                     Log.Logger.Log(GetType(), activityLevel
  22.                         , eventObject, null);
  23.                     break;
  24.                 case LoggingConstants.WEBORB_DEBUG:
  25.                     activityLevel =
  26.                         Log.Logger.Repository.LevelMap["WEBORB_DEBUG"];
  27.                     Log.Logger.Log(GetType(), activityLevel
  28.                         , eventObject, null);
  29.                     break;
  30.                 case LoggingConstants.WEBORB_ERROR:
  31.                     activityLevel =
  32.                         Log.Logger.Repository.LevelMap["WEBORB_ERROR"];
  33.                     Log.Logger.Log(GetType(), activityLevel
  34.                         , eventObject, null);
  35.                     break;
  36.                 case LoggingConstants.WEBORB_SERIALIZATION:
  37.                     activityLevel =
  38.                         Log.Logger.Repository.LevelMap["WEBORB_SERIALIZATION"];
  39.                     Log.Logger.Log(GetType(), activityLevel
  40.                         , eventObject, null);
  41.                     break;
  42.                 case LoggingConstants.WEBORB_EXCEPTION:
  43.                     activityLevel =
  44.                         Log.Logger.Repository.LevelMap["WEBORB_EXCEPTION"];
  45.                     Log.Logger.Log(GetType(), activityLevel
  46.                         , eventObject, null);
  47.                     break;
  48.                 case LoggingConstants.WEBORB_INSTRUMENTATION:
  49.                     activityLevel =
  50.                         Log.Logger.Repository.LevelMap["WEBORB_INSTRUMENTATION"];
  51.                     Log.Logger.Log(GetType(), activityLevel
  52.                         , eventObject, null);
  53.                     break;
  54.                 case LoggingConstants.WEBORB_SECURITY:
  55.                     activityLevel =
  56.                         Log.Logger.Repository.LevelMap["WEBORB_SECURITY"];
  57.                     Log.Logger.Log(GetType(), activityLevel
  58.                         , eventObject, null);
  59.                     break;
  60.                 case LoggingConstants.WEBORB_MESSAGE_SERVER:
  61.                     activityLevel =
  62.                         Log.Logger.Repository.LevelMap["WEBORB_MESSAGE_SERVER"];
  63.                     Log.Logger.Log(GetType(), activityLevel
  64.                         , eventObject, null);
  65.                     break;
  66.                 case LoggingConstants.WEBORB_THREADING:
  67.                     activityLevel =
  68.                         Log.Logger.Repository.LevelMap["WEBORB_THREADING"];
  69.                     Log.Logger.Log(GetType(), activityLevel
  70.                         , eventObject, null);
  71.                     break;
  72.                 case LoggingConstants.WEBORB_MESSAGINGEXCEPTION:
  73.                     activityLevel =
  74.                         Log.Logger.Repository.LevelMap["WEBORB_MESSAGINGEXCEPTION"];
  75.                     Log.Logger.Log(GetType(), activityLevel
  76.                         , eventObject, null);
  77.                     break;
  78.                 default:
  79.                     throw new Exception("All LoggingConstants should " +
  80.                                         "have been catched previously.");
  81.             }
  82.         }
  83.     }
  84. }

As you'll see, I defined some custom log levels in Log4Net. You really don't need this, you could just use the existing Log4Net levels and map custom WebORB levels to one of the existing Log4Net levels.

Here's how my Log4Net configuration section looks like if you want to work with these custom levels.

XML:
  1. <log4net>
  2.   <level>
  3.     <name value="WEBORB_INFO" />
  4.     <value value="40000" />
  5.   </level>
  6.   <level>
  7.     <name value="WEBORB_DEBUG" />
  8.     <value value="30000" />
  9.   </level>
  10.   <level>
  11.     <name value="WEBORB_ERROR" />
  12.     <value value="70000" />
  13.   </level>
  14.   <level>
  15.     <name value="WEBORB_SERIALIZATION" />
  16.     <value value="40000" />
  17.   </level>
  18.   <level>
  19.     <name value="WEBORB_EXCEPTION" />
  20.     <value value="70000" />
  21.   </level>
  22.   <level>
  23.     <name value="WEBORB_MESSAGINGEXCEPTION" />
  24.     <value value="70000" />
  25.   </level>
  26.   <level>
  27.     <name value="WEBORB_INSTRUMENTATION" />
  28.     <value value="40000" />
  29.   </level>
  30.   <level>
  31.     <name value="WEBORB_SECURITY" />
  32.     <value value="40000" />
  33.   </level>
  34.   <level>
  35.     <name value="WEBORB_MESSAGE_SERVER" />
  36.     <value value="40000" />
  37.   </level>
  38.   <level>
  39.     <name value="WEBORB_THREADING" />
  40.     <value value="40000" />
  41.   </level>
  42.   <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
  43.         <file value="Logs/Log4Net/"/>
  44.     <appendToFile value="true"/>
  45.     <rollingStyle value="Date"/>
  46.         <staticLogFileName value="false" />
  47.     <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
  48.     <datePattern value="yyyy-MM-dd-HH.lo\g" />
  49.     <maxSizeRollBackups value="10" />
  50.     <maximumFileSize value="1MB" />
  51.     <layout type="log4net.Layout.PatternLayout">
  52.       <conversionPattern value="%date [%thread] %-5level %logger - %message%newline"/>
  53.     </layout>
  54.   </appender>
  55.   <root>
  56.     <level value="ALL"/>
  57.     <appender-ref ref="RollingFileAppender"/>
  58.   </root>
  59. </log4net>

In this section you can configure not to log all levels. For more information on this see the Log4Net documentation.

Maybe that some of the Log Levels values (the integers) should be changed to meet your expectations. See Log4Net Levels for more information.

That's about it. If I forgot something, I'll update the post. If you have any question, please feel free to ask.

<

Nested TransactionScopes in .NET

Posted by admin on December 7th, 2009

In next example, the persistence to the database will not be executed.

C#:
  1. IBinaryAssetStructureRepository rep = new BinaryAssetStructureRepository();
  2. var userDto = new UserDto { id = 3345 };
  3. var dto = new BinaryAssetBranchNodeDto("name", userDto, userDto);
  4. using (var scope1 = new TransactionScope())
  5. {
  6.     using (var scope2 = new TransactionScope())
  7.     {
  8.         //Persist to database
  9.         rep.CreateRoot(dto, 1, false);
  10.         scope2.Complete();
  11.     }
  12.     scope1.Dispose();
  13. }
  14. dto = rep.GetByKey(dto.id, -1, false);

scope1.Dispose(); is not necessary in this example, but I added it for clearance.

<

Getting an embedded resource out of the assembly + convert to bytearray

Posted by admin on December 7th, 2009

My file is located under /images/donkey.jpg, so in that case, don't forget to add .images.

C#:
  1. var assemblyName =  typeof(BinaryAssetFileDataProviderTest).Assembly.GetName().Name;
  2. var str = typeof (BinaryAssetFileDataProviderTest).Assembly.GetManifestResourceStream(assemblyName + ".images." + "donkey.jpg");
  3. var blob = new byte[str.Length];
  4. str.Read(blob, 0, (int)str.Length);
  5. var size = (int)str.Length;
  6. str.Close();

<

Saving a connectionString at runtime to .NET app.config

Posted by admin on December 7th, 2009
C#:
  1. var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
  2. var connectionStringSettings =
  3. new ConnectionStringSettings("ConnectionString", _arguments["connectionString"], "System.Data.SqlClient");
  4. config.ConnectionStrings.ConnectionStrings.Add(connectionStringSettings);
  5. config.Save(ConfigurationSaveMode.Modified);
  6. ConfigurationManager.RefreshSection("connectionStrings");

<

Database version control MSSQL C#[dot]NET Program

Posted by admin on November 5th, 2009

Some time ago I read an article on the importance of version control for databases by Jeff Atwood. I knew that in our team we had to start using something like this because updating different databases to a new release took a lot of time. We always used sql compare to look for the difference between the two databases and to update the database. Sometimes, with small changes, this wasn't that bad, but with big differences and with data that also needed to be inserted, it was a nightmare. And if you had done one database, you needed to do it all over on another... very bad, very bad, but hey, we learn, we learn. Now I've created a C#.NET Console Application that handles most of the things described in the blog of Jeff Atwood. It's certainly not done and lots of things can be improved, but due to a lack of time right now, this will currently have to do it. It does its job and that's all we needed. This is the source code and this is the release build. In order to follow this serie of posts, you'd have to read the article by Jeff Atwood because I will not repeat everything he has said. In the source code zip there's a folder lib with libraries I used and a folder files with the files I will explain further on.

What's not implemented?

  • Setting up a database from scratch using the baseline scripts is not implemented.
  • Configuring more things like folder structure
  • Support for databases other than mssql
  • Views are not implemented as we do not use them yet in our application. Implementing them however is pretty easy and you should be able to do it yourself with the source code.
  • ...

How does it work?

First you have to have a certain folderstructure because I haven't had time to configure that. Here's a screenshot of how it should look:

Screenshot Folder Structure

Screenshot Folder Structure

I quickly wrote the program so that we could use Database Version Control as quick as possible. Because of that default it works with this folder structure. Under Program is the Release build of the C# Console Application. Under the root of Scripts are the update scripts along with some files I'll discuss later. Under the functions and storedprocedures are the create (no alter scripts because the sp's and functions are removed when running the exe!) scripts for functions and stored procedures.

How does the root of Scripts look like:

Screenshot Files under Scripts folder

Screenshot Files under Scripts folder

  • DropStoredProceduresAndFunctions.sql: Script that will be used to delete all sp's and functions out of the database. This will be necessary before creating all sp's and functions.
  • excludeList.csv: This is a text file with comma seperated values. If one of the values appears in the name of a sp or function, then this sp or function will be ignored (will not be deleted and not be created).
  • sc.xx.xx.xxxx.sql: The update scripts. The name is important as the exe depends on that.
  • sc.template.sql: This is a template script that is best used to create an update script

Explanation of scripts

DropStoredProceduresAndFunctions.sql script:

SQL:
  1. -- variable to object name
  2. declare @name varchar(1000)
  3. -- variable to hold object type
  4. declare @xtype varchar(20)
  5. -- variable to hold sql string
  6. declare @sqlstring nvarchar(4000)
  7. declare SPViews_cursor cursor FOR
  8. SELECT QUOTENAME(ROUTINE_SCHEMA) + '.' + QUOTENAME(ROUTINE_NAME)
  9. AS name, ROUTINE_TYPE AS xtype
  10. FROM
  11. INFORMATION_SCHEMA.ROUTINES
  12. UNION
  13. SELECT QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME) AS
  14. name, 'VIEW' AS xtype
  15. FROM
  16. INFORMATION_SCHEMA.VIEWS
  17.  
  18. open SPViews_cursor
  19. fetch next FROM SPViews_cursor INTO @name, @xtype
  20. while @@fetch_status = 0
  21. begin
  22. -- test object type if it is a stored procedure
  23. IF @xtype = 'PROCEDURE' ###
  24. begin
  25. SET @sqlstring = 'drop procedure ' + @name
  26. exec sp_executesql @sqlstring
  27. SET @sqlstring = ' '
  28. end
  29. -- test object type if it is a function
  30. IF @xtype = 'FUNCTION' ###
  31. begin
  32. SET @sqlstring = 'drop FUNCTION ' + @name
  33. exec sp_executesql @sqlstring
  34. SET @sqlstring = ' '
  35. end
  36. -- test object type if it is a view
  37. --if @xtype = 'VIEW'
  38. -- begin
  39. -- set @sqlstring = 'drop view ' + @name
  40. -- exec sp_executesql @sqlstring
  41. -- set @sqlstring = ' '
  42. -- end
  43. -- get next record
  44. fetch next FROM SPViews_cursor INTO @name, @xtype
  45. end
  46. close SPViews_cursor
  47. deallocate SPViews_cursor

I've got this from here. The ### tokens will be replace by transact-sql by the program to exclude sp's and funtions with certain keywords (excludeList.csv).

Example of an update script:

SQL:
  1. /*
  2. Comment
  3. */
  4. SET NUMERIC_ROUNDABORT OFF
  5. GO
  6. SET ANSI_PADDING, ANSI_WARNINGS, CONCAT_NULL_YIELDS_NULL, ARITHABORT, QUOTED_IDENTIFIER, ANSI_NULLS ON
  7. GO
  8. IF EXISTS (SELECT * FROM tempdb..sysobjects WHERE id=OBJECT_ID('tempdb..#tmpErrors')) DROP TABLE #tmpErrors
  9. GO
  10. CREATE TABLE #tmpErrors (Error int)
  11. GO
  12. SET XACT_ABORT ON
  13. GO
  14. SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
  15. GO
  16. BEGIN TRANSACTION
  17. GO
  18.  
  19. -- sql code
  20.  
  21. GO
  22.  
  23. INSERT INTO [dbo].[SchemaChanges]
  24. ([MajorReleaseNumber]
  25. ,[MinorReleaseNumber]
  26. ,[PointReleaseNumber]
  27. ,[ScriptName]
  28. ,[DateApplied])
  29. VALUES
  30. ('03'
  31. ,'01'
  32. ,'0011'
  33. ,'Comment'
  34. ,GETDATE())
  35.  
  36. GO
  37. IF EXISTS (SELECT * FROM #tmpErrors) ROLLBACK TRANSACTION
  38. GO
  39. IF @@TRANCOUNT&gt;0 BEGIN
  40. PRINT 'The database update succeeded'
  41. COMMIT TRANSACTION
  42. END
  43. ELSE PRINT 'The database update failed'
  44. GO
  45. DROP TABLE #tmpErrors
  46. GO

These sort of scripts will be executed by a SqlCommand (DbCommand from Enterprise Library). We are still experimenting and have seen that the Transactions do not work. Somehow there's a difference between these kind of scripts being executed in Management Studio and being executed by a SqlCommand. I think we'll have to use TransactionScrope in the C# Console Application to make the transactions work. These Transactions are important because if only a part of the script would be executed, then the build would be broken. You would then have to restore a previous backup, fix the script and run the exe again against the database. Later on I'll talk about some best practices to use database version control. The GO statements are also a program and the program filters them out before executing the script (again with some other keywords that cause problems). In a couple of months, when we have been able to work things out a little better, I'll post a new version. For now, this is what you get ;-) .

In the script there's place to put your sql code and then there's an important part, updating the SchemaChanges table. Each update script inserts a new record in the table SchemaChanges so that the update gets registered in the database. With this table, the program can check what the last update was and look for new scripts in the Script folder. It's very important that the values you insert into the SchemaChanges table correspond to the name you gave to the file, otherwise the whole thing will not work correctly.

sc.template.sql script:

SQL:
  1. /*
  2. Commentaar
  3. */
  4. SET NUMERIC_ROUNDABORT OFF
  5. GO
  6. SET ANSI_PADDING, ANSI_WARNINGS, CONCAT_NULL_YIELDS_NULL, ARITHABORT, QUOTED_IDENTIFIER, ANSI_NULLS ON
  7. GO
  8. IF EXISTS (SELECT * FROM tempdb..sysobjects WHERE id=OBJECT_ID('tempdb..#tmpErrors')) DROP TABLE #tmpErrors
  9. GO
  10. CREATE TABLE #tmpErrors (Error int)
  11. GO
  12. SET XACT_ABORT ON
  13. GO
  14. SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
  15. GO
  16. BEGIN TRANSACTION
  17. GO
  18.  
  19. -- Plaats SQL Code hier
  20.  
  21. GO
  22.  
  23. INSERT INTO [dbo].[SchemaChanges]
  24. ([MajorReleaseNumber]
  25. ,[MinorReleaseNumber]
  26. ,[PointReleaseNumber]
  27. ,[ScriptName]
  28. ,[DateApplied])
  29. VALUES
  30. ('00'
  31. ,'00'
  32. ,'0000'
  33. ,'Short Description'
  34. ,GETDATE(), 'Delete this parameter (is to make sure you change the version numbers')
  35.  
  36. GO
  37. IF EXISTS (SELECT * FROM #tmpErrors) ROLLBACK TRANSACTION
  38. GO
  39. IF @@TRANCOUNT&gt;0 BEGIN
  40. PRINT 'The database update succeeded'
  41. COMMIT TRANSACTION
  42. END
  43. ELSE PRINT 'The database update failed'
  44. GO
  45. DROP TABLE #tmpErrors
  46. GO

This is the template, which will surely change in the next months. The INSERT query will not work like it is written because there's a value too much. I've done this so that my teammembers wouldn't forget to edit the release numbers.

Explanation of the program and source code

Let's start of with explaining how the exe (DatabaseVersionControl.exe) works:

Parameters that are required (one):

-connectionString="Data Source=OMITTED;Persist Security Info=True;Integrated Security=SSPI;Initial Catalog=OMITTED;MultipleActiveResultSets=True"

Parameters that are optional (two):

-include "value1,value2,..." : If one of these values are in the excludeList.csv, then they will be overruled so that they will not be excluded.

-scriptsDirectory="..." : Path to the directory which contains the scripts. Under this folder the two folders storedprocedures and functions must be existing, otherwise the program will not work. Maybe that in the future this can be configured, but for now it'll do. If this parameter is left out, then the exe searches for the scripts folder at ../Scripts. Change this if you want in the soruce code.

The source code itself is explained in the code. You'll need some 4.1 enterprise libraries, but I guess you'll see that if you try to compile the code. I have put them in a lib folder, but the project uses the dll's from the registry.

We use cruisecontrol to set up a part of continuous integration. One project of cruisecontrol is listening to the scripts svn repository and executes the exe with the proper connectionstring is something has changed.

Best practices that we use in our team

  1. Everybody that wants to work at the database has a local database.
  2. Before starting to write an update script or change a sp or function, update your svn repository and execute the exe against your local database (and make sure you use the right one if you also have maintenance branches). I create a local bat file for each branch and for the trunk.
  3. If you want to change an sp or function, open the corresponding file, edit it, save it and run the exe. If everything works, commit the changed sp or function.
  4. If you want to create an update script, copy the template and give it the right name. If the last update script is sc.03.01.0017.sql, then the new one should be sc.03.01.0008.sql or sc.03.02.0000.sql or sc.04.00.0000.sql, depending on what kind of change you are going to make. !Do not commit at this point!
  5. Open the change script in an editor or in management studio and put in your scripts. Change the release numbers and put in some comment. Save the script and run the exe. If everything works, commit the change script.

Executing a change script, sp script or function script in management studio is a bad idea. It may work in management studio but not with the exe. In the future we'll have to check some things that happen with the exe and not with executing them in management studio, but for now we use what we have. If you do want to test changes in management studio you should take a backup before you start doing that. If you think your script is done, then restore your db and run the exe. It's not fun for other developers if things go wrong on the development database after you committed.

<

Teach Yourself Programming in Ten Years

Posted by admin on October 26th, 2009

Just read a very interesting article on programming by Peter Norvig. If you're a programmer, read it and check out some of the links.

--> http://norvig.com/21-days.html

<

Copyright © 2007 Johlero – Cardoen Lieven. All rights reserved.