How to: Create a Content Class That Can Be Backed Up and Restored

Introduction

If you have a custom content component that you want to be included in Windows SharePoint Server 3.0 backups and restores, you must represent the component with a class that implements the IBackupRestore interface. This post explains how to do that. There is a complete example following the procedures. This post assumes that you are familiar with my earlier post: Programming with the Windows SharePoint Services Backup/Restore Object Model.

Note: Unless explicitly stated otherwise, all classes and interfaces referred to in this post are in the Microsoft.SharePoint.Administration.Backup namespace. Classes that you will create are in bold pink. Also, this post refers at times to the “reference topic” for this or that method/property for supplementary information. As of the date of this posting these reference topics in the most recent versions of the WSS SDK (1.3), in either download or MSDN form, did not yet contain this new material. Look for another update soon. The additional information is not necessary for using this post as a learning exercise or implementing the example it contains.

Your class does not have to derive from Microsoft.SharePoint.Administration.SPPersistedObject but if your content is a database, we recommend that you derive your class from either Microsoft.SharePoint.Administration.SPDatabase or from Microsoft.SharePoint.Administration.SPContentDatabase. Both of the latter classes are derived from SPPersistedObject and both implement IBackupRestore. Therefore, you will have default implementations of members of IBackupRestore that you can use when appropriate.

You can create as many types of IBackupRestore classes you want and, if you want, they can be nested as a tree of component classes. But the highest class in any such tree must derive (directly or indirectly) from SPPersistedObject object and must be a child of Microsoft.SharePoint.Administration.SPFarm. If your content class is not a child of any other custom content class, it must derive (directly or indirectly) from SPPersistedObject object and must be a child of SPFarm.

If your class derives from a class that already implements IBackupRestore object (whether or not it derives from SPPersistedObject), and you want to replace an inherited implementation of an IBackupRestore member, your class declaration should explicitly reference IBackupRestore like this:

[C#]

public class MyClass : SPPersistedObject, IBackupRestore

Your "override" of any IBackupRestore member should explicitly include "IBackupRestore" in the member name and it should not include the public keyword. The following is an example:

[C#]

UInt64 IBackupRestore.DiskSizeRequired { ... }

Alternatively, if the implementation of the member in the parent class used the virtual or override keywords, you can use the override keyword in your implementation like this:

[C#]

public override UInt64 DiskSizeRequired { ... }

Warning: Do not hide the inherited member implementation by redeclaring the member either with or without the new keyword ([new] public UInt64 DiskSizeRequired { ... }). In the procedure below, the member signatures are written as they would be for a class that does not derive from a class that already implements IBackupRestore. Be sure to change them to the required pattern if your class does derive from such a parent.

If your class derives from SPPersistedObject, let the Id and Name properties of that class serve as the implementation of the IBackupRestore.Id and IBackupRestore.Name properties. You may override the properties, but do not create a second implementation of either of them. Your class should have just one Name and one Id property.

Procedures

 

To Implement the Members of IBackupRestore

1. Begin a new Class project in Visual Studio.

2. Add a reference to Windows SharePoint Services to your Visual Studio project and add using statements for the Microsoft.SharePoint.Administration and Microsoft.SharePoint.Administration.Backup namespaces to your class file.

3. If your class does not derive from SPPersistedObject, implement the IBackupRestore.Name property. This will serve as the name of the content component in the UI of stsadm.exe, the Central Administration application and the UI of any custom backup and restore application. In most cases you implement the property by creating a private field for the name value and implement the public property as a wrapper around the field. For information on possible variant implementations, see the reference topic for the property.

[C#]

private String name;

public String Name

{

get {return name;}

set {name = value;}

}

4. If your class does not derive from SPPersistedObject, implement the IBackupRestore.Id property. In most cases, you implement the property by creating a private field for the name value and implement the public property as a wrapper around the field. For information on possible variant implementations, see the reference topic for the property.

[C#]

private Guid id;

public Guid Id

{

get {return id;}

set {id = value;}

}

5. Implement the IBackupRestore.DiskSizeRequired property. If your class is just a container for some child IBackupRestore classes, the property should return 0. Otherwise, the property should calculate the size of the content. (Include the size of any non-IBackupRestore child objects, but do not include the size of any child IBackupRestore objects. They each have their own IBackupRestore.DiskSizeRequired property and Windows SharePoint Server 3.0 will add those values in automatically.) The following example sums the sizes of all the files whose paths are contained in a collection called FrontEndFilePaths.

[C#]

public UInt64 DiskSizeRequired

{

    get

    {

    UInt64 total = 0;

        List<FileInfo> FrontEndFiles = new List<FileInfo>(NUMBER_OF_FILES_TO_BACK_UP);

       

        foreach (String path in FrontEndFilePaths)

        {

            FileInfo file = new FileInfo(path);

            FrontEndFiles.Add(file);

        }

       

        foreach (FileInfo file in FrontEndFiles)

        {

            total = total + (UInt64)file.Length;

        }

      

        return total;

    }

}

6. Implement the IBackupRestore.CanSelectForBackup property. If users should never be able to backup objects of your class independently of a backup of the parent object, the get accessor should return false. If users should always be able to select any object of your class for independent backup, the get accessor should return true. In either case, the set accessor should be an empty pair of braces "{ }". If some objects of your class can be backed up independently of their parent, but some cannot be, implement the property as a wrapper around a private System.Boolean field.

7. Implement the IBackupRestore.CanSelectForRestore property. If users should never be able to restore objects of your custom component class independently of a restoration of the parent object, the get accessor should return false. If users should always be able to select any object of your class for independent restoration, the get accessor should return true. In either case, the set accessor should be an empty pair of braces "{ }". If some objects of your class can be restored independently of their parent, but some cannot be, implement the property as a wrapper around a private System.Boolean field.

8. Implement the IBackupRestore.CanRenameOnRestore property. If users should never be able to restore objects of your custom component class to a new location, the get accessor should return false. If users should be able to migrate any object of your class, the get accessor should return true. If objects of your class can sometimes be migrated, but not always, implement the property as a wrapper around a private System.Boolean field.

9. Implement the IBackupRestore.AddBackupObjects method.

a. Your implementation code should begin by throwing an exception if there is no valid parent to which the component can be added.

b. Use the SPBackupRestoreObject.AddChild method to add your component to the tree of objects that the backup or restore operation will process.

c. Use the SPBackupRestoreInformation.SetParameter method to specify a type name and description of the component that can be used by the UI of backup/restore applications.

d. If the component has child IBackupRestore objects, your implementation should iterate through them and recursively call the IBackupRestore.AddBackupObjects method of each child.

e. See the reference topic for the IBackupRestore.AddBackupObjects method for more ideas about implementations of it.

The following example code assumes that your content class has a ChildContentCollection of child IBackupRestore objects. If your class has more than one type of child component, you may have separate collections for each type and iterate through each collection.

[C#]

public void AddBackupObjects(SPBackupRestoreObject parent)

{

    if (parent == null)

    {

        throw new ArgumentNullException("parent");

    }

 

    SPBackupRestoreObject self = parent.AddChild(this);

  self.Information.SetParameter(SPBackupRestoreObject.SPTypeName, this.GetType());

    self.Information.SetParameter(SPBackupRestoreObject.SPDescription,

    "Description of custom content component");

 

    foreach (ChildContent child in ChildContentCollection)

    {

        IBackupRestore childIBR = child as IBackupRestore;

        childIBR.AddBackupObjects(self);

    }

}

10. Implement the IBackupRestore.OnAbort method. It should always return true. In most cases it should do nothing more, but see the reference topic for IBackupRestore.OnAbort for information about exceptions to this general rule.

11. Implement the IBackupRestore.OnPrepareBackup method. At a minimum, you should use the SPBackupRestoreInformation.SetParameter method to specify a name for the content object. Beyond that, few generalizations can be made. See the reference topic for IBackupRestore.OnPrepareBackup for more information. The following example shows a minimal implementation of the method, which is often all that is needed.

[C#]

public Boolean OnPrepareBackup(Object sender, SPBackupInformation args)

{

    if (args == null)

    }

        throw new ArgumentNullException("args");

    }

    args.SetParameter(SPBackupRestoreObject.SPName, this.Name);

    return true;

}

12. Implement the IBackupRestore.OnBackup method. If your content class has no content outside of any IBackupRestore child objects it may have, your implementation should simply set the SPBackupRestoreInformation.CurrentProgess to a value that approximately represents the percentage of the total backup operation time that is consumed by the IBackupRestore.OnBackup and IBackupRestore.OnPrepareBackup methods. It should then return true as seen in the following example. Do not call the IBackupRestore.OnBackup method of any IBackupRestore child objects.

[C#]

public Boolean OnBackup(Object sender, SPBackupInformation args)

{

    if (args == null)

    {

        throw new ArgumentNullException("args");

    }

    args.CurrentProgress = 50;

    return true;

}

If your class does have content outside of any IBackupRestore child objects it may have, your implementation must copy this content to args.SPBackupRestoreInformation.Location and return false if the copy fails. You should include logic to backup any child objects that do not implement IBackupRestore, but you should not explicitly backup any child objects that do implement IBackupRestore. They will be backed up by their own IBackupRestore.OnBackup method, which the runtime will call. You should not call the IBackupRestore.OnBackup methods of the child objects in your own code. The following example shows the overall structure of a substantive implementation of IBackupRestore.OnBackup.

[C#]

public Boolean OnBackup(Object sender, SPBackupInformation args)

{

    if (args == null)

    {

        throw new ArgumentNullException("args");

    }

    args.CurrentProgress = 50;

    Boolean successSignal = true;

 

    // TODO: Implement copying your content to args.Location

    // If the copy fails, set successSignal to false.

 

    return successSignal;

}

13. Implement the IBackupRestore.OnBackupComplete method. At a minimum, your implementation should set SPBackupRestoreInformation.CurrentProgess to 100 percent and return true as shown in the following example. This is typically all that is required. For information about other work your implementation may need to perform, see the reference topic for IBackupRestore.OnBackupComplete.

[C#]

public Boolean OnBackupComplete(Object sender, SPBackupInformation args)

{

    if (args == null)

    {

        throw new ArgumentNullException("args");

    }

    args.CurrentProgress = 100;

    return true;

}

14. Implement the IBackupRestore.OnPreRestore method. In most situations, a restoration operation requires no preparation and your implementation of IBackupRestore.OnPreRestore should just return true. For information about other work your implementation may need to perform, see the reference topic for IBackupRestore.OnPreRestore.

15. Implement the IBackupRestore.OnRestore method.

· If your content class can be migrated, your code should check to see what the restore method is and call SPBackupRestoreInformation.Rename if the method is New.

· If your content class has no content outside of any IBackupRestore child objects it may have, your implementation should simply set the SPBackupRestoreInformation.CurrentProgess to a value that approximately represents the percentage of the total restore operation time that is consumed by the IBackupRestore.OnRestore and the IBackupRestore.OnPreRestore methods. It should then return true as seen in the following example. Do not call the IBackupRestore.OnRestore method of any IBackupRestore child objects.

[C#]

public Boolean OnRestore(Object sender, SPRestoreInformation args)

{

    if (args == null)

    {

        throw new ArgumentNullException("args");

    }

    if (args.RestoreMethod == SPRestoreMethodType.New)

    {

        args.Rename();

    }

    args.CurrentProgress = 50;

    return true;

}

· If your class does have content outside of any IBackupRestore child objects it may have, your implementation must copy this content to the restoration destination. Return false, if for any reason the copy of content fails.

The following example shows the overall structure of a substantive implementation of IBackupRestore.OnRestore:

[C#]

public Boolean OnRestore(Object sender, SPRestoreInformation args)

{

    if (args == null)

    {

        throw new ArgumentNullException("args");

    }

    if (args.RestoreMethod == SPRestoreMethodType.New)

    {

        args.Rename();

    }

    args.CurrentProgress = 50;

    Boolean successSignal = true;

 

    // TODO: Implement copying your content to the destination.

    // If the copy fails, set successSignal to false.

 

    return successSignal;

}

16. Implement the IBackupRestore.OnPostRestore method. At a minimum, your implementation should set SPBackupRestoreInformation.CurrentProgess to 100 percent and return true as shown in the following example. This is typically all that is required. For information about other work your implementation may need to perform, see the reference topic for IBackupRestore.OnPostRestore.

[C#]

public Boolean OnPostRestore(Object sender, SPRestoreInformation args)

{

    if (args == null)

    {

        throw new ArgumentNullException("args");

    }

    args.CurrentProgress = 100;

    return true;

}

 

 

Add other Members to Your Class As Needed

17. Add fields, properties, and helper methods as needed to complete your class. As you work, keep these points in mind:

· Use fields and properties to hold child content objects.

· If your class derives from Microsoft.SharePoint.Administration.SPPersistedObject, then the declaration of fields that you want to persist in the configuration database must be preceded with the [Persisted] attribute. However, you can only mark the following types of fields in this way: primitive types such as strings, integers, and GUIDs; other Microsoft.SharePoint.Administration.SPPersistedObject objects or Microsoft.SharePoint.Administration.SPAutoserializingObject objects; or collections of any of the above. For example, the class cannot have a System.IO.FileInfo field marked with the [Persisted] attribute. If the data you would like to persist is not of a persistable class, use a persistable substitute. The sample implementation above of the IBackupRestore.DiskSizeRequired property envisions a class that persists a collection of file names and uses them to create a temporary collection of System.IO.FileInfo objects at runtime.

· If your class can have multiple children of the same type, create a property or field of a collection type or other enumerable type to hold a collection of all children of a given type. This is particularly important if the child type itself implements IBackupRestore, because your implementation of the IBackupRestore.AddBackupObjects method should iterate through such children and call the IBackupRestore.AddBackupObjects method of each child. See the procedure step for implementing the IBackupRestore.AddBackupObjects method above, for more information.

18. Add constructors to your class to initialize its fields and properties as needed. If the class derives from SPPersistedObject, there must be at least one constructor that names the object and assigns it to a parent. Typically, such a constructor takes at least these two arguments:

· A String argument that will be the name of the content object.

· An SPPersistedObject argument that represents the parent of the content object.

This constructor must call the base constructor that takes the same two arguments. The following is an example:

[C#]

public MyContentComponent(String componentName, SPPersistedObject parent, SomeType someOtherArgument, ... )

                   : base(componentName, parent)

{

    somePrivateField = someOtherArgument;

    ...

}

You must pass Microsoft.SharePoint.Administration.SPFarm.Local as the parent when the content object is the top most object in a tree of custom IBackupRestore objects. If your custom component type is always the top most object, then leave out the SPPersistedObject argument and hard code a reference to SPFarm.Local in the call to the base constructor. The following is an example:

[C#]

public MyContentComponent(String componentName, SomeType someOtherArgument, ... )

                   : base(componentName, SPFarm.Local)

{

    somePrivateField = someOtherArgument;

    ...

}

If objects of your class always have the same name, you can leave out the String argument and hard code the name in the call to the base constructor. (If all objects of a given type have the same name, there should never be more than one child of that type for a given parent and; thus, no more than one object of that type on the entire farm if the object is a child of the farm.)

19. Compile your class project.

Warning: You must give the assembly a strong name and put the assembly in the General Assembly Cache (GAC).

 

 

To Create an Object of Your Class and Make it a Child of the Farm

20. Start a new console application project in Visual Studio.

21. Add a reference to the DLL of your custom component class to the project.

22. Add a using statement for Microsoft.SharePoint.Administration.

23. Add a using statement for the namespace that you used in your custom component class (or just use the same namespace in your console application).

24. Add to the Main method of your project a call to the constructor of your custom component class. If you created a hierarchy of custom types, call the constructor of the top most class.

25. If needed, precede the call to the constructor of the component with code that creates parameters for the constructor.

26. After the call to the constructor of your component, your code should call the component object's SPPersistedObject.Update method. The following is an example of what you should have in the Main method:

[C#]

MyContentComponent myContentObject = new MyContentComponent("component name", SPFarm.Local);

myContentObject.Update();

27. Compile and run the application.

28. In the Central Administration application navigate to Operations | Perform a Backup. Your object should appear as child of the farm on the Perform a Backup page.

Note: There is a sample console application for creating and deleting custom content objects in the Example section below.

 

Development Advice

The following tips may be helpful as you develop your custom content classes particularly because you will probably be creating objects and adding them to the farm multiple times as you work.

 

Points to Keep in Mind

1. If you need to delete your object from the configuration database, use SPPersistedObject.Delete.

2. An exception is thrown if you call obj.Update() and there is already an object of the same class as obj with the same SPPersistedObject.Name property value and the same parent in the configuration database. There is an overloaded version of SPPersistedObject.Update that may be preferable.

3. There is an example console application in the second Example section below that can be used to add or delete your custom objects from the configuration database.

4. Run iisreset at the command line after every recompile of your IBackupRestore class. You may need to reboot the server as well.

5. The various IBackupRestore.On* methods take either a SPBackupInformation parameter or a SPRestoreInformation parameter. You can use their members for debugging purposes. Particularly helpful is the SPBackupRestoreInformation.Log method.

 

Example

The following code implements a custom content component that represents a single Web.config file on a front end server. Replace the TestSite part of the file path in the constructor implementation with a directory name from your test server. The compiled assembly must be strong-named and installed in the GAC.

In the example following the class implementation, there is the code for a simple console application that will register the component as a child of the farm or delete it from the farm.

using System;

using System.IO;

using System.Collections.Generic;

using Microsoft.SharePoint.Administration;

using Microsoft.SharePoint.Administration.Backup;

 

namespace MyCompany.SharePoint.Administration

{

    public class CriticalFiles : SPPersistedObject, IBackupRestore

    {

 

        public CriticalFiles() { }

 

        public CriticalFiles(String componentName, SPPersistedObject parent)

                   : base(componentName, parent)

        {

            String pathOfFile = @"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS\TestSite\Web.config";

            FrontEndFilePaths.Add(pathOfFile);

        }

 

        [Persisted]

        private const Int32 NUMBER_OF_FILES_TO_BACK_UP = 1;

 

        [Persisted]

        private List<String> FrontEndFilePaths = new List<String>(NUMBER_OF_FILES_TO_BACK_UP);

 

        public Boolean CanSelectForBackup

        {

            get { return true; }

            set { }

        }

 

        public Boolean CanSelectForRestore

        {

            get { return true; }

            set { }

        }

 

        public Boolean CanRenameOnRestore

        {

            get { return false; }

        }

 

        public UInt64 DiskSizeRequired

        {

            get

            {

                UInt64 total = 0;

                List<FileInfo> FrontEndFiles = new List<FileInfo>(NUMBER_OF_FILES_TO_BACK_UP);

               

                foreach (String path in FrontEndFilePaths)

                {

                    FileInfo file = new FileInfo(path);

                    FrontEndFiles.Add(file);

                }

               

                foreach (FileInfo file in FrontEndFiles)

                {

                    total = total + (UInt64)file.Length;

                }

               

                return total;

            }

        }

 

        public void AddBackupObjects(SPBackupRestoreObject parent)

        {

            if (parent == null)

            {

                throw new ArgumentNullException("parent");

            }

 

            SPBackupRestoreObject self = parent.AddChild(this);

            self.Information.SetParameter(SPBackupRestoreObject.SPTypeName, this.GetType());

            self.Information.SetParameter(SPBackupRestoreObject.SPDescription, "The critical files on all front end servers.");

        }

 

        public Boolean OnAbort(Object sender, SPBackupRestoreInformation args)

        {

            return true;

        }

 

        public Boolean OnPrepareBackup(Object sender, SPBackupInformation args)

        {

            if (args == null)

            {

                throw new ArgumentNullException("args");

            }

            args.SetParameter(SPBackupRestoreObject.SPName, this.Name);

            return true;

        }

 

        public Boolean OnBackup(Object sender, SPBackupInformation args)

        {

            if (args == null)

            {

                throw new ArgumentNullException("args");

            }

           

            Boolean successSignal = true;

 

            foreach (String path in FrontEndFilePaths)

            {

                FileInfo file = new FileInfo(path);

       try

                {

                    String mappedFileName = args.GenerateFileMapping(file.Name);

                    file.CopyTo(args.Location + @"\" + mappedFileName, true);

                    args.Log(SPBackupRestoreLogSeverity.Verbose, "Backed up " + file.Name + " in (" + mappedFileName + ")");

                }

                catch (Exception e)

                {

                    args.Log(SPBackupRestoreLogSeverity.Verbose, file.Name + " not backed u " + e.Message);

                 successSignal = false;

                }

            }

 

            args.CurrentProgress = 50;

            return successSignal;

        }

 

        public Boolean OnBackupComplete(Object sender, SPBackupInformation args)

        {

            if (args == null)

            {

                throw new ArgumentNullException("args");

            }

            args.CurrentProgress = 100;

            return true;

        }

 

        public Boolean OnPreRestore(Object sender, SPRestoreInformation args)

        {

            if (args == null)

            {

                throw new ArgumentNullException("args");

            }

            return true;

        }

 

        public Boolean OnRestore(Object sender, SPRestoreInformation args)

        {

            if (args == null)

            {

                throw new ArgumentNullException("args");

            }

 

            // If the CriticalFiles object was deleted from the farm after it was

            // backed up, restore it to the configuration database.

         CriticalFiles cf = SPFarm.Local.GetChild<CriticalFiles>(this.Name);

            if (cf == null)

            {

                this.Update();

                args.Log(SPBackupRestoreLogSeverity.Verbose, this.Name + " added back to configuration database.");

            }

 

            Boolean successSignal = true;

 

            // TODO: The following loop restores files to the local server. If there are

            // multiple front end servers, your code must iterate through all of

            // SPFarm.Local.Servers and restore the same files to every server whose

            // Role property is SPServerRole.WebFrontEnd

 

            foreach (String path in FrontEndFilePaths)

            {

                FileInfo backupCopy = new FileInfo(path);

                String mappedFileName = args.ReverseFileMapping(backupCopy.Name);

                FileInfo file = new FileInfo(args.Location + @"\" + mappedFileName);

 

                try

                {

                    file.CopyTo(path, true);

                    args.Log(SPBackupRestoreLogSeverity.Verbose, "Restored " + backupCopy.Name);

                }

                catch (Exception e)

                {

                    args.Log(SPBackupRestoreLogSeverity.Verbose, file.Name + " not restored: " + e.Message);

                    successSignal = false;

                }

            }

           

            args.CurrentProgress = 50;

            return successSignal;

        }

       

        public Boolean OnPostRestore(Object sender, SPRestoreInformation args)

        {

            if (args == null)

            {

                throw new ArgumentNullException("args");

            }

 

            args.CurrentProgress = 100;

            return true;

        }

 

    }

}

Description

The following is a console application that will add or delete your content object from the configuration database.

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.SharePoint.Administration;

using Microsoft.SharePoint.Administration.Backup;

 

namespace MyCompany.SharePoint.Administration

{

    class Program

    {

        static void Main(string[] args)

        {

            CriticalFiles cf = SPFarm.Local.GetChild<CriticalFiles>("Critical Front End Files");

            if (cf == null)

            {

                Console.WriteLine("There is no CriticalFiles object in the configuration database.");

                Console.Write("Enter 'A' to add it. Press Return to do nothing:");

                String response = Console.ReadLine();

                if (response == "A")

                {

                    CriticalFiles myCriticalFiles = new CriticalFiles("Critical Front End Files", SPFarm.Local);

                    myCriticalFiles.Update();

                }

            }

    else

            {

                Console.WriteLine("There is a CriticalFiles object in the configuration database.");

                Console.Write("Enter 'D' to delete it. Press Return to do nothing:");

                String response = Console.ReadLine();

                if (response == "D")

                {

                    cf.Delete();

                }

            }

        }// end Main

    }// end Program

}

 

See Also

Programming with the Windows SharePoint Services Backup/Restore Object Model

How to: Programmatically Back Up Content

How to: Programmatically Restore Content