Koen’s Weblog

SharePoint developer with a life!

HOWTO: Add a button to the ribbon in SharePoint 2010

Posted by koenvosters on November 5, 2009

This post is intended for BETA1. It will work on BETA2 as well, just remove the … for

After a few hours of messing around in XML, we (Jopx and I) figured out how to put a button on the Ribbon. First of all, how does it work? There is some XML (located in the 14 hive/templates/global/xml) in a file called CMDGui.XML. This contains the entire Ribbon XML that is used to render the ribbon. To get that XML into a Ribbon, there are two javascript files that translate that data into a workeable ribbon (CUI.js and SP.Ribbon.js). What do we need to do to create our custom button? It’s quite simple, you just create some XML, push it to the SharePoint environment by making use of an empty element, add some stuff to the templates/xml folder and you’re set. That is once you figured it all out :)

First of all, create a new Empty SharePoint project in Visual Studio. (no screenshots after this as my laptop here can’t run SP2010)

image

  • Rename your project to MyCoolButton
  • Add an empty Element.
  • In that element, paste the following xml:
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction Id="MeSoCool" Location="CommandUI.Ribbon.Documents.New.Controls._children">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition Location="CommandUI.Ribbon.Documents.New.Controls._children">
          <Button Id="Ribbon.Documents.New.Controls.MyCoolButton"
                  Image16by16="/_layouts/images/edit.gif"
                  Image32by32="/_layouts/images/placeholder32x32.png"
                  Description="MyDescription"
                  Command="MyCoolButton"
                  LabelText="Banzai"
                  Sequence="60"
                  TemplateAlias="o1" />
        </CommandUIDefinition>
      </CommandUIDefinitions>
    </CommandUIExtension>
  </CustomAction>
</Elements>

What does all this XML mean?

First of all we will be adding a CustomAction, and the most important attribute there is the Location. This location attribute will define where the button is going to appear. Notice that the same attribute is also defined in the CustomUIDefinition which is the location where it will need to be defined in Beta2. So in Beta1 apply the Location to the CustomAction, in Beta2 only apply the location to the CommandUIDefinition. Notice the _children in the end, you need to add it to be able to add buttons to that Control collection.

Where does this location come from? In the CMDGui.XML file that location is defined in a group. This group is part of a group collection called Ribbon.Documents.Groups which is part of the Tab called Ribbon.Documents.  This tab is the ribbon we will see when we are working with documents. Important to notice is the Template the group is using. If the Template used is Ribbon.Templates.Flexible2 you can add buttons to that group. If it isn’t a flexible template (like Ribbon.Templates.ThreeRowsSixAlignedControls2) then you can’t add buttons as those are fixed templates. As an example I added the xml for the Ribbon.Documents.New group.

<Group
            Id="Ribbon.Documents.New"
            Sequence="10"
            Command="DocumentNewGroup"
            Description=""
            Title="$Resources:core,cui_GrpNew;"
            Image32by32Popup="/_layouts/images/placeholder32x32.png"
            Template="Ribbon.Templates.Flexible2"
          >

 

Ok, on to the next item in our XML, the CommandUIExtensions. They contain the CommandUIDefinitions who are defining the element that you are showing (a Button in our case). The name of the second element escapes me and I can’t check SP2010 at the moment, but the other element within the CommandUIExtensions defines the action of the button (what happens if you click on it) Our little post is only focused on making it appear. Now back to our Button Control.

<Button Id="Ribbon.Documents.New.Controls.MyCoolButton
             Image16by16="/_layouts/images/edit.gif"

             Image32by32="/_layouts/images/placeholder32×32.png"

             Description="MyDescription"

             Command="MyCoolButton"

             LabelText="Banzai"

             Sequence="60"

            TemplateAlias="o1" />

The Id needs to start with Ribbon.Documents.New.Controls. After that you can put whatever you want. The image attributes are used and the images are shown depending on what is defined in the GroupTemplates. What does that mean? In the GroupTemplates (also defined in the CMDGui.xml) it is defined how a button in the ribbon should behave when the page gets resized. Should it hide itself, should it change it’s template (for instance instead of 3 large buttons next to eachother 3 small buttons above eachother)That way we can control how our Ribbon is behaving when the page gets resized. Description is quite easy to understand what it does, Command links to the command that you can define afterwards (you can deploy without having implemented the command), the LabelText is used in the template and shown on the ribbon, the Sequence defines where the button needs to be places and the TemplateAlias links to the template that you are using for that button.

Ok, now that we have that XML we just need to do one more thing:

Add a SharePoint mapped folder to your project and link it to the templates/xml folder. It should automatically create a MyCoolButton folder under the XML folder, but if it doesn’t make sure you do. Add an xml file to it callee MyCoolButton.xml. In that file we will define our link to the javascript files.

<script OnDemandKey="ribbon">
  <File></File>
  <File>SP.Ribbon.js</File>
</script>


Now we are set to go. Rightclick your project, select deploy. Go to a document library, on the ribbon select Documents and you should see a new button next to the New Folder button. (More images will follow as soon as I get home, but we were so excited when it finally worked that I at least had to put it online, but I’m sure Joris will do the same very soon :) )

Should I have missed something, let me know, but this should do the trick for you :)

