摘要:
本文基于SharePoint SDK文档,总结了开发者最佳实践,以对SharePoint2010开发提供指南。本指南覆盖2007以及2010两个版本,包括对SPSite和SPWeb对象的使用指南,系统Template文件夹下部署内容时的命名规范,事件处理器内SPWeb, SPSite对象使用、大文件夹以及大列表的处理、对象缓存以及代码优化的一些例子。
避免使用构造不必要的SPWeb和SPSite对象
1. SPWeb和SPSite会占用大量的内存,开发时尽量避免构造新的SPWeb和SPSite对象,特别是只是为了取得对SPWebApplication的引用等情况,为了取得SPWebApplication,可以调用SPWebApplication.Lookup(uri)来取得引用,接下类也可以通过webApplication.Farm来取得对farm的引用,进一步如果知道内容数据库的索引值,可以通过webApplication.ContentDatabase[index]来取得对内容数据库的引用。
1 |
SPWebApplication webApplication = SPWebApplication.Lookup( new Uri( "http://localhost/" ); |
2 |
SPFarm farm = webApplication.Farm; |
3 |
SPContentDatabase content = webApplication.ContentDatabases[0]; |
2. 如果一定要构造SPWeb和SPSite对象,务必注意使用完后对象的释放,可以使用以下三种技术:
Dispose方法
using
try, catch, finally
使用using如下:
String str; |
using (SPSite oSPsite = new SPSite( "http://server/" )) |
{ |
using (SPWeb oSPWeb = oSPSite.OpenWeb()) |
{ |
str = oSPWeb.Title; |
str = oSPWeb.Url; |
} |
} |
但是在使用的时候一定要特别注意,避免释放了不该释放的内容,比如:
using( SPWeb web = SPControl.GetContextWeb(HttpContext.Current)) { ... }
SPContext由SharePoint框架来进行维护,不应该释放,SPContext.Site, SPContext.Current.Site, SPContext.Web, SPContext.Current.Web同理也不能释放。
try, catch, finally本质和using是一样,对实现了IDisposal接口的对象,.NET运行时环境会自动将using转换成try, catch, finally.
01 |
String str; |
02 |
SPSite oSPSite = null ; |
03 |
SPWeb oSPWeb = null ; |
04 |
|
05 |
try |
06 |
{ |
07 |
oSPSite = new SPSite( "http://server/" ); |
08 |
oSPWeb = oSPSite.OpenWeb(..); |
09 |
|
10 |
str = oSPWeb.Title; |
11 |
} |
12 |
catch (Exception e) |
13 |
{ |
14 |
//Handle exception, log exception, etc. |
15 |
} |
16 |
finally |
17 |
{ |
18 |
if (oSPWeb != null ) |
19 |
oSPWeb.Dispose(); |
20 |
|
21 |
if (oSPSite != null ) |
22 |
oSPSite.Dispose(); |
23 |
} |
页面重定向时候的资源释放:如下例,在Response.Redirect调用时会生成ThreadAbortedException异常,而之后finally会被执行,此时会导致线程的异常,无法保证资源一定会得到释放,因此在任何Response.Redirect调用之前一定要确保SPSite,SPWeb对象的释放
01 |
String str; |
02 |
SPSite oSPSite = null ; |
03 |
SPWeb oSPWeb = null ; |
04 |
|
05 |
try |
06 |
{ |
07 |
oSPSite = new SPSite( "http://server/" ); |
08 |
oSPWeb = oSPSite.OpenWeb(..); |
09 |
|
10 |
str = oSPWeb.Title; |
11 |
if (bDoRedirection) |
12 |
{ |
13 |
if (oSPWeb != null ) |
14 |
oSPWeb.Dispose(); |
15 |
|
16 |
if (oSPSite != null ) |
17 |
oSPSite.Dispose(); |
18 |
|
19 |
Response.Redirect( "newpage.aspx" ); |
20 |
} |
21 |
} |
22 |
catch (Exception e) |
23 |
{ |
24 |
} |
25 |
finally |
26 |
{ |
27 |
if (oSPWeb != null ) |
28 |
oSPWeb.Dispose(); |
29 |
|
30 |
if (oSPSite != null ) |
31 |
oSPSite.Dispose(); |
32 |
} |
以上问题也适用于using。
不要创建静态的SPWeb和SPSite对象
调用SPSiteCollection.Add方法创建返回的SPSite对象需要释放;
通过SPSiteColleciton的索引器SPSiteColleciton[]返回的SPSite对象需要释放;
在SPSiteCollection集合中进行foreach是的SPSite对象需要释放;
以下是推荐的做法:
01 |
void SPSiteCollectionForEachNoLeak() |
02 |
{ |
03 |
using (SPSite siteCollectionOuter = new SPSite( "http://moss/" )) |
04 |
{ |
05 |
SPWebApplication webApp = siteCollectionOuter.WebApplication; |
06 |
SPSiteCollection siteCollections = webApp.Sites; |
07 |
|
08 |
foreach (SPSite siteCollectionInner in siteCollections) |
09 |
{ |
10 |
try |
11 |
{ |
12 |
// ... |
13 |
} |
14 |
finally |
15 |
{ |
16 |
if (siteCollectionInner != null ) |
17 |
siteCollectionInner.Dispose(); |
18 |
} |
19 |
} |
20 |
} // SPSite object siteCollectionOuter.Dispose() automatically called. |
21 |
} |
通过SPSite.AllWebs.Add返回的SPWeb对象需要释放;
通过SPWebColleciton.Add返回的SPWeb对象需要释放;
通过SPSite.AllWebs[]索引器返回的SPWeb对象需要释放;
通过foreach循环SPWebColleciton的SPWeb对象需要释放;
通过OpenWeb打开的SPWeb对象需要释放;
通过SPSite.SelfServiceCreateSite创建的SPWeb对象需要释放;
SPSite.RootWeb不需要进行释放;
通过Microsoft.Office.Server.UserProfiles.PersonalSite返回的SPSite对象需要释放;
推荐的开发方式如下:
01
void
PersonalSiteNoLeak()
02
{
03
// Open a site collection
04
using
(SPSite siteCollection =
new
SPSite(
"http://moss/"
))
05
{
06
UserProfileManager profileManager =
new
UserProfileManager(ServerContext.GetContext(siteCollection));
07
UserProfile profile = profileManager.GetUserProfile(
"domain\\username"
);
08
using
(SPSite personalSite = profile.PersonalSite)
09
{
10
// ...
11
}
12
}
13
}
此处可以通过ProfileLoader省去构造新的SPSite以提高性能
1 |
UserProfile myProfile = ProfileLoader.GetProfileLoader().GetUserProfile(); |
2 |
using (SPSite personalSite = myProfile.PersonalSite) |
3 |
{ |
4 |
// ... |
5 |
} |
特别,如果为MySite创建web部件,可以使用PersonalSite而不用释放:
1 |
IPersonalPage currentMySitePage = this .Page as IPersonalPage; |
2 |
if (currentMySitePage != null && !currentMySitePage.IsProfileError) |
3 |
{ |
4 |
SPSite personalSite = currentMySitePage.PersonalSite; // Will not leak. // ... |
5 |
} |
通过GetContextSite返回的SPSite不需要释放
1
void
SPControlBADPractice()
2
{
3
SPSite siteCollection = SPControl.GetContextSite(Context);
4
//siteCollection.Dispose(); 不要释放
5
SPWeb web = SPControl.GetContextWeb(Context);
6
//web.Dispose(); 不要释放
7
}
SPLimitedWebPartManager含有内部对SPWeb的引用,需要释放
01
void
SPLimitedWebPartManagerLeak()
02
{
03
using
(SPSite siteCollection =
new
SPSite(
"http://moss/"
))
04
{
05
using
(SPWeb web = siteCollection.OpenWeb())
06
{
07
SPFile page = web.GetFile(
"Source_Folder_Name/Source_Page"
);
08
SPLimitedWebPartManager webPartManager =
09
page.GetLimitedWebPartManager(PersonalizationScope.Shared);
10
webPartManaber.Web.Dispose();
11
}
// SPWeb object web.Dispose() automatically called.
12
}
// SPSite object siteCollection.Dispose() automatically called.
13
}
Microsoft.SharePoint.Publishing.PublishingWeb(SharePoint2007 only)
PublishingWeb.GetPublishingWebs会返回PublishingWebCollection,foreach时候需要调用close方法释放每一个对象:
01 |
void PublishingWebCollectionNoLeak() |
02 |
{ |
03 |
using (SPSite siteCollection = new SPSite( "http://moss/" )) |
04 |
{ |
05 |
using (SPWeb web = siteCollection.OpenWeb()) |
06 |
{ |
07 |
// Passing in SPWeb object that you own, no dispose needed on |
08 |
// outerPubWeb. |
09 |
PublishingWeb outerPubWeb = PublishingWeb.GetPublishingWeb(web); |
10 |
PublishingWebCollection pubWebCollection = outerPubWeb.GetPublishingWebs(); |
11 |
foreach (PublishingWeb innerPubWeb in pubWebCollection) |
12 |
{ |
13 |
try |
14 |
{ |
15 |
// ... |
16 |
} |
17 |
finally |
18 |
{ |
19 |
if (innerPubWeb != null ) |
20 |
innerPubWeb.Close(); |
21 |
} |
22 |
} |
23 |
} // SPWeb object web.Dispose() automatically called. |
24 |
} // SPSite object siteCollection.Dispose() automatically called. |
25 |
} |
同样,调用PublishingWebCollection.Add返回的PublishingWeb也需要释放;
3.在事件处理器里可以使用以下方法避免生成新的SPSite或者SPWeb
1 |
// Retrieve SPWeb and SPListItem from SPItemEventProperties instead of |
2 |
// from a new instance of SPSite. |
3 |
SPWeb web = properties.OpenWeb(); //此处通过properties.OpenWeb()返回的SPWeb不用释放; |
4 |
// Operate on the SPWeb object. |
5 |
SPListItem item = properties.ListItem; |
6 |
// Operate on an item. |
文件名限制:
在实战中如果需要部署文件夹或者文件到%ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATE目录,出于安全考虑,SharePoint Foundation只能够读取文件名有ASCII字符、数字、下划线、句号、破折号(dashed)组成的名字,特别是文件名不能包括两个连续的句号,例如,以下是允许的文件名:
AllItems.aspx
Dept_1234.doc
Long.Name.With.Dots.txt
以下是不允许的名字:
Cæsar.wav
File Name With Spaces.avi
Wow...ThisIsBad.rtf
揵.htm
大文件夹、大列表的处理
不要使用SPList.Items,因为这个调用会返回所有子文件夹下的所有记录,使用以下方法替代:
添加记录:使用SPList.AddItem,不使用SPList.Items.Add;
查询记录:使用SPList.GetItemById,不适用SPList.Items.GetItemById;
返回列表所有记录:使用SPList.GetItems(SPQuery query)而不是SPList.Items,根据需要使用条件过滤,仅仅挑选必要的字段返回,如果返回结果超过2000条,采用分页技术:
01 |
SPQuery query = new SPQuery(); |
02 |
SPListItemCollection spListItems ; string lastItemIdOnPage = null ; // Page position. |
03 |
int itemCount = 2000 while (itemCount == 2000) |
04 |
{ |
05 |
// Include only the fields you will use. |
06 |
query.ViewFields = "<FieldRef Name=\"ID\"/><FieldRef Name=\"ContentTypeId\"/>" ; query.RowLimit = 2000; // Only select the top 2000. |
07 |
// Include items in a subfolder (if necessary). |
08 |
query.ViewAttributes = "Scope=\"Recursive\"" ; |
09 |
StringBuilder sb = new StringBuilder(); |
10 |
// To make the query order by ID and stop scanning the table, specify the OrderBy override attribute. |
11 |
sb.Append( "<OrderBy Override=\"TRUE\"><FieldRef Name=\"ID\"/></OrderBy>" ); |
12 |
//.. Append more text as necessary .. |
13 |
query.Query = sb.ToString(); // Get 2,000 more items. SPListItemCollectionPosition pos = new SPListItemCollectionPosition(lastItemIdOnPage); |
14 |
query.ListItemCollectionPosition = pos; //Page info. |
15 |
spListItems = spList.GetItems(query); |
16 |
lastItemIdOnPage = spListItems.ListItemCollectionPosition.PagingInfo; |
17 |
// Code to enumerate the spListItems. |
18 |
// If itemCount <2000, finish the enumeration. |
19 |
itemCount = spListItems.Count; |
20 |
|
21 |
} |
分页显示:
01 |
SPWeb oWebsite = SPContext.Current.Web; |
02 |
SPList oList = oWebsite.Lists[ "Announcements" ]; |
03 |
|
04 |
SPQuery oQuery = new SPQuery(); |
05 |
oQuery.RowLimit = 10; |
06 |
int intIndex = 1; |
07 |
|
08 |
do |
09 |
{ |
10 |
Response.Write( "<BR>Page: " + intIndex + "<BR>" ); |
11 |
SPListItemCollection collListItems = oList.GetItems(oQuery); |
12 |
|
13 |
foreach (SPListItem oListItem in collListItems) |
14 |
{ |
15 |
Response.Write(SPEncode.HtmlEncode(oListItem[ "Title" ].ToString()) + "<BR>" ); |
16 |
} |
17 |
|
18 |
oQuery.ListItemCollectionPosition = collListItems.ListItemCollectionPosition; |
19 |
intIndex++; |
20 |
} while (oQuery.ListItemCollectionPosition != null ); |
性能差不推荐使用的API |
性能更好的推荐使用的API |
SPList.Items.Count |
SPList.ItemCount |
SPList.Items.XmlDataSchema |
创建SPQuery,仅仅返回需要的数据 |
SPList.Items.NumberOfFields |
创建SPQuery,指定ViewFields,仅仅返回需要的数据 |
SPList.Items[System.Guid] |
SPList.GetItemByUniqueId(System.Guid) |
SPList.Items[System.Int32] |
SPList.GetItemById(System.Int32) |
SPList.Items.GetItemById(System.Int32) |
SPList.GetItemById(System.Int32) |
SPList.Items.ReorderItems(System.Boolean[],System.Int32[],System.Int32) |
使用SPQuery分页 |
SPList.Items.ListItemCollectionPosition |
ContentIterator.ProcessListItems(SPList, ContentIterator.ItemProcessor, ContentIterator.ItemProcessorErrorCallout) (Microsoft SharePoint Server 2010 only) |
参考:
释放检查工具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