Navigation

Search

Categories

On this page

Convert.ChangeType() to the rescue of a generic method (and my sanity)...
Hosting Windows Workflow Foundation in Windows Services
The Server Side of the .NET Framework 3.0 Presentation Materials
Restarting Persisted Workflow Instances in Windows Workflow Foundation
'Sink'ing with the Windows SMTP Service - Updated
Windows Workflow Foundation Configuration
v.01

Archive

Blogroll

Disclaimer
None - these are my opinions and they're also my employer's!

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

Total Posts: 14
This Year: 0
This Month: 0
This Week: 0
Comments: 15

Sign In

 Tuesday, December 05, 2006
Wednesday, December 06, 2006 3:31:37 AM (Mountain Standard Time, UTC-07:00) ( )

Ever had one of those moments when you wished you either knew everything there was to know about software development or had decided to become a pea farmer in Brazil (not that they even have pea farms in Brazil, but they might, but....) instead of a software developer.  Well, I have and a show of hands in a crowded technical gathering would prove my point.  This self-realization should give the reader enough pause about reading on without me clearly stating that I'm sure someone else has solved this problem in a much more elegant way - after all, I'm not even sure they farm peas in Brazil.

Anyway, just when I had decided that I should've given myself more time when I made that fateful "farming vs. programming" decision, along comes an Intellisense moment which made it all better.  I knew about the Convert class and had used it numerous times so I did the fateful "conv+tab+." and started looking for gold.  Sure enough I found it in the ChangeType() method.

The problem...

A fellow developer had written a handy generic method for the type friendly retrieval of application configuration settings.  Everything was working well until I tempted Fate and asked for a value type, int in this case, back from the method:

Error 1 The type 'int' must be a reference type in order to use it as parameter 'T' in the generic type or method 'ConsoleApplication1.Program.GetSetting<T>(string)'

Well, that seemed like an important capability to have so diving into the wrapper I went:

public static T GetSetting<T>(string key) where T : class
{
   return ConfigurationManager.AppSettings[key] as T;
}

To start with there was no way to call this method passing int as the generic type because of the class constraint.  So, I quickly modified it to the following:

public static T GetSetting<T>(string key)
{
   return ConfigurationManager.AppSettings[key] as T;
}

Which, of course, just means I'm a lazy programmer (which, of course, translates to reduced crops of peas if I had chosen that profession.)  At first glance, I thought removing the class constraint would be the answer.  Since we don't have to think things out all the time in this age of change/compile/test/change/compile/test/scream/change/compile/test development I let the compiler tell me if my guess was wrong (isn't that the whole point of generics anyway?) kind of like a game of "Who Wants to Be a Millionaire?" without the lifelines:

Error 1 The type parameter 'T' cannot be used with the 'as' operator because it does not have a class type constraint nor a 'class' constraint

This error message was so useful since I had just purposefully removed the class constraint.  The super-fast-non-thinking-programmer in me changed it to:

public static T GetSetting<T>(string key)
{
   return (T)ConfigurationManager.AppSettings[key];
}

The very brief moment of insight which caused me to change the cool and friendly as cast to the traditional brute force cast proved NOT to be insightful at all as the trusty compiler let me know: 

Error 1 Cannot convert type 'string' to 'T'

And I thought you could cast pretty easily in the .NET Framework.  Well, you can, but the compiler has no idea what T is going to be so it stops you from making a bad cast and getting a runtime error which of course is apparent evil.  Strong-typing vs. dynamic-typing aside, we were in a real pickle.  We needed this generic method to deal well with a variety of types so...

The solution...(well, you read the title, right?)

public static T GetSetting<T>(string key)
{
   return (T)Convert.ChangeType(ConfigurationManager.AppSettings[key], typeof(T));
}

I am InvincibleChangeType() has several overloads but the one I was interested in took two parameters: 1) object value and 2) Type.  This was exactly what was needed and I had another moment of "I am invincible" and was sure that I had chosen the right pill (talk about mixing movie metaphors.)

The catch...

Everything compiled and my unit tests proved it to be working swimmingly so what was that gnawing concern growing in the pit of my "been around the bend a few times" stomach.  Things seemed happy and well adjusted.  That's about the time I always have hair stand up on the back of my neck.  What's the catch?  Performance?  Odd inconsistencies that will absolutely drive me nuts later?  Deprecation in the next release of the framework?

Armed with nothing but paranoia, I decided to research this little bad boy and see just how awful it was.  Come to find out my worries weren't too justified as I found out while Reflectoring the method in mscorlib.dll.  They actually did what my cohort and I thought we were going to have to do in examining the incoming T parameter's type and finding the right translation from the string results of the ConfigurationManager.AppSettings[key].  So maybe I'm not as crazy as I thought since the framework folks did just what I had planned. 

However, one thing jumped out when I looked closely at the disassembly:

IConvertible convertible1 = value as IConvertible;
if (convertible1 == null)
{
   if (value.GetType() != conversionType)
   {
      throw new InvalidCastException(Environment.GetResourceString("InvalidCast_IConvertible"));
   }
   return value;
}

As I pondered the deeper meaning of what I had just found I realized that it might be a bad thing if I passed in a type parameter for T which was not of the IConvertible ilk.  Which led me to the final version below:

public static T GetSetting<T>(string key) where T : IConvertible
{
   return (T)Convert.ChangeType(ConfigurationManager.AppSettings[key], typeof(T));
}

With the IConvertible constraint on the T parameter I could rest easy that this method should provide years of use without any harmful side effects and that I could lay to rest the ongoing controversy in my mind of peas or C# (yeah, right.)

Comments [3] | | # 
 Thursday, November 30, 2006
Friday, December 01, 2006 6:02:21 AM (Mountain Standard Time, UTC-07:00) ( )

I've been spending some time working with hosting the WF runtime in a WCF-wrapped Windows service.  The idea is to allow these services to be run on servers behind a load balancing technology like NLBS.  In my research I ran across a few great articles and samples which might be useful so I thought I'd post them here.

My goal for these hosting services is to make them as loosely-coupled as possible.  Ultimately, I'd like to have the full set of runtime and service events available through a publish/subscribe architecture similar to Juval Lowy's so that the disconnected and potentially remote consumers could be notified of status and process changes in their respective workflows.  Also, to accomplish the loose coupling, I am taking bits and pieces from the Workflow Adapter/Connector and WS-Transfer Service for Workflow articles that Roman Kiss posted on CodeProject.com.  There's a lot of work to do but I think in the end the architecture is a sound one.  I'm not sure I'll go all the way to WS-Transfer yet but it's definitely a compelling protocol.

There's a good example of hosting workflows in a Windows service out on the Workflow community site.  It also demonstrates the use of IExtension in WCF for hosting the Workflow runtime.

Comments [1] | | # 
 Wednesday, November 29, 2006
Thursday, November 30, 2006 12:22:50 AM (Mountain Standard Time, UTC-07:00) ( )

Just wanted a common place I could reference this presentation and code samples.  It was a fun side project that Matt Brown and I have enjoyed presenting to the local user group and a couple of code camps here in Colorado.  It was originally done pre-Beta 2 so there are definitely things we would do differently now but it does show some cool WCF and WF concepts.  And, it might help in your next job search.

Enjoy.

Comments [1] | | # 
 Monday, November 27, 2006
Tuesday, November 28, 2006 12:12:41 AM (Mountain Standard Time, UTC-07:00) ( )

Well, it's kind of a hack but it works.  If your workflow runtime host goes away for whatever reason, whether it be ASP.NET, a Forms or Console application, or a Windows service you can peer into the persistence database using the LoadExpiredTimerWorkflowIds method on the SqlWorkflowPersistenceService instance associated with your runtime.  The code snippet below gets the persistence service instance from the runtime and resumes the processing on each workflow.  This allows the timer events to fire which in turn will cause your workflows to flow once more.

ReadOnlyCollection<SqlWorkflowPersistenceService> sqlSvc = 
   _workflowRuntime.GetAllServices<SqlWorkflowPersistenceService>();
 
IList<Guid> readyWorkflows = sqlSvc[0].LoadExpiredTimerWorkflowIds();
foreach (Guid wfGuid in readyWorkflows)
{
   _workflowRuntime.GetWorkflow(wfGuid).Resume();
}

I haven't fully tested it in a practical sense where I've got many runtimes sharing a persistence repository but I'll know more as the week progresses.

Matt Milner posted in the MSDN Workflow Foundation forum that the LoadIntervalSeconds setting on the SqlWorkflowPersistenceService will automatically load any persisted workflows based on that interval.  So the only reason to directly resume the workflows is if you feel that delay would be a problem for some reason.  Thanks, Matt.

Comments [1] | | # 
 Saturday, November 25, 2006
Saturday, November 25, 2006 11:33:20 PM (Mountain Standard Time, UTC-07:00) ( )

I needed to get to SMTP messages in the most efficient way possible without the overhead of POP3, etc.  When I searched around I found a great Microsoft article on how to create managed event sinks using some pre-fabbed event interop and wrapper code.  For the most part the instructions are self-explanatory but I did run into some issues with the samples.