Posted in Development, Moss 2010, Ribbon | Tagged: , , | 1 Comment »

Using Javascript and jQuery in MOSS

Posted by koenvosters on October 15, 2009

Jan Tielens released a very nice solution to allow you to easily integrate Javascript and jQuery in your SharePoint sites without having to go through several configurations steps. It allows you to just upload your javascript files to a document library and they will be available in your SharePoint pages. Check it out here.

Posted in Moss 2007 | Leave a Comment »

HOWTO : Create a Site with a custom template through code and assign specific user security (User has no rights to create a site).

Posted by koenvosters on September 7, 2009

Imagine the following scenario. In your site collections visitors need to be able to create 1 type of sites. When they create that type of site (with a custom template) they need to become the administrator of that site, as well as the people maintaining the site collection.

How do we do that in SharePoint? We create a site while running with Elevated Privileges, let it inherit the rights of the Site Collection. Once the site is created, we break the inheritance and add specific user rights for that user.

First of all we will be creating a function that accepts a few strings that we need to create the site.

Code Snippet
  1. public string CreateSite(string parentSiteUrl, string siteUrlRequested, string siteTitle, string siteTemplateName)
  2.         {
  3.             return “”;
  4.         }

The parentSiteUrl is the url in which we will be creating the site, the requested siteUrlRequested is the url we will be creating the site in, the siteTitle is the title we will be giving to the site, and the siteTemplateName is the name of the Custom Template we will be using.

Ok, let’s write the code to create our site in our parentsite. In the GetCustomWebTemplates functions we will be getting the custom web template list corresponding the language of the site. In that list we take the template as specified in the parameter of our function (siteTemplateName). As we can’t be sure that the user has checked if a site url already exists we increase a counter till we find a free siteUrl (this is not mandatory, you can also raise an error on the Exists boolean).

Code Snippet
  1. string siteUrlValid = “”;
  2.                 const Int32 localeIdEnglish = 1043;
  3.                 SPSecurity.RunWithElevatedPrivileges(delegate
  4.                                                          {
  5.                     
  6.                     using (SPSite siteCollection = new SPSite(parentSiteUrl))
  7.                     {
  8.                         using (SPWeb parentWeb = siteCollection.OpenWeb())
  9.                         {
  10.                             SPWebTemplateCollection templates = siteCollection.GetCustomWebTemplates(Convert.ToUInt32(localeIdEnglish));
  11.                             SPWebTemplate siteTemplate = templates[siteTemplateName];
  12.                             int counter = 1;
  13.                             siteUrlValid = siteUrlRequested + “_” + counter;
  14.                             while (parentWeb.Webs[siteUrlValid].Exists)
  15.                             {
  16.                                 counter++;
  17.                                 siteUrlValid = siteUrlRequested + “_” + counter;
  18.                             }
  19.                             parentWeb.AllowUnsafeUpdates = true;
  20.                             using (SPWeb myWeb = parentWeb.Webs.Add(
  21.                                 siteUrlValid,
  22.                                 siteTitle,
  23.                                 siteTitle,
  24.                                 Convert.ToUInt32(localeIdEnglish),
  25.                                 siteTemplate,
  26.                                 false, false))
  27.                             {
  28.                                 
  29.                             }
  30.                             parentWeb.AllowUnsafeUpdates = false;
  31.                         }
  32.                     }
  33.                 });
  34.                 return siteUrlValid;

 

With this code our site will be created by making use of the custom template. We are running with Elevated Privileges as the user does not have any permissions to create a subsite. In the using statement of myWeb we will be adding the security changes.

Code Snippet
  1. using (SPWeb myWeb = parentWeb.Webs.Add(
  2.                                 siteUrlValid,
  3.                                 siteTitle,
  4.                                 siteTitle,
  5.                                 Convert.ToUInt32(localeIdEnglish),
  6.                                 siteTemplate,
  7.                                 false, false))
  8.                             {
  9.                                 myWeb.BreakRoleInheritance(true);
  10.                                 SPUser user = myWeb.EnsureUser(SPContext.Current.Web.CurrentUser.LoginName);
  11.                                 SPRoleDefinition def = new SPRoleDefinition(myWeb.RoleDefinitions.GetByType(SPRoleType.Contributor));
  12.                                 SPRoleAssignment assignment = new SPRoleAssignment(user.LoginName, user.Email, user.Name,
  13.                                                                                    user.Notes);
  14.                                 assignment.RoleDefinitionBindings.Add(myWeb.RoleDefinitions[def.Name]);
  15.                                 myWeb.AllowUnsafeUpdates = true;
  16.                                 myWeb.RoleAssignments.Add(assignment);
  17.                                 myWeb.Update();
  18.                                 myWeb.AllowUnsafeUpdates = false;
  19.                                 
  20.                             }

 

With BreakRoleInheritance we break the inheritance of the rules. We do this to be able to add the user to the security of that specific site. We are breaking the security afterwards because we want to make sure that the existing security on the site is copied as well. With the EnsureUser statement we make sure the user exists in that site. The SPRoleDefinition isn’t necessary, but I’m using it to make sure that my code runs on multilingual systems. You could use:

Code Snippet
  1.                                 assignment.RoleDefinitionBindings.Add(myWeb.RoleDefinitions["Contributor"]);

