Wednesday, November 16, 2011

Event handler to archive items when deleted

A person just asked this on a mailing list I am on, so I thought I'd share this code. The requirement - copy a list item that is deleted to an archive list. The solution - event receiver that copies the item. The code below does the job, but again is only a sample - you still need to implement error handling, and there are hard coded variables there you will want to change.
Note: this code is specifically for list items, not for documents in document libraries. You can easily change the code to support documents as well (see the CopyAttachments function for an example on how to copy files).

public override void ItemDeleting(SPItemEventProperties properties)
       {
           //note: may require permission elevation.
           //TODO: add error handling

           //get the item being deleted
           SPListItem item = properties.ListItem;
           //get the target list
           SPList targetList = properties.Web.Lists["Announcements Archive"];
           //create the new item
           SPListItem newItem = targetList.Items.Add();
           //copy the list item to the target
           foreach (SPField f in item.Fields)
           {
                if (!f.ReadOnlyField && newItem.Fields.ContainsField(f.InternalName))
                    newItem[newItem.Fields.GetFieldByInternalName(f.InternalName).Id] = item[f.Id];   
           }
           //copy "special" read only fields that can be written to
           newItem["Created By"] = item["Created By"];
           newItem["Modified By"] = item["Modified By"];
           newItem["Modified"] = item["Modified"];
           newItem["Created"] = item["Created"];
           newItem.SystemUpdate(false);
           CopyAttachments(item, newItem);
           base.ItemDeleting(properties);
       }

Note - for the "CopyAttachments" I used code that I published in the past: http://www.sharepoint-tips.com/2008/11/how-to-copy-attachments-from-one-list.html

Tuesday, November 15, 2011

Setting item permissions for new items

A common question that I recently answered in a sharepoint conference is how to make a sharepoint list only show items to the users who wrote the items, and users specified in the item's properties.
A good example of this is a task list where only the original creator of the task and the assignee can see and change the task. This is impossible to do in sharepoint out of the box, and that is where we, as developers, come in.
The way to do it is by developing a simple event handler and attaching it to the list. I prefer to attach to ItemAdded and ItemUpdated - put the exact same code in both events (so that people can reassign the task). The code sample below is a good starting point for you to figure out how to do this. The code sample assumes you know how to use visual studio to create a new event handler, and is meant only as a sample - it doesn't have error handling and it does have some hard coded values for a task list that you may want to change.

public override void ItemAdded(SPItemEventProperties properties)
        {
            //get the list item that was created
            SPListItem item = properties.ListItem;
            //get the author user who created the task
            SPFieldUserValue valAuthor = new SPFieldUserValue(properties.Web, item["Created By"].ToString());
            //get the "assigned to" user. Note - this will throw an error if the task is not assigned to anyone - implement error handling!
            SPFieldUserValue valAssignedTo = new SPFieldUserValue(properties.Web, item["Assigned To"].ToString());
            //disconnect the security from the list, and delete all permissions
            item.BreakRoleInheritance(false);
            //create the object that will hold the roles for the author user
            SPRoleAssignment authorRole = new SPRoleAssignment(valAuthor.User);
            //create the object that will hold the roles for the assigned to user
            SPRoleAssignment assignedToRoles = new SPRoleAssignment(valAssignedTo.User);

            //get the contribute role from the web. Alternatively use code to create a new role definition with custom permissions.
            SPWeb oWebsite = properties.Web;
            SPRoleDefinitionCollection collRoles = oWebsite.RoleDefinitions;
            SPRoleDefinition oRoleDefinition = collRoles["Contribute"];

            //assign permissions to task author
            authorRole.RoleDefinitionBindings.Add(oRoleDefinition);
            item.RoleAssignments.Add(authorRole);
            //assign permissions to task assignee
            assignedToRoles.RoleDefinitionBindings.Add(oRoleDefinition);
            item.RoleAssignments.Add(assignedToRoles);
            //update the item
            item.Update();
            base.ItemAdded(properties);
        }

