| Subcribe via RSS

How SharePoint extensions can block your upgrade path

Juli 2nd, 2010 | No Comments | Posted in SharePoint

SharePoint extensions are a risky factor when upgrading a SharePoint farm to a new version, e.g. From SharePoint 2007 to 2010. Every SharePoint extension has a potential to no longer function in the new version.

In my opinion a good upgrade strategy is this:

  1. Uninstall all extensions from your existing farm
  2. Upgrade the farm to the next version
  3. Upgrade each solution and reinstall it

The point here is that some solutions might not be able to be upgraded because the vendor does no longer support it or your last SharePoint developer has left the company as the new SharePoint version was released. If this is the case, your site might no longer work with the new SharePoint version.

If your site breaks in the new SharePoint version depends on the kind of extension you use. A site will continue to work if you cannot upgrade a web part solution. But here are some kinds of solutions that cause content to be not accessible when the solution is not upgradable:

  • Custom field definitions
  • Custom site definitions
  • Custom list definitions

I recommend avoiding these kinds of solutions if possible and using site or web templates instead of definitions and using list templates instead of list definitions.

If you have any opinion on this please feel free to comment on this.

SharePoint List vs. Database and Excel

Oktober 30th, 2009 | No Comments | Posted in SharePoint

Yesterday I had a discussion with my colleague about SharePoint lists. Some people use SharePoint to store documents and manage tasks. List data is often stored in Excel files within SharePoint like phone numbers or birthday lists.

We asked us: What would be the best place to store about five linked lists with a few hundred items. We didn’t come up with a final decision because of personal opinions so I tried to find some facts I can make my decision on.

SharePoint List Benefits

When you create a SharePoint List you get a couple of features a Database for example does not deliver:

  • User interface that allows you to edit, filter, sort, group the data
  • Ability to search
  • Security management on list level and item level
  • Integration with other lists and libraries
  • Recycle bin
  • Multiple users can edit data simultaneously
  • Rich text support
  • SharePoint alerts
  • RSS
  • Select data from other systems using BDC lookup columns

SharePoint List Limitations

Electronical processing of data

Automatic processing and modifying data within SharePoint lists using Event Receiver, Workflows oder Web Services is much more difficult and slower that using Excel or Databases.

Accessing the data from an external system

SharePoint lists cannot be accessed from systems outside of SharePoint very easily. You will have to fight security and the web services of SharePoint. If this is the case a dedicated database might be the best option even if you have to create a user interface for the data management. With SharePoint 2010 it looks like this will become veery easy without coding.

Complex relations between tables

Relations between SharePoint lists is something I try to avoid if there is some potential that things might grow.

Conclusion

I still am not sure where to store 5 linked lists with a few hundred items. But I do see lots of reasons to use SharePoint lists.

I will add more facts while the discussion goes on. If you have any suggestions, feel free to add your comments.

SharePoint Performance Checklist

September 7th, 2009 | No Comments | Posted in SharePoint

I’ve made lots of performance tunings during the last weeks and figured out that this can be a weird, long and complex task. On the one hand there are many built-in mechanism that can be configured and on the other hand there are lots of things you shound pay attention to when you develop your own web parts.

I’ve assmebled a little checklist you can walk through. I do not cover everything in detail but I’ve linked a couple of online posts and articles where you can start reading if you need more information on specific tasks.

checkbox

Limit global Navigation & Permission breaks

If you have a collaboration site collection with individual permissions on each website or at list level then your navigation controls can slow down your sharepoint performance. SharePoint has to check your permissions for each item listed in the navigation controls what can be very time consuming. Because the global navigation is placed on every web site this impacts your whole site collection.

So if you have lots of permission breaks either limit the items displayed by the navigation controls and eleminate the default navigation treeview or implement your custom navigation provider using caching mechanisms and activate object caching.

checkbox

Activate Blob Cache

The blob cache can be used to reduce the round trips between the WFE and the database server. If activated, all images stored within the site collection are cached on the hard disk of the WFE server. The blob cache can only be configured within the web.config of each web application.

