Archive

Archive for the ‘SharePoint’ Category

Archiving documents in SharePoint 2010 OTB

September 13, 2012 1 comment

There might be other ways of setting this up but I thought I should write about a way of achieving this by purely utilising SharePoint 2010 out-of-the-box features.

Scenario

You have a document library, we will call it Documents, that has a custom Content Type (we will call it ER Documents). Based on a certain criteria, lets say 6 months after a document has been approved, you would like to archive the document by moving it to another document library called ‘ER Compliance Archived Documents’.

Assumptions

You have two identical document libraries setup with the same content type enabled.

The Solution

The solution involves:

  • Enabling the Content Organizer Feature
  • Creating a Content Organizer Rule
  • Configuring a ‘send to’ connection
  • Configuring the Document Library

Enabling the Content Organizer Feature

On the root web of your Site Collection browse to Site Actions > Site Settings > Manage Site Features and activate the ‘Content Organizer’ Feature.

After this feature is activated you will notice two new options in Site Settings (highlighted in the screenshot below) and a new document library called ‘Drop Off Library’:

Content Organizor Options

Click on ‘Content Organizer Settings’ and make a note of the Web Service URL:

Web Service URL

Creating a Content Organizer Rule

Click on the ‘Content Organizer Rules’ link and add a new item to create a new rule and set it up as below (click on the image if it appears a bit blurred):

Content organizer rule

This rule basically specifies where and how to route the archived documents.

Configuring a ‘send to’ connection

Browse to Central Administration > General Application Settings > Configure send to connections. Select the correct Web Application and create a new send to connection by filling out the form as below pasting the Web Service URL you copied earlier:

Send To Connection

Click on ‘Add Connection’ and then ‘OK’.

Configuring the Document Library

Browse to the main ‘Documents’ document library > Library Settings > Information management policy settings > ER Documents (this is our content type) > Check ‘Enable Retention’ and fill the form out as below:

Enable Retention

Testing the solution

Add a document that matches the archiving criteria (i.e. Approval Date more than 6 months ago).

Go to Central Administration > Monitoring > Review job definitions > select your Web Application and manually run the ‘Information management policy’ timer job. This job process and marks the documents, that match the criteria we have setup, for transfer. After the job has completed manually run the ‘Expiration policy’ timer job this timer job does the actual transfer of the marked documents.

After this the relevant documents that match the Information management policy criteria will be moved to the archived library.

You can also test this manually by browsing to the ‘Documents’ document library> Accessing the context menu > Send to > ER Compliance Archive (screenshot below).

Manually archive document

The document should then appear in the archive document library. Please note that the manual send to method moves the document immediately but it is a useful way of testing whether you have configured the routing correctly.

SharePoint 2010: Basic List search / filter WebPart

April 19, 2012 89 comments

I have created a very simple SharePoint list search / filter WebPart which was inspired by the following blog post. This WebPart allows you to search records in a list where a selected field contains a specified text. It is useful in scenarios where you dont have SharePoint Search setup and just need a simple way of performing some search operations in a SharePoint List.

Adding the web part to SharePoint List View

Simply drop this web part on top of a page that contains a SharePoint View and it will allow you to apply a very simple search criteria.

The screenshot below shows the WebPart in action:

Main view of the List Search WebPart

The field name DropDownList allows you to select from the fields that are present in the view. Once you select the field and add the text to search by, the relevant results are displayed:

Screenshot showing search results

You can also specify multiple text values by seperating the text with a semi-colon (;):

Screenshot showing search results, multiple text criteria

In the above example the specified criteria will display all the records where the manufacturer’s name contains ‘Honda’ OR ‘Audi’. The screenshot below shows the pagination working as expected:

Screenshot showing search results, multiple text criteria

Adding the web part to a page with an XsltListViewWebPart

You can also add this web part to a page that contains an XsltListViewWebPart. The web part will automatically detect that it has been added to a page (rather than a List View) and display a message asking you to select an XsltListViewWebPart that you would like to apply the filters to:

The screen shot below shows how to select the XsltListViewWebPart:

You can download the solution by clicking on the link below:

SharePoint WSP Download link

You can view the codeplex project site by clicking on the link below:

Codeplex Project Site

Please note that this is setup as a Farm Solution and not a Sandboxed Solution therefore it will not work if you deploy it to the SharePoint Solutions Gallery, you need to deploy the SharePoint Solution via Central Administration, via stsadm commands or via PowerShell commands.

How to it works

On a page that contains a ListViewWebPart you can apply filter by adding a couple of query strings:

  • FilterName
  • FilterMultiValue

In our example, when a user types ‘honda’ and then clicks on the search button we simply append ‘?FilterName=LinkTitle&FilterMultiValue=*honda*;’ to the query string and redirect the user to that page. Please note that ‘LinkTitle’ is the internal name of the ‘Manufacturer’ field.

The * in the *honda* is used to do a wildcard search (contains). If you would like to search for multiple texts you can seperate them by a semi-colon for example ‘FilterMultiValue=*honda*;*audi*;’ will search for records where the ‘Manufacturer’ name either contains ‘honda’ or ‘audi’. If you would like to search for an exact match rather than apply a contains filter then simply remove the *’s from the filter value text.

Although, this WebPart does not allow you to filter / search by more than one field this is very much possible. To apply filters on additional fields you simply need to append ‘FilterField1=Model&FilterValue1=Accord’ to the URL. You can apply further filters by incrementing the number i.e. FilterField2, FilterField3 …. and so on. I am not sure if there is a limit on this.

Please note that I haven’t found a way to get the wildcard search to work with this (multiple filters) approach.

Building the WebPart

In your Visual Studio solution (assuming you have created a Blank SharePoint Project) add a ‘Visual WebPart’. A Visual WebPart loads a UserControl that contains most of the code. Below is the code of the .ascx file:

<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls"
    Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages"
    Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ListSearchUserControl.ascx.cs"
    Inherits="Exaction.ListSearch.WebParts.ListSearch.ListSearchUserControl" %>
<script type="text/javascript" src="/_layouts/Exaction.ListSearch.Javascripts/jquery.min.js"></script>
<table>
    <tr>
        <td>
            <strong>Search Criteria:</strong>
        </td>
        <td>
            <asp:TextBox ID="TbSearchText" runat="server" Width="300px"></asp:TextBox>
        </td>
        <td>
            &nbsp;
        </td>
        <td>
            <strong>Field name:</strong>
        </td>
        <td>
            <asp:DropDownList ID="DdlListFields" runat="server">
            </asp:DropDownList>
        </td>
        <td>
            &nbsp;
        </td>
        <td>
            <div align="right">
                <asp:Button ID="BtnSearch" runat="server" OnClick="BtnSearch_Click" Text="Search" />
                <asp:Button ID="BtnClearFilter" runat="server" Visible="false" OnClick="BtnClearFilter_Click"
                    Text="Clear Criteria" />
            </div>
        </td>
    </tr>
</table>
<script type="text/javascript">
    $(document).ready(function () {
        var base_RefreshPageTo = RefreshPageTo;
        RefreshPageTo = function (event, url) {

            var filterName = getQuerystring('FilterName');
            var filterValue = getQuerystring('FilterMultiValue');
            var newUrl = url + '&FilterName=' + filterName + '&FilterMultiValue=' + filterValue;
            if (filterName != '' && filterValue != '') {
                base_RefreshPageTo(event, newUrl);
            }
            else {
                base_RefreshPageTo(event, url);
            }
            return;
        }
    });
    function getQuerystring(key, default_) {
        if (default_ == null) default_ = "";
        key = key.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
        var regex = new RegExp("[\\?&]" + key + "=([^&#]*)");
        var qs = regex.exec(window.location.href);
        if (qs == null)
            return default_;
        else
            return qs[1];
    }
</script>

