Koen’s Weblog

SharePoint developer with a life!

Archive for the ‘Development’ Category

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 »

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: , , , , | 2 Comments »

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 »

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 »

Linq4SP – RC1 Available

Posted by koenvosters on October 10, 2008

Dotneteers reports that Release Candidate 1 of Linq4SP has been released. What is it? Linq4SP is a readable-writable Linq provider to SharePoint (WSS 3.0 and MOSS 2007), with a lot of interesting and useful features: support the handling of list items, documents, folders, content types, enumerations usable for choice columns, … Basically it allows you to use LINQ on SharePoint objects. I haven’t tried it out yet, probably will this weekend, but it looks promising. Get it here.

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

Visual Studio 2010 and .Net Framework 4.0

Posted by koenvosters on September 30, 2008

It’s official :) Microsoft announced Visual Studio 2010 and .Net Framework 4.0. Appearantly, to have a bigger impact they will be leaking news slowly instead of giving it all to you in one big bang. There is already a nice overview page available which lists some features, there are a few interesting news posts. One quite interesting part of it all is that the new Team System will no longer be running on SQL Server 2005 but on SQL Server 2008 due to its’ more advanced reporting abilities.

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

Review: Balsamiq Mockup For Desktop

Posted by koenvosters on July 25, 2008

 Yesterday I received a license for Balsamiq Mockup For Desktop. Thanks a lot for that Peldi! As creating mockups is an important part of a functional analysis / workshop, I figured I would give it a try and see if it fits my needs. As you launch it you get a nice interface, and honestly, in less than 5 minutes I created a standard SharePoint Team Sites mockup. Cool! 5 minutes later I had the mockup of two of my SharePoint Pages, and a little later the InfoPath form design was done as well. Great! Fast! But now what? I got my mockups, I can talk about it with the customer, change it and finalize it. Or even better, you give your customer a license of Balsamiq and send him the files for verification, adaptation and approval. No need for Visual Studio, Visio, … This is a great advantage of the product. Instead of sitting in your ivory tower designing everything, sending it back, receiving it, sending it back again, you can INVOLVE the customer in the creation of the screens. And at the end, when the screen looks exactly as they designed it, the satisfaction will be there as well. Due to the basic look and feel of the controls in Balsamiq, you can easily separate the logical design of each page from the sexy webdesign that you need to provide as well. You define what is displayed on each page, and where it is displayed. And up till here, all is great in Balsamiq world. And in my opinion, this is where the tool should be used for, to sit in a meeting or a workshop with the customer, and decide what we are gonna put where on each page and give them the possibility to make minor improvements in the design themselves.  After this phase, I found Balsamiq quite lacking, but in my opinion the potential is there. You got the concept all worked out, and now you can go into your Visual Studio or SharePoint and do the exact same thing, again? That is what struck me as rather odd. Yes, Balsamiq mockups are xml files, and you could write atool to to read the xml from the file and convert that into a WinForm or a WPF Form. But why not provide that functionality out of the box? I mean, the program IS great, sometimes a little bit sluggish but that might be my pc, how insane would it be if you could select : Export to WPF, .Net WinForm, Java Form, PHP, ASP, … No matter what technology you are developing in (idealism striking again ;) ),  you could use one tool to design your screens and export them to your development environment.

You would be able to create your mockups easily, and just have it exported into code in your project. Right now, although Balsamiq is great, doing the mockup in Visual Studio from the start onwards will still save me time. The workshops will be going a bit slower, but I won’t have to do the design twice. (Note: this of course depends on the project/customer as well. There will be a point where you had to make so much changes to your design that making use of Balsamiq may have been the better choice) In case of paper mockups it is of course better to use Balsamiq during the workshops as it will save you quite some time. So, on to a little wrap up:
 
Advantages:
  •  Fast mockup design
  • Basic layout completely ignoring the graphical aspect of a website/application
  • Mockups are XML Files
  • Low license cost, easy to provide to your customer and have them collaborate on mockups
  • Low complexity, easily useable by business users
  • Did I mention you can create mockups fast?

Disadvantages

  • No real export functionalities : would be nice to be able to export to WPF, WinForm, ASP, ASP.Net, PHP, …
  • No possibility to add your own controls (in case of custom webparts/controls it would be nice if you could create your own mockup control)
  • No hierarchy in mockups (workaround through folders possible) 
  • No possibility to add your own controls (in case of custom webparts/controls it would be nice if you could create  your own mockup control)

Last but not least I would like to stress that I tested the Desktop version of the product, I have no clue if certain functionalities that are now lacking are available through the JIRA integrations or other integrations. Will I use Balsamiq Mockups for functional workshops. Yes I will, depending on the type of project. Would I use it if the export stuff was there? Yes, but then for each project.

Link : Balsamiq Mockups

Posted in Development | Tagged: | 4 Comments »

Visual Studio 2008 not loading

Posted by koenvosters on July 7, 2008

Today I experienced a rather weird error. Upon launching VS 2008 (Beta2) it refused to launch. After checking out my processes I could see that devenv was running, but nothing was happening. After checking all the possible settings that stop an exe from starting up, I finally came up with the solution.

When launching VS2008 it talks to a process called PresentationFontCache.exe. When I stopped that process, VS2008 continued to launch, and PresentationFontCache got launched as well.

On a sidenote, I went to the BPIO training concerning Collaboration, Unified Communcation, … and it was good stuff. A bit too SharePoint minded, but at least we know have a clear vision of Ms’s vision on IO.

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

Look of LINQ

Posted by koenvosters on July 7, 2008

I finally decided to take a look at LINQ. A quick google around took me some time to figure out how to get a full example working (which references are needed etcetera…) Therefor I decided to write a quick tutorial on how to work with DLINQ. To make this sample working you should create a SQL Server database Blog with 1 table in it called Post. In that table you will have id, title and body as columns, where id is a unique identifier (int auto-increment)
Create a new asp.net website project and add a class library called Business to it as well.Rename Class1.cs to Post.cs (also make sure the class is renamed to Post)
Ok, now we are good to go. First of all we will add the connectionstring to our web.config:

<connectionStrings>
<add name=BlogConn
connectionString=Data Source=(local);Initial Catalog=Blog;Integrated Security=True

providerName=System.Data.SqlClient />
</connectionStrings>

Your app.config should look like the above. Now, what we will do first is create a class named Post and add all the private variables to it and the properties.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Linq;
using System.Data.Linq.Mapping;
namespace Business
{
[Table(Name = "Post")]
public class Post
{
private int _id;
private string _title;
private string _body;
[Column(Storage="_id" , IsPrimaryKey=true)]
public int Id
{
get { return _id; }
set { _id = value; }
}
[Column(Storage="_title")]
public string Titel
{
get { return _title; }
set { _title = value; }
}
[Column(Storage="_body")]
public string Body
{
get { return _body; }
set { _body = value; }
}
}
}

 

If you put it like this, it will not compile. Add a reference to the System.Data.Linq dll and then your business should compile. What does all of the tags above the properties mean: [Table(Name = "Post")] : it tells LINQ how the table is called in the database
[Column(Storage="_id" , IsPrimaryKey=true)] : this property is stored in the private variable _id. Should our table column name be different from id, we would have to use [Column(Storage="_id" , Name"DBColumnName", IsPrimaryKey=true)]
Ok, so now we have connected Linq to the database fields. How do we tell Linq which database to do and what queries to perform?
We will create a new class in the Business Project called “BlogDB”. That class will look like the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Linq;

namespace Business
{
class BlogDB : DataContext
{
public Table<Post> Post;
public BlogDB(string connection) : base(connection) { }
}
}

This class just exists to make our life a bit easier. BlogDB inherits from DataContext. The DataContext class allows you to use any data holder (xml, array,…) as datasource for your LINQ code. Now up to the LINQ work. I made a Posts class that is a Collection of Post classitems:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Configuration;

namespace Business
{
public class Posts : CollectionBase
{
public void GetAllPosts()
{
BlogDB db = new BlogDB(ConfigurationManager.ConnectionStrings["BlogConn"].ConnectionString);
var q =
from p in db.Post
where p.Titel == “Test”
select p;
foreach (var pst in q)
base.InnerList.Add(pst);
}
}
}

For those who aren’t familiar with ConfigurationManager, it’s the next gen version of ConfigurationSettings.AppSettings. To make it work a simple using System.Configuration will not do the trick. You will need to manually add the System.Configuration.dll to your references. Ok, so now we have a collection that contains all the posts that have test as title. Add a few items to your database of which at least 1 has test or Test as title. What we are doing here is getting all teh posts with the LINQ query:

var q = from p in db.Post
where p.Titel == “Test”
select p;

 

and then putting all the results in the collection. One more step to go is showing it on screen. Add a dropdownlist to your web form in your web project and then put the following codebehind:

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml.Linq;
using Business;
namespace Blog
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Posts p = new Posts();
p.GetAllPosts();
DropDownList1.DataSource = p;
DropDownList1.DataTextField = “Titel”;
DropDownList1.DataBind();
}
}
}

 

Load it up and everything should work just fine. Don’t hesitate to ask me questions through the comments.

Posted in Development | Tagged: | Leave a Comment »