Another big point of the blob cache ist the max-age attribute. It tells the browser not to request that image again for a given period of time. So you can use this attribute to reduce the requests between the browser and the WFE.

In the web.config file search for the <BlobCache …> section and set it to the following example:

<BlobCache location="C:\blobCache" path="\.(gif|jpg|png|css|js)$" maxSize="10" max-age="86400" enabled="true"/>

Now the cache is enabled and the files with the defined extensions are stored on WFE hard disk and the browser will not rerequest them for about 24 hours.

Selvagan posted a great article about the blob cache.

Tip: Store the cached files on a different hard disk as the web server to maximize the I/O throughput.

If you want to clear the blob cache on all WFE servers at once you need this codeplex solution.

checkbox

Activate Output Cache

Output caching enables SharePoint to cache HTML markup of Web Parts or complete web pages. This makes sense most if you have a publishing page with anonymous access enabled. If you enable output caching on a site with authenticated users the output is cached for each user individually. This can become a memory issue.

Read more in the output caching secion of this MSDN article.

checkbox

Activate Object Cache

SharePoint can cache objects like navigation elements and Cross-list query results. This should be activated on every SharePoint site. Microsoft has posted an article on how to activate and configure the object cache.

checkbox

Indexing of columns

If you have large list views that are ordered or filtered you should place an index on those list columns where you set the filter on. An index can also increase the performance if you have a lookup field with many items in the lookup list.

Have a look at this Microsoft article on managing lists and libraries with many items and read the Microsoft White Paper: Working with large lists in Office SharePoint Server 2007.

Andreas Grabner wrote an article on how list column indexing works under the hood.

checkbox

Be careful using the Content Query Web Part

If you use the Content Query Web Part to collect data from large lists or from your whole site collection you have to be knowing what’s happening in detail. You can increase CQWP performance using object cache and implementing your own CQWP. Ranjan Banerji wrote a great article on content query web part performance.

checkbox

Use Warmup Timer Job

If you restart your server for maintenance or reset your IIS during a solution deployment all your web applications are shut down. Now, when the first user requests your website it takes up to a couple of minutes until the web application is reloaded and caches are filled. It would be nice if theres someone who takes a browser and calls every website before the first user does.

Joel Oleson wrote a post about a script that acts like that “someone” and triggers all websites of your site.

Andrew Connell write a post on how to implement the script as a timer job. You can download the timer job from msdn.

checkbox

Watch your crawler

A misconfigured crawler can push your server cpu usage to 100%  from dusk till dawn. This can happen very quickly if you have an intranet website with lots of members creating new content and uploading tons of documents. Then the crawl duration is growing very quickly.

For example if you scheduled your crawler to run every 30 minutes and the crawl duration is longer than 30 minutes your server is crawling all the day. So you should check the crawl log periodically. If your crawl duration becomes too long you have to reconfigure your crawl schedule and crawl your content only once every two hours. If your search index may not be out of date for more than 30 minuts you have to add more search server to the farm.

Patrick Thisseghem wrote a great book about the SharePoint search and indexing engine: Inside the Index and Search Engines: Microsoft Office SharePoint Server 2007

checkbox

Use tools to measure site request performance

Use tools like Fiddler, YSlow and Firebug to find out whats going on between the browser and your SharePoint server.  Find out if there are long waiting times, too many server requests or ineffective java scripts slowing down your website. Use these tools to check if blob caching works.

SharePoint works best if used with Microsoft Internet Explorer but I love the Firefox plugins YSlow and Firebug because they are very easy to use, detailed and give lots of hints if your performance is bad.

checkbox

Measure your Web Part performance

Every custom web part can slow down your SharePoint website. The SharePoint API often performes not as expected and this can impact your Web Part performance. Use the Stopwatch class in your web part to measure critical code parts and write the results into the web part. Implement a Web Part property to switch on/off the stopwatch results.

There are developers out there saying perfomance measures should be part of productive code and rather be made using unit tests or console applications. Those developers are basically right but might have never been to the wild west of SharePoint. A Web Part lives within a SharePoint site and no unit test or console application can simulate that so you have to use your productive web part to make performance tests.