but in a multilingual environment that RoleDefinition isn’t called Contributor. That’s why I will first create a SPRoleDefinition object to make sure that whatever Contributor is called will return the correct name in my SPRoleAssignment. I add the roledefinition to the assignment and then I add the user/role link to the site. Once you have implemented all this your code should look like this:

Code Snippet
  1. public string CreateSite(string parentSiteUrl, string siteUrlRequested, string siteTitle, string siteTemplateName)
  2.         {
  3.             if (siteUrlRequested == null) throw new ArgumentNullException(“siteUrlRequested”);
  4.             try
  5.             {
  6.                 bool returnCondition = false;
  7.                 string siteUrlValid = “”;
  8.                 const Int32 localeIdEnglish = 1043;
  9.                 SPSecurity.RunWithElevatedPrivileges(delegate
  10.                                                          {
  11.                     int counter = 1;
  12.                     using (SPSite siteCollection = new SPSite(parentSiteUrl))
  13.                     {
  14.                         using (SPWeb parentWeb = siteCollection.OpenWeb())
  15.                         {
  16.                             SPWebTemplateCollection templates = siteCollection.GetCustomWebTemplates(Convert.ToUInt32(localeIdEnglish));
  17.                             SPWebTemplate siteTemplate = templates[siteTemplateName];
  18.                             siteUrlValid = siteUrlRequested + “_” + counter;
  19.                             while (parentWeb.Webs[siteUrlValid].Exists)
  20.                             {
  21.                                 counter++;
  22.                                 siteUrlValid = siteUrlRequested + “_” + counter;
  23.                             }
  24.                             parentWeb.AllowUnsafeUpdates = true;
  25.                             using (SPWeb myWeb = parentWeb.Webs.Add(
  26.                                 siteUrlValid,
  27.                                 siteTitle,
  28.                                 siteTitle,
  29.                                 Convert.ToUInt32(localeIdEnglish),
  30.                                 siteTemplate,
  31.                                 false, false))
  32.                             {
  33.                                 myWeb.BreakRoleInheritance(true);
  34.                                 SPUser user = myWeb.EnsureUser(SPContext.Current.Web.CurrentUser.LoginName);
  35.                                 SPRoleDefinition def = new SPRoleDefinition(myWeb.RoleDefinitions.GetByType(SPRoleType.Contributor));
  36.                                 SPRoleAssignment assignment = new SPRoleAssignment(user.LoginName, user.Email, user.Name,
  37.                                                                                    user.Notes);
  38.                                 assignment.RoleDefinitionBindings.Add(myWeb.RoleDefinitions[def.Name]);
  39.                                 myWeb.AllowUnsafeUpdates = true;
  40.                                 myWeb.RoleAssignments.Add(assignment);
  41.                                 myWeb.Update();
  42.                                 myWeb.AllowUnsafeUpdates = false;
  43.                             }
  44.                             returnCondition = true;
  45.                             parentWeb.AllowUnsafeUpdates = false;
  46.                         }
  47.                     }
  48.                 });
  49.                 if (!returnCondition)
  50.                 {
  51.                     siteUrlValid = “”;
  52.                 }
  53.                 return siteUrlValid;
  54.             }
  55.             catch (Exception)
  56.             {
  57.                 return null;
  58.             }
  59.         }

Calling the function can be done like this (Web is the SPContext.Current.Web object).

Code Snippet
  1. siteUrl = CreateSite(Web.Url, siteUrl, siteTitle, “tbtemplate.stp”);

Posted in Development, Moss 2007, SharePoint | Tagged: , , , , | Leave a Comment »

Unable to copy VM Harddrives to external disk – FAT 32

Posted by koenvosters on September 3, 2009

As from time to time I buy a new disk to store my old VM’s on at the first copy I get an error message when I try to copy hard disk of my vm to my external disk. That’s due to the fact that those disks are mainly formatted as FAT32 (which can handle 4 GB). As I wanted to put a vhd on my work backup disk I got the same message, and I did not want to delete the backups I already took I looked for a command to change it without having to format the drive, and I found it :)

convert <drive> /fs:ntfs works. You should execute it in a command window that you are running as an administrator.

Posted in Moss 2007, SharePoint | Tagged: | Leave a Comment »

HOWTO : Get the Last ItemID in a list

Posted by koenvosters on August 20, 2009

The following code shows you how to get the last item of an ID in a list. Warning, this code gets you the last item from that list meaning that if you use this code to get the last item you added, it will go wrong if someone adds an item between you adding it and executing this query. In that case I advise you to get a unique identifier that you add to each item and get the item by that id.

