The Best Practice Guidelines of Development on SharePoint 2010/SharePoint 2007

The Best Practice Guidelines of Development on SharePoint 2010/SharePoint 2007 - part 1

The Best Practice Guidelines of Development on SharePoint 2010/SharePoint 2007 – Part 2

Summary:

This article is based on SharePoint SDK documents.Summarized the best practice guideline of development on SharePoint, you could apply this content to SharePoint 2010 and SharePoint 2007. Contains the using method to SPSite and SPWeb object, the naming when you deploy files to template file; Using SPSite and SPWeb object in event handler ; the handling to large files and large list items and the object cache and some examples of  code optimization.

Avoid unnecessary construct to the SPSite and SPWeb object

1. SPSite and SPWeb object will consume much cache, when development based on SharePoint, you should avoid unnecessary construct these new object. Specially just for getting the reference to SPWebApplication. For getting SPWebApplication, you could invoke SPWebApplication.LookUp(url) to get the referencing. Then getting the referencing to Farm by WebApplication.Farm. If you know the index value of content database, you could get the referencing to the content database by webApplication.Farm.

Code snippet:

SPWebApplication webApplication = SPWebApplication.Lookup(new Uri("http://localhost/");
SPFarm farm = webApplication.Farm;
SPContentDatabase content = webApplication.ContentDatabases[0];







2. If you have to construct the SPSite and SPWeb object, you have to dispose them when finish, you could using 3 method to do this:




A. Dispose method



B. using



C. try, catch, finally




When you use the using method, below is the code snippet:




String str;  

using(SPSite oSPsite = new SPSite("http://server"))

{

using(SPWeb oSPWeb = oSPSite.OpenWeb())

{

str = oSPWeb.Title;

str = oSPWeb.Url;

}

}







But at you using this method, you should avoid release the object which should not be released. For example:




using( SPWeb web = SPControl.GetContextWeb(HttpContext.Current)) { ... }





SPContext is maintained by SharePoint framework, do not release, SPContext.Site, SPContext.Current.Site, SPContext.Web, SPContext.Current.Web should not be released. try, catch , finally is sample to using, they implements the interface object of IDispose, the .NET RunTime will transfer using to try, catch, finally.




String str;
SPSite oSPSite = null;
SPWeb oSPWeb = null;

try
{
oSPSite = new SPSite("http://server");
oSPWeb = oSPSite.OpenWeb(..);

str = oSPWeb.Title;
}
catch(Exception e)
{
//Handle exception, log exception, etc.
}
finally
{
if (oSPWeb != null)
oSPWeb.Dispose();

if (oSPSite != null)
oSPSite.Dispose();
}





Resources released of redirect page:



As below example,when invoke Response.Redirect there will throw the exception:ThreadAbortedException. then the finally will be run, the thread exception will be throw, can’t ensure the resource be released. So before to any invokes of Response.Redirect, ensure the the SPSite and SPWeb object released.




String str;
SPSite oSPSite = null;
SPWeb oSPWeb = null;

try
{
oSPSite = new SPSite("http://server");
oSPWeb = oSPSite.OpenWeb(..);

str = oSPWeb.Title;
if(bDoRedirection)
{
if (oSPWeb != null)
oSPWeb.Dispose();

if (oSPSite != null)
oSPSite.Dispose();

Response.Redirect("newpage.aspx");
}
}
catch(Exception e)
{
}
finally
{
if (oSPWeb != null)
oSPWeb.Dispose();

if (oSPSite != null)
oSPSite.Dispose();
}







This method is for using also.



Do not create the static SPSite and SPWeb object



The returned SPSite object which created calling SPSiteCollection.Add() method need to be released.



The returned SPSite object by SPSiteColleciton index SPSiteColleciton[] need to be released.



The SPSite object using foreach in SPSiteCollection.



Next is the recommendation methods:




void SPSiteCollectionForEachNoLeak()
{
using (SPSite siteCollectionOuter = new SPSite("http://moss"))
{
SPWebApplication webApp = siteCollectionOuter.WebApplication;
SPSiteCollection siteCollections = webApp.Sites;

foreach (SPSite siteCollectionInner in siteCollections)
{
try
{
// ...
}
finally
{
if(siteCollectionInner != null)
siteCollectionInner.Dispose();
}
}
} // SPSite object siteCollectionOuter.Dispose() automatically called.
}







SPWeb object returned by SPSite.AllWebs.Add need to release;



SPWeb object returned by SPWebColleciton.Add need to release;



By SPSite.AllWebs [] SPWeb object returned by the indexer needs to release;



SPWeb object by Foreach loop through the SPWebColleciton need to release;



SPWeb object over the open OpenWeb need to release;



SPWeb objects created by SPSite.SelfServiceCreateSite need to release;



SPSite.RootWeb do not need to release;



SPSite object returned by Microsoft.Office.Server.UserProfiles.PersonalSite need to release;



Recommended the development as follows:




void PersonalSiteNoLeak()
{
// Open a site collection
using (SPSite siteCollection = new SPSite("http://moss"))
{
UserProfileManager profileManager = new UserProfileManager(ServerContext.GetContext(siteCollection));
UserProfile profile = profileManager.GetUserProfile("domain\\username");
using (SPSite personalSite = profile.PersonalSite)
{
// ...
}
}
}







Here you can save by constructing a new SPSite ProfileLoader to improve performance




UserProfile myProfile = ProfileLoader.GetProfileLoader().GetUserProfile();
using (SPSite personalSite = myProfile.PersonalSite)
{
// ...
}







In particular, if you create web parts for the MySite, you can use PersonalSite without release:




IPersonalPage currentMySitePage = this.Page as IPersonalPage; 
if (currentMySitePage != null && !currentMySitePage.IsProfileError)
{
SPSite personalSite = currentMySitePage.PersonalSite; // Will not leak. // ...
}







The returned SPSite by GetContextSite not need to release




void SPControlBADPractice()
{
SPSite siteCollection = SPControl.GetContextSite(Context);
//siteCollection.Dispose(); do not to release
SPWeb web = SPControl.GetContextWeb(Context);
//web.Dispose(); do not to release
}







Within SPLimitedWebPartManager contains a reference to the SPWeb, need to release




void SPLimitedWebPartManagerLeak()
{
using (SPSite siteCollection = new SPSite("http://moss"))
{
using (SPWeb web = siteCollection.OpenWeb())
{
SPFile page = web.GetFile("Source_Folder_Name/Source_Page");
SPLimitedWebPartManager webPartManager =
page.GetLimitedWebPartManager(PersonalizationScope.Shared);
webPartManaber.Web.Dispose();
} // SPWeb object web.Dispose() automatically called.
} // SPSite object siteCollection.Dispose() automatically called.
}







Microsoft.SharePoint.Publishing.PublishingWeb(SharePoint2007 only)



PublishingWeb.GetPublishingWebs will return  PublishingWebCollection, When use  the  foreach you should to invoke close() method to release every object.




void PublishingWebCollectionNoLeak()
{
using (SPSite siteCollection = new SPSite("http://moss"))
{
using (SPWeb web = siteCollection.OpenWeb())
{
// Passing in SPWeb object that you own, no dispose needed on
// outerPubWeb.
PublishingWeb outerPubWeb = PublishingWeb.GetPublishingWeb(web);
PublishingWebCollection pubWebCollection = outerPubWeb.GetPublishingWebs();
foreach (PublishingWeb innerPubWeb in pubWebCollection)
{
try
{
// ...
}
finally
{
if(innerPubWeb != null)
innerPubWeb.Close();
}
}
} // SPWeb object web.Dispose() automatically called.
} // SPSite object siteCollection.Dispose() automatically called.
}







Similarly, the returns PublishingWeb calling  PublishingWebCollection.Add need release;



3. In the event handler, you could use below method to avoid create new SPSite or SPWeb object.




// Retrieve SPWeb and SPListItem from SPItemEventProperties instead of 
// from a new instance of SPSite.
SPWeb web = properties.OpenWeb();//Here by properties.OpenWeb () returns the SPWeb not released;// Operate on the SPWeb object.
SPListItem item = properties.ListItem;
// Operate on an item.
Here by properties.OpenWeb () returns the SPWeb not released;







File name restrictions in SharePoint:



If you need to be deployed files or file folder to% ProgramFiles% \ Common Files \ Microsoft Shared \ web server extensions \ 14 \ TEMPLATE directory in actual combat,for security reasons,SharePoint Foundation is only able to read the file name with ASCII characters, numbers, underscores, periods, dashes (dashed) components of the name,Particular file name can not include two consecutive periods, for example, the following is to allow the file name:




AllItems.aspx



Dept_1234.doc



Long.Name.With.Dots.txt






The following are not allowed to name:




Cæsar.wav



File Name With Spaces.avi



Wow...ThisIsBad.rtf






The Processing to Large file and Large list:



Do not use SPList.Items, because this call will return all the sub-folder all the records, use the following method instead:



Add record: use SPList.AddItem, do not use SPList.Items.Add;



Query : Using SPList.GetItemById, does not apply SPList.Items.GetItemById;



Back to list all the records: Use SPList.GetItems (SPQuery query) and not SPList.Items, by the requirements to use conditions filters, just select the necessary fields to return, if the return results over 2000, the use of paging:




SPQuery query = new SPQuery();
SPListItemCollection spListItems ; string lastItemIdOnPage = null; // Page position.
int itemCount = 2000 while (itemCount == 2000)
{
// Include only the fields you will use.
query.ViewFields = "<FieldRef Name=\"ID\"/><FieldRef Name=\"ContentTypeId\"/>"; query.RowLimit = 2000; // Only select the top 2000.
// Include items in a subfolder (if necessary).
query.ViewAttributes = "Scope=\"Recursive\"";
StringBuilder sb = new StringBuilder();
// To make the query order by ID and stop scanning the table, specify the OrderBy override attribute.
sb.Append("<OrderBy Override=\"TRUE\"><FieldRef Name=\"ID\"/></OrderBy>");
//.. Append more text as necessary ..
query.Query = sb.ToString(); // Get 2,000 more items. SPListItemCollectionPosition pos = new SPListItemCollectionPosition(lastItemIdOnPage);
query.ListItemCollectionPosition = pos; //Page info.
spListItems = spList.GetItems(query);
lastItemIdOnPage = spListItems.ListItemCollectionPosition.PagingInfo;
// Code to enumerate the spListItems.
// If itemCount <2000, finish the enumeration.
itemCount = spListItems.Count;

}







Pages display:




SPWeb oWebsite = SPContext.Current.Web;
SPList oList = oWebsite.Lists["Announcements"];

SPQuery oQuery = new SPQuery();
oQuery.RowLimit = 10;
int intIndex = 1;

do
{
Response.Write("<BR>Page: " + intIndex + "<BR>");
SPListItemCollection collListItems = oList.GetItems(oQuery);

foreach (SPListItem oListItem in collListItems)
{
Response.Write(SPEncode.HtmlEncode(oListItem["Title"].ToString()) +"<BR>");
}

oQuery.ListItemCollectionPosition = collListItems.ListItemCollectionPosition;
intIndex++;
} while (oQuery.ListItemCollectionPosition != null);





Poor performance of the API is not recommended




SPList.Items.Count



SPList.Items.XmlDataSchema



SPList.Items.NumberOfFields



SPList.Items[System.Guid]



SPList.Items[System.Int32]



SPList.Items.GetItemById(System.Int32)



SPList.Items.ReorderItems(System.Boolean[],System.Int32[],System.Int32)



SPList.Items.ListItemCollectionPosition




Better performance of the recommended API




SPList.ItemCount



Create SPQuery, just return the required data



Create SPQuery, designated ViewFields, just return the required data



SPList.GetItemByUniqueId(System.Guid)



SPList.GetItemById(System.Int32)



SPList.GetItemById(System.Int32)



Using SPQuery paging



ContentIterator.ProcessListItems(SPList, ContentIterator.ItemProcessor, ContentIterator.ItemProcessorErrorCallout) (Microsoft SharePoint Server 2010 only)




Reference:



Release checking tool: SPDisposeCheck @ http://code.msdn.microsoft.com/SPDisposeCheck



http://msdn.microsoft.com/en-us/library/aa973248(office.12).aspx



http://msdn.microsoft.com/en-us/library/bb687949%28office.12%29.aspx