Thursday, November 29, 2007

Why event handlers are triggered twice?

A lot of people call me Mr Event Handlers (ok, that is not 100% correct. Currently I am the only one calling me that, but never mind), but today's article comes from a Friend who we will refer to as Antin.
Antin did some research on why event handlers behaved strangely in publishing sites and emailed me the results. He logged every event that happens when you check-in a file in a publishing site, and logged the thread of each call, along with the time, so he figured out the sequence.

I think his results are important for all of us writing event handlers - we need to know what is happening when. If you do find this helpful, post a "thank you antin" comment on this post, and I will make sure he know about it (enough encouragment and he may start his own blog, the lazy bugger).

The test results:
(In brackets will be thread number, then list item is old/new, then before properties is old/new, then after values is old/new) Change AND Check-in:
1. Updating (10; old; old; old)
2. Updating (10; new; new; new)
3. CheckingIn (10; new; new; new)
4. CheckedIn (15; new; new; new)
5. Updated (11; new; new; new)
6. Updated (1; new; old; old;)

Change AND Save:
1. Updating (10; old; old; old)
2. Updated (12;new; old; old)

Check-in ONLY
1. CheckingIn (9; new; new; new)
2. CheckedIn (11; new; new; new)
3. Updated (13; new; new; new)
4. Updated (10; new; new; new)

Publish ONLY
1. Updating (1; new; new; new)
2. Updated (13; new; new; new)

If you look at what happens for the save only and the check in only and the combination of new/old values it looks like it working like this:
1. ItemUpdating - from the Save event
2. ItemUpdating - from the CheckIn event (which should fire after the Updated from the Save)
3. ItemCheckingIn
4. ItemCheckedIn
5. ItemUpdated - from the CheckIn event
6. ItemUpdated - from the Save event

Tuesday, November 27, 2007

Avoiding CSS caching issues

I had a problem in several projects where we changed the CSS and deployed it to the load balanced server environment, and still the old CSS file was cached somewhere, and we couldnt roll out the changes to the users.
Another problem on the same line are browsers that cache the css files, and users do not see the changes we make to the css until we tell them to clear their browser's cache.

To resolve this, we took a trick from Microsoft - notice how Microsoft always links to the css with a version number in sharepoint? this is done to avoid caching when you put a new version.

My trick was to develop a custom web control (that I call the "CssInjector") that gets as a property the link to the css file, and renders that link with a random querystring. this forces the browsers (as well as IIS or other caching applications) not to cache the file.
The problem with this solutions is that it will have performance implications - slower loading of pages, so what I'd really like to do is what MS did - rely on a version number. However, I have yet to come up with the architecture that will let me manage version numbers in my solution package without having to manually setting the version every time. I thought of reading the version number from the CSS file itself, but that will introduce performance issues again - as the code will have to load and read the css file each time a page is opened.

So until I get a better solution, here is the code I am using for my web control:

using System;
using System.Web.UI.WebControls;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
namespace SharePointTips.SharePoint.WebControls{
public class CssInjector : WebControl
    {
        private const string CSS_LINK_FORMAT = @"<link rel=""stylesheet"" type=""text/css"" href=""{0}"">";
        private string _CSSFileLink = "";
        public string CSSFileLink
        {
            get { return _CSSFileLink; }
            set { _CSSFileLink = value; }
        }
        protected override void Render(HtmlTextWriter writer)
        {
            if (this.CSSFileLink.Length > 0)
            {
                writer.Write(string.Format(CSS_LINK_FORMAT, this.CSSFileLink + "?k=" + GetUniqueKey(8)));
            }
        }
        public static string GetUniqueKey(int length)
        {
            //create an array of acceptable characters
            char[] chars = new char[62];
            chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
            StringBuilder result = new StringBuilder(length);
            Random rnd = new Random();
            //create the random string in the specified length
            for (int i = 0; i < length; i++)
            {
                result.Append(chars[rnd.Next(chars.Length)]);
            }
            return result.ToString();
        }
    }
}
And here is how I use it in the master page:
<%@ Register Tagprefix="SharePointTipsWebControls" Namespace="SharePointTips.SharePoint.WebControls" Assembly="SharePointTips.SharePoint.WebControls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxx" %>
<SharePointTipsWebControls:CssInjector runat="server" ID="SharePointTipsCSS1" CSSFileLink="/_layouts/SharePointTips/SharePointTipsCustomCss.css" />

Wednesday, November 07, 2007

MVP Night in Vegas

Text to come...

Monday, November 05, 2007

SharePointPedia is live

