Tag Archive for 'API'

MOSS FullTextSqlQuery API: Little Known Flags on Managed Properties

I just wrapped up a custom implementation of a faceted search web part that uses the MOSS Full Text Sql Query API. During the development, I ran into issues querying certain multi-valued Managed Properties, specifically Skills and Interests. In my query, I was using the CONTAINS predicate like such:

SELECT
UserProfile_GUID, PreferredName, JobTitle, Department, WorkPhone, OfficeNumber,
AboutMe, PictureURL, WorkEmail, WebSite, Path, HitHighlightedSummary,
HitHighlightedProperties, Responsibility, Skills, SipAddress
FROM scope()
WHERE freetext(defaultproperties,'+trent')
AND ("scope" = 'People')
AND CONTAINS(Skills, '"numchucks"')
AND CONTAINS(Skills, '"rockstar"')

Use of the CONTAINS predicate was purely due to my need to query multi-valued properties. If you are not querying a multi-valued property, you may simply use the ‘=’ or ‘LIKE’ predicates.

It turns out that the CONTAINS predicate will only work against managed properties that have been enabled as FullTextQueriable. I am not aware of a way to enable a property in the SSP Search Settings, so this has to be done using the API. I ended up including the following method as part of a Web Application scoped Feature Receiver to ensure certain Managed Properties were ‘FullTextQueriable’.

    using Microsoft.Office.Server;
    using Microsoft.Office.Server.Search.Administration;
 
    private void EnsureFullTextQueriableManagedProperties(ServerContext serverContext, params string[] managedPropertyNames)
    {
        var schema = new Schema(SearchContext.GetContext(serverContext));
        foreach (ManagedProperty managedProperty in schema.AllManagedProperties)
        {
            if (!managedPropertyNames.Contains(managedProperty.Name))
                continue;
 
            if (managedProperty.FullTextQueriable)
                continue;
 
            try
            {
                managedProperty.FullTextQueriable = true;
                managedProperty.Update();
                Log.Info(m => m("Successfully set managed property {0} to be FullTextQueriable", managedProperty.Name));
            }
            catch (Exception e)
            {
                Log.Error(m => m("Error updating managed property {0}", managedProperty.Name), e);
            }
        }
    }

So on the same note of Managed Property flags not visible in the SSP Search Settings, you may also want to know about the Retrievable flag. This flag prevents a Managed Property’s value from being returned if specified as a column in the SELECT statement.

The following table lists most of the out of the box Managed Properties and their default FullTextQueriable, HasMultipleValues, and Retrievable flag values.

Name FullTextQueriable HasMultipleValues Retrievable
AboutMe     X
Account X   X
AccountName     X
AssignedTo X   X
Assistant     X
Author X   X
Authority     X
BestBetKeywords X X X
Birthday     X
CachedPath     X
CategoryNavigationUrl X   X
CollapsingStatus     X
Company     X
contentclass X   X
ContentSource     X
ContentType X   X
Created     X
CreatedBy X   X
DataSource     X
DatePictureTaken     X
Department     X
Description X   X
DisplayTitle     X
DocComments X   X
DocKeywords X X X
DocSignature     X
DocSubject X   X
DottedLine     X
EMail X   X
EndDate     X
Fax     X
FileExtension     X
Filename X   X
FirstName X   X
FollowAllAnchor X    
HighConfidenceDisplayProperty1     X
HighConfidenceDisplayProperty10     X
HighConfidenceDisplayProperty11     X
HighConfidenceDisplayProperty12     X
HighConfidenceDisplayProperty13     X
HighConfidenceDisplayProperty14     X
HighConfidenceDisplayProperty15     X
HighConfidenceDisplayProperty2     X
HighConfidenceDisplayProperty3     X
HighConfidenceDisplayProperty4     X
HighConfidenceDisplayProperty5     X
HighConfidenceDisplayProperty6     X
HighConfidenceDisplayProperty7     X
HighConfidenceDisplayProperty8     X
HighConfidenceDisplayProperty9     X
HighConfidenceImageURL     X
HighConfidenceMatching   X X
HighConfidenceResultType     X
HireDate     X
HitHighlightedProperties     X
HitHighlightedSummary     X
HomePhone     X
Interests   X X
IsDocument     X
JobTitle     X
Keywords X   X
LastModifiedTime     X
LastName X   X
Location     X
Manager     X
MemberOf     X
Memberships X X X
MobilePhone     X
ModifiedBy X   X
MySiteWizard     X
NLCodePage      
Notes X   X
objectid     X
OfficeNumber     X
OWS_URL X    
PastProjects   X X
Path X   X
Peers   X X
PersonalSpace     X
PictureHeight X   X
PictureSize X   X
PictureThumbnailURL     X
PictureURL     X
PictureWidth X   X
PreferredName     X
Priority X   X
ProxyAddresses     X
PublicSiteRedirect     X
Purpose X   X
Rank     X
RankDetail     X
Responsibilities   X X
Schools   X X
SID     X
SipAddress     X
Site      
SiteName     X
SiteTitle     X
Size     X
Skills   X X
StartDate     X
Status X   X
Title X   X
UrlDepth X    
UserName     X
UserProfile_GUID     X
WebId     X
WebSite     X
WorkAddress X   X
WorkCity X   X
WorkCountry X   X
WorkEmail     X
WorkId     X
WorkPhone     X
WorkState X   X
WorkZip X   X

