SharePoint 2010 - Coding standards
and best practices
In this post we will go through some of the coding standards and best
practices that have been recommended by Microsoft for SharePoint
2010.
Using SharePoint Data and Objects Efficiently
Caching is one good way to improve system performance. However, you must
weigh the benefits of caching against the need for thread safety. Additionally,
you should not create certain SharePoint objects within event receivers because
this will cause performance problems related to excessive database calls.
1) Caching Data and Objects
Many developers use the Microsoft .NET Framework caching objects (for
example, System.Web.Caching.Cache) to help take better advantage of memory and
increase overall system performance. But many objects are not "thread
safe" and caching those objects can cause applications to fail and
unexpected or unrelated user errors.
2) Caching SharePoint Objects That Are Not Thread Safe
You might try to increase performance and memory usage by caching
SPListItemCollection objects that are returned from queries. In general, this
is a good practice; however, the SPListItemCollection object contains an
embedded SPWeb object that is not thread safe and should not be cached. For
example, assume the SPListItemCollection object is cached in a thread. As other
threads try to read this object, the application can fail or behave strangely
because the embedded SPWeb object is not thread safe. For more information
about the SPWeb object and thread safety, see the Microsoft.SharePoint.SPWeb
class. The guidance in the following section describes how you can prevent
multiple threads from attempting to read the same cached object.
3) Understanding the Potential Pitfalls of Thread Synchronization
You might not be aware that your code is running in a multithreaded
environment (by default, Internet Information Services, or IIS, is
multithreaded) or how to manage that environment. The following example shows
the code some developers use to cache Microsoft.SharePoint.SPListItemCollection
objects.
Bad Coding Practice
Caching an Object That Multiple Threads Might Read
public override void ItemDeleting(SPItemEventProperties properties)
{
using (SPSite site
= new SPSite(properties.WebUrl))
{
using (SPWeb web
= site.OpenWeb())
{
SPList list = web.Lists[properties.ListId];
SPListItem item
= list.GetItemByUniqueId(properties.ListItemId); // Operate on item.
}
}
}
Applying a Lock
Checking for null
//
Retrieve SPWeb and SPListItem from SPItemEventProperties instead
of from a new instance of SPSite.
SPWeb web
= properties.OpenWeb(); // Operate on SPWeb object.
SPListItem item
= properties.ListItem; // Operate on item.
Working with Folders and Lists
When folders and lists grow in size, custom code that works with them
needs to be designed in ways that optimize performance. Otherwise, your
applications will run slowly and even cause timeouts to occur. The following
recommendations for addressing performance concerns while working with large
folders and lists are based on the test results reported in Steve Peschka's
white paper, Working with Large Lists in Office SharePoint Server 2007.
1) Do not use SPList.Items.SPList.Items
Selects all items from all subfolders, including all fields in the list.
Use the following alternatives for each use case.
Retrieving all items in a list Use SPList.GetItems(SPQuery query)
instead. Apply filters, if appropriate, and specify only the fields you need to
make the query more efficient. If the list contains more than 2,000 items, you
will need to paginate the list in increments of no more than 2,000 items. The
following code example shows how to paginate a large list. Getting items by
identifier Instead of using SPList.Items.GetItemById, use
SPList.GetItemById(int id, string field1, params string[] fields). Specify the
item identifier and the field that you want.
2) Do not enumerate entire SPList.Items collections or SPFolder.Files
collections.
Accessing the methods and properties that are listed in the left column
of the following table will enumerate the entire SPList.Items collection, and
cause poor performance and throttling for large lists. Instead, use the
alternatives listed in the right column.
3) Whenever possible, acquire a reference to a list by using the list's
GUID or URL as a key.
You can retrieve an SPList object from the SPWeb.Lists property by using
the list's GUID or display name as an indexer. Using SPWeb.Lists[GUID]
andSPWeb.GetList(strURL) is always preferable to using
SPWeb.Lists[strDisplayName]. Using the GUID is preferable because it is unique,
permanent, and requires only a single database lookup. The display name indexer
retrieves the names of all the lists in the site and then does a string
comparison with them. If you have a list URL instead of a GUID, you can use the
GetList method to look up the list's GUID in the content database before
retrieving the list.
4) Avoid creating and destroying objects unnecessarily in code
Avoid creating and destroying
objects unnecessarily in code, as this may require that extra queries be made
against the database and may even involve code that is incorrect.
In the following example,
separate objects for the Tasks list must be instantiated each time the indexer
is used to set properties and the method for updating is called. This is not a
recommended practice.
Bad Practice
SPWeb myWeb = SPContext.Current.Web;
myWeb.Lists["Tasks"].Title = "List_Title";
myWeb.Lists["Tasks"].Description =
"List_Description";
myWeb.Lists["Tasks"].Update();
Good Practice
SPWeb myWeb = SPContext.Current.Web;
SPList myList = myWeb.Lists["Tasks"];
myList.Title="List_Title";
myList.Description="List_Description";
myList.Update();
Note:
a) The object models in the Microsoft.SharePoint assembly optimize
performance and minimize the number of SQL queries that are made. However, to
monitor code performance, it is recommended that you use the SQL Profiler.
b) To return a single item from a collection, always use a Get* method when
one is provided through a parent object, instead of iterating through the entire
collection and using an indexer.
5) Handling large folders and lists
When the size of folders and lists increases, you must design custom
code that works with them to optimize performance. Otherwise, your applications
will run slowly and can cause service or page load timeouts. The two primary
areas for concern when handling large folders and lists are the following:
Query throttling, which can cause the behavior of your code to change in
unexpected and unpredictable ways over time as your site evolves and your
queries begin to return items that exceed the query threshold.
Efficient retrieval of items from large folders and lists.
6) Throttling for Large List Queries
Microsoft SharePoint Foundation 2010 and Microsoft SharePoint Server
2010 apply a default query threshold of 5,000 items. Any custom code that
relies on query result sets that can exceed this maximum will not perform as
expected. Queries on lists consisting of more than 5,000 items that include
fields that are not indexed in their query conditions will also fail, because
those queries must scan all rows in a list. Follow the steps listed below to
view and increase this limit or to enable the object model to override the
limit:
To view and increase this threshold or to enable the object model to
override the threshold
On the Central Administration site, under Application Management, click
Manage Web Applications.
Click General Settings, and then click Resource Throttling.
View and update the threshold or allow the object model to override the
limit.
7) Deleting Multiple Versions of a List Item
When you delete multiple versions of a list item, use the DeleteByID
method; do not use the Delete method. You will experience performance problems
if you delete eachSPListItemVersion object from an SPListItemVersionCollection
object. The recommended practice is to create an array that contains the ID
properties of each version and then delete each version by using the
SPFileVersionCollection.DeleteByID method. The following code examples
demonstrate both the approach that is not recommended and the recommended
approach to deleting all versions of the first item of a custom list.
Bad Coding Practice
Deleting each SPListItemVersion object
SPSite site = new SPSite("site url");
SPWeb web = site.OpenWeb();
SPList list = web.Lists["custom list name"];
SPListItem item
= list.GetItemById(1);
SPListItemVersionCollection vCollection = item.Versions;
ArrayList idList = new ArrayList();
foreach(SPListItemVersion ver in vCollection)
{
idList.Add(ver.VersionId);
}
foreach(int verID in idList)
{
SPListItemVersion version
= vCollection.GetVersionFromID(verID);
try
{
version.Delete();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Good Coding
Practice
Deleting each version of a list item
by using the SPFileVersionCollection.DeleteByID method
SPSite site = new SPSite("site url");
SPWeb web = site.OpenWeb();
SPList list = web.Lists["custom list name"];
SPListItem item
= list.GetItemById(1);
SPFile file
= web.GetFile(item.Url);
SPFileVersionCollection collection
= file.Versions;
ArrayList idList = new ArrayList();
foreach (SPFileVersion ver in collection)
{
idList.Add(ver.ID);
}
foreach (int verID in idList)
{
try
{
collection.DeleteByID(verID);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
8) Using SPQuery Objects
SPQuery objects can cause performance problems whenever they return
large result sets. The following suggestions will help you optimize your code
so that performance will not suffer greatly whenever your searches return large
numbers of items.
Do not use an unbounded SPQuery object. An SPQuery object without a
value for RowLimit will perform poorly and fail on large lists. Specify a
RowLimit between 1 and 2000 and, if necessary, page through the list.
Use indexed fields. If you query on a field that is not indexed, the
query will be blocked whenever it would result in a scan of more items than the
query threshold (as soon as there are more items in the list than are specified
in the query threshold). Set SPQuery.RowLimit to a value that is less than the
query threshold.
If you know the URL of your list item and want to query by FileRef, use
SPWeb.GetListItem(string strUrl, string field1, params string[] fields)instead.
Using Web Controls
When you inherit and override controls in the
Microsoft.SharePoint.WebControls namespace, remember that SharePoint Web
controls are templated controls. Unlike Microsoft ASP.NET Web controls, they
are defined and rendered with templates instead of with the CreateChildControls
method. Instead of having a thickCreateChildControls method that uses the new
operator to create child controls, perform most child control creation and
rendering by using the rendering templates that are referenced in the Template,
AlternateTemplate, DisplayTemplate, CustomTemplate, and AlternateCustomTemplate
properties of the SharePoint Web control. SharePoint Web controls do inherit
the CreateChildControls method, but that method should typically do little or
nothing beyond calling the parent control's CreateChildControlsmethod and
perhaps a bit of "final polish" rendering, such as assigning default
values to child control properties in New mode or assigning the current values
in Edit mode.
Creating Timer Jobs
Design your timer jobs so that they consist of small, manageable pieces.
Because administrators and other events, such as system restarts, can stop
timer jobs, you can minimize the amount of rework after any interruption by
breaking timer jobs into small pieces of work.
Avoiding Unnecessary Construction of SPWeb and
SPSite Objects
An SPWeb or SPSite object can occupy a lot of memory. Avoid constructing
objects of these types simply to get a reference to a parent object. Instead,
to get a reference to a web application, use the static
SPWebApplication.Lookup(Uri) method, and pass it a Uri object that is created
with the URI of the web application. You can then get a reference to the farm
by using the Farm property of the web application object. (You can get a
reference to a remote farm by using the static Open(String) method.)
TheContentDatabases property of the web application object contains a
collection of the content databases in the web application. You can get a
reference to a particular content database through this property if you know
its index in the collection. For more information, see the reference topic for
the SPContentDatabaseCollection class. The following code illustrates some of
these points.
SPWebApplication webApplication = SPWebApplication.Lookup(new Uri("http://localhost/");
SPFarm farm
= webApplication.Farm;
SPContentDatabase content
= webApplication.ContentDatabases[0];
File Naming Restrictions
For security reasons, SharePoint Foundation reads files in the
%ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATE
directory tree only if their names are composed of ASCII letters, numbers,
periods, underscores, or dashes. In addition, file names cannot contain two or
more consecutive periods. For example:
Following are permitted file names:
a) AllItems.aspx
b) Dept_1234.doc
c) Long.Name.With.Dots.txt
The following are not permitted file names:
a) Cæsar.wav
b) File Name With Spaces.avi
c) Wow...ThisIsBad.rtf
d) 揵.htm
Accessing Web and Site Objects
1) Get Current Site details using SPSite
SPSite oSiteCollection =
SPContext.Current.Site;
2) Get Current Web details using SPWeb
SPWeb oWebSite =
SPContext.Current.Web;
3) When your code is contained in an .aspx file or the "code
behind" in an .aspx.cs file, you can use methods to get the SPWeb.
SPWeb oWebSite = SPControl.GetContextWeb(Context); where Context =
System.Web.UI.Page.Context
4) To get reference of the current web object from .aspx page that is
inherited from LayoutsPageBase is :
SPWeb oWebSite = this.Web;
5) Use the Microsoft.SharePoint.SPSite.AllWebs property to obtain a
reference to a Web site other than the current one.
SPWeb oWebSite = SPContext.Current.Site.AllWebs["myOtherSite"];
6) Get reference of server farm or current physical server :
SPFarm oFarm = SPFarm.Local;
Access external systems
When the data source is a Web service or database, consider using the
Business Data Catalog (BDC) for SP 2007 and Business Connectivity Services
(BCS) for SP 2010.
SharePoint Databases
Do not make calls directly to the SharePoint content and configuration
databases.
Packaging
Structure your code project so that solution packaging is integrated into
the project
Dispose Objects
We should not dispose any SPWeb, SPSite object create using
SPContext.Current.Site, SPContext.Current.Web,
SPControl.GetContextWeb(Context), this.Web
We should explicitly dispose of references to objects obtained through
the AllWebs property
The code should dispose any SPSite or SPWeb object obtained by using a
constructor.
Do not dispose dispose of the SPWeb.ParentWeb
The SPWeb.Webs property returns an SPWebCollection object. The SPWeb
objects in this collection must be disposed.
The SPWeb.Webs.Add method (or Add) creates and returns a new SPWeb
object. You should dispose of any SPWeb object returned from this method call.
If the object is obtained from the SharePoint context objects (GetContextSite
method and GetContextWeb method), the calling application should not call the
Dispose method on the object.
You should still dispose of an object that is created from these
objects, for example, if a Web site is opened from an SPSite object that you
obtained by using the GetContextSite method.
Wrong Way
void CombiningCallsLeak()
{
using (SPWeb web =
new SPSite(SPContext.Current.Web.Url).OpenWeb())
{
// ...
New SPSite will be leaked.
}
// SPWeb object web.Dispose() automatically called.
}
Right Way
void CombiningCallsBestPractice()
{
using
(SPSite siteCollection = new SPSite(SPContext.Current.Web.Url))
{
using
(SPWeb web = siteCollection.OpenWeb())
{
//Perform operations on site.
} // SPWeb object web.Dispose()
automatically called.
}
// SPSite object siteCollection.Dispose() automatically called.
}
Exception Handling in SharePoint
1. Avoid empty Catch blocks
All the errors should be reported using error logging functionality, if
not handled sufficiently.
Wrong Way
try
{
// code
}
catch { }
2. Use throw; if you’re going to throw an exception
Wrong Way
try
{
// code
}
catch (Exception ex)
{
throw ex;
}
Right Way
try
{
// code
}
catch
{
throw;
}
3. Handle the specific exception wherever possible
Right Way
try
{
spList = spWeb.GetList("/Path/List");
}
catch (FileNotFoundException ex)
{
// handle exception
}
catch (ArgumentException ex)
{
// handle exception
}
4. Try to check condition before it throws exception
Wrong Way
try
{
myString =
listItem["MyField"].ToString();
}
catch (Exception ex)
{
// handle exception
}
Right Way
if (String.IsNullOrEmpty(listItem["MyField"]))
{
myString =
listItem["MyField"].ToString();
}
6. Use an appropriate form of logging exception.
The most important takeaway is to ensure that the errors are
logged. The 2 most logical locations to log the errors would be the ULS or
Event Log, however this may not always be appropriate. If these locations
aren’t regularly monitored or accessible by the people who need to know about
the errors, then it is essentially no better than swallowing the errors in the
first place. Email, CRM, a database or even a SharePoint list are other
potential locations that could be more accessible. Logging to a file on the
file system makes little sense seeing if that is an accessible option you could
just use the ULS.
7. Use a custom error page.
Nothing looks worse on a public facing internet site or even an intranet
application than when you get the SharePoint default error page. Wherever
possible you should display a user friendly message rather than an ugly generic
error page.