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>