SharePoint: The Wicked SPWeb.Properties PropertyBag

*** This article is a must read for any developer reading or writing Web (Site) properties ***

If you haven’t already noticed, there are two different API properties on the SPWeb class – AllProperties (a Hashtable) and Properties (a PropertyBag). Apparently the AllProperties is meant to replace Properties, but Properties was left in place for backwards compatibility. Here’s where things get wicked …

The unconventional PropertyBag data type stores its keys in all lowercase, thus not supporting case-sensitive keys, while the conventional Hashtable does support case-sensitive keys. On top of that, while entries added to Properties get propagated to AllProperties with a lowercase key, entries added to AllProperties do not get propagated to Properties. So this what Microsoft gives us developer peons to work with. This is why consultants like myself stay employed.

If you are working with property entries that only your custom application will be reading and writing from, simply always use AllProperties and you will be good to go. However I often find myself having to work with properties that are read or written by SharePoint internals, and SharePoint itself is very inconsistent in its interaction with Web (Site) properties, probably due to its evolving nature being a rather old product as far as software is concerned.

So the best solution that I have come up with to date is to add your entries to both API properties. This ensures the entry will be present in both collections, and also ensures the key will have the correct case in AllProperties. I have found though in order to add to both, the order of the update API calls is important – you must first update the SPWeb object followed by updating the SPWeb.Properties PropertyBag. Performing the updates in the reverse order prevents an entry with a case-sensitive key from being added to AllProperties, and only the lowercase-keyed entry will be exposed in AllProperties. Now that you are probably thoroughly confused, here is the code:

// Add a property entry
web.Properties[key] = value;
web.AllProperties[key] = value;
web.Update();
web.Properties.Update();
 
// Remove a property entry
web.AllProperties.Remove(key);
web.Properties[key] = null;
web.Update();
web.Properties.Update();

Take a look, or straight up copy, my SharePoint utility class that abstracts away Web property interactions.

Generic Feature Receiver for Features that Deploy WebParts

Greg Galipeau wrote a thorough post on cleaning up your Web Part Gallery that is the basis for this post. To summarize, Greg shares a Feature Receiver he uses as a generic Feature Receiver for all his WebPart Features that simply removes the WebPart having the same name as the Feature’s DisplayName when the Feature is deactivated.

There are a couple of issues with this though:

  1. It assumes that the WebPart has the same name as the Feature
  2. It limits the Feature to deploying just a single WebPart

An alternative approach would be to inspect the Feature’s element definitions and extract the exact WebPart names that were deployed, thus resolving the two issues noted above.

Below is the alternative Feature Receiver (it extends the BaseFeatureReceiver I mentioned in my previous post):

    public class WebPartFeatureReceiver : BaseFeatureReceiver<SPSite>
    {
        public override void FeatureDeactivating(SPSite site, SPFeatureReceiverProperties properties)
        {
            base.FeatureDeactivating(site, properties);
 
            var elements = properties.Definition.GetElementDefinitions(CultureInfo.CurrentCulture);
            var webparts = elements.Cast<SPElementDefinition>()
                .SelectMany(e => e.XmlDefinition.ChildNodes.Cast<XmlElement>()
                    .Where(n => n.Name.Equals("File"))
                    .Select(n => n.Attributes["Url"].Value)
                    )
                .ToList();
 
            var rootWeb = site.RootWeb;
            var wpGallery = rootWeb.Lists["Web Part Gallery"];
 
            var galleryItems = wpGallery.Items.Cast<SPListItem>()
                .Where(li => webparts.Contains(li.File.Name))
                .ToList();
 
            for (int i = galleryItems.Count - 1; i >= 0; i--)
            {
                var item = galleryItems[i];
                item.Delete();
            }
        }
    }

(SPWeb)properties.Feature.Parent no more … a handy Feature Receiver Base Class

If you are writing SharePoint feature receivers often, you will find there are several lines of code common to just about every feature receiver:

  1. A cast of properties.Feature.Parent to the appropriate scope (either SPWeb, SPSite, SPWebApplication, or SPFarm)
  2. Empty implementations of FeatureInstalled and FeatureUninstalling

Provided below is an abstract generic class that now serves as the base class for every feature receiver I write. It eliminates the redundant code by performing the cast for you based on the classes generic type parameter and also goes ahead and implements all the abstract methods, making it so that you to only need to override the actual methods you plan to handle. Your extending feature receiver now will look like the following:

    public class MyFeatureReceiver : BaseFeatureReceiver<SPWeb>
    {
        public override void FeatureDeactivating(SPWeb scope, SPFeatureReceiverProperties properties)
        {
            // Pointless, but what the heck
            base.FeatureDeactivating(scope, properties);
 
            // Now do something with your scope object, in this case an SPWeb
            ...
        }
    }

And now, the code for the BaseFeatureReceiver:

    /// <summary>
    /// Base class that makes the feature scope available as an argument to the
    /// FeatureActivated and FeatureDeactivating methods
    /// </summary>
    /// <typeparam name="T">
    /// T is the class type of the scope of the feature (SPFarm, SPWebApplication, SPSite, or SPWeb)
    /// </typeparam>
    public abstract class BaseFeatureReceiver<T> : SPFeatureReceiver
    {
        public sealed override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            FeatureActivated((T)properties.Feature.Parent, properties);
        }
 
        public virtual void FeatureActivated(T scope, SPFeatureReceiverProperties properties) { }
 
        public sealed override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            FeatureDeactivating((T)properties.Feature.Parent, properties);
        }
 
        public virtual void FeatureDeactivating(T scope, SPFeatureReceiverProperties properties) { }
 
        public override void FeatureInstalled(SPFeatureReceiverProperties properties) { }
        public override void FeatureUninstalling(SPFeatureReceiverProperties properties) { }
    }

Hope you find this helpful.

SharePoint Dependency Injection Approach

I just finished delivering a SharePoint workflow application in which I developed what could be called a start to framework that I would like to reuse in my next project.  The framework currently has many features such as:

  • An “Object-List Mapping” infrastructure complete with lazy initialization of objects and collections, converters, and caching
  • A list item event broker
  • Several handy web controls
  • Logging
  • State management
  • Plenty of Utility Helpers

Each major feature is defined with an interface, and there is a single context class scoped to an SPWeb that holds references to each feature provider.  For this last application, and I imagine I will do this for most applications going forward, I extended the context class to hold additional references to application-specific business logic helpers and data access interfaces.

In order to accomplish dependency injection of the feature providers, there is a context factory interface that is responsible for constructing and initializing contexts.   I implemented a static ”default” context factory that when called upon, it first looks in the SPWeb property bag to see if a predefined custom context factory type has been specified.  If it finds one, it constructs a new instance of the custom factory and returns the context from the custom factory’s CreateContext method.

    public static class SPAppContextFactory
    {
        public const string CustomSPAppContextFactoryKey = "custom_context_factory_key";
 
        public static void RegisterSPAppContextFactory<T>(this SPWeb web) where T : ISPAppContextFactory
        {
            web.AllProperties[CustomSPAppContextFactoryKey] = typeof(T).AssemblyQualifiedName;
            web.Update();
        }
 
        public static void UnregisterSPAppContextFactory(this SPWeb web)
        {
            if (web.Properties.ContainsKey(CustomSPAppContextFactoryKey))
            {
                web.AllProperties[CustomSPAppContextFactoryKey] = null;
                web.Update();
            }
        }
 
        public static SPAppContext CreateSPAppContext(this SPWeb web)
        {
            // First check Web Properties if an alternate factory is specified
            if (web.AllProperties.ContainsKey(CustomSPAppContextFactoryKey))
            {
                string customFactoryTypeName = web.AllProperties[CustomSPAppContextFactoryKey];
                Type customFactoryType = Type.GetType(customFactoryTypeName, true);
                ISPAppContextFactory customFactory = (ISPAppContextFactory)Activator.CreateInstance(customFactoryType);
                return customFactory.CreateContext(web);
            }
 
            // Create the Default Context
            return new SPAppContext(web);
        }

You can now register your applications custom context factory in the FeatureActivated method of a feature receiver. You’ll need to include the namespace of your static factory class in order to make the extension methods available, and then simply call the RegisterSPAppContextFactory extension method .

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            var web = (SPWeb)properties.Feature.Parent;
            web.RegisterSPAppContextFactory<MyCustomContextFactoryClass>();
        }
 
        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            var web = (SPWeb)properties.Feature.Parent;
            web.UnregisterSPAppContextFactory();
        }

The context factory now serves as a single place where all dependencies are wired up, and the framework is now able to instantiate custom contexts without being aware of any feature provider implementation.

SharePoint Security Validation and Unsafe Updates

Seeing this? The security validation for this page is invalid. I am sure this error has good intentions, but it is showing its face far too often. Hristo Pavlov has written a good explanation on AllowUnsafeUpdates here.

If you read my previous post and are attempting to update list items as the System User, you will likely run into this problem.  Setting AllowUnsafeUpdates to true for the duration of the update has allowed me to get around this exception.  Here is the helper method I am using to accomplish this:

        public static void DoUnsafeUpdate(SPWeb web, Action action)
        {
            bool allowUnsafeUpdates = web.AllowUnsafeUpdates;
            web.AllowUnsafeUpdates = true;
            action();
            web.AllowUnsafeUpdates = allowUnsafeUpdates;
        }

Couple this with the helper methods in my previous post, and you now can do both together:

        public static void DoUnsafeUpdateAsSystemUser(SPWeb web, SPWebAction action)
        {
            DoAsSystemUser(web, delegate(SPWeb systemWeb)
            {
                DoUnsafeUpdate(systemWeb, delegate()
                {
                    action(systemWeb);
                });
            });
        }