Archive for the ‘WF’ Category

 
Oct
31
Posted (Brandon Satrom) in Architecture, Composite Applications, WF on October-31-2007

 

For the past couple of weeks, I have been doing an in-depth look at various mobile communication technologies (SMS, EMS, MMS, etc.) for the purposes of incorporating these technologies into our existing strategies. While the scenarios vary, most of the use-cases driving us to SMS et al. revolve around information delivery in areas where Internet connectivity is problematic, yet cell phones abound. It sounds counter-intuitive maybe, but in many regions (like Africa for example), it’s far easier to put up a cell tower than it is to lay cable of any kind. Thus, mobile technology can be found in many places where land lines and Internet access cannot.

 

Being the agenda-pusher that I am, I can’t pass up an opportunity to integrate this technology study with work I have already done and am doing. Since my biggest interest right now is in the Composite Applications space, I wanted to use one of the demos in this study to prove out (to some degree) the composition argument I have been making both in this blog and internally. Thus, I created a demo scenario for this study that sends a 1-way, application-originated SMS Text Message through an SMS Gateway (Clickatell in this case) to a mobile subscriber when a particular field on a list in MOSS is changed. Now, this scenario isn’t rocket science, nor is it earth-shattering. What it is, however, is me taking my own medicine. If I’m going to preach the Composite Applications gospel, I’d better try it on for size myself. Here’s how I implemented the scenario:

 

I started by creating a stand-alone custom activity in Windows Workflow Foundation. The code, with some key details removed, is included below. Note: In order use this yourself, you’ll need access to an SMS Gateway (like Clickatell, which is used here) and access to their HTTP API, if they have one. Not the only way to send a text message, I know, but SS7 Gateways can provide guaranteed message delivery to the subscriber.

using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Text;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;

namespace MyCompany.Workflow.Messaging.Activities
{
    public class SendSMSActivity : Activity
    {
        public static DependencyProperty NumberProperty =
            DependencyProperty.Register(“Number”, typeof(System.String),
            typeof(SendSMSActivity));
        public static DependencyProperty MessageProperty =
            DependencyProperty.Register(“Message”, typeof(System.String),
            typeof(SendSMSActivity));

        [DesignerSerializationVisibilityAttribute
            (DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(true)]
        [DescriptionAttribute("Destination number of SMS message")]
        [CategoryAttribute("SendSMSActivity Number Property")]
        public string Number
        {
            get
            {
                return ((string)(base.GetValue(SendSMSActivity.NumberProperty)));
            }
            set
            {
                base.SetValue(SendSMSActivity.NumberProperty, value);
            }
        }

        [DesignerSerializationVisibilityAttribute
            (DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(true)]
        [DescriptionAttribute("Text of SMS message")]
        [CategoryAttribute("SendSMSActivity Message Property")]
        public string Message
        {
            get
            {
                return ((string)(base.GetValue(SendSMSActivity.MessageProperty)));
            }
            set
            {
                base.SetValue(SendSMSActivity.MessageProperty, value);
            }
        }

        protected override ActivityExecutionStatus
            Execute(ActivityExecutionContext context)
        {
            string response;

            try
            {
                // Send the SMS Message
                response = SendSMS(Number, Message);
            }
            catch (Exception e)
            {
                response = e.Message;
            }

            // Raise the PageFinished event back to the host
            messageSentEvent(null, new MessageSentEventArgs(response));

            // Notify the runtime that the activity has finished
            return ActivityExecutionStatus.Closed;
        }

        public delegate void MessageSentEventHandler(object sender,
            MessageSentEventArgs e);

        private event MessageSentEventHandler messageSentEvent;
        public event MessageSentEventHandler MessageSent
        {
            add
            {
                messageSentEvent += value;
            }
            remove
            {
                messageSentEvent -= value;
            }
        }

        public string SendSMS(string number, string text)
        {
            WebClient apiRequest = new WebClient();
            apiRequest.Credentials = CredentialCache.DefaultCredentials;

            //Add a user agent header in case the requested URI contains a query
            apiRequest.Headers.Add(“user-agent”,
            “Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 2.0.50727;)”);

            //Add the SMS details to the apiRequest object
            apiRequest.QueryString.Add(“user”, “xxxxx”);
            apiRequest.QueryString.Add(“password”, “xxxxx”);
            apiRequest.QueryString.Add(“api_id”, “xxxxx”);
            apiRequest.QueryString.Add(“to”, number);
            apiRequest.QueryString.Add(“text”, text);

            string baseURI = “http://api.clickatell.com/http/sendmsg”;

            Stream responseStream = apiRequest.OpenRead(baseURI);
            StreamReader reader = new StreamReader(responseStream);
            string responseCode = reader.ReadToEnd();

            //Clean up in-memory objects
            reader.Close();
            responseStream.Close();

            return (responseCode);
        }
    }

