Welcome to MSDN Blogs Sign in | Join | Help

One of the things I've wanted to do on this blog is give people a clearer view into how we actually produce developer documentation for SharePoint; the processes we use, the decisions we make, and what factors influence those decisions. (The Office client developer docs team have done several great posts over on the Office Client Developer Content blog around similar topics.) With that in mind, when someone asked me the other day what we actually do with those comments people enter for our content on MSDN, I figured it was worth answering here.

 

The short answer is: we collect, classify, and prioritize them for action, pretty much like a software product team would triage bugs entered against their product.

 

And here's the long answer:

 

For each comment, when we initially receive it we assign a status, which is further broken down by a sub-status value as well. This enables us to separate out and focus on the comments for which some action can be taken, and group those comments in terms of what’s being requested. The status and sub-status values we use include:

·         New

This is the status automatically assigned to comments that haven’t been classified yet.

·         In progress

Comments that contain actionable content. The comment has been initially triaged and prioritized.

o   Code sample

User wants a code sample to illustrate what the topic covers.

o   Incorrect/Incomplete info

User points out that there is incorrect or incomplete information in the topic. (Not surprisingly, these are usually our first priority to fix.)

o   More Info

User is asking for more information to be added to the topic.

o   More Research Needed

We need to do more research on the comment before we can accurately categorize it.

o   New Topic Requested

User is asking for a new topic, separate from the topic where they added the comment.

·         No Action

o   Negative

Comments that are negative but not specific enough to be actionable.

o   Noise

Unintelligible comments, such as random typing.

o   Positive

Positive comment for which no action is necessary.

·         Off-topic

Comments that don't apply to the topic itself, but might be actionable at another level.

o   MSDN Issue

Comment refers to a wider MSDN issue.

o   Noise

Comments that are intelligible, but have nothing to do with the topic ("I like cheese, do you like cheese?")

o   Product Issue

Comment refers to a product issue, such as whether the user likes the feature the topic describes, rather than the documentation itself.

o   SDK Issue

Comment refers to a larger issue concerning our content set, that might be present in the topic.

·         Spam

Exactly what you'd expect: foreign financial scams, discounted prescriptions, offers to refinance our property at 1 Microsoft Way.

·         Fixed

The actionable content of the comment has been addressed. For sub-status, we leave the value at what is was when the comment was In progress.

 

Once we’ve got them classified, we can properly triage those comments that require action, prioritizing them along with the other content feedback we get from newsgroups, blogs, MVPs, MSDN Community Content comments, internal and external partner groups, and various other channels.

 

Now that I’ve outlined what we do with the comments users enter, let’s take a quick look at what those comments actually say. So what are people telling us about the WSS SDK? Taking a look at the comments entered for the WSS 3.0 SDK since its publication online, here's how they currently break out, according to status:

WSS SDK all comments by status

As you can see, 46% of the comments actually require no action beyond the initial triage. Of the actionable comments, about two-thirds are still in some state of being worked on. Like I said, these comments are prioritized in with all the content suggestions we receive from other channels as well, so that our documentation team is working on the highest priority work items, regardless of the path it took to get to us.

 

Looking a little closer at the comments still in progress, here's what you get when you break down those comments by sub-status:

 

WSS SDK In Progress comments by sub-status 

 

Users are overwhelmingly (78% of the total) asking for more information (52%) or code samples (26%). This is in line with feedback we’ve been getting through other channels, and is something we’ve been actively working to address.

 

When you include the comments we've fixed, here are the classifications for all actionable comments we've received to date:

 

WSS SDK all comments by sub-status 

 

One other metric we periodically look at is the trend of the positive and negative comments over time. I'm really happy to see that as we periodically republish the SDK with new, expanded, and revised material, positive comments have tended to increase at a faster rate than negative ones:

 

WSS SDK no action comments over time 

 

We do some additional data analysis on comments, but that’s the basics. So next time you’re looking at developer documentation on MSDN, take a moment to enter a comment to let us know how we’re doing. I guarantee we’re on the other end, listening.

 

(cross-posted from Enterprise Search Blog

Are you trying to think of ways to make your Search Server site more interactive and graphical? You can use Federation as a way to enhance the functionality of your search result pages.

The Federated Search Web Part makes it possible to display more than results from OpenSearch (1.0/1.1) sites on your search results page. The Search Server 2008 SDK explains how to include results from SQL Server database queries and search sites that do not expose XML feeds (such as Atom or RSS). In both scenarios, you do this by means of a "connector," a light-weight interface that sends queries to a given location or database, places the results into a structured XML document, and sends that XML to a Federated Search Web Part.


This sample demonstrates how you can extend the connector concept to Web service requests other than basic search queries. It shows how to use a connector to pass an address string to Microsoft's MapPoint Web service in order to obtain the latitude and longitude coordinates for that address. Once you have those coordinates, displaying a Microsoft Virtual Earth map requires only the addition of some Javascript to your Federated Location definition file. See this Codeplex project for the sample code (along with a sample Federated location definition file) and an explanation of how to implement it on your own site.

This sample is part of the Search Community Toolkit.

 

 Jim Crowley
Programming Writer
Microsoft Corp

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);