The code above is pretty self-explanatory but very briefly it contains the UI elements (TextBox, Labels, DropDownList and Buttons) and some jQuery. The jQuery code overrides the ‘RefreshPageTo’ SharePoint javascript function. This is basically to get our filtering to work with pagination. If you have a SharePoint List View that is displaying paginated date to you then clicking on the next or previous page calls the ‘RefreshPageTo’ JavaScript function. The problem is that when this function is called it clears the querystrings we use to filter the data. To ensure that the filtering is maintained we override this function, modify the URL ensuring the filtering querystrings are present and then pass it in as the second parameter to the function.

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Collections.Specialized;
using Microsoft.SharePoint;
using System.Collections.Generic;
using Exaction.ListSearch.UI.Entities;
using System.Text;

namespace Exaction.ListSearch.WebParts.ListSearch
{

    /// <summary>
    /// User control that deals with the registration process
    /// </summary>
    public partial class ListSearchUserControl : UserControl
    {

        /// <summary>
        /// Gets the share point list field items.
        /// </summary>
        /// <param name="filterCriteria">The filter criteria.</param>
        private List<OptionEntity> GetSharePointListFieldItems()
        {
            List<OptionEntity> fieldItems = new List<OptionEntity>();
            fieldItems = new List<OptionEntity>();
            OptionEntity item;
            SPField field;
            StringCollection viewFieldCollection = SPContext.Current.ViewContext.View.ViewFields.ToStringCollection();
            foreach (string viewField in viewFieldCollection)
            {
                field = SPContext.Current.List.Fields.GetFieldByInternalName(viewField);
                item = new OptionEntity();
                item.Id = field.InternalName;
                item.Title = field.Title;
                fieldItems.Add(item);
            }
            return fieldItems;
        }
        protected override void CreateChildControls()
        {
            base.CreateChildControls();
            List<OptionEntity> items = GetSharePointListFieldItems();
            DdlListFields.DataSource = items;
            DdlListFields.DataTextField = "Title";
            DdlListFields.DataValueField = "Id";
            DdlListFields.DataBind();
        }
        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.Load"/> event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param>
        protected override void  OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            if (!IsPostBack)
            {
                if (Request.QueryString["FilterName"] != null)
                {
                    DdlListFields.SelectedValue = Request.QueryString["FilterName"].ToString();
                }

                if (Request.QueryString["FilterMultiValue"] != null)
                {
                    TbSearchText.Text = Request.QueryString["FilterMultiValue"].ToString().Replace("*", "");
                    BtnClearFilter.Visible = true;
                }
            }
        }
        /// <summary>
        /// Handles the Click event of the BtnSearch control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void BtnSearch_Click(object sender, EventArgs e)
        {
            string redirectUrlFormat = "{0}?FilterName={1}&FilterMultiValue={2}";
            string[] selectionCollection = TbSearchText.Text.ToString().Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
            StringBuilder sbValues = new StringBuilder();
            foreach (string selection in selectionCollection)
            {
                sbValues.Append("*" + selection.Trim() + "*;");
            }

            string urlToRedirectTo = string.Format(redirectUrlFormat, Request.Url.GetLeftPart(UriPartial.Path), DdlListFields.SelectedValue, sbValues.ToString());
            Response.Redirect(urlToRedirectTo);
        }
        /// <summary>
        /// Handles the Click event of the BtnClearFilter control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void BtnClearFilter_Click(object sender, EventArgs e)
        {
            Response.Redirect(Request.Url.GetLeftPart(UriPartial.Path));
        }
    }
}

The code behind above initialises the controls and handles the Search and Clear Search Criteria Button click events.

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

namespace Exaction.ListSearch.UI.Entities
{
    public class OptionEntity
    {
        #region "Fields"
        public string Id { get; set; }
        /// <summary>
        /// Gets or sets the title.
        /// </summary>
        /// <value>The title.</value>
        public string Title { get; set; }
        #endregion

        #region "Constructor"
        public OptionEntity()
        {
        }
        #endregion
    }
}

