Mar 10, 2010

SharePoint Disposing Myth: You have to dispose SPWeb explicitly!?

It seems to me that every post, article and book tells you to explicitly dispose every SPWeb (excepts them coming from SPContext). But is that right?
Not quite! Every SPSite tracks the SPWebs that it has emitted. A internal list named “m_openedWebs” holds these references. It doesn’t matter from which member of the SPSite you will get the SPWeb (RootWeb, OpenWeb, AllWebs, Add) the SPSite holds a reference on it. When you dispose the SPSite it loops through all SPWebs in “m_openedWebs” and calls the Close method on each. The dispose method of SPWeb does nothing else than calling it’s close method. Means when you dispose a SPSite, all SPWebs that it has emitted will be disposed implicitly! A construct like this
using(SPSite site = new SPSite("http://server/site"))
{
     using(SPWeb web = site.OpenWeb())
     {
       //...
     }
}
is not necessary, because the using for the SPSite will implicitly dispose all of it SPWebs. By the way if you call the dispose method of the SPWeb explicitly it will internally remove itself from the “m_openedWebs” list. Sure there are scenarios where explicitly disposing of SPWebs is important, for example when you loop through the AllWebs property of the SPSite with maybe hundreds of SPWebs. Because every SPWeb consumes a lot of memory! On the other hand you don’t have to be afraid when you pass a SPWeb reference across your API. When you finally dispose the SPSite, all SPWebs it has emitted will be disposed too and you will not cause a memory leak. I think the SharePoint team have implemented this pretty well.

When you create an instance of SPSite you are responsible to dispose it. When to dispose SPWeb that have been emitted by your SPSite depends on the scenario.


What could cause problems indeed is when you still use a SPWeb that has already been disposed, doesn’t matter if it has been disposed implicitly or explicitly. The SPWeb will be reopened but will not be listed in the “m_openedWebs” of the SPSite. Means it can not be implicitly disposed by the SPSite anymore. A better and approach would have been to throw an ObjectDisposedException on all members after the SPWeb has been disposed.

You don’t believe? Just take a look at the following snippet or even better let it run:
Console.WriteLine("Create a SPSite");
SPSite site = new SPSite("http://localhost:101");

Console.WriteLine("Open 5 SPWebs...");
SPWeb web1 = site.RootWeb;
SPWeb web2 = site.OpenWeb();
SPWeb web3 = site.OpenWeb("my");
SPWeb web4 = site.AllWebs[1];
SPWeb web5 = site.AllWebs.Add(Guid.NewGuid().ToString());

int openedWebs = (typeof(SPSite).InvokeMember("m_openedWebs",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField,
null, site, null) as ICollection).Count;      

Console.WriteLine("Acutally opened SPWebs: " + openedWebs.ToString());

Console.WriteLine("Dispose web1 explicitly");
web1.Dispose();

openedWebs = (typeof(SPSite).InvokeMember("m_openedWebs",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField,
null, site, null) as ICollection).Count;     

Console.WriteLine("Dispose the SPSite");         
site.Dispose();

openedWebs = (typeof(SPSite).InvokeMember("m_openedWebs",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField,
null, site, null) as ICollection).Count;

Console.WriteLine("Acutally opened SPWebs: " + openedWebs.ToString());

Console.ReadKey();   


Any feedback?

Mar 1, 2010

Manage ASP.NET Providers with PowerShell

I’ve written this script to manage SQL users on a SharePoint box with form based security (FBA).

Usage:

Load the script
PS>. .\Manage_ASP_NET_Providers.ps1  “C:\..\web.config”

You have to provide the path to the web.config which contains the membership provider configurations.

The script will change the current app domain’s config path and then loads the System.Web Assembly. The sequence is important. If you want to change app config path later, you have to restart PowerShell and load the script again with another path.

Examples:

Get Membership Provider “sqlMembers”
PS> $provider = Get-MembershipProvider  “sqlMembers”

Add a new user (login, mail, question, answer)
PS> $provider | Add-MembershipUser “cglessner” “cg@test.de” “Best Portal” “SharePoint”

List first 1000 users
PS> $provider | Get-MembershipUser –maxResult 1000

….

 

Bye, Christian