Wednesday, April 25, 2007

C# 3.0: The Sweet and Sour of Syntactic Sugar

I’ve just started reading the blog of Scott Guthrie who is a general manager at Microsoft and is currently writing about the new C# 3.0 (code named Orcas). The post I felt strongly about describes three new language features including:

  • Automatic Properties
  • Object Initializers, and
  • Collection Initializers

All three features are purely syntactic sugar: they add nothing of real value to the language (unlike generics in C# 2.0 for example). One feature is wonderful, while two are terrible.

The Sweetest Syntactic Sugar Ever

Automatic properties are fabulous. I’ve always grumbled about the number of lines required to create simple properties. Anything that can turn:

public class Person {
    private string _firstName;

    public string FirstName {
        get { return _firstName; }
        set { _firstName = value; }
    }
}

Into:

public class Person {
    public string FirstName { get; set; }
}
Gets my vote.

Sickeningly Sweet

Perhaps I’m a purest, but with more language conveniences comes: a steeper learning curve because of a larger language, and decreased readability because of the numerous ways of doing the same thing. Picture reading this in someone else’s code:

Person p = new Person("Lee", "Richardson") {
    Phone = "111-111-1111",
    Age = 29,
    Company = new Company("Near Infinity") {
        Phone = "222-222-2222"
    }
}

Who would write this crap? Everyone else, now that Microsoft lets them. I vote for the verbose, the readable, the single approach:

Company company = new Company("Near Infinity");
company.Phone = "222-222-2222";

Person person = new Person("Lee", "Richardson");
person.Phone = "111-111-1111";
person.Age = 29;
person.Company = company;

Hmmm, and by the way did you count the number of lines?

Conclusion

So do the differences between my thoughts on automatic properties contradict with my thoughts on object initializers and collection initializers? They don’t. Basically a little sugar is good, but too much ruins the dish.

Friday, April 13, 2007

Multi Value Columns Solution #2 - Custom Activities in SPD

In the previous post in this series (Multi-Value Columns in SharePoint Designer - Solution #1), I described a problem where SharePoint Designer can’t send e-mail to multiple recipients if those recipients exist inside of a multi-value column in a SharePoint list. The simple hacky solution I described was to temporarily turn the column into a single value column just for SharePoint Designer. But this approach has problems, and there is a better way: developing custom activities in Visual Studio for use in SharePoint Designer.

In this post I will describe how to develop a custom activity in Visual Studio that will also solve this problem and I will also describe how to install it on a SharePoint server so that SharePoint Designer clients can automatically download and use it. Before I do let me back up and tell you why, from my perspective, this approach is probably not the best way to go.

SharePoint Designer is a Microsoft Office product that replaces FrontPage, integrates tightly with SharePoint, and allows non-developers (aka “knowledge workers” in the Microsoft lingo) to create simple workflows without writing any code.

The problem is that simple workflows and multi-value columns are like oil and water: not so compatible. If you’re using multi-value columns then your knowledge workers should admit defeat and let developers create the workflows in Visual Studio using the Windows Workflow Foundation (which, incidentally, will be the topic for third article in this series).

Still, you may have a good reason for continuing to develop workflows in SharePoint Designer, and I’ve already gone through the pain of writing and installing custom activities, so hopefully this post will make life easier for someone somewhere.

Creating the Custom Activity in Visual Studio

The code to create the custom activity in Visual Studio is the most interesting part of this solution. Make sure to check out the GetEmailAddressesFromUsernames() method if you have time to review the code. Here is the procedure assuming this is your first time working with Windows Workflow Foundation in Visual Studio.

1. The first step is to download and install Visual Studio 2005 extensions for .NET Framework 3.0 (Windows Workflow Foundation) which will add workflow options to Visual Studio.

2. If you aren’t running on Windows Server 2003, then you probably need to install the Windows Workflow Foundation DLL’s. You’ll know there’s a problem if, after creating the project in Visual Studio, your SharePoint references are invalid. I picked these DLL’s up from my Windows Server 2003 machine (actually a VMWare virtual machine) from the following location:

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\ISAPI

You should install these to the Global Assembly Cache (GAC) to make Visual Studio happier, although you may still need to reference them for your project. The easy way to install to the GAC is just to copy them to somewhere on your local (non-W2K3) machine and then drag them all over to C:\Windows\assembly.

3. After completing step #1 when you open Visual Studio you should get a new tree node under “Visual C#” (or Visual Basic) called “Workflow.” Select that, then “Workflow Activity Library” then call the project something like “MultiRecipientMail”.

4. Visual Studio should automatically create a blank activity called Activity1.cs. If you want a more reasonable name then delete it and “Add” a “New Item” of type “Activity” called something like “MultiRecipientMailActivity.” Don’t make the same painful mistake as me: name your activity something other than the name of the project or anything in the namespace.

5. At this point you could drag and drop a “Replicator” activity on the design surface and put a “Send Mail” activity inside, but I’ll cover that in my next post. For now I think code is the clearer way to go. Hit F7 or right click and “View Code” and paste the following which I’ll try to comment in-line rather than in this article.

// Note: this code was adopted from Todd Baginski at http://www.sharepointblogs.com/tbaginski/archive/2007/03/08/HOW-TO_3A00_-Create-a-custom-Windows-Workflow-Activity-and-make-it-available-in-SharePoint-Designer.aspx

 

using System;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Collections;

using System.Drawing;

using System.Workflow.ComponentModel;

using System.Workflow.ComponentModel.Design;

using System.Workflow.ComponentModel.Compiler;

using System.Workflow.ComponentModel.Serialization;

using System.Workflow.Runtime;

using System.Workflow.Activities;

using System.Workflow.Activities.Rules;

using System.Net.Mail;

using Microsoft.SharePoint;

using System.IO;

 

namespace MultiRecipientMail {

    public partial class MultiRecipientMailActivity : SequenceActivity {

 

        // Windows Workflow Foundation (WWF) will store the workflow state using these "instance property"

        //      dependency properties for more info check out: http://msdn2.microsoft.com/en-us/library/ms733604.aspx

 

        // the ToProperty must be bound to a multi-value column of users otherwise the activity will fail

        public static DependencyProperty ToProperty = DependencyProperty.Register("To", typeof(string), typeof(MultiRecipientMailActivity));

        // the FromEmailAddress does not support multi-value columns, it must be static or bound to a single-value column

        public static DependencyProperty FromEmailAddressProperty = DependencyProperty.Register("FromEmailAddress", typeof(string), typeof(MultiRecipientMailActivity));

        public static DependencyProperty SubjectProperty = DependencyProperty.Register("Subject", typeof(string), typeof(MultiRecipientMailActivity));

        public static DependencyProperty BodyProperty = DependencyProperty.Register("Body", typeof(string), typeof(MultiRecipientMailActivity));

        public static DependencyProperty SMTPServerNameProperty = DependencyProperty.Register("SMTPServerName", typeof(string), typeof(MultiRecipientMailActivity));

 

        // the ValidationOptionAttribute is new to .Net 3.0 and is used exclusively by WWF

        [ValidationOption(ValidationOption.Required)]

        public string To {

            get {

                return (string)base.GetValue(ToProperty);

            }

            set {

                base.SetValue(ToProperty, value);

            }

        }

 

        [ValidationOption(ValidationOption.Required)]

        public string FromEmailAddress {

            get {

                return (string)base.GetValue(FromEmailAddressProperty);

            }

            set {

                base.SetValue(FromEmailAddressProperty, value);

            }

        }

 

        [ValidationOption(ValidationOption.Required)]

        public string Subject {

            get {

                return (string)base.GetValue(SubjectProperty);

            }

            set {

                base.SetValue(SubjectProperty, value);

            }

        }

 

        [ValidationOption(ValidationOption.Optional)]

        public string Body {

            get {

                return (string)base.GetValue(BodyProperty);

            }

            set {

                base.SetValue(BodyProperty, value);

            }

        }

 

        [ValidationOption(ValidationOption.Required)]

        public string SMTPServerName {

            get {

                return (string)base.GetValue(SMTPServerNameProperty);

            }

            set {

                base.SetValue(SMTPServerNameProperty, value);

            }

        }

 

        public MultiRecipientMailActivity() {

            InitializeComponent();

        }

 

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) {

            try {

                // log workflow started

                LogThis("MultiRecipientMail started");

 

                // create a MailMessage object to send the email

                MailMessage msg = new MailMessage();

 

                // set the from address

                MailAddress fromAddress = new MailAddress(this.FromEmailAddress);

                msg.From = fromAddress;

 

                // the To property is bound to a multi-value column of usernames (e.g. "Domain\username1; Domain\username2",

                //      so parse its contents out into an array of e-mail addresses

                string[] astrEmails = GetEmailAddressesFromUsernames(To);

 

                // loop through and add the To addresses to the MailMessage

                foreach (string strEmail in astrEmails) {

                    MailAddress toAddress = new MailAddress(strEmail);

                    msg.To.Add(toAddress);

                }

 

                // set the subject and body

                msg.IsBodyHtml = true;

                msg.Subject = Subject;

                msg.Body = Body;

 

                // create an SmtpClient object to represent the mail server that will send the message

                SmtpClient client = new SmtpClient(SMTPServerName);

                // how to authenticate to the email server

                client.UseDefaultCredentials = true;

                // send the message

                client.Send(msg);

 

                // log workflow started

                LogThis("MultiRecipientMail completed successfully");

            } catch (Exception ex) {

                LogThis("MultiRecipientMail error: " + ex.ToString());

            }

 

            // indicate the activity has closed

            return ActivityExecutionStatus.Closed;

        }

 

        private string[] GetEmailAddressesFromUsernames(string strUsernames) {

            // SharePoint stores multi-value columns to the format: "domain\user1; domain\user2" so split on ';'

            string[] astrUsernames = strUsernames.Split(';');

 

            // This beautiful piece of code I adapted from Finnatic at http://rfinn.spaces.live.com/Blog/cns!4E19D0E183DFC38B!204.entry

            //      I looked for a while to find an easier way to get e-mail addresses from user names, but no luck.  Perhaps web services.

            //      Anyway RunWithElevatedPrivileges is necessary to avoid permissions problems with UserProfileManager. It's argument is a

            //      delegate, so the anonymous delegate works.  This will probably fail if the user has no WorkEmail, but it's a starting point.

            //     

            SPSecurity.RunWithElevatedPrivileges(

                delegate() {

                    // connect to the root Sharepoint site

                    using (SPSite site = new SPSite("http://nic-lrichard-03:34089/")) {

                        Microsoft.Office.Server.ServerContext context = Microsoft.Office.Server.ServerContext.GetContext(site);

                        Microsoft.Office.Server.UserProfiles.UserProfileManager profilemanager = new Microsoft.Office.Server.UserProfiles.UserProfileManager(context);

 

                        // for each of the usernames in astrUsernames

                        for (int i = 0; i < astrUsernames.Length; i++) {

                            string strUsername = astrUsernames[i].Trim();

 

                            // convert the username to the WorkEmail address

                            Microsoft.Office.Server.UserProfiles.UserProfile profile = profilemanager.GetUserProfile(strUsername);

                            astrUsernames[i] = profile[Microsoft.Office.Server.UserProfiles.PropertyConstants.WorkEmail].Value.ToString();

                        }

                    }

                }

            );

 

            return astrUsernames;

        }

 

        /// <summary>

        /// This logging method is NOT production ready.  Obviously you need to create a C:\MultiRecipientMailLog.log

        /// with appropriate for debugging.  In production it should log to the event log or be removed.

        /// </summary>

        /// <param name="str"></param>

        private void LogThis(string str) {

            FileInfo fi = new FileInfo("C:\\MultiRecipientMailLog.log");

            if (fi.Exists) {

                StreamWriter sw = fi.AppendText();

                sw.WriteLine(String.Format("{0:g} {1}", DateTime.Now, str));

                sw.Flush();

                sw.Close();

            }

        }

   

    }

}

If you’re new to .Net 3.0 you’ll find the whole dependency property thing pretty confusing. I’ll write about it in another post. For now just think of it as a big weakly typed hash table whose values WWF can use without prior knowledge of their existence.

6. To get this to compile you’ll need to add a reference to “Microsoft.SharePoint” and “Microsoft.Office.Server.” If you’re on a non-W2K3 machine you may need to reference the DLL’s manually that you copied from step 2. Regardless, double check you can compile (Control+Shift+B or Build->Build MultiRecipientMail).

Deploying the Custom Activity to the SharePoint Server

To give credit up front, a large part of this procedure came from Todd Baginski’s Blog. However, I still had a lot of difficulties, and based on the comments on the Microsoft forums regarding the multi-value column problem, a refinement of Todd Baginski’s work from a different perspective will be valuable to the MOSS community. In any event everything regarding this multi-value column solution is now in one place.

7. You’ll need to strongly sign the project because it needs to go into the Global Assembly Cache (GAC). To do this:

    1. Right click on the project;
    2. Select properties;
    3. Select the signing tab;
    4. Check “Sign the assembly;”
    5. Select “New” from the dropdown;
    6. Enter a name like “MultiRecipientMail;”
    7. Uncheck “Protect my key file with a password;” and
    8. Click OK.

8. Compile (Control+Shift+B or Build->Build MultiRecipientMail)

9. Get the public key token which was generated as a result of 7 & 8:

1. Open the “Visual Studio 2005 Command Prompt,” from Start->Microsoft Visual Studio 2005->Visual Studio Tools;

2. Change directory to your output directory, which I usually copy and paste from Windows Explorer (e.g. cd “C:\Users\lrichard\Documents\Visual Studio 2005\Projects\MultiRecipientMail\MultiRecipientMail\bin\Debug”)

3. Type “sn -T MultiRecipientMail.dll” and copy and paste the public key token by right clicking and selecting mark then hitting Control-C. Save this token for later perhaps in notepad or commented out in your AssemblyInfo.cs file.

10. Now install your dll to the W2K3 GAC. To do this copy the dll to somewhere on the W2K3 machine (if it’s a separate machine), and either drag it to “c:\windows\assembly” or at a command prompt type something similar to:

gacutil.exe -uf MultiRecipientMail.dll (to uninstall if previously installed)

gacutil.exe -if MultiRecipientMail.dll (to install)

The latter is the correct way and works in a deployment script, but I’d opt for the former for convenience.

11. The next step is get SharePoint Designer to recognize the new action. You could modify the WSS.ACTIONS file as Todd Baginski suggests, but you’re better off leaving this core file alone and creating a new one in the same directory that SharePoint will automatically pick up upon reboot (ref). So inside of:

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\Workflow\

Create a new text file called MultiRecipientMail.ACTIONS and paste in the following and save:

<?xml version="1.0" encoding="utf-8"?>
<WorkflowInfo Language="en-us">
    <Actions>
        <Action Name="Multi Recipient E-mail"
            ClassName="MultiRecipientMail.MultiRecipientMailActivity"
            Assembly="MultiRecipientMail, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d73739679587735e"
            AppliesTo="all"
            Category="Custom Actions">

            <RuleDesigner Sentence="Send %1, %2, %3, %4 via %5.">
                <FieldBind Field="To" Text="to" Id="1" DesignerType="TextArea"/>
                <FieldBind Field="FromEmailAddress" Text="from" Id="2" DesignerType="TextArea"/>
                <FieldBind Field="Subject" Text="Subject" Id="3" DesignerType="TextArea"/>
                <FieldBind Field="Body" Text="body" Id="4" DesignerType="TextArea"/>
                <FieldBind Field="SMTPServerName" Text="SMTP Server" Id="5" DesignerType="TextArea"/>
            </RuleDesigner>
            <Parameters>
                <Parameter Name="To" Type="System.String, mscorlib" Direction="In" />
                <Parameter Name="Subject" Type="System.String, mscorlib" Direction="In" />
                <Parameter Name="Body" Type="System.String, mscorlib" Direction="Optional" />
                <Parameter Name="FromEmailAddress" Type="System.String, mscorlib" Direction="In" />
                <Parameter Name="SMTPServerName" Type="System.String, mscorlib" Direction="In" InitialValue="127.0.0.1" />
            </Parameters>
        </Action>
    </Actions>
</WorkflowInfo>

You’ll need to substitute your own public key token from step 9 in the “Assembly=” statement.

12. Next you need to tell SharePoint designer clients to trust this new dll. To do so open the Web.Config in the virtual directory for your site (e.g. C:\Inetpub\wwwroot\wss\VirtualDirectories\34089\ web.config). Find the section called “” and add the following new line (substitute your own public token):

(again substitute your own public key token)

13. Restart IIS by opening a command prompt and typing “iisreset.” SharePoint will pick up the new MultiRecipientMail.ACTIONS file, find the dll in the GAC, and provide the new information to any SharePoint Designer clients that access the site.

14. Open SharePoint Designer and either navigate to the “Workflows\Multi Recipient Email.xoml” project from my previous article or create a new workflow project based on a list with a multi-value column. You should get a message like the following while SharePoint Designer downloads the new dll:

15. If everything worked correctly you should now be able to 1. click Actions; 2. More Actions; 3. Select the “Custom Actions” category from the dropdown (this category came from the .ACTIONS file in Step 11); and 4. Select the “Multi Recipient E-mail” action. If it doesn’t show up in the Workflow Designer page after clicking "add", then you probably referenced it incorrectly in the Web.Config.

16. Now you should be able to 1. click the Fx button next to “To;” 2. Select your multi value column (e.g. Peers To Review from my previous article); 3. Hit Ok; and 4. Fill in the remaining fields with values.

17. Now if you head back to Sharepoint, select the dropdown of a list item in the list associated with your workflow, click “Workflows”, select the workflow you created in SharePoint Designer, and click “Start” you should receive an e-mail at each address in your multi-value column.

Well … at least it worked for me. :) I did kitchen test this article, but please leave comments if it doesn't work for you.

Redeploying

If you want to make any changes to the code the steps to redeploy are:

1. Compile (you don’t need to sign again, that’s one time only)

2. Copy the dll to your W2K3 server

3. Re-install the dll to the GAC. You can just re-copy it over to c:\windows\assembly if you like.

4. Restart iis with an “iisreset” from the command line (no need to change the .ACTIONS or Web.Config files)

5. Finally, if you need to make changes to your workflow, you may need to restart SharePoint Designer to download the new dll (I usually have to).

You’re Done! Easy huh?

So you should now have a reusable component that non-developers can use in SharePoint Designer to send e-mail to multiple recipients as determined by a multi-value column in a SharePoint list. Of course if you decide that developing the workflow in Visual Studio is better, then the third article in this series may be for you.

Monday, April 9, 2007

Multi-Value Columns in SharePoint Designer - Solution #1

Recently I’ve been working with Microsoft Office SharePoint Server (MOSS) 2007. Since this is my first post on the topic I’d love to start at a high level about what it is and how it works, but let’s get to the interesting stuff: what doesn’t work well and how to get around it.

Specifically, this will be the first in a series regarding a deficiency in the workflow component of SharePoint that doesn’t allow you to send e-mail to multiple recipients from a multi-value column of a list using SharePoint Designer 2007 (SPD).