    public class MessageSentEventArgs
    {
        private string response;
        public string Response
        {
            get { return response; }
        }

        public MessageSentEventArgs(string response)
        {
            this.response = response;
        }
    }
}

Like I said, nothing earth-shattering. However (and I won’t go into the details of WF here as there are plenty of good resources that do that) the end-result is a stand-alone Workflow Foundation Activity which encapsulates the business logic for distributing an SMS Text Message via our Gateway provider and which can be hosted in any application that provides the WF runtime. This Activity can easily be dropped into a container Sequential or State-Machine Workflow project (as I did when testing this activity on my machine), or it can be deployed to MOSS as a stand-alone activity which can be included in a Workflow built using SharePoint Designer. Two scenarios with different composers, both using the same asset. That’s the vision of Composite Applications. (As an aside, with the recent Oslo announcement, I imagine that we’re not far from a day where that same activity could be also be reused in BizTalk and Microsoft CRM, among others).

 

So what do those scenarios look like? For the developer adding a custom activity to a WF Workflow, it’s a simple matter of dragging the activity from the Toolbox to the designer, then binding to the DependencyProperties (Number and Message in this case) and creating an event-handler to receive the event raised by the activity (in this case, sendSMSActivity_MessageSent). See the image below for an example:

 

CustomWFActivityInVS

For an individual slinging SharePoint Designer, the experience is different, but also quite powerful. However, The first step I must take as an activity developer is to deploy said activity to the MOSS server. This requires deploying the Activity assembly to the GAC on the MOSS server, adding said assembly as a safe control in the web.config file, and adding the Activity to the WSS.actions file (or a new .actions file in the same directory, which is probably a better idea), which SharePoint designer uses to load up the list of available workflow actions. All of this can be done via a feature in SharePoint (I know that the first two can and I think that the last can, so feel free to comment if I am incorrect). Here is the code for the WSS.ACTIONS file (Added in the <Actions> section):

<Action Name=Send an SMS Text Message  ClassName=MyCompany.Workflow.Messaging.Activities.SendSMSActivity  Assembly=MyCompany.Workflow.Messaging.Activities, Version=1.0.0.0,
    Culture=neutral, PublicKeyToken=fe2315b70tbc147c  AppliesTo=all  Category=Messaging Actions>
  <RuleDesigner Sentence=Send %1 to number %2>
    <FieldBind Field=Message Text=this message DesignerType=TextBox Id=1/>
    <FieldBind Field=Number Text=this number DesignerType=TextBox Id=2/>
  </RuleDesigner>
  <Parameters>
    <Parameter Name=Number Type=System.String, mscorlib Direction=In />
    <Parameter Name=Message Type=System.String, mscorlib Direction=In />
  </Parameters>
</Action>

Once that entry has been added, the activity is ready to be included in an SP Designer workflow. In this example, I bound the workflow to a custom list created in a demo site, set a condition to check the value of a key field, then added the “Send SMS Message” activity and configured its inputs. In this case, the message is constructed from existing information in the list and the destination number is determined by looking up the mobile phone of a user in another list. An image of the Workflow Designer Wizard in SP Designer can be seen below:

 

WFActivityInSharePointDesigner

If the workflow validates, we’re in business and the activity will run each time a list item is added or updated and the field in question isn’t blank. Here is the Workflows application page, as seen from MOSS itself:

 

WFPageInMOSS

In addition, that activity is now available to include in any workflow where sending an SMS notification is a requirement. You be the judge if this is a blessing or a curse… In either case my hope is that this post illustrates the power of composition. And MOSS is just an example in this case, not the end-all for composition by any means. What important in any composition scenario is a platform and technologies that provide the ability to create reusable assets and then reuse those assets across tiers and containers.