The first sample describes building a sink that basically does nothing but access the parameters passed through the wireup interface.  The article walks you through setting the project build property Register for COM interop which basically runs RegAsm behind the scenes.  Well, if you forget to put the ComVisible attribute on your class you won't actually register any types which of course means you can't register with the SMTP service to be called via COM.  Running RegAsm directly will alert you to this fact, so I recommend not setting the property until you know your COM goo is in place correctly.

   [ComVisible(true), Guid("B3AEF136-3064-429c-8D61-4D8AA8EE8B29")]
   public class EventSink : ISmtpInCommandSink, IEventIsCacheable
   {

A key piece to making all this work is the registration with the SMTP service of your new COM type.  This is accomplished using the handy smtpreg.vbs script.  The /enum option is great for displaying all currently registered sinks.  Your assembly must be RegAsm'd first which, of course, requires strong naming.

The second sample has a more practical use in that it will actually terminate any SMTP session based on the EHLO request being received.  There is a code typo, though:

   if (null != pCcontext OOPS!)
   {
       Marshal.ReleaseComObject(pContext);
   }

In the ShieldsUp sample they also implement the IEventIsCacheable interface method IsCacheable().  This implementation allows the SMTP Service Extension Objects (SEO) process to avoid tearing down and recreating your component thus avoiding the overhead of CoCreateInstance() on each call.

Once I worked through the samples and got my feet wet creating and registering the SEO components, I was able to build what I needed pretty quickly.  In essence, my sink needed to intercept the messages that were inbound to my SMTP server and send them to a transactional MSMQ queue.  I found some help related to handling incoming messages using IMailTransportSubmission here

I also found some System.Messaging MSMQ optimization tips here that I followed since this process will need to be able to handle thousands of messages.  The event sink wrappers exposed a CopyContentToStream() method on the Message object which worked well with the serialization optimization strategies outlined in the document.  I basically took the message and after verifying that it isn't too long (over 1,000,000 bytes) sent the entire contents to a queue as you can see below: 

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using MSMQ = System.Messaging;
using Mail = System.Net.Mail;
using Microsoft.Exchange.Transport.EventInterop;
using Microsoft.Exchange.Transport.EventWrappers;
 
namespace SMTPEventSink
{
   [ComVisible(true), Guid("AD1F3BB9-22E9-4207-BBAC-F767DB32FA0D")]
   public class QueueMailMessage : IMailTransportSubmission, IEventIsCacheable
   {
      #region IMailTransportSubmission Members
      void IMailTransportSubmission.OnMessageSubmission(
         MailMsg pIMailMsg, 
         IMailTransportNotify pINotify, 
         IntPtr pvNotifyContext)
      {
         MSMQ.MessageQueueTransaction msmqTran = new MSMQ.MessageQueueTransaction();
         try
         {
            Message msg = new Message(pIMailMsg);
            if (msg.GetContentSize() < 1000000)
            {
               MemoryStream contentStream = new MemoryStream(Convert.ToInt32(msg.GetContentSize()));
               msg.CopyContentToStream(contentStream);
               MSMQ.MessageQueue msmqQueue = 
                  new MSMQ.MessageQueue(
                     @".\private$\SMTPInBoundMessages", 
                     System.Messaging.QueueAccessMode.Send);
               msmqTran.Begin();
               using (MSMQ.Message msmqMsg = new MSMQ.Message())
               {
                  msmqMsg.BodyStream = contentStream;
                  msmqMsg.Label = msg.Rfc822MsgId;
                  msmqMsg.Recoverable = false;
                  msmqQueue.Send(msmqMsg, msmqTran);
               }
               msmqTran.Commit();
 
               // abort message processing so message is intercepted
               msg.MessageStatus = Message.MessageStatusEnum.AbortDelivery;
               throw new COMException("Abort delivery");
            }
            else
            {
               LogEvent(string.Format(
                  "QueueMailMessage - message too long ({0} bytes)", 
                  msg.GetContentSize()));
            }
         }
         catch (COMException)
         {
            // ignore - we want this to bubble up
         }
         catch (Exception ex)
         {
            msmqTran.Abort();
            LogEvent(ex.ToString());
         }
         finally
         {
            if (null != pIMailMsg)
            {
               Marshal.ReleaseComObject(pIMailMsg);
            }
            if (null != pINotify)
            {
               Marshal.ReleaseComObject(pINotify);
            }
            if (null != pvNotifyContext)
            {
               Marshal.ReleaseComObject(pvNotifyContext);
            }
         }
      }
      #endregion
 
      #region IEventIsCacheable Members
      void IEventIsCacheable.IsCacheable()
      {
         // returns S_OK by default
      }
      #endregion
 
      private void LogEvent(string message)
      {
         if (!EventLog.SourceExists("SMTPEventSink"))
         {
            EventLog.CreateEventSource("SMTPEventSink", "Application");
         }
         EventLog.WriteEntry("SMTPEventSink", message);
      }
   }
}

Another gotcha that I experienced while debugging and building this managed SMTP event sink is that once the SMTP service has accessed your assembly file you will have to do IISReset to get it freed again.  I tried removing the binding using smtpreg.vbs and stopping the SMTP service in the IIS management MMC but to no avail.  Guess that's a small price to pay for such close to the edge control.

I used the following command lines to add and remove the binding for this new event sink with SMTP SEO:

cscript smtpreg.vbs /add 1 OnTransportSubmission SMTP2MSMQ SMTPEventSink.QueueMailMessage "mail from=*"

cscript smtpreg.vbs /remove 1 OnTransportSubmission SMTP2MSMQ

Overall, I was impressed with how easy it was to build this event sink and found the documentation pretty straightforward and reliable.  Now all I have to do is move everything to the appropriate environment and harden it a bit.

Comments [1] | | # 
 Wednesday, November 22, 2006
Wednesday, November 22, 2006 10:54:04 PM (Mountain Standard Time, UTC-07:00) ( )
So I've flattened my forehead trying to get the configuration section for WF set the way I wanted.  The trick is to learn what the actual named parameters each service is expecting and then adding those as attributes to the <Services><add> element.

I'm sure others have already forged a trail here but I couldn't find any significant information about using the application config file to drive WF runtime configuration settings.  The MSDN documentation give some hints but unfortunately there isn't nearly as much detail in the documentation for Workflow Foundation as there is for Windows Communication Foundation.

The best place I've found to see what named parameters are available to a service is to Reflector the workflow runtime assembly (System.Workflow.Runtime.dll ) which is located in the reference assemblies folder "\Program Files\Reference Assemblies\Microsoft\Framework\v3.0".  I was looking for the settings I could configure for the SqlWorkflowPersistenceService so I drilled down to the constructor that accepted a NameValueCollection.  Then I could see in the disassembly what settings would be read (screenshot below) in that constructor.

Click to enlarge

Using the names as indicated in the constructor I was able to add attributes to the element configuring the SqlWorkflowPersistenceService.  The key one I was trying to figure out how to add was the OwnershipTimeoutSeconds which tells the service how to behave when locking instances in a common persistence database.

The configuration that I'm starting with is below.  Eventually I'll be changing to a different palette of services but this meets the needs for now.

   <configSections>
      <section name="WorkflowRuntime" 
           type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, 
           System.Workflow.Runtime, Version=3.0.00000.0, 
           Culture=neutral, 
           PublicKeyToken=31bf3856ad364e35" />
   </configSections>
   <WorkflowRuntime Name="WorkflowMultiHost">
      <CommonParameters>
         <add name="ConnectionString" 
            value="Initial Catalog=WorkflowStore;Data Source=(local);Integrated Security=SSPI;" />
         <add name="EnableRetries" 
            value="True" />
      </CommonParameters>
      <Services>
         <add type="System.Workflow.Runtime.Hosting.DefaultWorkflowSchedulerService, 
            System.Workflow.Runtime, 
            Version=3.0.00000.0, 
            Culture=neutral, 
            PublicKeyToken=31bf3856ad364e35" />
         <add type="System.Workflow.Runtime.Hosting.SharedConnectionWorkflowCommitWorkBatchService,
            System.Workflow.Runtime, 
            Version=3.0.00000.0, 
            Culture=neutral, 
            PublicKeyToken=31bf3856ad364e35" />
         <add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, 
            System.Workflow.Runtime, 
            Version=3.0.00000.0, 
            Culture=neutral, 
            PublicKeyToken=31bf3856ad364e35" 
            UnloadOnIdle="True"
            LoadIntervalSeconds="5" 
            OwnershipTimeoutSeconds="90" />
         <add type="System.Workflow.Runtime.Tracking.SqlTrackingService, 
            System.Workflow.Runtime, 
            Version=3.0.00000.0, 
            Culture=neutral, 
            PublicKeyToken=31bf3856ad364e35" />
      </Services>
   </WorkflowRuntime>

Notice that I'm using the SharedConnectionWorkflowCommitWorkBatchService which allows the storage of persistence and tracking data to the same database.  This allows non-DTC transactions which of course will perform better.  I'll know more as I begin load testing and I'll be doing a post on what I find.  Some folks at MS did a great writeup on various WF performance issues here.

Go with the flow...

Comments [0] | | # 
Wednesday, November 22, 2006 10:52:03 PM (Mountain Standard Time, UTC-07:00) ( )

Well, I decided I needed to get out there with my rambling thoughts and misguided attempts at solving the world's problems with the limited technical skills I possess.  Welcome to the journey...

Comments [1] | | #