Code Snippet
  1. const string siteurl = “http://sitecollection”;
  2.             using (var site = new SPSite(siteurl))
  3.             {
  4.                 using (var web = site.OpenWeb(“myweb”))
  5.                 {
  6.                     var list = web.Lists["mylist"];
  7.                     var query = new SPQuery
  8.                                     {
  9.                                         Query = “   <OrderBy> <FieldRef Name=’ID’ Ascending=’False’ /> </OrderBy>”
  10.                                     };
  11.                     var items = list.GetItems(query);
  12.                     SPListItem item;
  13.                     if (items.Count > 0)
  14.                         item = items[0];
  15.                 }
  16.             \

Posted in Development, Moss 2007, SharePoint | Tagged: , , , | Leave a Comment »

Your client does not support opening this list with Windows Explorer

Posted by koenvosters on August 17, 2009

Your client does not support opening this list with Windows Explorer is an error I see popping up a lot of times. What can you do to fix it?

If you are using IE6:
http://support.microsoft.com/kb/325355/

If you are using IE7 OR IE8
Vista, Windows Server 2003, XP : http://www.microsoft.com/downloads/details.aspx?FamilyId=17C36612-632E-4C04-9382-987622ED1D64&displaylang=en

Posted in Moss 2007, SharePoint | Tagged: , , | Leave a Comment »

HOWTO : Getting the User Properties from Active Directory with People Picker

Posted by koenvosters on August 10, 2009

A while ago I had to get additional properties from Active Directory. It was important that the information was live (I could not consider the information that got imported by the User Profile Import to be 100% up-to-date. To get this done I wrote this little piece of code (don’t forget to reference System.DirectoryServices):

private ResultPropertyCollection GetUserProperties(string userAccount)

    {

        DirectoryEntry entry = new DirectoryEntry();

        entry.Path = "LDAP://CUSTOMER";

        entry.AuthenticationType = AuthenticationTypes.Secure;

        //DirectorySearcher _searcher = new DirectorySearcher(entry);

 

        String account = userAccount.Replace(@"CUSTOMER\", "");

        try

        {

            using(HostingEnvironment.Impersonate())

            {

                        DirectorySearcher search = new DirectorySearcher(entry);

                        search.Filter = "(SAMAccountName=" + account + ")";

                        search.PropertiesToLoad.Add("department");

                        search.PropertiesToLoad.Add("mail");

                         search.PropertiesToLoad.Add("title");

                        search.PropertiesToLoad.Add("company");

                        SearchResult result = search.FindOne();

                       

                        if (result != null)

                        {

                            return result.Properties;

                        }

                        else

                        {

                            return null;

                        }

            }

        }

        catch (Exception ex)

        {

            lblResult.Text = ex.Message;

            return null;

        }

    }

Replace CUSTOMER with your DOMAIN.
I added one more function to easily get a property out of the collection:

private string GetFromUserProperties(ResultPropertyCollection _properties, string _prop)

    {

        try

        {

            if (_properties[_prop].Count > 0)

            {

                return _properties[_prop][0].ToString();

            }

            else

            {

                return "";

            }

        }catch(Exception ex){

            return "";

        }

    }

When you select a user in a People Picker it calls a postback. In the Onload event of your page handle the call of the following function

private void CheckAndFillInfo(SPWeb site)

    {

       

        if (spPELeidinggevende.ResolvedEntities.Count > 0)

        {

            PickerEntity _pe = (PickerEntity)spPELeidinggevende.ResolvedEntities[0];

            SPUser _spuser = site.EnsureUser(_pe.Key);

 

               

                ResultPropertyCollection _results = GetUserProperties(_spuser.LoginName);

                if (_results != null)

                {

                   

                    txtFunction.Text = GetFromUserProperties(_results, "title");

                }

        }

        else

        {

            txtFunction.Text = “”;

        }

    }

That should do the trick.

Posted in Development, Moss 2007, SharePoint | Tagged: , , | Leave a Comment »

Putting workflow code in a separate project (using wspbuilder)

Posted by koenvosters on August 7, 2009

As I am rebuilding one of our projects to make it easier to deploy I had to put the workflow code in one separate project. As I thought this would be a piece of cake I didn’t expect Visual Studio to show me a bunch of errors when doing so. The one that kept appearing was "The service ‘System.Workflow.ComponentModel.Compiler.ITypeProvider’ must be installed for this operation to succeed. Ensure that this service is available". After looking it up I got a bunch of different guids that I could put in my project file, but none of them seemed to do the trick. Until I checked out a WPF workflow post that told me to use some other guids and they worked fine. So, how do we start.

First of all, we will be creating a project that is called MyCustomer.MySuperSolution. We will make it a WSP Project or a class library. We will add a new item, which will be a blank feature (WSPBuilder Item). Secondly, we will be creating another WSP Project or class Library and call it MyCustomer.MySuperSolution.Workflows. We will be adding a sequential workflow feature that project. Let’s call it EmailWorkflow. As this will create it’s own solution (and that is not what we want, we want 1 wsp for the whole project) we will be making some changes. First of all, rename the feature folder that is called EmailWorkflow to MyCustomer.MySuperSolution.EmailWorkflow. That way it will have a decent naming convention in the feature folder. Move that folder to the same location in your MyCustomer.MySuperSolution project. Then delete the complete 12 folder structure from your MyCustomer.MySuperSolution.Workflows project. You can also remove the solutionid.txt file as it is no longer needed. Do not remove the snk as your assembly needs to be strong named.

Two steps remain, which are making sure that when you recompile your workflow solution that it is added to the manifest.xml by wspbuilder when you select build wsp, and to remove the dreadful errors in your workflow project. To make sure the assembly is added, right-click on your project, properties, build, output path. Point the output path to the bin/debug folder of your MyCustomer.MySuperSolution project. It is a good idea to also do that for the release configuration as many times you forget to set that up while building for release (which is then the bin/Release folder). To remove the dreadful ITypeProvider errors you need to open your project with notepad. Change your ProjectTypeGuids to:

<ProjectTypeGuids>{14822709-B5A1-4724-98CA-57A101D1B079};{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>

and add the following line near the end of the file (there is already one import statement)

<Import Project="$(MSBuildExtensionsPath)\Microsoft\Windows Workflow Foundation\v3.0\Workflow.Targets" />

If you want to target the WF of .Net Framework 3.5 just change 3.0 to 3.5

Posted in Development, Moss 2007, SharePoint | Tagged: , , , , | Leave a Comment »

Real World Branding in SharePoint on MSDN

Posted by koenvosters on August 7, 2009

There is a very nice article on MSDN, published by Andrew Connell and Randy Drisgill explaining you how to customize SharePoint and give it a very sexy look. Definetely a must read.

http://msdn.microsoft.com/en-us/library/ee354191.aspx

Posted in Development, Moss 2007, SharePoint | Tagged: | Leave a Comment »

Showing the real error message in SharePoint

Posted by koenvosters on August 6, 2009

It seems like a lot of those questions still are popping up on forums, even though Bing & Google bring you right to it. Instead of pointing those people to other blogs, I might as well point them to this nice blog :)

There are two settings in your web.config you need to change to enable custom errors. This web.config is located in c:\inetpub\wwwroot\VirtualDirectories. In that folder you have 2 possible foldernames: foldernames with a number and foldernames with a name. If you are using hostnames then the web.config file you are looking for is containt in the <hostname> folder. If you are not using hostnames, the name of the folder corresponds to the port your web application is running on.

Open the web.config and look for the CallStack=”false” attribute. Put it to true.
Then search for the CustomErrors=”on” tag and change it to off.

Once those two actions are done you will see a more detailed error message.

Posted in Development, Moss 2007, SharePoint | Leave a Comment »

SharePoint : stop firing events while I run my code

Posted by koenvosters on August 2, 2009

Ok, this small post to keep coverning events:

How do I stop events from firing in my code?

this.DisableEventFiring(); 

And how do I put it back on?

this.EnableEventFiring();

Posted in Development, Moss 2007, Moss 2010, SharePoint | Leave a Comment »

HOWTO: Create an Event Handler for SharePoint(MOSS 2007)

Posted by koenvosters on July 31, 2009

As I see this question popping up on many forums, I thought it would be time to write a tutorial about it, even though there are already quite a few of them handling the subject.

Prerequisites:

How-to Create an Event Handler by making use of a feature:

To start with : what exactly is an event handler for SharePoint? It’s a piece of code that is triggered when something (an event!) happens. When that happens, our event handler can replace what is supposed to happen with our own code. How do we build that in Visual Studio (using WSP Builder)?

First of all, we will be creating a WSP Builder Project called MyEventHandler:

image

Once the project is created, we will right-click the project and select add new item. In the left column, select WSPBuilder and select Event Handler in the template list. In the name field I chose to call it DemoEventHandler.

image

You’ll get a new screen where you can define the scope of the feature. You can leave it at Web

image

After this step your project will have three additional files added to it and a few folders:

image

feature.xml: the CAML based declaration of your feature.
elements.xml: the CAML based declaration of the element(s) in your feature, which is your event handler in this case.
DemoEventHandler.cs : the code that will be overriding the existing SharePoint Event Handlers.
Let’s take a look at our feature.xml code:

<?xml version="1.0" encoding="utf-8"?>

<Feature Id="875e92bb-782c-40b4-a5a9-f55423df667e"

   Title="DemoEventHandler"

   Description="Description for DemoEventHandler"

   Version="12.0.0.0"

   Hidden="FALSE"

   Scope="Web"

   DefaultResourceFile="core"

   xmlns="http://schemas.microsoft.com/sharepoint/">

   <ElementManifests>

      <ElementManifest Location="elements.xml"/>

   </ElementManifests>

</Feature>

What this file does is identify the feature for SharePoint (Id), give it a title and description (which will be shown on the feature activation site), define a scope (where Web means site, where Site means Site Collection and then there is Web Application and Farm as possible scopes.
Another important part of the feature is the <ElementManifests> area. That area defines all the items that make part of the feature while the manifests themselves describe that specific part. As it is here the case with the event handler:

<?xml version="1.0" encoding="utf-8" ?>

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

   <Receivers ListTemplateId="100">

      <Receiver>

         <Name>AddingEventHandler</Name>

         <Type>ItemAdding</Type>

         <SequenceNumber>10000</SequenceNumber>

         <Assembly>MyEventHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ca176e059473d6b1</Assembly>

         <Class>MyEventHandler.DemoEventHandler</Class>

         <Data></Data>

         <Filter></Filter>

      </Receiver>

   </Receivers>

</Elements>

What is important for us? First of all the ListTemplateId. The ListTemplateId defines to which List Types the Event Handler will be targeting. The table here below shows which list types there are available in SharePoint 2007:

ID Name
100 Generic list
101 Document library
102 Survey
103 Links list
104 Announcements list
105 Contacts list
106 Events list
107 Tasks list
108 Discussion board
109 Picture library
110 Data sources
111 Site template gallery
112 User Information list
113 Web Part gallery
114 List template gallery
115 XML Form library
116 Master pages gallery
117 No-Code Workflows
118 Custom Workflow Process
119 Wiki Page library
120 Custom grid for a list
130 Data Connection library
140 Workflow History
150 Gantt Tasks list
200 Meeting Series list
201 Meeting Agenda list
202 Meeting Attendees list
204 Meeting Decisions list
207 Meeting Objectives list
210 Meeting text box
211 Meeting Things To Bring list
212 Meeting Workspace Pages list
300 Portal Sites list
301 Blog Posts list
302 Blog Comments list
303 Blog Categories list
1100 Issue tracking
1200 Administrator tasks list
2002 Personal document library
2003 Private document library

 

Once we have defined which list we are going to target we will define that we are overriding an ItemAdding event. ItemAdding means that the event will be fired right before the item is added to the list. This allows us to modify the item before it is saved to the list. The other parameters aren’t that important at the moment, apart from the Assembly and Class that will be linking to the assembly that contains the code of your event handler.

Possible events that you can override:

ItemAdded
ItemAdding
ItemAttachmentAdded
ItemAttachmentAdding
ItemAttachmentDeleted
ItemAttachmentDeleting
ItemCheckedIn
ItemCheckedOut
ItemCheckingIn
ItemCheckingOut
ItemDeleted
ItemDeleting
ItemFileConverted
ItemFileMoved
ItemFileMoving
ItemUncheckedOut
ItemUncheckingOut
ItemUpdated
ItemUpdating

Ok, so we checked out the feature.xml and the elements.xml, but there is also the DemoEventHandler.cs file. That contains the actual code of our event handler:

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.SharePoint;

 

namespace MyEventHandler

{

    class DemoEventHandler : SPItemEventReceiver

    {

      public override void ItemAdded(SPItemEventProperties properties)

      {

          base.ItemAdded(properties);

      }

 

      public override void ItemAdding(SPItemEventProperties properties)

      {

          base.ItemAdding(properties);

      }

 

      public override void ItemUpdated(SPItemEventProperties properties)

      {

          base.ItemUpdated(properties);

      }

 

      public override void ItemUpdating(SPItemEventProperties properties)

      {

          base.ItemUpdating(properties);

      }

 

    }

}

If you deploy it like this your event handler will run, but it will just call the base class and nothing special will happen. Let’s change the ItemAdding Event (as it is already defined in our CAML to be deployed). We will change the itemadding event so that it will check, when an item is being added, by making sure the CheckValue column does not contain the string “dontadd”. If it does contain dontadd, an error message is displayed and the item is NOT added to the list. To do this, we modify the ItemAdding Event to this:

public override void ItemAdding(SPItemEventProperties properties)

{

    if (properties.AfterProperties["CheckValue"].ToString() == "dontadd")

    {

        properties.ErrorMessage = string.Format("The CheckValue column equals dontadd -> item will not be added.");

        properties.Status = SPEventReceiverStatus.CancelWithError;

        properties.Cancel = true;

    }

}

A little extra explanation. The AfterProperties contain the NEW values of an item. The BeforeProperties contain the OLD values of an item in case of an update. For an ItemAdding event the BeforeProperties are empty. What we do here is check the CheckValue properties value. If it contains “dontadd” we show the error message and by making use of properties.Status = SPEventReceiverStatus.CancelWithError we cancel the base.ItemAdding(properties) call. By adding properties.Cancel = true we cancel the Itemadding event.

Ok, so now we built this, but how do we get this working on our SharePoint site? With WSPBuilder that is quite easy. Rightclick on your project, select WSPBuilder / Build WSP. This will create a solution file to be deployed on your SharePoint farm. Once that is done, select WSPBuilder / Deploy and the solution will be installed and deployed to your farm.

image  

Ok, one thing to note here is that the event handler will be targeting all lists. This means that every list that does not contain the CheckValue column will no longer work. But checking if the column exists is something that you should be able to do yourself. Once it is deployed to your SharePoint farm, create a new Custom List and Add the column CheckValue of type text. Then go to Site Settings, Site Features (NOT Site Collection Features as the scope was Web) and activate our newly deployed feature:

image

Ok, now we can test it by adding a new item with CheckValue equal to dontadd.

image

If you did everything according to plan, this is the information you should be receiving when you click ok:

image

Happy Coding!

In addition I added a few other interesting bits regarding Event Handlers

How-to Register an Event Handler through C# code:

The following code allows you to register an event handler by making use of code. RunWithElevatedPrivileges isn’t always necessary, but I added it to it so that you know that you can’t run the code in an application page as a user who doesn’t have the necessary rights.

SPSecurity.RunWithElevatedPrivileges(delegate()

{

   impersonateweb.AllowUnsafeUpdates = true;

   _spListAanwezige.EventReceivers.Add(SPEventReceiverType.ItemAdded, "Namespace, Version=1.0.0.0, Culture=neutral, PublicKeyToken=61942ef99a051977", "Namespace.EventClass");

   _impersonateweb.AllowUnsafeUpdates = false;

});

How-to see if your event handler is deployed properly to the list:

Out of the box SharePoint doesn’t display if your event handler is correctly hooked to a list. I make use of SharePoint Inspector(http://www.codeplex.com/spi) to check out my SharePoint Farm. To see if something is hooked to your list go to the following structure in your SharePoint Inspector to check out which events are registered to your list:

image

Tags van Technorati: ,,

Posted in Development, Moss 2007 | Tagged: , | 9 Comments »

Getting the SPUser Property at an event

Posted by koenvosters on July 30, 2009

Lets say we have a list containing the attending people of a meeting. On the same site we also have a list of the people that are not attending the meeting (absent). To make sure people that are on the attending list aren’t put on it twice, the obvious choice is to create an eventhandler that checks if the user is already in either of the list. Piece of cake. Or not? The code to achieve this seems quite obvious:

public override void ItemAdding(SPItemEventProperties properties)
        {
            
using (SPWeb web properties.OpenWeb())
            {
                CheckForDoubles(properties, web)
;
            
}      
        }
        
private static void CheckForDoubles(SPItemEventProperties properties, SPWeb web)
        {
            SPUser user 
web.AllUsers.GetByID(Convert.ToInt32(properties.AfterProperties["Persoon"]));
            
SPList attendinglist web.Lists["Attending"];
            
SPQuery query = new SPQuery();
            
query.Query = string.Format(“<Where><Eq><FieldRef Name=\”Persoon\” /><Value Type=\”User\”>{0}</Value></Eq></Where>”, user.Name);
            
SPListItemCollection attendingitems attendinglist.GetItems(query);
            
SPList absentlist web.Lists["Absent"];
            
SPQuery queryafw = new SPQuery();
            
queryafw.Query = string.Format(“<Where><Eq><FieldRef Name=\”Persoon\” /><Value Type=\”User\”>{0}</Value></Eq></Where>”, user.Name);
            
// execute the query    
            
SPListItemCollection absentitems absentlist.GetItems(query);
            if 
((absentitems!= null &;&absentitems.Count >0) || (attendingitems != null &;&attendingitems.Count >0))
            {
                properties.ErrorMessage 
= string.Format(“The user {0} is already in the attending or absent list.”, user.Name);
                
properties.Status SPEventReceiverStatus.CancelWithError;
                
properties.Cancel = true;
            
}
        }

And it worked perfectly on my dev box. But for some reason, when deploying it to the acceptance/test environment, this piece of code no longer worked. After checking out the afterproperties (see my other post) I found out that instead of the ID I was getting ;1#domain\username back in the acceptance environment. I haven’t quite figured out why, but to solve it for both systems (and to prevent problems in the future), I changed the code to:

SPUser user;
            if 
(properties.AfterProperties["Persoon"].ToString().Split(‘#’).GetLongLength(0) > 1)
                user 
web.AllUsers[properties.AfterProperties["Persoon"].ToString().Split(‘#’)[1]];
            else
                
user web.AllUsers.GetByID(Convert.ToInt32(properties.AfterProperties["Persoon"]));

Posted in Development, Moss 2007 | Leave a Comment »

Getting the values of the AfterProperties of an event

Posted by koenvosters on July 30, 2009

As I was having some troubles with a different behavior between Development and Acceptance (AfterProperties["FieldName"] was giving errors in Acceptance but not in Development) I had to figure out a way on how to display the values in the AfterProperties.  In the end, I came up with this, which requires you to add ”using System.Collections;”

foreach (DictionaryEntry de in properties.AfterProperties)
            {
                EventLog.WriteEntry(
“MOSS 2007 Test Properties”“key:” + de.Key + “- value:” + de.Value);
            
}

Posted in Development, Moss 2007 | Tagged: , , | Leave a Comment »

Removing the thousand seperator from a YEAR calculated value

Posted by koenvosters on July 30, 2009

If you wish to remove the annoying thousand seperator from a YEAR calculated column use this formula:

=TEXT(YEAR([YourDateField]);”0″)

Posted in Development, Moss 2007 | Tagged: | Leave a Comment »

OpenFileDialog InitialDirectory not working properly

Posted by koenvosters on July 30, 2009

As it’s been a while since I did some real Windows development I started building a tool that makes it easier to manage content types during a development cycle (changes to content types, changes of documententation to those content types, linked list definitions, …) One of the required abilities is to be able to load an xml file *duh*. As I wanted people to get to the initial directory of the application I used:

OpenFileDialog ofd = new OpenFileDialog();
ofd.Reset();
ofd.Multiselect = false;
ofd.Filter “XML Files|*.xml”;
ofd.InitialDirectory System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
if 
(ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
    txtContentTypeLocation.Text 
ofd.FileName;
}

To my great surprise, that did not work. I looked around on the web but didn’t really find a solution, while in debug the folder was giving the correct information. However, when I changed it into:

ofd.InitialDirectory = Environment.CurrentDirectory;

everything worked fine. I guess it’s a bit picky on how it gets the folder information…

Posted in Development | Tagged: , | Leave a Comment »

SharePoint Products and Technologies: 2010 (Technical Preview) Developer Documentation released

Posted by koenvosters on July 16, 2009

 Microsoft released the SharePoint Products and Technologies: 2010 (Technical Preview) Developer Documentation and the
Microsoft SharePoint Products and Technologies Protocol Documentation. Definetely worth checking out although the API isn’t really documented yet :s

Posted in Moss 2010 | Tagged: , | Leave a Comment »

SharePoint 2010 Sneak Preview

Posted by koenvosters on July 13, 2009

If you are not connected to twitter, don’t get RSS feeds and what not to get news, you might have missed this:

SharePoint 2010 Sneak Preview is online. Check it out here!

This is also officially my FIRST MOSS 2010 post :p

Posted in Moss 2010 | Tagged: | Leave a Comment »

Automated builds using VSeWSS 1.3

Posted by koenvosters on July 9, 2009

I stumbled upon this post from John W Powell explaining how to create a nightly build using the Visual Studio Extensions for WSS 3.0. Exteremely interesting and something I’m gonna try out later this week. Let’s hope it’s a bit easier to do so in VS 2010 with MOSS 2010 :)

Posted in Moss 2007, Team System | Tagged: , | Leave a Comment »

Authentication problem on MOSS (login box keeps popping up)

Posted by koenvosters on June 18, 2009

Today I had a rather weird problem at a client. Whenever he tried to log on to his new extranet he had a login box coming up constantly, whatever he clicked on. First thought were changing the browser settings to automatically log in when on an intranet, but that didn’t fix it. When I connected to the site everything did work perfectly, so I still searched for a client setting. When checking out online I got some information about this error popping up when you have external content on your site. That was not the case, everything was local. The only thing on the site that wasn’t OOTB were the design elements. Just to try, I removed the design elements. The user tested and the login box disappeared, everything was working just fine. First idea was to check the security of the _layouts folder, but the standard SharePoint images were working just fine, so it couldn’t be the folder. I decided to check out the images in the 12 hive to see if there was something weird with them. And there it was, appearantly by copying the images to the 12 hive only the administrator had read/write acces to the images. Giving the authenticated users reading rights on the images fixed the issue.

Posted in Moss 2007 | Leave a Comment »

Access Denied when using hostname (search and site) on MOSS 2007.

Posted by koenvosters on June 15, 2009

Lately I’ve ran into a weird bug that appearantly has to do with the Windows Server 2003 Service Pack. When I tried a SharePoint site with http://spdev1:35000 everything worked like a charm. However, if I used a hostname http://customerdemo then all of a sudden I got Access is denied. As I used the administrator account with full access that was quite weird. As I didn’t get into SharePoint it meant that the problem had to be in IIS. After looking up some stuff I came to a Microsoft hotfix that sorted my problem : http://support.microsoft.com/kb/896861

That hotfix does not really mention the hostname problem, but it does fix the problem. Method1 is what you should go for on a production environment, Method 2 is what you can use on a dev box.
Method 2:

Method 2: Disable the loopback check

Follow these steps:

  1. Click Start, click Run, type regedit, and then click OK.
  2. In Registry Editor, locate and then click the following registry key:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa
  3. Right-click Lsa, point to New, and then click DWORD Value.
  4. Type DisableLoopbackCheck, and then press ENTER.
  5. Right-click DisableLoopbackCheck, and then click Modify.
  6. In the Value data box, type 1, and then click OK.
  7. Quit Registry Editor, and then restart your computer.

I recalled seeing this solution on some other Belgian blog post by Sven Gillis. As it turns out there they linked it to the search no longer working (which in my case is quite normal as no account has access to the site). So if you get the following in your search you can use the above solution as well:
Access is denied. Verify that either the Default Content Access Account has access to this repository, or add a crawl rule to crawl this repository. If the repository being crawled is a SharePoint repository, verify that the account you are using has “Full Read” permissions on the SharePoint Web Application being crawled. (The item was deleted because it was either not found or the crawler was denied access to it.)

Posted in Moss 2007 | Leave a Comment »

Quite a few new releases: IE 8.0, SharePoint Themes, Silverlight 3 Beta

Posted by koenvosters on March 20, 2009

There have been quite some releases this week :)

(Microsoft)Internet Explorer 8.0 out of beta: Link
(Microsoft)Ten SharePoint Themes: Link
(Microsoft)SilverLight 3 Beta: Link

Now all I lack is the time to check them out indepth. Gah.

Posted in Uncategorized | Leave a Comment »

InfoPath either cannot connect to the data source or the service has timed out.

Posted by koenvosters on March 10, 2009

This error is one we get confronted with quite a lot (especially our juniors :) ). As a clear fix isn’t really described out there I’ll put it here and hope it bubbles up on google :-)

If you are using http://localhost in your webservice url, use http://servername. That should do the trick.

Posted in Moss 2007 | Leave a Comment »

I’ve finally switched to the Dark Side : using Twitter

Posted by koenvosters on February 27, 2009

As I get more and more social push to use facebook, my blog, twitter, … I’ve decided to start using twitter as well :) If you want to see what I’m up to : http://www.twitter.com/koenvosters

Posted in Personal | Leave a Comment »

Approaches to Creating Master Pages and Page Layouts in SharePoint Server 2007

Posted by koenvosters on February 27, 2009

Andrew Connell (Microsoft MVP from the Ted Pattison Group) posted a new article on MSDN regarding the creation of master pages and custom page layouts. It explains the different (two) approaches that you can take to create them. Quite an interesting read, especially if you want to know the consequences (pro’s and con’s) of your choice.

Link : http://msdn.microsoft.com/en-us/library/dd164422.aspx#MOSS2007CreatingMasterPagesAndPageLayoutsTwoApproaches

Posted in Moss 2007 | Leave a Comment »