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.

Wednesday, December 22, 2010

Controls not showing the value you choose after submitting

A quick one before I forget to post about it - common issue when developing a web control (mostly EditorParts these days, or web parts that do not have an ASCX) is that the user chooses values for the text boxes and dropdownd and other controls, click the submit button, but the value in the function that deals with the button click (or the ApplyChanges function in an editorpart) doesnt get the value the user chose. Instead, the value is the old value.
There are several possible things that can cause this (I covered some in a previous article about common mistakes) but one that is so obviously simple that I forget to make sure of is this:
Make sure "base.CreateChildControls();" is the first thing called in the "CreateChildControls" function.
If that doesnt work, read my previous article about the web part life cycle.

Sandbox solutions and the publishing image field type

I recently found out (to my horror) that sandbox solutions do not support (among many other things) the publishing field types. For example, if you want to use the "LinkFieldValue" class from the Microsoft.SharePoint.Publishing.Fields namespace in a sandbox solution, then you are out of luck. So far, fair enough - annoying, but fair. I figured that I can overcome this by not using that class to parse the details of the image from publishing pages, and I will write my own function that will tease out the image URL from the string that the field returns.

How naive am I!

When I tried running this: imageUrl = item[field.Internalname);

The code would time out, and not return anything. The entire page would just hang. No exception thrown or any reason given.

My solution was to use: imageUrl = item.GetFormattedValue(field.InternalName);

Apparently this gets the string value without trying to go through anything that is not supported in sandboxed solutions. Update: after a bit of more trial and error I found that the exception thrown (sometimes) is that the assembly (the publishing one) is not serialiazble:
'item[this.ImgFieldName]' threw an exception of type 'System.Runtime.Serialization.SerializationException'

Sunday, December 12, 2010

"Cannot complete this action." error when adding a field to a list (or a content type)

If you are trying to add a site column to a list, or a content type that has a site column to a list, and get the dreaded "Cannot complete this action.", then have a read below. Note - this is valid for both sharepoint 2007 and 2010.

To troubleshoot this, I turned off the custom errors to see the entire stack trace of the error. The error was thrown by the Microsoft.SharePoint.Library.SPRequest.AddField function. This lead me to read Eric's blog post about the error and he pointed me in the right direction.

It turns out that a lookup column that allows multiple selection is not supported as an indexed column. You'd expect SharePoint to validate that, and indeed you do not get that option when configuring such a column using the user interface.
However - sharepoint does not validate that when you create the site column using CAML (which is what Eric did) or using code (which is what I did).

Eric's blog post shows how you can recreate and resolve the problem if you are creating the column using XML. So just in case you are making the same mistake I was doing, this is how I resolved my issue - I just make sure none of my code-created lookup columns is both multi select AND indexed, as you can see in the code below.

SPFieldLookup newField = web.Fields.GetFieldByInternalName(newColumnInternalName) as SPFieldLookup;
newField.Title = "My Lookup";
newField.AllowMultipleValues = isMultiSelect;
newField.Group = "My Site Columns";
if (!isMultiSelect) newField.Indexed = true; else newField.Indexed = false;
newField.LookupField = myList.Fields["Title"].InternalName; newField.Update();

Sunday, November 21, 2010

The Web application at ... could not be found when creating a new SPSite object

A quick hint for anyone hitting this issue - you are trying to open a SPSite object using a URL and hitting the following error: "The Web application at [...] could not be found. Verify that you have typed the URL correctly. If the URL should be serving existing content, the system administrator may need to add a new request URL mapping to the intended application." is common, and can have several causes.

  1. The obvious: The URL does not exist on the farm. Solution: change the URL you are loading to the correct one.
  2. The not so obvious: the code is in a console or windows application which you are now running with an account that does not have permissions to connect to the SQL server. Solution: either run the application as a different user or grant the user permissions on the SQL database.
  3. The one hiding behind the sofa: the code is in a console or windows application that is compiled with platform target x86, while your SharePoint server is x64. Solution: set the platform target to "Any CPU".

Tuesday, October 19, 2010

Coming to Canada's SharePoint Summit

Hello to all my Canadian readers!

I am excited to announce that I will be speaking in the upcoming Toronto SharePoint Summit!
And not just speaking - from the looks of it I seem to be doing the most speaking of all the other speakers (I guess the organizers figured that the cost of a plane ticket from Australia needs to be covered by three sessions). Please come see me talk on:

Oh - and if anyone has recommendations what to do the week before the conference (last week of January) - please comment here! Ski resorts recommendations would be most welcome.

Saturday, October 16, 2010

PowerPivot For SharePoint

Are you in Canberra this week? even if not, you may want to read this post.

This week I am presenting in the Canberra SharePoint User Group about PowerPivot for SharePoint. I have spent the weekend reading and testing this application - mostly trying to figure out how it is different from Excel Services.

Basically, powerpivot for excel is a powerfull database analysis add on to excel that creates connections to external databases, analyses the relationships for you and allows you to create great pivot tables and charts from this relational external data. This makes creating pivot tables from complex databases very simple.

PowerPivot for SharePoint is a service application that you install from SQL2008R2 features. This adds the ability to open the workbooks that were created by powerpivot on the browser, while still remaining connected to the external database. It also adds a "powerpivot gallery" list template that creates a special document library with a nice and smooth interface to allow the users to look for the pivot sheet they want to open, and it has the default "open in browser" behaviour. The data gets refreshed and accessed on the server, so even users with weak desktops can analyse huge databases (up to 100 million rows - depending on the amount of RAM the server has). A good high level overview can be found on the powerpivot blog on MSDN, installation instructions on MSDN and a demo and a lot of marketing information on the official powerpivot site.

Two thing you should know:
to allow the web application to authenticate to the database the pivot is connected to, you will probably need to activate the "Claims to Windows Token Service" service (which is by default off if you used the configuration wizard to configure for you). Otherwise, the power pivot will keep giving you authentication errors.
The last thing is a problem with the way the powerpivot application is deployed. Don't ask me why, but there is a problem with how it is deployed, as described in this powerpivotgeek's blog post. If you get an error when opening a powerpivot gallery list, check out the geek's post for the solution (simply deploy the solution to the web application...).

So, if you are in town, come say hello and watch me install, set up, and run powerpivot in the Canberra user group. If not, you have a chance to see me next week in the South East Asia SharePoint Conference in singapore.