This first post will describe the problem, provide lots of screenshots so it can double as a fast introduction to workflows in SharePoint for the uninitiated, and then provide a quick hacky solution.

The Multi-Value Column Problem

Here’s how it should work. You create a list (e.g. Performance Review) and add a column (e.g. “Peers To Review”) and select a type of “Person or Group” with “Allow multiple selections” set to Yes.

(click any image to enlarge)

Now if you add a blank workflow in SharePoint Designer (from the SharePoint Content tab):

And you select the list you created (Performance Review):

Then you should be able to

  1. Add a “Send an Email” action
  2. View the message’s properties
  3. Select the recipient
  4. Add a function by clicking “Workflow Lookup”
  5. Select the Current Item (aka the current list item, which is like a row in a spreadsheet and in my example this would be the current performance review that the workflow is being run on)
  6. Then select the new column “Peers to Review”

But wait. Where’s your column? And here is the problem. The UI of SharePoint Designer filters out all columns that are marked with “Allow multiple selections.”

Simple Hacky Solution #1

Believe it or not the Workflow Engine knows how to send e-mail to a column with “Allow multiple selections” but it’s the SharePoint Designer UI that doesn’t. This means that a simple solution to the problem is this:

  1. Turn off “Allow multiple selections” for your column (note the warning “This will remove all person values except the first one”)
  2. Close and reopen the workflow in SharePoint Designer and follow steps above and magically your column will appear in the dropdown

  3. Finish creating your workflow
  4. Turn back on “Allow multiple selections”
  5. Create an item for the list and run the workflow you created in SharePoint Designer

And voila, it works! Both (or all) recipients have received an e-mail.

Sadly, this simple technique isn’t an acceptable solution for the long term. What happens if you want to change your workflow down the road? You may need to turn “Allow multiple selections” back off, thus deleting your multi-user data. A better solution is to create a custom action in Visual Studio and get it to plug into SharePoint Designer. And that will be the topic of my next post:

Multi Value Columns Solution #2 - Custom Activities in SPD

---

Note 1: this series is largely in response to the several users having similar problems at: http://forums.microsoft.com/MSDN/showpost.aspx?postid=1443799&siteid=1

Note 2: If you’re interested in the ERD for the topics in this SharePoint post this diagram might be useful to get you up to speed: