I’ve been meaning to blog about this for a while but just haven’t gotten around to it. Have you ever needed to add a web part to a page during Feature activation? Of course you can do this declaratively using CAML but I usually prefer to do this stuff via code. The challenge is that occasionally you will need to activate the Feature outside the context of a web request – such as via STSADM – this becomes critical for certain web parts, such as earlier versions of the KPI List Web Part, which required a valid SPContext in order to be added to a page (this web part was fixed in the August 2008 Cumulative Update (or thereabouts) so that it no longer requires a valid SPContext object).

If you’re faced with adding a web part (or any other artifact) which requires a valid SPContext object outside of a web request than you can create your own context with the following three lines of code:

1HttpRequest httpRequest = new HttpRequest("", web.Url, "");
2HttpContext.Current = new HttpContext(httpRequest, new HttpResponse(new StringWriter()));
3SPControl.SetContextWeb(HttpContext.Current, web);

I actually can’t take the credit for this – I was talking with Dan Attis during the summer about an issue and he pointed out this simple little fix for me. Using this I created a simple helper function that allows me to easily add a web part to a page within my Feature activation event. The code is below – just pass in the SPWeb object, the URL to a page, and the path to the web part file. I put this in a shared library which I use for all of my Features thus making it super easy to add a web part to a page:

 1public static void AddWebPart(SPWeb web, string page, string webPartXmlFile, string zone, int zoneId, bool deleteExistingIfFound)
 2{
 3    bool cleanupContext = false;
 4    try
 5    {
 6        if (HttpContext.Current == null)
 7        {
 8            cleanupContext = true;
 9            HttpRequest httpRequest = new HttpRequest("", web.Url, "");
10            HttpContext.Current = new HttpContext(httpRequest, new HttpResponse(new StringWriter()));
11            SPControl.SetContextWeb(HttpContext.Current, web);
12        }
13
14        using (SPLimitedWebPartManager manager = web.GetLimitedWebPartManager(page, PersonalizationScope.Shared))
15        {
16            string err;
17
18            XmlTextReader reader = null;
19            System.Web.UI.WebControls.WebParts.WebPart wp;
20            try
21            {
22                string webPartXml = File.ReadAllText(webPartXmlFile);
23                webPartXml = webPartXml.Replace("${siteCollection}", web.Site.Url);
24                webPartXml = webPartXml.Replace("${site}", web.Url);
25                webPartXml = webPartXml.Replace("${webTitle}", HttpUtility.HtmlEncode(web.Title));
26                reader = new XmlTextReader(new StringReader(webPartXml));
27
28                wp = manager.ImportWebPart(reader, out err);
29                if (!string.IsNullOrEmpty(err))
30                    throw new Exception(err);
31            }
32            finally
33            {
34                if (reader != null)
35                    reader.Close();
36            }
37
38            // Delete existing web part with same title so that we only have the latest version on the page
39            foreach (System.Web.UI.WebControls.WebParts.WebPart wpTemp in manager.WebParts)
40            {
41                if (wpTemp.Title == wp.Title)
42                {
43                    if (deleteExistingIfFound)
44                        manager.DeleteWebPart(wpTemp);
45                    else
46                    {
47                        wpTemp.Dispose();
48                        return;
49                    }
50                    break;
51                }
52                wpTemp.Dispose();
53            }
54
55            manager.AddWebPart(wp, zone, zoneId);
56        }
57    }
58    finally
59    {
60        if (HttpContext.Current != null && cleanupContext)
61        {
62            HttpContext.Current = null;
63        }
64    }
65}