You can download the entire code sample as a visual studio project from my company's site's code samples document library at http://www.extelligentdesign.com/Code%20Sample%20Downloads/Forms/AllItems.aspx. You will want to change the project's properties to point to your local development site before debugging.

Saturday, October 15, 2011

Speaking in the upcoming Asia conferences

Next month I will be in both Singapore and Hong Kong for the Asian sharepoint conferences. If you are coming, why not jump in my session to learn about sharepoint development? Who knows, there may be prizes...


http://www.sharepointconference.asia

Friday, October 14, 2011

Setting the images for a publishing page from a module

Here is a small tip for developers (about time I wrote one again). What do you do if you want to provision a publishing page to a sharepoint site, and set the page's image (or rollup image) during the provisioning?
Well, step one should be obvious - make sure you upload the image you want to display. This can be done in the same feature or a seperate one. I will assume that you already know how do do that (if not, then the MSDN article "how to provision a file" is your friend). The same goes for actually how to build the feature and module that uploads your publishing page (again, refer to the MSDN article, or better yet, see my friend Waldek's article discussing just this).
So what am I talking about? I want to focus on how to set two specific properties on the page - the page's image and the page's rollup image. These two are not regular properties, since they are publishing properties. They do not expect the same kind of value that regular image properties expect (which is the same as a hyperlink - first part is the link to the image, than a space and a comma and then the description of the image). With publishing fields you need to supply the actual HTML that will be shown as the image.
Since this is done in the module, you will have to create a "Property" element in your "File" element in the module. The value of that property should be the HTML, but encoded so that the XML doesnt become invalid. The value (when not encoded) should look like this:

<img alt='This is a picture of a chicken' src='/PublishingImages/chicken.jpg' style='border:0px solid;'>

And the entire property (with the value encoded) should look like this:

<Property Name="PublishingRollupImage" Value="&lt;img alt='AFL peace Team at Whitten Oval' src='/newsandblog/PublishingImages/22-gal-tn.jpg' style='border:0px solid;'&gt;" />

Hope that helps someone!

Saturday, August 13, 2011

Office 365 development

I am a bit excited about office 365.

My company (Extelligent Design) is now a 365 partner, and we aim to support clients implementing it. We want to help people set it up, understand how to use it (my book may come in handy, seeing as the end user interface to the sharepoint online is the same as regular sharepoint) and customise it.

Customising is where the real challange begins. As you probably know, to develop for office 365, you can only use the sandbox - which limits what you can achieve in your customisations. I find that not being able to install application pages (pages that go to the _layouts folder) especially frustrating, and, like a fish out of the water for the first time in its life, I suddenly realise how much I tend to rely on having that resource.

Ok, maybe the fish analogy is a bit much. I can still write web parts, and features with event receivers - not to mention list and library event receivers as well! But this is when I hit another wall - a lot of code I wrote in the past relied on me being able to run sections of the code with elevated privilages. This is gone if you develop a sandbox solution, and is very frustrating. Some obvious things that people will want to do in their hosted sharepoint site rely on code being able to run as other users. Workflows are a great example - having a user with permissions on list A enter data into that list, and then the workflow enters data into another list to which the user does not have permissions. Oh, wait, workflows are also not allowed in hosted sharepoint, so replace the word "workflow" with "event receiver" in the sentance above and you still get the same functionality, and you still hit a wall with not being able to write code that implements that requirement.

PS - the security limitation is blocked for both usual methods of running in elevated privilages mode - you cannot use the security namespace (preveting you from using SPSecurity.RunWithElevatedPrivilages) and you cannot use the SPSite constructore - preventing you from loading a new SPSite object with the system user's token.

So where does this leave us? Not all is doom and gloom - we can still write web parts and event handlers and features - as long as they are not too complex. This is great for deploying look and feel, features that implement site templates and more. I see a lot of developers now focusing on creating turn-key solutions that implement industry-specific templates for sites. Small businesses have a lot in common, and they are the real target of office 365 - so anyone who comes up with a feature that creates lists and libraries that solve a common problem for a small business may hit a jackpot similar to the mobile phone app developers.

Finally, hosting companies that host office 365 may let you deploy code to the servers directly, allowing non-sandbox development to take place. I think this will be hardest to do, since those companies will be very afraid to change their environment from the microsoft default, but a strong partnership with such a company may be a great thing for a sharepoint developer. Come to think of it- if you ARE a hosting company that office 365 hosting and you want some value add to your clients - talk to me!

Office 2010 SP1

Ok, this may not be a sharepoint post, but...

If you are like me, you have "Virtual Clone Drive" installed, allowing you to mount ISO files as virtual CDs (other virutal CD software is available).

If you are like me, you do not have a physical CD drive in your laptop (having pulled it out and put a second SSD in instead).

If you are like me, you are running office 2010.

If you are like me, you always install whatever Windows Update tells you to install.

So...if you are like me, then you have been unable to install service pack 1 of Office 2010, failing (of all things) on installing a sharepoint client component.

The solution turns out to be simple - disable the virtual CD drive and then do the update. Some recommendations from Microsoft also suggests to put a DVD or CD in your physical CD drive while patching. Seems to be a bug in SP1.

Lucky for us sharepoint people that the SharePoint SP1 doesnt have all these annoying installation bugs. Oh, wait...

Thursday, March 31, 2011

A new methology for planning SharePoint Projects - how to win a tender

Today I want to discuss a methodology I have been working on for a while now.
For the last ten years, when developing a solution architecture for clients, I have always stuck to the KISS principle. If you are unfamiliar with that one, it means "Keep It Simple, Stupid!" - which is a good advice when you submit your solution to the review of your peers - other technical specialists and assorted IT professionals. A simple design usually wins through simplicity - it is easy to grasp, easy to implement and easy to maintain and expand on. By KISSing other technical people, you usually get the design you want approved. If you want to read more about KISS, see the wikipedia article about it: http://en.wikipedia.org/wiki/KISS_principle.

The problem I found with KISS is that when you pitch your design to other types of audience, for example upper management, it tends not to be accepted. Initially I thought it may be that management are less prone to like being called "stupid" (which is why I always phrased it "Keep it simple, silly!" - but that never improved things much), but the more I thought about it, I realised that those audiences just expect more out of a solution design than just simplicity.
In other words - KISSing the upper management is not enough.

This is why I decided to come up with my own new principle that extends KISS and adds the things that upper management really wants from a solution architect:
Keep It Simple, Stable, Available, Scalable and Secure.
By KISSASSing upper management, you are almost assured to get the project you are tendering for, or get your solution approved. I think KISSASS should be practiced - do not think you are ready to KISSASS today - it does require a lot of experience - which is why more experienced professionals may get projects over others - they are much better KISSASSers.

I intend to come up with some KISSASS best practices, KISSASS guidelines and perhaps some flow charts explaining how you should practice KISSASS in other aspects of life. If you have some ideas - please share them with me in the comments for this post.
But in the meanwhile - have a great April!

Monday, March 21, 2011

SharePoint 2010 developer training - thoughts

I spent the entire last week in Perth (Australia) delivering developer training to 5 students - and it was great. We covered the basic skills that every sharepoint developer must have, and we talked about what skills they should still learn after the course to enhance their skills. I feel this course (written by MVPs from all around the world, guided by my friend and fellow MVP Randy Williams from www.synergyonline.com, is the best course material available to train SharePoint developers.

Why? first, the labs are excellent - we only had one or two cases where the text in the lab (instructing the students) were slightly off. Synergy Online's quick reaction to fix and modify the labs to correct those typos is one of the reasons I love working with them.
Next, there is the chapters themselves. In 2006, a SharePoint development course could be completed in 3 days or so. Today, it is impossible to teach all aspects of SharePoint development in a five day course. Since students are working people, and cannot spend two weeks learning everything, prioritization is most important, and Synergy Online did a great job figuring out what most developers need to know, and then balancing it so that even within a specific topic, if the topic is too long to teach in 2-3 hours, the class teaches enough for the students to know how to begin and to know they should look into more details when they are back at work before starting a project using those skills.

The only recommendations that I have made to Synergy Online was to drop the Business Intelligence chapter, as while it is important for solution architects, it is less developer oriented, and a much more important chapter that should replace it would be custom field types. I also suggested that we include EditorParts in the web part chapter - at least one code sample so that students can carry on after the course knowing that this functionality is there.

My very favorite thing about training is to tell students about real life scenarios that I had with the technology. For example, in the chapter about querying sharepoint data (comparing SPQuery, SPLinq, and more), I can tell about projects that I had where I built web parts that aggregated sharepoint data, and even open the source code for a web part I have developed in the past and run through the code. This way the students get to see more complex examples of what they see in the lab, and I can even give "best practice" advice about things like error logging and error handling in web parts - as a side note.
I feel this adds so much value to the course...maybe I should be asking for tips at the end of the week?

Next month I am delivering the same course again, this time in Canberra (my home town). If you are seeking developer training and can take a week to be tought by myself - register now at the Dimension Data web site (make sure you choose "Canberra" as location - the other locations are not courses that I am teaching. If you cannot make it to Canberra, by all means register to one of the other ones - the other Syngery trainers are highly professional and knowlegeable - for example, Eric Cheng is a fantastic trainer and I enjoyed teaching with him in the past).

Sunday, January 23, 2011

Setting Target Audiences with code

If you search the web you will find many articles explaining how to set audiences on web parts (also on list items). Most are curious - explaining that you need to specify the IDs of the audiences, separated by colons, and then four semi colons to finish it.

Really? four semi colons at the end without any explanation why? I just had to investigate.
It turns out that the four semi colons have a use. The syntax of the "target to" audience field type is as follows:
[Guid Ids separated by comma];;[Active directory groups' LDAP paths separated by line breaks];;[SharePoint security group names separated by commas]

For example:
decd0c08-4649-4e61-a8d6-8fdf5e4017ad,decd0c08-4649-4e61-a8d6-8fdf5e4017ad;;CN=SecGroup,CN=Users,DC=Development,DC=Local
CN=samplegroup1,CN=Users,DC=Development,DC=Local
;;Admin Office, Authors

So if you only have global (user profile) audiences, the value will be guids followed by four semi colons (because the values of the other type of audiences are blank). If you only choose security\distribution lists then you will have two semi colons before them, and two after. If you chose only sharepoint groups you will get 4 semi colons before the group names.

To make things simpler for me, I wrote a small class to create a value for me, or to parse it out for me. Here is the code:

public class AudienceFieldValue
    {
        /// <summary>
        /// A list of the IDs of the sharepoint global audiences (the one defined in the user profile database, accessible with  Microsoft.Office.Server.Audience.AudienceManager
        /// </summary>
        public List<Guid> GlobalAudienceIds;
        /// <summary>
        /// A list of sharepoint security groups
        /// </summary>
        public List<string> SharePointSecurityGroupNames;
        /// <summary>
        /// A list of active directory LDAP paths (CN=SecGroup,CN=Users,DC=Development,DC=Local) pointing to security groups or distribution lists
        /// </summary>
        public List<string> DirectoryGroupsOrDistListLDAPPaths;

        public AudienceFieldValue()
        {
            GlobalAudienceIds = new List<Guid>();
            SharePointSecurityGroupNames = new List<string>();
            DirectoryGroupsOrDistListLDAPPaths = new List<string>();
        }
        /// <summary>
        /// Use this constructor if you have a value from an existing item and you want to parse it. 
        /// You can even join values from more than one item seperated by semi colons. 
        /// For example: AudienceFieldValue val = new AudienceFieldValue(item1["Target Audiences"].toString() +;"+ item1["Target Audiences"].toString())
        /// </summary>
        /// <param name="value">The value of a "audience target to" column from a SPListItem (or more than one, seperated by semi colons)</param>
        public AudienceFieldValue(string value) 
        {
            GlobalAudienceIds = new List<Guid>();
            SharePointSecurityGroupNames = new List<string>();
            DirectoryGroupsOrDistListLDAPPaths = new List<string>();
            string[] arrTargets = value.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            foreach (string audienceGroup in arrTargets)
            {
                if (audienceGroup.Contains("CN="))
                {
                    //it is one or more AD groups (CN) split by line breaks
                    string[] adgroups = audienceGroup.Split('\n');
                    foreach (string audienceCN in adgroups)
                    {
                        DirectoryGroupsOrDistListLDAPPaths.Add(audienceCN);
                    }
                }
                else
                {
                    string[] arrAudiences = audienceGroup.Split(',');
                    foreach (string audienceName in arrAudiences)
                    {
                        try
                        {
                            Guid g = new Guid(audienceName);
                            GlobalAudienceIds.Add(g);
                        }
                        catch (Exception ex)
                        {
                            //its not a guid - so it is a local sharepoint security group
                            SharePointSecurityGroupNames.Add(audienceName);
                        }
                    }
                }
            }
        }


        /// <summary>
        /// Get the value that you can set to a list item's target audience field
        /// </summary>
        /// <returns>A string value containing all the IDs specified in a format that sharepoint understands</returns>
        public override string ToString()
        {
            if (GlobalAudienceIds.Count == 0 && SharePointSecurityGroupNames.Count == 0 && DirectoryGroupsOrDistListLDAPPaths.Count == 0)
            {
                return "";
            }
            else
            {
                StringBuilder result = new StringBuilder();
                //first add any global audiences
                foreach (Guid id in GlobalAudienceIds)
                {
                    if (result.Length > 0)
                        result.Append(",");
                    result.Append(id.ToString());
                }
                //must add two semi colons to seperate even if there is nothing after
                result.Append(";;");
                //add any directory group path, seperated by a line break
                bool addedDirGroup = false;
                foreach (string directoryGroupPath in DirectoryGroupsOrDistListLDAPPaths)
                {
                    if (addedDirGroup)
                        result.Append("\n");
                    result.Append(directoryGroupPath);
                    addedDirGroup = true;
                }
                //must add two semi colons to seperate even if there is nothing after
                result.Append(";;");
                //add any sharepoint group names seperated by commas
                bool addedSPGroup = false;
                foreach (string spGroupName in SharePointSecurityGroupNames)
                {
                    if (addedSPGroup)
                        result.Append(",");
                    result.Append(spGroupName);
                    addedSPGroup = true;
                }

                return result.ToString();
            }
        }
    }

Tuesday, January 18, 2011

Creating static and dynamin menus in the Site Actions menu

I am presenting tonight in the Canberra SharePoint User Group (and will show the code again in the upcoming Australian SharePoint Conference) about adding actions to the site actions menu. Instead of writing all I have to say, I will just share the source code with your - trusting that you will understand the difference between the two methods I am showing (static and dynamic). Enjoy!
Source code can be downloaded from:
MenuActions.zip

Friday, January 07, 2011

Adding Modules or Elements to a VS2010 SharePoint project

If you encounter the error "The Project Item "[Item Name]" cannot be deployed through a Feature with Farm scope." when you are deploying a Visual Studio 2010 SharePoint project, the problem may be that you have done what I have just done - try to force the wrong item type to deploy...
Let me explain: Lets say you want to add a custom action - something that can be deployed as a farm feature. But by mistake you added a project item of type "Module" instead of "Empty Element". You think to yourself - I can just clear the module, and add the custom action instead in the XML. The intellisense definitly supports it.
Well, it turns out that Visual Studio remembers that you added the XML as a module, and even if you edit out the module instructions, it will not let you deploy the feature as a farm feature, since modules are not supported at the farm scope.
The solution? delete your module, and add an empty element.