Tags: , , , , ,

What to do if SharePoint throws “500 Internal Server Error”?

Mai 28th, 2009 | No Comments | Posted in SharePoint

Yesterday we added a new server to the SharePoint farm. When calling a site over that new server we got an 500 Internal Server Error. There was no entry in the EventLog and even no information in the server log files. We wer trying lots of things but nothing got better. So what could we do to get more information?

We changed the diagnostics logging level from Unexpected to Verbose:

Diagnostics Logging Level

(Central Administration > Operations > Diagnistic Logging)

Then we recalled the website and finally got an error in the log files:

"Cannot make a cache safe URL for "init.js", file not found.
Please verify that the file exists under the layouts directory."

The file is part of every language pack so in our case we did not install a language pack on the new server that is used by the site.

Although this is a simple option to get more error information it’s forgotten or overseen very often.

Alex
Tags: , , ,

SharePoint Groups vs. Active Directory Groups

Mai 19th, 2009 | 4 Comments | Posted in SharePoint

I’ve discussed this topic quite often during the last months. After those discussions I figured out that its more a question when to use what kind of group rather than what kind is better than the other. In this post I just write down some advantages and disadvantages of the group types and let you choose what kind fits better for your needs.

SharePoint Group Active Directory Group
plus Members of this group can be added/removed from within SharePoint. The permission to add or remove users from the group can be delegated to SharePoint users. plus Members of this group can be managed within Active Directory. Only Active Directory administrators have the permission to modify group memberships.
plus Members of this group can be visible to users. minus Members of this group are not visible to users.
minus Cannot contain another SharePoint group as member. plus Can contain another Active Directory Group.
plus Must have a unique name on site collection level. The name is the unique identifier of the group. minus Can cause serious problems in lage scale scenarios: A user might only be a member of 1024 Active Directory groups (recoursively). If this number is reached the user is no longer able to log on to Windows.
Read the Microsoft documentation for more information.
plus Can contain SharePoint users that do not exist in the Active Directory.
Tags: , , ,

Create SPFieldLookup programatically

Februar 21st, 2009 | 5 Comments | Posted in .NET, SharePoint

There aren’t many posts describing how to add a SPFieldLookup column to a list programatically. Here’s how I do it:

I have a list named “MyList” and I have a list named “MyLookupList”. I want to create a lookup column within “MyList” looking up values from “MyLookupList”.

SPList myList = web.Lists["MyList"];
SPList myLookupList = web.Lists["MyLookupList"];
 
myList.Fields.AddLookup("Lookup", myLookupList.ID, false);
SPFieldLookup fieldLookup = myList.Fields["Lookup"] as SPFieldLookup;
// Display Title Column in lookup field 
// (this is shown by default but this way you can change it)
fieldLookup.LookupField = 
  myLookupList.Fields[SPBuiltInFieldId.Title].InternalName;
fieldLookup.Update();
Tags:

Simplify reading and writing field values of type SPFieldUser, SPFieldUrl and SPFieldLookup using extension methods

Februar 8th, 2009 | 11 Comments | Posted in .NET, SharePoint

To read or write values to fields of type SPFieldUser or SPFielLookup is an annoying task because you have to create field value objects bevore you can write your values into a field. The following post descripes how you can encapsulate those tasks to extension methods and slim your code to make it more readable.

SPFieldUser

The following code displays a couple of extension methods extending the SPListItem class for simple reading and writing access to fields of type SPFieldUser.

using System;
using System.Collections.Generic;
using Microsoft.SharePoint;
 
namespace ExtensionMethods
{
    public static class SPListItemExtensions
    {
        /// <summary>
        /// Returns the login name of an User-Field.
        /// </summary>
        public static string GetFieldValueUserLogin(this SPListItem item, 
          string fieldName)
        {
            if (item != null)
            {
                SPFieldUserValue userValue = 
                  new SPFieldUserValue(
                    item.Web, item[fieldName] as string);
                return userValue.User.LoginName;
            }
            else
            {
                return string.Empty;
            }
        }
 
        /// <summary>
        /// Sets the value of a User-Field to a login name.
        /// </summary>
        public static void SetFieldValueUser(this SPListItem item, 
          string fieldName, string loginName)
        {
            if (item != null)
            {
                item[fieldName] = item.Web.EnsureUser(loginName);
            }
        }
 
        /// <summary>
        /// Sets the value of a User-Field to an SPPrincipal 
        /// (SPGroup or SPUser).
        /// </summary>
        public static void SetFieldValueUser(this SPListItem item, 
          string fieldName, SPPrincipal principal)
        {
            if (item != null)
            {
                item[fieldName] = principal;
            }
        }
 
        public static void SetFieldValueUser(this SPListItem item, 
          string fieldName, IEnumerable<SPPrincipal> principals)
        {
            if (item != null)
            {
                SPFieldUserValueCollection fieldValues = 
                  new SPFieldUserValueCollection();
 
                foreach (SPPrincipal principal in principals)
                {
                    fieldValues.Add(
                      new SPFieldUserValue(
                        item.Web, principal.ID, principal.Name));
                }
                item[fieldName] = fieldValues;
            }
        }
 
        /// <summary>
        /// Sets the value of a multivalue User-Field to 
        /// a list of user names.
        /// </summary>
        public static void SetFieldValueUser(this SPListItem item, 
          string fieldName, IEnumerable<string> loginNames)
        {
            if (item != null)
            {
                SPFieldUserValueCollection fieldValues = 
                  new SPFieldUserValueCollection();
 
                foreach (string loginName in loginNames)
                {
                    SPUser user = item.Web.EnsureUser(loginName);
                    fieldValues.Add(
                      new SPFieldUserValue(
                        item.Web, user.ID, user.Name));
                }
 
                item[fieldName] = fieldValues;
            }
        }
    }
}

The following lines of code demonstrate how these extension methods can be used.

using ExtensionMethods;
 
SPList list = web.Lists["MyList"];
SPListItem item = list.Items[0];
 
// Get a user login of a SPFieldUser field
string userLogin = item.GetFieldValueUserLogin("Author");
 
// Set a SPFieldUser field to a user using the login
item.SetFieldValueUser("Person", "alexander.bruett");
 
// Set a SPFieldUser field that allows multiple values to a list of users
string[] persons = { "alexander.bruett", "paul.panzer" };
item.SetFieldValueUser("Mehrere Personen", persons);
 
// Set a SPFieldUser field to a SPUser
SPUser user = web.Users[1];
item.SetFieldValueUser("Person", user);
 
// Set a SPFieldUser field to a list of SPPrincipal
SPGroup group = web.Groups[0];
SPUser user = web.Users[0];
SPUser user2 = web.EnsureUser("alexander.bruett");
SPUser user3 = web.EnsureUser("Domain Users"); ;
SPPrincipal[] principals = { group, user, user2, user3 };
item.SetFieldValueUser("AssignedTo", principals);

Especially setting fields that allow multiple values now needs only a couple of code lines.

SPFieldLookup

The SPFieldLookup field is a little bit more complicated if you want to set the field only by the lookup string. You have to query the ID of the lookup value before you can set the field value. The following extension methods simplify this task.

/// <summary>
/// Returns the value of a Lookup Field.
/// </summary>
private static string GetFieldValueLookup(this SPListItem item, 
    string fieldName)
{
    if (item != null)
    {
        SPFieldLookupValue lookupValue = 
            new SPFieldLookupValue(item[fieldName] as string);
        return lookupValue.LookupValue;
    }
    else
    {
        return string.Empty;
    }
}
 
/// <summary>
/// Returns the value of a Lookup-Field with multiple values.
/// </summary>
public static IEnumerable<string> GetFieldValueLookupCollection(
    this SPListItem item, string fieldName)
{
    List<string> result = new List<string>();
    if (item != null)
    {
        SPFieldLookupValueCollection values = 
            item[fieldName] as SPFieldLookupValueCollection;
 
        foreach (SPFieldLookupValue value in values)
        {
            result.Add(value.LookupValue);
        }
    }
    return result;
}
 
/// <summary>
/// Returns the SPFieldLookupValue instance of a lookup value. 
/// The ID value will be obtained using SPQuery.
/// </summary>
private static SPFieldLookupValue GetLookupValue(
    SPWeb web, SPFieldLookup field, string lookupValue)
{
    string queryFormat = 
        @"<Where>
            <Eq>
                <FieldRef Name='{0}' />
                <Value Type='Text'>{1}</Value>
            </Eq>
          </Where>";
 
    string queryText = 
        string.Format(queryFormat, field.LookupField, lookupValue);
    SPList lookupList = web.Lists[new Guid(field.LookupList)];
 
    SPListItemCollection lookupItems = 
        lookupList.GetItems(new SPQuery() { Query = queryText });
 
    if (lookupItems.Count > 0)
    {
        int lookupId = 
            Convert.ToInt32(lookupItems[0][SPBuiltInFieldId.ID]);
 
        return new SPFieldLookupValue(lookupId, lookupValue);
    }
    else
    {
        return null;
    }
}
 
/// <summary>
/// Sets the value of a Lookup-Field.
/// </summary>
public static void SetFieldValueLookup(
    this SPListItem item, string fieldName, string lookupValue)
{
    if (item != null)
    {
        SPFieldLookup field = 
            item.Fields.GetField(fieldName) as SPFieldLookup;
        item[fieldName] = GetLookupValue(item.Web, field, lookupValue);
    }
    else
    {
        item[fieldName] = null;
    }
}
 
/// <summary>
/// Set the values of a Lookup-Field with multiple values allowed.
/// </summary>
public static void SetFieldValueLookup(this SPListItem item, 
    string fieldName, IEnumerable<string> lookupValues)
{
    if (item != null)
    {
        SPFieldLookup field = 
            item.Fields.GetField(fieldName) as SPFieldLookup;
 
        SPFieldLookupValueCollection fieldValues = 
            new SPFieldLookupValueCollection();
 
        foreach (string lookupValue in lookupValues)
        {
            fieldValues.Add(
                GetLookupValue(item.Web, field, lookupValue));
        }
        item[fieldName] = fieldValues;
    }
}

That’s been a lot of code but take a look at the following lines of code. Especially the setting task becomes very easy now.

// Reads the value of a SPFieldLookup field
string value = item.GetFieldValueLookup("LookupFieldName");
 
// Read lookup values of a SPFieldLookup field 
// with multiple values allowed
IEnumerable<string> values = 
  item.GetFieldValueLookupCollection("MultiLookupFieldName");
 
// Set the value of a SPFieldLookup field
item.SetFieldValueLookup("LookupFieldName", "LookupValue");
 
//Set the value of a SPFieldLookup field with multiple values allowed
string[] values = {"Hamburg", "London" };
item.SetFieldValueLookup("MultiLookupFieldName", values);

SPFieldUrl

Setting and getting SPFieldUrl field values is very simple:

/// <summary>
/// Returns the value of an Url-Field.
/// </summary>
public static string GetFieldValueUrl(
    this SPListItem item, string fieldName)
{
    if (item != null)
    {
        SPFieldUrlValue urlValue = 
            new SPFieldUrlValue(item[fieldName] as string);
        return urlValue.Url;
    }
    else
    {
        return string.Empty;
    }
}
 
/// <summary>
/// Sets the value of an URL-Field.
/// </summary>
public static void SetFieldValueUrl(this SPListItem item, 
    string fieldName, string url, string description)
{
    if (item != null)
    {
        item[fieldName] = 
            new SPFieldUrlValue() 
                { 
                    Description = description, 
                    Url = url 
                };
    }
}

The following lines of code show how to get and set values of the SPFieldUrl field using the extension methods above:

// Get the url of a SPFieldUrl field
string url = item.GetFieldValueUrl("URL");
 
// Set the url and description of a SPFieldUrl field
item.SetFieldValueUrl("URL", "http://www.alexbruett.net", "Alex' Blog");

Conclusion

Using extension methods to simplify the access to complex and SharePoint list fields makes your code more readable and reduces the lines of code.

You could also use helper classes instead of extension methods but you will have to write more code to call a helper method instead of calling a extension method.

Tags: , , , , , , ,

Query whole site collection using SPSiteDataQuery

Februar 6th, 2009 | 1 Comment | Posted in .NET, SharePoint

Using the SPQuery class you can query one SharePoint list for items.

To set up a query across a whole site collection you can use the SPSiteDataQuery object.

The following method fetches all .doc files from all doclibs of the site collection and prints out a list of urls to those items.

public void TestSiteDataQuery()
{
  using (SPSite site = new SPSite("http://localhost"))
  {
    using (SPWeb web = site.OpenWeb("/"))
    {
      SPSiteDataQuery query = new SPSiteDataQuery();
 
      // Search in doclibs only
      query.Lists = "<Lists BaseType='1' />";
 
      // Only .doc files
      query.Query =
      @"<Where>
          <Eq>
            <FieldRef Name='DocIcon' />
            <Value Type='Computed'>doc</Value>
          </Eq>
        </Where>";
 
      // Select only needed columns: file reference
      query.ViewFields = "<FieldRef Name='FileRef' />";
 
      // Search in all webs of the site collection
      query.Webs = "<Webs Scope='SiteCollection' />";
 
      // Perform the query
      DataTable table = web.GetSiteData(query);
 
      // Generate an absolute url for each document
      foreach (DataRow row in table.Rows)
      {
        string relativeUrl = 
          row["FileRef"].ToString().Substring(
            row["FileRef"].ToString().IndexOf("#") + 1);
        string fullUrl = site.MakeFullUrl(relativeUrl);
 
        // Write urls to console
        Console.WriteLine(fullUrl);
      }
    }
  }
}
Tags: , , ,

Silverlight Host Web Part

Oktober 4th, 2008 | No Comments | Posted in SharePoint, Silverlight

Description
Lot’s of people start developing Silverlight applications these days and I asked myself: Why not host these applications in SharePoint?

I developed a very simple web parts that loads a silverlight application file (.xap) stored within a document library and runs it within its viewspace.

Requirements
Before you can deploy and use the web part in your SharePoint farm you have to complete the following tasks:

  • 1. Install Silverlight 2 Beta 2 on the server.
  • 2. Copy the file System.Web.Silverlight.dll to the GAC (The file is usually located in C:\Program Files\Microsoft SDKs\Silverlight\v2.0\Libraries\Server)
  • 3. Make neccessary adjustments to the web.config. This step is quite complex so I recommend you watch this screencast from Patrick Tisseghem and follow it step by step.
  • 4. Add this MIME type mapping to the IIS Website: .xap -> application/x-silverlight-2-b2

If you want to recompile the source code of this web part you have to install Visual Studio 2008 and the Silverlight Tools for VS 2008 and the Visual Studio Extensions for SharePoint 1.2.

Download, Deploy and Use

  • 1. Download the wsp file (setup.bat included) and install the web part.
  • 2. Download this sample .xap file that you can show within your silverlight web part.
  • 3. Ensure that the feature “Silverlight 2 Beta 2 Host Web Part Feature” is activated within the site collection.
  • 4. Upload the .xap file into a document library.
  • 5. Edit a page and add the web part “Silverlight 2 Beta 2 Host Web Part” to a web part zone.

  • 6. Now edit the web part properties and set the property “XAP URL” with the url to the xap file.

  • 7. Exit edit mode and refresh the page. The web part should show the silverlight application now.

Source Code

You can download the web part project here.

The first step is to ensure that a script manager exists on the page.

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    ScriptManager sm = ScriptManager.GetCurrent(this.Page);
    if (sm == null)
    {
        sm = new ScriptManager();
        Controls.AddAt(0, sm);
    }
}

The second step is to provide a web part property to consume a URL to the .xap file.

[WebBrowsable(true)]
[Personalizable(PersonalizationScope.Shared), Category("Silverlight")]
[FriendlyName("XAP URL")]
[Description("URL to XAP file")]
public string XapUrl
{
    get { return m_xapUrl; }
    set { m_xapUrl = value; }
}
protected string m_xapUrl = string.Empty;

The third step is to create a Silverlight control and load the Silverlight application into it:

protected override void CreateChildControls()
{
    base.CreateChildControls();
 
    try
    {
        if (!string.IsNullOrEmpty(XapUrl))
        {
            Silverlight ctrl = new Silverlight();
            ctrl.ID = this.ID + "_" + "SilverLightControl";
            ctrl.Source = XapUrl;
            ctrl.Width = new Unit(100, UnitType.Percentage);
            ctrl.Height = new Unit(m_controlHeight, 250);
            Controls.Add(ctrl);
        }
        else
        {
            Label lbl = new Label();
            lbl.Text = "Provide the url to a .xap file.";
            lbl.ID = this.ID + "_" + "LblMessage";
            Controls.Add(lbl);
        }
    }
    catch (Exception ex)
    {
        Label lbl = new Label();
        lbl.Text = string.Format("Error: " + ex.Message);
        lbl.ID = this.ID + "_" + "LblMessage";
        Controls.Add(lbl);                
    }
}

Have a nice Silverlight day,
Alex

Tags: , ,

Best practices: SharePoint Timer Jobs

August 18th, 2008 | 8 Comments | Posted in SharePoint

Part 1: Experiences and bad practices

I’ve developed a couple of SharePoint Timer jobs during the last months and gained a lot of experiences with deploying, configuring, debugging and updating them. Adding your code directly into a SharePoint timer job has many disadvantages:

Configuration

Timer jobs are run from the SharePoint Timer Service also known as owstimer.exe. You can place a owstimer.exe.config and add your configuration settings here. It serves as a shared app.config for all timer jobs. If you modify the configuration file you have to restart the timer job service to let the changes take effekt.

The config file is not designed to be used for timer job configuration so it doesn’t exist after you installed SharePoint. You have to create it on your own.

There are some alternative possibilities like custom defined xml files stored in a file at a defined location on the hard disc. Andrew Connel describes some ways to configure SharePoint timer jobs at MSDN. In my opinion none of them are simple to implement and nice to handle like a web.config or app.config.

Degbugging

To debug a timer job you have to attach the debugger to the owstimer.exe process, set breakpoint and wait until your timer job is executed. This could take minutes for each debugging session.

Deployment and redeployment

The deployment of a timer job is easy stuff if you have a visual studio template doing the dirty work for you. However, redeploying a solution means that you have to restart the SharePoint Timer Service and IIS what means session reset to all connected users.

Part 2: Extract code from timer job to web service

A default timer job is implemented like this:

Your code is inside the timer job and accessing your SharPoint using the API.

If you are going to write many lines of code your timer job you have a huge potential for error and you will need a couple of debugging sessions to fix all errors. Since debugging timer jobs take much time this doesn’t seem to be the best way implementing timer jobs.

Here’s my idea: Extract your code from the timer job into a web service.

In this scenaro your timer job has just those lines of code needed to call the web service. The configuration parameters of this timer job is quite short - just the URL of your webservice. On single server farms you could reference your web service using “localhost:portxy” and do not need a configuration for this. If you need a configuration option you could chose on of the alternatives described by Andrew Connell at MSDN.

Using a web service has some big advantages:

Configuration file

You can deploy a web.config file and put as many configurations in it as you wish and do not have to worry about affecting other job configurations. If you modify the web.config file you do not have to restart any services or the IIS.

Deployment

You can deploy and redeploy your web service without having to restart the SharePoint timer service or IIS.

Testing

You can write your own test client to call your web service whenever you want. You don’t have to wait until the timer service runs your code.

External job engine

If you have an external job engine like Control-M you can forego timer jobs and let the external job engine call your web servce. In this case you do not need the owstimer.exe.config anymore.

Security considerations

A web service accessing the SharePoint with administration rights adds a point of attack. Only the persons or service accounts that use the web service should be allowed to call the service.

Tags: