Upgrading a Web Part Assembly for Microsoft SharePoint Products and Technologies

 

Nilly Banerjee
Dale Rector
Microsoft Corporation

August 2003

Applies to:
    Microsoft® Windows® SharePoint™ Services
    Microsoft Office SharePoint Portal Server 2003

Summary: Upgrade a Web Part assembly for Microsoft Windows SharePoint Services or Microsoft Office SharePoint Portal Server 2003, including adding, deleting, renaming, repurposing, or upgrading properties for a Web Part. (9 printed pages)

Note   The information in this article also applies to Microsoft Office SharePoint Portal Server 2003, which is built on the Windows SharePoint Services platform. The samples that accompany this article should work when loaded into sites created with SharePoint Portal Server.

Contents

Introduction
Assembly Redirection
Web Part Upgrade Scenarios
Conclusion

Introduction

As a Web Part developer, you may need to deploy multiple upgrades for your Web Parts to patch security holes, add new features, remove old features, or for many other reasons. When you upgrade a Web Part, you may need to add new properties, repurpose old properties, remove old properties, or assign new values to existing properties, which can complicate the upgrade process. This paper describes how to upgrade your Web Parts successfully.

Assembly Redirection

To provide the simplest possible upgrade experience for your users, use assembly redirection in the Microsoft® .NET® Framework. When the Web Part infrastructure looks for version one of an assembly, but you want the site to use version two, you can use assembly redirection to redirect the Web Part infrastructure to version two of the assembly. For more information about assembly redirection in the .NET Framework, see Redirecting Assembly Versions.

Note   When you add an assembly redirection line to the Web.config file, instances of the Web Part are not immediately upgraded. Instead, the first time a user browses an instance of the Web Part, the server upgrades that instance of the Web Part. If an instance of a Web Part is on a page that is never browsed, then that instance of the Web Part is not upgraded to use the new assembly reference. For this reason, you should never remove an assembly redirection line from the Web.config file.

Web Part Upgrade Scenarios

The Web Part upgrade model requires that data from earlier versions of a Web Part must directly deserialize into the current version of that part (for example, version one data must deserialize into a version two or later Web Part). In other words, the current version of the Web Part must be able to read data from all previous versions of the Web Part. The Web Part assembly must be able to use a property set from any version of a Web Part from 1 through N to successfully create a version N Web Part.

If you simply add one or more new properties, no upgrade logic is required. However, you must do some additional work to support the following upgrade scenarios:

  • Deleting a Web Part property

  • Renaming a Web Part property

  • Repurposing a Web Part property

    For example, in a Web Part that uses a property named SampleProp, the valid values for SampleProp in version one are 1, 2, or 3, the valid values for SampleProp in version two are A, B, C, or D, and the SampleProp values for version one are not related to the SampleProp values for version two.

  • Upgrading a Web Part property.

    For example, in a Web Part that uses a property named SampleProp, the valid values for SampleProp in version one are 1, 2, or 3, the valid values for SampleProp in version two are 4, 5, or 6, and the SampleProp values in version one directly correspond to the SampleProp values in version two.

Renaming or repurposing a Web Part property can significantly increase the complexity of your upgrade logic. For best results, avoid renaming or repurposing Web Part properties.

Using the AfterDeserialize() Method

In most cases, you should use the WebPart.AfterDeserialize() method to implement your upgrade code. By overriding the WebPart.AfterDeserialize() method, you can run your choice of code to modify the data of your Web Part. The AfterDeserialize() method has the following two advantages over the OnInit() method:

  • The Web Part is fully upgraded to a known, consistent state before any other Web Part code is run.
  • AfterDeserialize() is run when the object model deserializes the Web Part if there is no HTTP context and the Web Part does not receive events such as OnInit().

For more information about the WebPart.AfterDeserialize() method, see the AfterDeserialize Method.

Handling a Deleted Property

When you work with multiple versions of a Web Part, one or more properties frequently are removed from different versions of the Web Part. Because elements that the assembly cannot recognize are added to the UnknownXmlElements collection, you must write code to iterate through the collection and remove the unknown elements. The following code sample shows how to do this:

// Override the AfterDeserialize method to remove any items in
// the UnknownXmlElementCollection within the myWebPart NamespaceUri
public override void AfterDeserialize()
{
try
{
bool itemsRemoved = false;
if (UnknownXmlElements.Count>0)
{
XmlElement element;
int index = UnknownXmlElements.Count - 1;

// Loop from the end of the collection so we don't need to
// update the index based on whether or not an item was
// deleted.
while (index >= 0)
{
element = UnknownXmlElements[index];

// Check if the element is within the namespace.
// Do a case-insensitive match to be consistent with the
// XmlSerializer.
if (element.NamespaceURI == "MyWebPartNamespace")
{
// If in the namespace remove it from the collection.
UnknownXmlElements.RemoveAt(index);
itemsRemoved = true;
}
--index;
}
}
if (itemsRemoved)
{
SaveProperties = true;
}
}
catch
{
// handle or ignore errors
}
}

Advanced Example

You can implement an advanced version of the preceding method that only targets deleted properties and runs only if the Web Part is not already upgraded. The following code sample shows how to do this in an example where one shared property and one personal property were deleted from the new version of a Web Part:

private int _sharedPropertiesVersion=1;
private int _personalPropertiesVersion=1;

public override void AfterDeserialize()
{
    // This is v2 of the Web Part
    const int currentVersion = 2;

    if (SharedPropertiesVersion < currentVersion || PersonalPropertiesVersion < currentVersion)
    {
        // The caller of AfterDeserialize catches and ignores all exceptions so
        // a try/catch is technically not required here, but having one makes it
        // clearer what is going to happen if an exception is thrown in this code.
        try
        {
            int i = 0;
            bool itemsRemoved = false;
            XmlElement element;
            
            while (i < UnknownXmlElements.Count)
            {
                element = UnknownXmlElements[i];
                if (element.NamespaceURI == "MyWebPartNamespace")
                {
                    switch (element.LocalName)
                    {
                        case "DeletedSharedProperty":
                        {
                            UnknownXmlElements.Remove(element);

                            // Signals shared property removal was handled
                            SharedPropertiesVersion = currentVersion;

                            itemsRemoved = true;
                            break;
                        }
                        case "DeletedPersonalProperty":
                        {
                            UnknownXmlElements.Remove(element);

                            // Signals personal property removal was handled
                            PersonalPropertiesVersion = currentVersion;

                            itemsRemoved = true;
                            break;
                        }
                    }
                }
                
                i++;
            }

            if (itemsRemoved)
            {
                SaveProperties = true;
            }
        }
        catch
        {
            // ignore upgrade errors
        }
    }
}

// The Shared and Personal properties can be upgraded independently of
// one another. Therefore, if one or more of the properties deleted is a Shared
// property, we need a SharedPropertiesVersion property to track when
// it gets upgraded. If only a Personal property is deleted, then just a
// PersonalPropertiesVersion property is necessary.
[WebPartStorage(Storage.Shared), DefaultValue(1)]
public int SharedPropertiesVersion
{
    get {return _sharedPropertiesVersion;}
    set {_sharedPropertiesVersion = value;}
}

// The Shared and Personal properties can be upgraded independently of
// one another. Therefore, if one or more of the properties deleted is a Personal
// property, you need a PersonalPropertiesVersion property to track when
// it gets upgraded. If only a Shared property is deleted, then just a
// SharedPropertiesVersion property is necessary.
[WebPartStorage(Storage.Personal), DefaultValue(1)]
public int PersonalPropertiesVersion
{
    get {return _personalPropertiesVersion;}
    set {_personalPropertiesVersion= value;}
}

Changing the Storage Attribute of a Property

If you need to change the Storage attribute of a property from personal to shared or from shared to personal, you can use the AfterDeserialize() method. If you change the Storage attribute of a property from Personal to Shared, discard the property with the personal Storage attribute. You do not need to persist the property with the personal Storage attribute, and if you transfer values from a personal property to a shared property, you violate user privacy by making each user's personal data visible to all users. The following example shows how to use the AfterDeserialize() method to discard the property with the personal Storage attribute:

// The original property has a Storage attribute of Storage.Personal.
[XmlRootAttribute(Namespace = "MyWebPartNamespace")]
class MyWebPart
{

    //Personal User property.
    [XmlElement("MyTextPersonal", IsNullable=false)]
    [WebPartStorageAttribute (Storage = Storage.Personal)]
    [DefaultValue(null)]
    public string MyText
    {
        get;
        set;
    }
}

// To change the property to shared the Storage is changed to Storage.Shared
// and the XmlElementName is given the new name "MyTextShared".
[XmlRootAttribute(Namespace = "MyWebPartNamespace")]
class MyWebPart
{

    //Shared User property.
    [XmlElement("MyTextShared", IsNullable=false)]
    [WebPartStorageAttribute (Storage = Storage.Shared)]
    [DefaultValue(null)]
    public string MyText
    {
        get;
        set;
    }
}

// If the old property value should be discarded the AfterDeserialize() method can be implemented.
public override void AfterDeserialize()
{
    if (UnknownXxlElements.Count > 0)
    {
        XmlElement element;
        int index = UnknownXmlElements.Count - 1;

        while (index >= 0)
        {
            element = UnknownXmlElements[index];

            // check if the element is in the WebPart's
            // namespace and has the old property name.
            if (element.NamespaceURI == "MyWebPartNamespace"
             && element.LocalName == "MyTextPersonal")
            {

                // Remove the element from the UnknownXml
                UnknownXmlElements.RemoveAt(index);
                SaveProperties = true;
                break;
            }

            --index;
        }
    }
}

Conclusion

Upgrading a Web Part can present interesting challenges. However, with .NET assembly redirection and careful use of the AfterDeserialize() method, you can successfully implement upgrade code that handles adding, deleting, upgrading, renaming, or repurposing properties and values for your Web Parts.