Microsoft has just released a site built on sharepoint, that will hold information (links) for sharepoint. (The short link is http://sharepointpedia.com)
Check out Lawrence Liu post about this.

Sunday, November 04, 2007

First night in Vegas

Yes!
I am there at last, Vegas for the sharepoint dev connections conference. It only took me 30 hours to get from home to here, and I landed only 12 hours after I left (confused? so am I...)

So far, Vegas stinks. I don't mean it sucks - I mean it stinks with cigarette smoke. Man, its like going back to eastern europe (or Amsterdam). One reason I love Australia so much is the anti smoking laws.

So just a reminder - If you want to meet, in two days (Tuesday) we are having the SharePoint by Day, SharePint by Night event, with all MVPs having a few drinks and talking sharepoint with everyone who will stop to listen. Must be funny!
See you there!

Wednesday, October 31, 2007

Another simple code snippet - using the user field


A user field in a list allows people to choose a user for each list item.


When looking at the value that is stored, we can see it is a lookup field - and is stored using the format ID#useraccount. For example:

listitem["User Name"] will have "1#development\ishai"
Click on the image below to see it in action in visual studio:





Where does this number come from? well, everytime you create a site in sharepoint, sharepoint creates a list for users. Each user gets an ID in the list, and when you add a user column to a list in that site, it is infact a special case of a lookup column into the users list. So in each site the same user will have a different ID.



So how do we use this field in code?

Get a user from a list item:



To get a user we use the SPFieldUserValue class, which accepts a SPWeb and a string value as parameters. The string value is the value from the list that contains the ID and the account name. Once we have that, we can use the SPFieldUserValue to get information about the user.

Example:


 using (SPSite site = new SPSite("http://portal"))

            {

                using (SPWeb web = site.OpenWeb())

                {

                    SPList list = web.Lists["Example For Users"];

                    SPListItem item = list.Items[0];

 

                    SPFieldUserValue userValue = new SPFieldUserValue(web, item["User Name"].ToString());

                    if (userValue.User.LoginName == web.CurrentUser.LoginName)

                    {

                        //do something!

                    }

                }

            }







Adding a value to a user field



This is the same, but in reverse. We use the same class (SPFieldUserValue ) and give it the values we want for the user.



using (SPSite site = new SPSite("http://portal"))

            {

                using (SPWeb web = site.OpenWeb())

                {

                    SPList list = web.Lists["Example For Users"];

                    SPListItem item = list.Items[0];

 

                    SPFieldUserValue userValue = new SPFieldUserValue(web, web.CurrentUser.ID, web.CurrentUser.LoginName);

                    item["User Name"] = userValue;

                    item.Update();

                }

            }



The image below shows the list after I updated the list item:






Monday, October 29, 2007

Failed to instantiate file "default.master"

I was building a new solution package that deployed a site definition, and when I created a new site from that definition I got the error "Failed to instantiate file "default.master" from module "DefaultMasterPage": Source path "default.master" not found.".
It turns out that I wrote the wrong folder name in the webtemp xml file. Thank you Bjarne L. Gram for writing about this.

Wednesday, October 24, 2007

Code Practices - getting\setting values from\to the lookup and the hyperlink fields

How to get data from a field that is a lookup? how to set data into a field that is a hyperlink?
I don't believe I didn't write about this yet. It's so common!

For both cases, sharepoint object model exposes classes to help us get or set the data we want. These are the SPFieldLookupValue and the SPFieldUrlValue.

Example 1: Set the url field of a link


Use the SPFieldUrlValue class to create an object that holds the url to link to, and the title to display:

SPList list = web.Lists["Links"];

SPListItem newLink = list.Items.Add();

SPFieldUrlValue value = new SPFieldUrlValue();

value.Description = "test";

value.Url = "http://www.microsoft.com/sharepoint";

newLink["URL"] = value;

newLink.Update();

Example 2: Get the url field of a link


Use the SPFieldUrlValue class to create an object that gets the url and description:

SPList list = web.Lists["Links"];

SPListItem existingLink = list.Items[0];

SPFieldUrlValue value = new SPFieldUrlValue(existingLink["URL"].ToString());

string linkTitle = value.Description;

string linkURL = value.Url;

Example 3: Set the value of a lookup field for a known title and ID


In the following example I am using SPFieldLookupValue to set the value of a lookup field ("Group Name") to item "Program Operations", whose ID is 14:

SPList list = web.Lists["Branches"];

SPListItem newBranch = list.Items.Add();

newBranch["Title"] = "A New Branch";

SPFieldLookupValue newValue = new SPFieldLookupValue(14,"Program Operations");

newBranch["Group Name"] = newValue;

newBranch.Update();

Example 4: Get the value of a lookup field from an item


Here I am reading the value of the group name field (which is a lookup field in the branches list):

SPList list = web.Lists["Branches"];

SPListItem existingBranch = list.Items[0];

SPFieldLookupValue group = new SPFieldLookupValue(existingBranch["Group Name"].ToString());

int lookedUpItemID = group.LookupId;

string lookedUpItemTitle = group.LookupValue;

Sunday, October 21, 2007

Performing joins between SharePoint lists (link)

Check it out: Performing joins between SharePoint lists by fellow MVP Sahil Malik, explains how to use sharepoint designer to show two linked lists as one. I was going to write about it, but he beat me to it.
Maybe next time I won't go bush-walking on the weekend and write more?


Oh, about the weekend - I found some MOSS that I thought would interest you:



Red Moss (Seratadon?)





Wednesday, October 17, 2007

Using the SharePoint People Picker


A long time ago (beta2 time) I published a post about the people picker control (MOSS 2007 controls - have a bit of fun with the "people editor" form control!).
Lately I had many comments on that post from people who have been having issues getting the value the user selected, and asked for another demo of using the control.
So here it is:



The code creates three controls - a PeopleEditor (from the Microsoft.SharePoint.WebControls namespace), a button and a textbox. The button has an click event that itirates over the users picked in the PeopleEditor and writes them to the text box. Simple!





public class PeoplePickerWebPart : System.Web.UI.WebControls.WebParts.WebPart

    {

        PeopleEditor pe;

        TextBox t;

        Button b;

 

        protected override void CreateChildControls()

        {

            base.CreateChildControls();

            pe = new PeopleEditor();

 

 

            this.Controls.Add(pe);

            b = new Button();

            b.Text = "Click me to see the users";

            this.Controls.Add(b);

            b.Click += new EventHandler(b_Click);

            t = new TextBox();

            t.TextMode = TextBoxMode.MultiLine;

            this.Controls.Add(t);

        }

 

        void b_Click(object sender, EventArgs e)

        {

            foreach(string ent in pe.CommaSeparatedAccounts.Split(','))

            {

                t.Text += ent + Environment.NewLine;

            }

        }








Results:

The web part as the page loads





The web part does "check names"





The web part after the click of the button