Summary: Learn the appropriate ways to write your code when using Microsoft Windows SharePoint Services objects so that you can avoid retaining the objects in memory with the Microsoft .NET Framework. (24 printed pages)
Scott Harris, Microsoft Corporation
Mike Ammerlaan, Microsoft Corporation
June 2006
Updated: November 2006
Applies to: Microsoft Windows SharePoint Services 3.0, Microsoft Windows SharePoint Services 2.0, Microsoft Office SharePoint Server 2007, Microsoft Office SharePoint Portal Server 2003
Contents:
Introduction to Using Disposable Windows SharePoint Services Objects
The objects in the Microsoft Windows SharePoint Services 3.0 object model serve as an interface for working with Windows SharePoint Services data and configuration. Frequently, developers call into the object model to read or write new data in the Windows SharePoint Services store.
The Windows SharePoint Services object model contains objects that implement the IDisposable interface. You must take precautions when using these objects to avoid their long-term retention in memory in the Microsoft .NET Framework. Specifically, you should explicitly dispose of those SharePoint objects when you are finished using them.
In scenarios in which you use SharePoint objects extensively—for example, in SharePoint sites that use custom Web Parts—you can cause the following unusual behaviors by not disposing of those objects that can be disposed of:
-
Frequent recycles of the Microsoft Windows SharePoint Services application pool, especially during peak usage
-
Application crashes that appear as heap corruption in the debugger
-
High memory use for Microsoft Internet Information Services (IIS) worker processes
-
Poor system and application performance
This article serves as a guide to the proper procedures for handling and disposing of SharePoint objects.
Background
Several of the Windows SharePoint Services objects, primarily the SPSite class and SPWeb class objects, are created as managed objects. However, these objects use unmanaged code and memory to perform the majority of their work. The managed part of the object is small; the unmanaged part of the object is much larger. Because the smaller managed part of the object does not put memory pressure on the garbage collector, the garbage collector does not release the object from memory in a timely manner. The object's use of a large amount of unmanaged memory can cause some of the unusual behaviors described earlier. Calling applications that work with IDisposable objects in Windows SharePoint Services must dispose of the objects when the applications finish using them. You should not rely on the garbage collector to release them from memory automatically.
You can use any of several techniques to ensure that the objects are disposed of properly. We discuss them in the following sections.
Coding Techniques
You can take advantage of certain coding techniques to ensure that objects are disposed of. These techniques include using the following in your code:
-
Dispose method
-
using clause
-
try, catch, and finally blocks
Dispose vs. Close Method Usage
The Dispose and Close methods for the SPWeb object and SPSite object function in the same way. The Dispose method simply calls the object's Close method. We recommend calling the Dispose method, instead of Close, for the following reasons:
-
SPWeb and SPSite objects implement the IDisposable interface, and the standard .NET Framework process calls the Dispose method to free from memory any resources associated with the object.
-
Future releases of your code are ensured to be called properly.
using Clause
You can automatically dispose of SharePoint objects that implement the IDisposable interface by using the Microsoft Visual C# using clause.
The following code provides an example.
C#
|
String str; using(SPSite oSPsite = new SPSite("http://server")) { using(SPWeb oSPWeb = oSPSite.OpenWeb()) { str = oSPWeb.Title; str = oSPWeb.Url; } } |
try/catch/finally Blocks
The following is a recommendation of how to code try/catch/finally blocks. When you use try blocks, it is important to add a finally block to ensure that all objects are properly disposed of.
Any code that is ultimately contained within a try { } catch { } block should have a governing finally clause, which ensures that the respective objects are disposed of. These try { } catch { } blocks can exist outside of the function where you use SharePoint objects. In those cases, you should consider using try/catch/finally in the methods to wrap your usages of the SharePoint object model directly.
C#
|
String str; SPSite oSPSite = null; SPWeb oSPWeb = null; try { oSPSite = new SPSite("http://server"); oSPWeb = oSPSite.OpenWeb(..); str = oSPWeb.Title; } catch(Exception e) { } finally { if (oSPWeb != null) oSPWeb.Dispose(); if (oSPSite != null) oSPSite.Dispose(); } |
Calling Response.Redirect WILL NOT execute the finally block. Therefore, before any redirection or transfer of processing can occur, you must dispose of the objects. For example, if your code must redirect, implement it in a way similar to the following.
C#
|
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(); } |
Recommendations to Reduce Long-Term Object Retention
To reduce long-term retention of Windows SharePoint Services objects, we offer the following general recommendations.
-
If you create the object with a new operator, the creating application must dispose of it.
Good Coding Practice #1
C#SPSite oSPSite = new SPSite("http://server"); ... additional processing on SPSite ... oSPSite.Dispose();
Good Coding Practice #2
C#using (SPSite oSPSite = new SPSite("http://server")) { ... additional processing on SPSite ... }
-
SharePoint methods that return other SPWeb objects (such as SPSite.OpenWeb) create new items and should be disposed of.
Good Coding Practice #1
C#String str; SPSite oSPSite = new SPSite("http://server"); SPWeb oSPWeb = oSPSite.OpenWeb(); str = oSPWeb.Title; str = oSPWeb.Url; ... additional processing on SPWeb ... oSPWeb.Dispose(); oSPSite.Dispose();
Good Coding Practice #2
C#String str; using(SPSite oSPSite = new SPSite("http://server")) { using(SPWeb oSPWeb = oSPSite.OpenWeb()) { str = oSPWeb.Title; str = oSPWeb.Url; ... additional processing on SPWeb ... } }
-
The SPSite.RootWeb property and SPWeb.ParentWeb property create new objects and assign them to a local member variable. Each succeeding access to the property uses the item assigned to the member variable. As a result, you can call the property as often as you want without creating multiple items. However, you must call the Dispose method of each property when you finish using it. In the following example, you can use the RootWeb and ParentWeb properties in the same way.
Good Coding Practice #1
C#String str; SPSite oSPSite = new SPSite("http://server"); str = oSPSite.RootWeb.Title; str = oSPSite.RootWeb.Url; ... additional processing on RootWeb ... oSPSite.RootWeb.Dispose(); oSPSite.Dispose();
Good Coding Practice #2
C#String str; using(SPSite oSPSite = new SPSite("http://server")) { str = oSPSite.RootWeb.Title; str = oSPSite.RootWeb.Url; ... additional processing on RootWeb ... oSPSite.RootWeb.Dispose(); }
Good Coding Practice #3
C#String str; using(SPSite oSPSite = new SPSite("http://server")) { using(SPWeb oRootWeb = oSPSite.RootWeb) { str = oRootWeb.Title; str = oRootWeb.Url; … additional processing on RootWeb … } }
SPSite Objects
This section describes the situations in which new SPSite objects are returned and must be disposed of.
In general, any time a calling application uses the new SPSite() constructors (any signature), it should call the SPSite.Dispose() method when it is finished using the object. If the SPSite object is obtained from SPControl.GetContextSite, the calling application should NOT dispose of the object. Because the SPWeb and SPSite objects keep an internal list that is derived in this way, disposing of the object may cause the SharePoint object model to behave unpredictably. Internally, Windows SharePoint Services enumerates over this list after page completion to dispose of the objects properly.
SPSiteCollection Class
This section describes the methods, properties, or operators in the SPSiteCollection object that require the returned SPSite object to be closed after access.
SPSiteCollection.Add Method
The SPSiteCollection.Add method creates and returns a new SPSite object. You should dispose of any SPSite object returned from the SPSiteCollection.Add method.
Good Coding Practice #1
C#
|
SPGlobalAdmin oSPGlobalAdmin = new SPGlobalAdmin();
SPSiteCollection aSites = oSPGlobalAdmin.VirtualServers[0].Sites;
SPSite oSPSite = aSites.Add( ... );
... Process the site info ...
oSPSite.Dispose();
oSPGlobalAdmin.Dispose();
|
Good Coding Practice #2
C#
|
SPGlobalAdmin oSPGlobalAdmin = new SPGlobalAdmin(); SPSiteCollection aSites = oSPGlobalAdmin.VirtualServers[0].Sites; using(SPSite oSPSite = aSites.Add( ... )) { ... Process the site info ... } oSPGlobalAdmin.Dispose(); |
SPSiteCollection [ ] Index Operator
The SPSiteCollection [] index operator returns a new SPSite object for each access. An SPSite instance is created even if that object has already been accessed. The following code samples demonstrate improper disposal of the SPSite object.
Bad Coding Practice #1
C#
|
int i; SPSite oSPSite; SPGlobalAdmin oSPGlobalAdmin = new SPGlobalAdmin(); SPSiteCollection aSites = oSPGlobalAdmin.VirtualServers[0].Sites; for (i = 0;i < aSites.Count;i++) { oSPSite = aSites[i]; BuildTableRow(oDisplayTable, "Site", oSPSite.Url); } oSPGlobalAdmin.Dispose(); |
Bad Coding Practice #2
C#
|
SPGlobalAdmin oSPGlobalAdmin = new SPGlobalAdmin(); SPSiteCollection aSites = oSPGlobalAdmin.VirtualServers[0].Sites; foreach(SPSite oSPSite in aSites) { BuildTableRow(oDisplayTable, "Site", oSPSite.Url); } oSPGlobalAdmin.Dispose(); |
The recommended fix is to place a Dispose() at the end of each loop.
Good Coding Practice #1
C#
|
int i; SPSite oSPSite; SPGlobalAdmin oSPGlobalAdmin = new SPGlobalAdmin(); SPSiteCollection aSites = oSPGlobalAdmin.VirtualServers[0].Sites; for(i = 0;i < aSites.Count;i++) { oSPSite = aSites[i]; BuildTableRow(oDisplayTable, "Site", oSPSite.Url); oSPSite.Dispose(); } oSPGlobalAdmin.Dispose(); |
Good Coding Practice #2
C#
|
SPGlobalAdmin oSPGlobalAdmin = new SPGlobalAdmin(); SPSiteCollection aSites = oSPGlobalAdmin.VirtualServers[0].Sites; foreach(SPSite oSPSite in aSites) { BuildTableRow(oDisplayTable, "Site", oSPSite.Url); oSPSite.Dispose(); } oSPGlobalAdmin.Dispose(); |
Good Coding Practice #3
C#
|
int i; SPGlobalAdmin oSPGlobalAdmin = new SPGlobalAdmin(); SPSiteCollection aSites = oSPGlobalAdmin.VirtualServers[0].Sites; for(i = 0;i < aSites.Count;i ++) { using(SPSite oSPSite = aSites[i]) { BuildTableRow(oDisplayTable, "Site", oSPSite.Url); } } oSPGlobalAdmin.Dispose(); |
SPSite.AllWebs Property (SPWebCollection)
This section describes the methods, properties, or operators in the SPSite.AllWebs property collection that require the SPWeb object to be closed after access.
SPSites.AllWebs.Add Method
The SPSite.AllWebs.Add method(or SPWebCollection.Add) creates and returns an SPWeb object. You should dispose of any SPWeb object returned from SPSite.AllWebs.Add.
Good Coding Practice #1
C#
|
SPWeb oSPWeb; SPSite oSPSite = SPControl.GetContextSite(Context); oSPWeb = oSPSite.AllWebs.Add( ... ); ... Process the SPWeb info ... oSPWeb.Dispose(); |
Good Coding Practice #2
C#
|
SPSite oSPSite = SPControl.GetContextSite(Context);
using(SPWeb oSPWeb = oSPSite.AllWebs.Add( ... ))
{
... Process the SPWeb info ...
}
|
Note: |
---|
The SPSite object was obtained from the GetContextSite() method and does not need to be disposed of. |
SPSite.AllWebs [ ] Index Operator
The SPSite.AllWebs [] index operator returns a new SPWeb instance each time it is accessed. An object is created during the indexing operation even if that object has already been accessed. If not properly closed, the following code samples leave an SPWeb object in the .NET Framework garbage collector.
Bad Coding Practice #1
C#
|
int i; SPWeb oSPWeb; SPSite oSPSite = SPControl.GetContextSite(Context); for(i=0;i < oSPSite.AllWebs.Count; i++) { oSPWeb = oSPSite.AllWebs[i]; BuildTableRow(oDisplayTable, "Web", oSPWeb.Title); } |
Bad Coding Practice #2
C#
|
SPSite oSPSite = SPControl.GetContextSite(Context); foreach(SPWeb oSPWeb in oSPSite.AllWebs) { BuildTableRow(oDisplayTable, "Web", oSPWeb.Title); } |
The recommended way to fix this is to place Dispose() at the end of each loop.
Good Coding Practice #1
C#
|
int i; SPWeb oSPWeb; SPSite oSPSite = SPControl.GetContextSite(Context); for(i = 0;i < oSPSite.AllWebs.Count; i++) { oSPWeb = oSPSite.AllWebs[i]; BuildTableRow(oDisplayTable, "Web", oSPWeb.Title); oSPWeb.Dispose(); } |
Good Coding Practice #2
C#
|
SPSite oSPSite = SPControl.GetContextSite(Context); foreach(SPWeb oSPWeb in oSPSite.AllWebs) { BuildTableRow(oDisplayTable, "Web", oSPWeb.Title); oSPWeb.Dispose(); } |
Good Coding Practice #3
C#
|
int i; SPWeb oSPWeb; SPSite oSPSite = SPControl.GetContextSite(Context); for(i = 0;i < oSPSite.AllWebs.Count; i++) { using(oSPWeb = oSPSite.AllWebs[i]) { BuildTableRow(oDisplayTable, "Web", oSPWeb.Title); } } |
SPSite.OpenWeb and SPSite. SelfServiceCreateSite Methods
The SPSite.OpenWeb method and SPSite.SelfServiceCreateSite method (all signatures) create an SPWeb object and return it to the caller. This new object is not stored in the SPSite object and is not disposed of anywhere in the SPSite class. For this reason, you should dispose of any object created via these methods.
Good Coding Practice #1
C#
|
SPSite oSPSite = new SPSite("http://Server"); SPWeb oSPWeb = oSPSite.OpenWeb(..); ... additional processing ... oSPWeb.Dispose(); oSPSite.Dispose(); |
Good Coding Practice #2
C#
|
using(SPSite oSPSite = new SPSite("http://Server")) { using(SPWeb oSPWeb = oSPSite.OpenWeb(..)) { ... additional processing ... } } |
SPSite.LockIssue, SPSite.Owner, and SPSite.SecondaryContact Properties
The following properties reference data from the top-level Web site and use the SPSite.RootWeb property:
For more information, see the discussion of SPSite.RootWeb. If you use any of these respective properties, you must call the Dispose method on the SPSite.RootWeb property.
Good Coding Practice #1
C#
|
String str; SPSite oSPSite = new SPSite("http://server"); str = oSPSite.LockIssue; oSPSite.RootWeb.Dispose(); oSPSite.Dispose(); |
Good Coding Practice #2
C#
|
String str; using(SPSite oSPSite = new SPSite("http://server")) { str = oSPSite.Owner; oSPSite.RootWeb.Dispose(); } |
SPSite.RootWeb Property
The first time the SPSite.RootWeb property is called, the property determines whether a member variable is assigned with a non-null value. If the member variable is null, a new SPWeb object is created by calling SPSite.OpenWeb method and assigns the new SPWeb object to the member variable. The next call to the SPSite.RootWeb property simply returns the value that was stored in the member variable.
The calling application should dispose of the SPSite.RootWeb property just before disposing of the SPSite object that is using it, as shown in the following example.
Good Coding Practice #1
C#
|
String str; SPSite oSPSite = new SPSite("http://server"); str = oSPSite.RootWeb.Title; ... additional processing ... oSPSite.RootWeb.Dispose(); oSPSite.Dispose(); |
Good Coding Practice #2
C#
|
String str; using(SPSite oSPSite = new SPSite("http://server")) { str = oSPSite.RootWeb.Title; ... additional processing ... oSPSite.RootWeb.Dispose(); } |
Good Coding Practice #3
C#
|
String str; using(SPSite oSPSite = new SPSite("http://server")) { using(SPWeb oRootWeb = oSPSite.RootWeb) { str = oRootWeb.Title; ... additional processing ... } } |
SPWeb Objects
This section describes the situations in which SPWeb objects are returned and may need to be disposed of.
SPWeb.ParentWeb Property
The first time the SPWeb.ParentWeb property is called, it determines whether a member variable is assigned with a non-null value. If the member variable is null, a new SPWeb object is created if a valid parent Web site exists by calling the OpenWeb method, and assigns the new SPWeb object to the member variable. The next call to the SPWeb.ParentWeb property simply returns the value that was stored in the member variable.
The calling application should dispose of the SPWeb.ParentWeb property just before disposing the SPWeb object that is using it. Following are examples.
Good Coding Practice #1
C#
|
String str; SPSite oSPSite = new SPSite("http://server"); SPWeb oSPWeb, oSPWebParent; oSPWeb = oSPSite.OpenWeb(); oSPWebParent = oSPWeb.ParentWeb; if (oSPWebParent != null) { ... additional processing ... } if (oSPWebParent != null) oSPWebParent.Dispose(); oSPWeb.Dispose(); oSPSite.Dispose(); |
Good Coding Practice #2
C#
|
String str; SPWeb oSPWeb, oSPWebParent; using(SPSite oSPSite = new SPSite("http://server")) { using(SPWeb oSPWeb = oSPSite.OpenWeb()) { oSPWebParent = oSPWeb.ParentWeb; if(oSPWebParent != null) { ... additional processing ... } If(oSPWebParent != null) oSPWebParent.Dispose(); } } |
SPWeb.Webs Property (SPWebCollection)
This section describes the methods, properties, or operators in the SPWeb.Webs property collection that require disposal of the SPWeb object after access.
SPWeb.Webs.Add
The SPWeb.Webs.Add method (or SPWebCollection.Add) creates and returns a new SPWeb object. You should dispose of any SPWeb object returned from this method call.
Good Coding Practice #1
C#
|
SPWeb oSPWeb SPSite oSPSite = SPControl.GetContextSite(Context); oSPSWeb = oSPSite.AllWebs.Add( ... ); ... Process the SPWeb info ... oSPWeb.Dispose(); |
Good Coding Practice #2
C#
|
SPSite oSPSite = SPControl.GetContextSite(Context);
using(SPWeb oSPSWeb = oSPSite.AllWebs.Add( ... ))
{
... Process the SPWeb info ...
}
|
Note: |
---|
The SPSite object was obtained from the GetContextSite() method and does not need to be disposed of. |
SPWeb.Webs[ ] Index OperatorH9
The SPWeb.Webs [] index operator returns a new SPWeb for each access. A new SPWeb is created by calling the OpenWeb method even if that object has already been accessed. The following code samples cause long-term retention of these objects in memory used by the .NET Framework.
Bad Coding Practice #1
C#
|
int i; SPWeb oSPWeb, oSPWeb2; SPSite oSPSite = SPControl.GetContextSite(Context); oSPWeb = oSPSite.OpenWeb(); for(i = 0;i < oSPWeb.Webs.Count;i++) { oSPWeb2 = oSPWeb.Webs[i]; BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title); } |
Bad Coding Practice #2
C#
|
SPWeb oSPWeb, oSPWeb2; SPSite oSPSite = SPControl.GetContextSite(Context); oSPWeb = oSPSite.OpenWeb(); foreach(SPWeb oSPWeb2 in oSPWebe.Webs) { BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title); } |
The recommended fix is to place a close at the end of each loop.
Good Coding Practice #1
C#
|
int i; SPWeb oSPWeb, oSPWeb2; SPSite oSPSite = SPControl.GetContextSite(Context); oSPWeb = oSPSite.OpenWeb(); for(i = 0;i < oSPWeb.Webs.Count;i++) { oSPWeb2 = oSPWeb.Webs[i]; BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title); oSPWeb2.Dispose(); } oSPWeb.Dispose(); |
Good Coding Practice #2
C#
|
SPWeb oSPWeb, oSPWeb2; SPSite oSPSite = SPControl.GetContextSite(Context); oSPWeb = oSPSite.OpenWeb(); foreach(SPWeb oSPWeb2 in oSPWeb.Webs) { BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title); oSPWeb2.Dispose(); } oSPWeb.Dispose(); |
Good Coding Practice #3
C#
|
int i; SPWeb oSPWeb, oSPWeb2; SPSite oSPSite = SPControl.GetContextSite(Context); using(oSPWeb = oSPSite.OpenWeb()) { for(i = 0;i < oSPWeb.Webs.Count;i++) { Using(oSPWeb2 = oSPWeb.Webs[i]) { BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title); } } } |
Other Objects that Require Disposal
This section describes when to call the Dispose method on other SharePoint objects.
Microsoft.SharePoint.Portal.SiteData.Area.Web Property
The Area.Web property returns a new SPWeb object each time it is accessed. Any use of the Area.Web property should have a corresponding call to the Dispose method.
Bad Coding Practice #1
C#
|
String str;
Area oArea = AreaManager.GetArea(PortalContext.Current, new Guid(AreaGiud);
str = oArea.Web.Title; // Web is not reclaimed
str = oArea.Web.Url // Web is not reclaimed
|
Good Coding Practice #1
C#
|
String str;
Area oArea = AreaManager.GetArea(PortalContext.Current,
new Guid(AreaGiud);
SPWeb oSPWeb = oArea.Web;
str = oSPweb.Title;
str = oSPWeb.Url;
...
oSPWeb.Dispose();
|
Good Coding Practice #2
C#
|
String str; Area oArea = AreaManager.GetArea(PortalContext.Current, new Guid(AreaGiud); using(SPWeb oSPWeb = oArea.Web) { str = oSPweb.Title; str = oSPWeb.Url; } |
SPControl.GetContextSite and SPControl.GetContextWeb Methods
If the object is obtained from the SharePoint context objects (SPControl.GetContextSite method and SPControl.GetContextWeb method), the calling application should NOT call the Dispose method on the object. Doing so may cause the SharePoint object model to behave unpredictably or fail. This is due to an internal list that is kept in the SPSite and SPWeb objects derived in this way. Internally, the object model enumerates over this list after page completion to dispose of the objects properly.
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 SPControl.GetContextSite method.
Bad Coding Practice #1
C#
|
SPSite oSPSite = SPControl.GetContextSite(..);
... additional processing ...
oSPSite.Dispose();
// Could cause problems in the SharePoint object model
|
Good Coding Practice #1
C#
|
SPSite oSPSite = SPControl.GetContextSite(..); SPWeb oSPWeb = oSPSite.OpenWeb(..); ... additional processing ... oSPWeb.Dispose(); |
Good Coding Practice #2
C#
|
SPSite oSPSite = SPControl.GetContextSite(..);
using(SPWeb oSPWeb = oSPsite.OpenWeb())
{
... additional processing ...
}
|
WebPartPage.RootWeb Property
The WebPartPage.RootWeb property is similar to the SPSite.RootWeb property in that the first time the property is called, it determines whether a member variable is assigned with a non-null value. If the member variable is null, a new SPWeb object is created by calling the SPSite.OpenWeb method; the new SPWeb object is assigned to the member variable, or the member variable is assigned to WebPartPage.Web if that SPWeb object is a top-level Web site.
The calling application should dispose of the WebPartPage.RootWeb property only if WebPartPage.IsRootWeb returns true.
Good Coding Practice #1
C#
|
String str; WebPartPage oWebPartPage = new WebPartPage(); str = oWebPartPage.RootWeb.Title; ... additional processing ... if(oWebPartPage.Web.IsRootWeb oWebPartPage.Dispose(); |
Conclusion
Many SharePoint objects implement the IDisposable interface, so you must take care when using these SharePoint objects to avoid retaining them in memory. By following the guidelines for their disposal, as described in this article, you can help to ensure reliability of your Windows SharePoint Services customizations.
Acknowledgements
We would like to thank the following people for their input and guidance in creating this document:
-
Keith Richie, Microsoft Corporation
-
Rob Anderson, Microsoft Corporation
-
Steve Sheppard, Microsoft Corporation
-
Chris Gideon, Microsoft Corporation
-
Kelvin Lo, Microsoft Corporation
-
Rashid Aga, Microsoft Corporation
For more information on this and other SharePoint issues, see Keith Richie's blog.