Copy & Paste WebParts (ASP.NET 2.0 portal framework)

Some developers in the ASP.NET 2.0 Forums have asked questions on how to copy a WebPart (including it's configuration) from one page to another.

The standard solution for this is to use the 'Export/Import' functionality that ASP.NET 2.0 offers for WebParts. Note: for a WebPart to be export-enabled, its 'ExportMode' property should have a value other than 'None' (which is the default) and enableExport="true" should be set in the webParts section of the web.config. The export and import of a (configured) WebPart is done in six steps:

  • Switch to EditDisplayMode
  • Select 'Export' from the verbs menu of the WebPart you want to copy
  • Download the WebPart file to your local PC
  • Navigate to the destination page, switch to CatalogDisplayMode
  • Select the 'Imported Web Part Catalog', and upload the WebPart file
  • Add the uploaded WebPart to a zone

Not very user-friendly, is it? Most users wouldn't even know what to do with the downloaded Webpart file.

However, the ASP.NET 2.0 portal framework is very extensible, so I've build a custom solution where the user can just 'copy' the WebPart to a serverside 'clipboard' (i.e. the Session object), and 'paste' it from a custom Catalog (which I named the 'Copied Web Part Catalog'). So this is done without downloading and uploading WebParts files!

Here's a screenshot of the copy/paste functionality:

So you want to know how this is done? I'll show you the code in 3 parts:

Part 1: Add a custom 'Copy' verb to your WebPart code.

/// <summary>
///
Override the Verbs collection to add the 'copy webpart' verb
///
</summary>
public override WebPartVerbCollection
Verbs
{
  get
  {
    if (_verbs == null
)
    {
      WebPartVerb myVerb = new WebPartVerb("copy"
, OnWebPartCopy);
      myVerb.Description = "Copy this WebPart"
;
      myVerb.Text = "Copy"
;
      myVerb.ImageUrl = "~/images/CopyVerb.gif"
;
      _verbs = new WebPartVerbCollection(new WebPartVerb
[] { myVerb });
    }
    return
_verbs;
  }
}

/// <summary>
///
Called when the user selects 'Copy WebPart' from the verbs menu.
///
The selected WebPart is Exported to a xml string, and added to
///
a list in the Session. This list is used by the CopiedCatalogPart
///
to add the copied WebParts to a new page or zone
///
</summary>
public void OnWebPartCopy(object sender, WebPartEventArgs
e)
{
  //get reference to WebPart to copy
  WebPart
webPart = e.WebPart;

 
//temporarily set ExportMode to 'All' to enable WebPart exporting
  //this is a kind of hack, but assumed legitimate because the exported WebPart
  //stays on the server (in Session) 

  WebPartExportMode origExportMode = ExportMode;
  webPart.ExportMode = WebPartExportMode
.All;

 
//export WebPart to string
  StringWriter stringWriter = new StringWriter
();
  XmlTextWriter xmlwriter = new XmlTextWriter
(stringWriter);
  WebPartManager
.ExportWebPart(webPart, xmlwriter);
  string
copiedWebPart = stringWriter.ToString();
 
  //reset original ExportMode of WebPart
  webPart.ExportMode = origExportMode;

 
//reset original ExportMode in exported WebPart string
  XmlDocument doc = new XmlDocument
();
  doc.LoadXml(copiedWebPart);
  XmlNode exportModeNode = doc.SelectSingleNode("//property[@name='ExportMode']"
);
  if (exportModeNode != null
)
  {
    exportModeNode.InnerText = this
.ExportMode.ToString();
    copiedWebPart = doc.OuterXml;
  }

  //get or create list of copied WebParts
  List<KeyValuePair<string, string
>> copiedWebParts;
  if (Context.Session["COPIED_WEBPART_KEY"] != null
)
  {
    copiedWebParts = (List<KeyValuePair<string, string
>>)
      Context.Session["COPIED_WEBPART_KEY"
];
  }
  else
  {
    copiedWebParts = new List<KeyValuePair<string, string
>>();
  }

  //insert new copied WebPart as first on list
  copiedWebParts.Insert(0, new KeyValuePair<string, string
>(
    string.Format("{0} [copied {1}]"
this.Title, DateTime.Now.ToLongTimeString()),
    copiedWebPart));

  //add list to Session
  Context.Session["COPIED_WEBPART_KEY"
] = copiedWebParts;
}

Part 2: The CopiedWebPartCatalogPart

using System;
using
System.Collections.Generic;
using
System.Web.UI.WebControls.WebParts;

namespace
myClassLib.CatalogParts
{
  ///
<summary>
  ///
Catalog for reading WebParts copied to the users Session (copy/paste functionality)
  ///
</summary>
  public class CopiedWebPartCatalogPart :
CatalogPart
  {
    ///
<summary>
    ///
Overrides the Title to display "Copied Web Part Catalog" by default
    ///
</summary>
    public override string
Title
    {
      get
      {
        string title = base
.Title;
        return string.IsNullOrEmpty(title) ? "Copied Web Part Catalog"
: title;
      }
      set
      {
        base.Title = value
;
      }
    }

    //dictionary to hold the availabe copied webparts
    private Dictionary<WebPartDescription, string
> _webparts 
      = new Dictionary<WebPartDescription, string
>();

    ///
<summary>
    ///
Returns the WebPartDescriptions for the catalog part
    ///
</summary>
    public override WebPartDescriptionCollection
GetAvailableWebPartDescriptions()
    {
      _webparts.Clear();
      
      if
(Context.Session["COPIED_WEBPART_SESSION_KEY"] != null
)
      {
        List<KeyValuePair<string, string
>> copiedWebParts = 
          (List<KeyValuePair<string, string>>)Context.Session["COPIED_WEBPART_SESSION_KEY"
];
        
        foreach
(KeyValuePair<string, string> copiedWebPart in
copiedWebParts)
        {
          WebPartDescription desc = new WebPartDescription
(
            copiedWebParts.IndexOf(copiedWebPart).ToString(), 
            copiedWebPart.Key,
            null

            null
);

          _webparts[desc] = copiedWebPart.Value;
        }
      }
      return new WebPartDescriptionCollection
(_webparts.Keys);
    }

    ///
<summary>
    ///
Returns a new instance of the WebPart specified by the description
    ///
</summary>
    public override WebPart GetWebPart(WebPartDescription
description)
    {
      //get a xmltextreader to the exported WebPart
      System.Xml.XmlTextReader
reader =
        new System.Xml.XmlTextReader(new System.IO.StringReader
(_webparts[description]));
      string errorMessage;
//will contain errorMessage on import errors

      //return an instance of the requested WebPart
      return WebPartManager.ImportWebPart(reader, out
errorMessage);
    }
  }
}

Part 3: Add the CopiedWebPartCatalogPart in the CatalogZone of your aspx page

<%@ Register TagPrefix="myCatalogParts" Assembly="myClassLib" Namespace="myClassLib.CatalogParts" %>

<
asp:CatalogZone ID="CatalogZone" runat="server" >
 
<ZoneTemplate>
   
<asp:PageCatalogPart ID="PagePart" runat="server"
/>
   
<asp:ImportCatalogPart Visible="false" ID="ImportPart" runat="Server"
/>
   
<myCatalogParts:CopiedWebPartCatalogPart ID="PastePart" runat="server"
/>
  </ZoneTemplate
>
</asp:CatalogZone>

No Comments