We set a collection of the OptionEntity items as the DataSource of the Field name DropDownList.

That is basically it. In this simple manner you have a WebPart that you can drop on top of any List View and apply some basic free text Filterting.

Known issues

There are two minor known issues which I haven’t found a solution for yet:

  • Adding the WebPart on top of the page of a List View takes the focus away from the ListViewWebPart which in turn hides the ribbon. Once you click on the ListViewWebPart and focus on it then the ribbon becomes visible.
  • This WebPart does not work properly with Views that use groupings that are collapsed by default, it works if the groupings are expanded by default
  • As pointed out by Goran (see comments) it might not work with External SharePoint Lists

I hope you find this WebPart useful. Please post your comments and feedback and it would be helpful if you can rate this post.

0x80070057 Invalid data has been used to update the list item. The field you are trying to update may be read only.

March 22, 2011 2 comments

I came across this strange error today when I was trying to (programmatically) update a lookup column in a SharePoint list.

Below is the declaration for the lookup column in the schema.xml of the list:

<Field ID="{25b21cbb-c154-4dda-ac58-506a5853e5de}" Name="mlClientID" 
StaticName="mlClientID" Type="Lookup" DisplayName="Company" List="Lists/mcompany" FieldRef="ID" ShowField="Title" Group="Custom Columns" />

When I removed FieldRef=”ID” and then re-created the list it solved the problem.

I am not really sure why or what is going on in the background that causes this issue but I hope this helps someone incase you have the same problem.

Custom Action to Trigger a SharePoint Timer Job

November 27, 2010 Leave a comment

In my last post I demonstrated how to create a SharePoint Custom Action to appear on a specific SharePoint List. When a user clicked the action it ran some of our custom code.

Today we will try to extend that example and run a SharePoint Timer Job on the click of our custom action.

I am making the following assumptions:

  • You have read my previous post and have created the custom action
  • You know how to create and deploy Custom Timer Job definitions

The Problem

SharePoint Timer Job instances are persisted in the configuration database. Typically the SharePoint Farm service account or other accounts that have been given permissions on this database explicitly are able to write to this database.

For this reason when you try to execute a SharePoint Timer Job from within a site collection it will generally fail even if you run it within a SPSecurity.RunWithElevatedPrivileges code block. Running with elevated priviledges results in the code being run in the context of the Application Pool account (referred to as the System Account by SharePoint). Unless you have deviated from the best practices guidelines this will result in the following error:

Security Exception
Description: The application attempted to perform an operation not allowed by the security policy. To grant this application the required permission please contact your system administrator or change the application's trust level in the configuration file.

Exception Details: System.Security.SecurityException: Access denied.

You could always get around this by explicitly giving the Application Pool account permissions on the Configuration Database or by setting the Farm account as also being the Application Pool account for your Web Application. However, both these approaches are not recommended and are a deviation from the best practices guidelines.

The Solution

In my previous blog post, we created a custom action and deployed it. We used our custom action to send the user to a custom aspx page (with code-behind) that was located in the layouts folder. In the code behind we ran the custom code we wanted to execute. In this way we were able to create a custom action that ran some custom code on the click of our custom action.

To use our custom action to trigger a SharePoint Timer Job we need to add the following code in the aspx code-behind page:

namespace MyProject.CustomJobDefinitions
{
    public class JobInitiator: SPJobDefinition
    {
        public JobInitiator()
            : base()
        {
        }
        public JobInitiator(string jobName, SPService service, SPServer server, SPJobLockType targetType)	 
	            : base(jobName, service, server, targetType)
        {
	 
        }
        public JobInitiator(string jobName, SPWebApplication webApplication)
            : base(jobName, webApplication, null, SPJobLockType.None)
        {
            this.Title = "JobInitiator";
        }
         public override void Execute(Guid targetInstanceId)
        {
            using (SPSite site = this.WebApplication.Sites["/"])
            {
                using (SPWeb web = site.RootWeb)
                {
                    if (web.Properties["InitiatorJobFlag"] != null)
                    {
                        // Reset the flag 
                        Helper.SetSiteProperty(site, "InitiatorJobFlag", null);
                        // This is the job we want to run that does some stuff. It could be any job we want to run
                        CustomJobToRun process = new CustomJobToRun("My Job", WebApplication);
                        process.Execute(targetInstanceId);
                    }
                }
            }
        } 
    }
}

