Create history in another list with event receiver

I have decided to use an event receiver to create a copy of previous version of an item from a list into a separate list, created for keeping versions. So I created first a list called “Tickets” with the following structure.

Column Name Column Type
Ticket Title Single line of text
Priority Choice

I have also created another list with the same structure (I kept also column names for making work easier) and I called it “Tickets History”.
After I completed those steps, I created a static class for adding items to the SharePoint lists. In general, I prefer static classes for operations I execute often, because I do not rewrite a lot of code in each section of a project.

    public class ListOperations
        public static void AddListItem(Dictionary<string, string> listItem, string listName, SPWeb web)
            SPList lst = web.Lists[listName];
            SPListItem item = lst.AddItem();
            foreach (KeyValuePair<string, string> field in listItem)
                item[field.Key] = field.Value;

Event receiver was attached to “Sprint Tickets” list and triggered by update operations. Code presented below shows how event receiver is using my static class to add previous version into “Sprint Tickets History”.

 public class TicketChangedEvent : SPItemEventReceiver
        /// <summary>
        /// An item is being updated.
        /// </summary>
        public override void ItemUpdating(SPItemEventProperties properties)

            SPWeb web = properties.OpenWeb();
            Dictionary<string, string> historyItem = new Dictionary<string, string>();
            historyItem["Ticket Title"] = (String)properties.ListItem["Ticket Title"];
            historyItem["Priority"] = (String)properties.ListItem["Priority"];
            ListOperations.AddListItem(historyItem, "Tickets History", web);   

Note that SPContext cannot be used from an event receiver to get current site or web, but properties.OpenWeb() or properties OpenSite() can do the job in this case.


No access to SharePoint online log

Yesterday, I have decided to buy a subscription for Office 365 SharePoint Online. My developer’s subscription expired and there wasn’t any way to prolong it. So I choose a plan and paid only around 7 EUR, as I have only one user for SharePoint.
This being done, I started to customize my page on the website accessible for public. Once I did a mistake, I received a correlation id. As all developers, I tried to see if there is any way to have access to the log and I ended raising a service request to Microsoft. But, surprise… answer come quick. If I want to know what is behind that correlation id, I need to provide a business justification to Microsoft and server administration will give me requested information.
I really hope Microsoft will provide soon a way to access errors. I am wondering how developers for Office 365 are creating application for SharePoint online. Probably they are doing “blind” development. 🙂

Custom form slow performance

Recently, I have discovered some performance issues with custom forms created with SharePoint Designer. Mysteriously, form was slow even for adding new records to the list.
Looking into the code created by SharePoint Designer, I discovered that new form was based on SPDataSource with ListItem mode set and CAML was getting all data from the list.

<SharePoint:SPDataSource runat="server" DataSourceMode="ListItem" SelectCommand="&lt;View&gt;&lt;Query&gt;&lt;Where&gt;&lt;Eq&gt;&lt;FieldRef Name=&quot;ContentType&quot;/&gt;&lt;Value Type=&quot;Text&quot;&gt;Item&lt;/Value&gt;&lt;/Eq&gt;&lt;/Where&gt;&lt;/Query&gt;&lt;/View&gt;" UseInternalName="True" ID="Service_x0020_List_x0020_Marcom1">
         <WebPartPages:DataFormParameter ParameterKey="ListItemId" PropertyName="ParameterValues" DefaultValue="" Name="ListItemId" />
        <WebPartPages:DataFormParameter ParameterKey="ListID" PropertyName="ParameterValues" DefaultValue="{036D8075-3258-4DA2-8804-52897332DD3E}" Name="ListID" />

Analyzing CAML select command, I realized SPDataSource is getting all rows from Item content type. For a custom list this probably means all rows. So, I decided to change it.

<SharePoint:SPDataSource runat="server" DataSourceMode="List" SelectCommand="&lt;View&gt;&lt;Query&gt;&lt;Where&gt;&lt;Eq&gt;&lt;FieldRef Name=&quot;ID&quot;/&gt;&lt;Value Type=&quot;Counter&quot;&gt;{ItemId}&lt;/Value&gt;&lt;/Eq&gt;&lt;/Where&gt;&lt;/Query&gt;&lt;/View&gt;" UseInternalName="True" ID="Service_x0020_List_x0020_Marcom1">
         <WebPartPages:DataFormParameter ParameterKey="ItemId" PropertyName="ParameterValues" DefaultValue="" Name="ItemId" />
        <WebPartPages:DataFormParameter ParameterKey="ListID" PropertyName="ParameterValues" DefaultValue="{036D8075-3258-4DA2-8804-52897332DD3E}" Name="ListID" />

What I changed:

  • SPDataSource was set to List mode from ListItem.
  • CAML was changed and filter was applied on ID field.
  • ListItemId parameter was changed to another one. I called it ItemId, but you can choose different names. Is also a good idea not to bind this parameter to ID query string.

Applying this changes results in speed increase. Try it and you will see. Don’t let SharePoint Designer control you.

Get all users from a SharePoint group

I have searched for a solution to get all users from a SharePoint group and found nothing simple. Mostly all solutions are C# based. But looking on Microsoft website I discovered membership CAML clause. This was perfect fit for me. I created a SPDataSource for this and worked. See code below.

<SharePoint:SPDataSource runat="server" DataSourceMode="List" SelectCommand="&lt;View&gt;&lt;Query&gt;&lt;Where&gt;&lt;Membership Type=&quot;SPGroup&quot; ID=&quot;108&quot;&gt;&lt;FieldRef Name=&quot;ID&quot;/&gt;&lt;/Membership&gt;&lt;/Where&gt;&lt;OrderBy&gt;&lt;FieldRef Name=&quot;EMail&quot;/&gt;&lt;/OrderBy&gt;&lt;/Query&gt;&lt;/View&gt;" UseInternalName="True" ID="DSUsers">
<SelectParameters><asp:Parameter Name="ListName" DefaultValue="User Information List" /></SelectParameters>

SPDataSource above is displaying all users from SharePoint group with ID 108. Of course in your case this ID will be different. Decoded CAML looks like this.

<View><Query><Where><Membership Type="SPGroup" ID="108"><FieldRef Name="ID"/></Membership></Where><OrderBy><FieldRef Name="EMail"/></OrderBy></Query></View>

An error 0x800A0CB3 occured. No further information was provided.

I noticed this error once on a Windows 7 x64 operating system with an Office 2007 x32 installed. I have searched the internet for a solution, but nothing solved by problem. And finally I found an article on this link.

The solution was not provided directly, but it gives the idea it has something to do with OLE DB drivers. Installing Microsoft Access Database Engine 2010 Redistributable solved my issue. So, if you will see this error, you can try my solution. Please drop me a note if your issue is not solved.

Set up form authentication in an ASP.NET webforms website

ASP.NET websites support more authentication types, but probably the most used is form authentication. This kind of authentication requires a database containing information about users and roles and some changes in web.config for enabling membership and roles.  But let’s take this step by step.

1. Set-up the database

Locate the “aspnet_regsql.exe” file in “C:\Windows\Microsoft.NET\Framework\v4.0.30319” folder (note that folder can be different, depending on your installed .NET version). This is a small application which is setting your database for form authentication.

ASP.NET Register SQL

Once you enter all required information on the second step, you can set the connection to the database in web.config. In “configuration” section add connection string to database you have set-up before. Adapt code below your situation and set correct username, password and server location.

    <add name="authenticationConnection" connectionString="Data Source=ServerName\SqlServer;Initial Catalog=authentication;User ID=Username;Password=Password" providerName="System.Data.SqlClient" />

2. Set-up web.config

To enable form authentication on website you need to add some settings in web.config, all of them being placed in “system.web” section.

    <!-- Roles -->
    <roleManager enabled="true" defaultProvider="RoleProvider">
        <add name="RoleProvider"
             type="System.Web.Security.SqlRoleProvider, System.Web, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

    <!-- Membership -->
    <membership defaultProvider="FormMembershipProvider">
      <providers >
        <add name="FormMembershipProvider"
              type="System.Web.Security.SqlMembershipProvider, System.Web, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
              connectionStringName="authenticationConnection" enablePasswordRetrieval="false" enablePasswordReset="true" />

    <!-- Set auth form location and default url if no one is specified when site is accessed -->
   <authentication mode="Forms">
      <forms defaultUrl="/Default.aspx" loginUrl="/Login.aspx"></forms>

3. Set roles and usernames

This is the easiest step. Open your website with Visual Studio and to go to ASP.NET Configuration command.

ASP.NET Configuration

Your browser will open configuration pages for your websites. Choose security tab and start to set roles and some users.

Use MicrosoftAjax.js from custom location

MicrosoftAjax.js represent the base of ASP.NET Ajax client side development. It is a library developed by Microsoft which is automatically placed in your page if you include asp:ScriptManager control.  The file is taken from server resources and you cannot change it or add new code to it if you do not place it into a custom location.

But how do I specify the custom location for this file? First let’s start with the beginning and see how asp:ScriptManager is handling JavaScript files.

JavaScript files are added within Scripts tag inside asp:ScriptManager by specifying name or path to the file.

<asp:ScriptManager runat="server" LoadScriptsBeforeUI="false" ID="JSManager">
       <asp:ScriptReference Name="MicrosoftAjax.js" />
       <asp:ScriptReference Name="MicrosoftAjaxWebForms.js" />
       <asp:ScriptReference Path="/Scripts/Framework.js" />

Code posted above includes “MicrosoftAjax.js” and “MicrosoftAjaxWebForms.js” files from default location (those two files should be used together) and a custom JavaScript file from “Scripts” directory called “Framework.js”.

Now is time to create a copy of ASP.NET Ajax files and move them to “Scripts” directory near my custom code file.  By adding path near name attribute,  asp:ScriptManager will look for the location I specified, despite official documentation (please drop me a note if I am wrong).

<asp:ScriptManager runat="server" LoadScriptsBeforeUI="false" ID="JSManager">
       <asp:ScriptReference Name="MicrosoftAjax.js" Path="/Scripts/MicrosoftAjax.js" />
       <asp:ScriptReference Name="MicrosoftAjaxWebForms.js" Path="/Scripts/MicrosoftAjaxWebForms.js" />
       <asp:ScriptReference Path="/Scripts/Framework.js" />

Making this modifications I am now able to change built-in code and adapt it to new non-supported browsers like Chrome.