And below is the very useful ‘SetSiteProperty’ helper method:

        public static void SetSiteProperty(SPSite site, string propertyName, string value)
        {
            bool unsafeUpdateValue = site.RootWeb.AllowUnsafeUpdates;
            site.RootWeb.AllowUnsafeUpdates = true;
            if (site.RootWeb.Properties.ContainsKey(propertyName))
            {
                site.RootWeb.Properties[propertyName] = value;
            }
            else
            {
                site.RootWeb.Properties.Add(propertyName, value);
            }

            site.RootWeb.Properties.Update();
            site.RootWeb.AllowUnsafeUpdates = unsafeUpdateValue;
        }

Now that we have created our Custom Timer Job Definition, we can create a feature (scoped at Farm or WebApplication) and in the FeatureReciever feature activated event we add the following code:

JobInitiator job = new JobInitiator("JobInitiator", webApp)
            {
                Schedule = new SPMinuteSchedule
                {
                    BeginSecond = 0,
                    EndSecond = 59,
                    Interval = 1
                }
            };
            job.Update(true);

The schedule above ensures that our Custom Timer Job will run every minute. So our timer job executes every single minute and checks a property on the RootWeb of our Site Collection. If this property is set then it runs the specified Job of our choice.

And then finally in the code-behind file of our aspx page we add the following code:

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            Helper.SetSiteProperty(SPContext.Current.Site, "InitiatorJobFlag", "RunNow");
        }

So there we have it, by following this way we were able to create a Custom Action that, when clicked, triggers any SharePoint Timer Job we would like to run.

You might wonder why am I using a Job to execute another Job? Well there could be a scenario where you might want to run an OTB SharePoint Timer Job in which case this way would work.

In my specific scenario I had created a Job that was scheduled to run once every day. By using the above method I can easily trigger the execution of this Job on demand without interfering with its normal schedule.

SharePoint 2010: Custom action that executes custom code

November 21, 2010 18 comments

In 2007 it was possible to create a custom action and then link it to some code.

You did this by first declaring your custom action in an elements.xml file (which you would then deploy as part of a feature):

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
    Id="MyCustomAction"
    RegistrationType="List"
    GroupId="ActionsMenu"
    Location="Microsoft.SharePoint.StandardMenu"
    Sequence="1000"
    ControlAssembly="[Fully qualified assembly name]"
    ControlClass="MyNamespace.MyCustomAction">
  </CustomAction>
</Elements>

In the ControlAssembly and ControlClass attributes you specified your custom assembly and your custom class (a WebControl) that contained your custom code.

Then you created your custom WebControl:

public class MyCustomAction : WebControl 
{
    protected override void CreateChildControls() 
    {
        // Do some stuff
    }
}

Using this way we were able able to execute some custom code when someone clicked our custom action.

However, it seems that we cannot use this method in SharePoint 2010 although there are a few workarounds to achieve the same result. Below I will show you a way I used.

To start off with, I created an elements.xml file with the following declaration:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction Id="ContentTypeRibbonCustomization" RegistrationId="10005" RegistrationType="List" Location="CommandUI.Ribbon.ListView" Sequence="95" Title="Run Custom Code">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition Location="Ribbon.List.Settings.Controls._children">
          <Button Id="ContentTypeTest.Button" Image16by16="/_layouts/images/LSTPEND.gif" Image32by32="/_layouts/images/centraladmin_configurationwizards_farmconfiguration_32x32.png" Command="ContentTypeCommand" CommandType="General" Description="Runs some custom Code" TemplateAlias="o2" Sequence="95" LabelText="Perform My Action"/>
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler Command="ContentTypeCommand" CommandAction="/MyWeb/_layouts/CustomPages/MyCustomApplicationPage.aspx" />
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>
</Elements>

This specifies that my Custom Action will appear on my custom list with TemplateType of 10005 (specified in the RegistrationId attribute) in the list view section.

I then used a Feature to depoy my Custom Action.

All my Custom Action does is to send the user to a custom page that is located in the Layouts folder:

/MyWeb/_layouts/CustomPages/MyCustomApplicationPage.aspx

I then created my custom aspx page. Below is the mark up for the page:

<%@ Assembly Name="MyCustomAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e246903334b3e97b" %>
<%@ Page Language="C#" AutoEventWireup="true" Inherits="MyCustomNamespace.MyCustomControl"
    Debug="true" %>

And finally, below is the code behind for the aspx page:

namespace MyCustomNamespace
{
    public class MyCustomControl: UserControl
    {    
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            // run some custom code
            // then redirect the user back to the original page
        }
    }
}

Using this way I was able to link some custom code that would execute in response to someone clicking my custom action.

For everything you need to know about customising the SharePoin Ribbon please go here to Chris O’Brien’s blog post.

After spending a lot of time investigating how to customise the Ribbon to meet the requirements I had I can safely say this is by far the best and most comprehensive blog post I found on the net.

As I said earlier there are other ways you can use to achieve the same objective. You can easily link a custom action to execute some javascript function. I guess one option would be to link it to a javascript function that then invoked some server side code. I havent tried this myself so I cannot say for sure if this approach would work.

If you had a similar issue to the one I had above, I would be interested in hearing what approach you used to resolve it (if different to the approach I used).

How To: Enable Incomming emails on a Custom SharePoint List

November 11, 2010 Leave a comment

First of all if this list is hosted in a Meeting Workspace Web then this will not work. Please refer to this post for complete details.

Briefly, this is what you need to do:

  • Create your custom SharePoint list
  • Attach an Event handler of type ‘EmailReceived’ to your list (once you do this the ‘Incoming Email Settings’ option will appear in the List Settings section of the site)
  • Add some code to your event handler as per your requirements to process the email
  • Deploy your list the usual way and create an instance of it (could be done as part of the deployment or via the UI)

In the example below I am picking up the subject of the email, creating a new list item where I set the title field value to match the subject. This is a very simple example but you can do a whole lot of stuff in this event handler such as processing attachments e.t.c.

public override void EmailReceived(SPList list, SPEmailMessage emailMessage, String receiverData)
{
    SPListItem newItem = list.Items.Add();
    newItem["Title"] = emailMessage.Headers["subject"]
    newItem.Update();
}

For complete details on how to configure your environment to facilitate processing of incoming emails please click here or here. There is also a very useful technet video on the subject here.

Enable Incoming emails on a Custom SharePoint List

November 11, 2010 12 comments

We had a requirement recently whereby we needed to create a custom SharePoint list that would accept incoming emails. This post is going to be very long so bear with me …

Initially, I was very optimisitic that all I needed to do was to setup incoming notifications via Central Administration and then turn on Incoming Email notifications on the list itself and it should all fall into place.

How wrong I was!

Setting up the Development Enviornment

The first thing you have to do is to setup your development enviornment so that you can send emails and your SharePoint list can recieve them. This turned out to be a massive task. I spent hours searching through blogs, forums and various sites to try and setup the infrastructure on my dev (Hyper-v VM based) enviornment but I just could not get it to work. Eventually I stubmled upon this post from Marc Charmois that explained in detail how to get the whole thing to work on your Dev enriornment:

Enabling Incoming-emails on the SharePoint List. The Problem:

Ok, so I created a custom SharePoint List went to List Settings and looked for the ‘In-coming email settings’ link under the ‘Communication’ settings and found it to be non-existent. Googling on this brought all sorts of theories, hearsay and rumours to light. Some thought that this option was only available on x,y or z type of lists then there were others that thought it was only available on a,b,c or d type of lists but one thing was for sure no one seemed to know for a fact what was actually going on. As I was looking at the object model I found a property on the SPList object called ‘CanReceieveEmail’. I thought maybe this was the answer i.e. all I needed to do was to get my list and set this property to true and it will all work. However you cannot set this property you can only get its value. I believe this property is used by the SharePoint UI to decide whether or not to show the ‘In-coming Email Settings’ link in the list settings area.

So it was time to fire up Reflector to find a way to set this property maybe through reflection?

Looking at the property via reflection I found:

public bool get_CanReceiveEmail()
{
    if (!SPEmailHandler.HasHandler(this.BaseTemplate) && !this.HasExternalEmailHandler)
    {
        return false;
    }
    return !SPMeeting.IsMeetingWorkspaceWeb(this.ParentWeb);
}

And ..

public static bool HasHandler(SPListTemplateType templateType)
{
    if ((((templateType != SPListTemplateType.Announcements) && (templateType !=    SPListTemplateType.Events)) && ((templateType != SPListTemplateType.DocumentLibrary)  && (templateType != SPListTemplateType.PictureLibrary))) && ((templateType !=  SPListTemplateType.XMLForm) && (templateType != SPListTemplateType.DiscussionBoard)))
    {
        return (templateType == SPListTemplateType.Posts);
    }
    return true;
}

And finally ….

internal bool HasExternalEmailHandler
{
    get
    {
        bool flag = false;
        foreach (SPEventReceiverDefinition definition in this.EventReceivers)
        {
            if (definition.Type == SPEventReceiverType.EmailReceived)
            {
                flag = true;
            }
        }
        return flag;
    }
}

From this we can determine the following:

If the list, regardless of its type, appears in a meeting workspace web it will not be able to recieve in-coming emails.

The list either needs to have the BaseTemplate of one of the following:

  • Announcements
  • Events
  • DocumentLibrary
  • PictureLibrary
  • XMLForm
  • DiscussionBoard
  • Posts

or it needs to have an event handler of type ‘EmailReceived’ attached to it.

An important point to note here is that it mentions the base template which should not be mistaken for the BaseType. What this means is that you could create a custom list that inherits from the BaseType Document Library but that does not mean that it will have incoming emails enabled. Your list will need to have the same BaseTemplate as the out of the box lists mentioned above which is not really ideal.

Enabling Incoming-emails on the SharePoint List. The Solution:

So the only route left for me to take was to go the Event Handler path. I attached the Event Handler (and the incomming email settings link started to appear in the list settings area) then sent an email to my list and debugged my code with a break point on my event handler. However it just never seemed to get hit and neither were my emails appearing in the list itself.

The mistake I was making was to attach the w3wp process but this is not the process that processes the incoming emails. I dont want to go into this in detail but the incoming emails are processed by a SharePoint job that runs every minute therefore the process I needed to attach was the OWSTimer process. Once I attached this process it started to hit my break point however the emails were still not appearing in the list.

One thing to note here is that whenever you make any code changes to this event handler, after you deploy the dll’s to GAC, you need to ensure you restart the SharePoint Timer Job Service because it holds a cached version of the dll’s.

Finally, the reason the emails were not appearing in the list was because this needs to be done via the Event Handler. For the lists that are from one of the Templates I mentioned above SharePoint understands how to process and add the email messages however for your own custom list it is down to the Developer to write the code to do this processing. The event handler though provides you an object of type SPEmailMessage which has all the data you require. Below is an example of how it can be used to add the email subject to a simple custom list with only a title field:

public override void EmailReceived(SPList list, SPEmailMessage emailMessage, String receiverData)
{
    SPListItem newItem = list.Items.Add();
    newItem["Title"] = emailMessage.Headers["subject"]
    newItem.Update();
}

You can easily extend it to deal with attachments as well but that is for another day!

P.S Illustrations to be added soon….