Home | Blog | Screencasts | Projects
# Wednesday, February 24, 2010

One of the nice things about SharePoint 2010 is that the developers did not seal the classes of the common web parts, so we can now create our own web parts that derive and extent the functionality. I thought it might be fun to play with the search refiner web part that I blogged about recently, so I took the standard search refiner and plugged in the excellent tag cloud implementation from codeplex.

 

So instead of rendering the normal list of refiners with counts against them, it instead uses the count to determine the tag cloud size:

VisualTagCloudRefiner

 

 

The code for creating this is below:


public class VisualRefiner : Microsoft.Office.Server.Search.WebControls.RefinementWebPart
    {
        protected override void Render(System.Web.UI.HtmlTextWriter writer)
        {
            var xmlDoc = this._RefinementManager.GetRefinementXml();

            var filters = xmlDoc.GetElementsByTagName("Filter");

            Dictionary tags = new Dictionary();
            foreach (XmlNode filter in filters)
            {
                var valueNode = filter.SelectSingleNode("Value");
                var countNode = filter.SelectSingleNode("Count");
                if (countNode != null && valueNode != null)
                {
                    if (!string.IsNullOrEmpty(countNode.InnerText))
                    {
                        int tagCount;

                        if (int.TryParse(countNode.InnerText, out tagCount))
                        {
                            if (!tags.ContainsKey(valueNode.InnerText))
                            {
                                tags.Add(valueNode.InnerText, tagCount);
                            }
                        }
                    }
                }
            }

	    //TODO: Make the url useable
            var tagCloud = new TagCloud(tags, 
		new TagCloudGenerationRules { Order = TagCloudOrder.Random, TagUrlFormatString = "/pages/search.aspx" });

            writer.Write(tagCloud);
            
        }
        
    }




I’m not saying that this view adds any more value than the standard refiner view, in the screenshot above it actually looks pretty poor.

The interesting part about the web part is the use of the refinement manager, it returns the results of the refinement process in xml form. This is really powerful, it means that we could potentially create some really creative visuals around the search results. It will be interesting to watch the community to see what types of variations come out.

Wednesday, February 24, 2010 7:46:00 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
code | SharePoint 2010
# Saturday, February 20, 2010

If you’ve ever built a custom protocol handler for MOSS before you may have in the past used the object model to create the content source, since there is no way in Central Admin to do this.

 

Something like:

 

SearchContext context = SearchContext.GetCurrent(spSite);

Content content = new Content(context);

CustomContentSource source = (CustomContentSource)content.ContentSources.Create( ...
source.Update();

But the SearchContext in SharePoint 2010 is marked as obsolete, which makes total sense when you consider that the SSP is no more, it’s been replaced by service applications.

 

The good news is that we can use Powershell to create the custom content source:

 

> $sapp = Get-SPEnterpriseSearchServiceApplication –Identity “Search Service Application”

>New-SPEnterpriseSearchCrawlContentSource –SearchApplication $sapp –Name “Custom Source Name” –Type Custom –StartAddress  protocol://servername –CrawlPriority Normal –MaxPageEnumerationDepth 1 –MaxSiteEnumerationDepth 1

 

 

So much easier …

 

Also when you register your custom protocol handler for SharePoint 2010, remember that the registry hive location now has the number ‘14.0’ in its path:

 

HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Office  Server/14.0/Search/Setup/ProtocolHandlers

 

I still need to have a good play the new BCS stuff in SharePoint 2010, but looking at this post by Todd Baginski I have a feeling that we might have a few more tricks up our sleeves before we go down the custom protocol handler or FAST pipeline route.

Saturday, February 20, 2010 9:36:00 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
Search | SharePoint 2010

In MOSS 2007 we could use the codeplex faceted search solution to provide what is now called ‘Search Refiners’ in SharePoint 2010.

 

By default the standard refiner will look like this:

 

image

 

The really nice thing about the refiner is that it will only show a value if a search result is returned for it, so a user will never be faced with clicking on an option and have it return zero results.

But those of us who are familiar with the old faceted search solution will know that the web part displays a count of the results, well the SharePoint 2010 refiner can as well:

 

image

Notice the subtle counts next to the metadata property.

 

This can be achieved by adding the following XML attribute to the refiners configuration xml:

 

Find the <Category>  element that you wish to display counts for and add:   ShowCounts=”Count”

Saturday, February 20, 2010 9:21:00 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [2] - Trackback
Search | SharePoint 2010
# Monday, November 23, 2009

One of the more interesting features of SharePoint 2010 is the activity feed, it’s kind of like the news feed in Facebook a whole range of system wide events are collated in a users my site, these can include events generated by colleagues of a user. I really think this is cool and we will see lots of applications make use of this feature. Even in a business environment the concept of a status feed is useful, SharePoint 2010 provides what seems like a nice enterprise wide service for collecting these events.

 

I thought it might be fun to have a play with some of the activity feed API’s to create something like a Twitter web part for SharePoint 2010, just to be upfront, this code isn’t really useable in a real sense, its just me playing around.

First I’ve created a visual web part for the user to enter a tweet:

 

image

 

Now if your a colleague, you can visit your ‘MySite’ and view the aggregated activities of your colleagues:

 

image

 

Here you can see that the user ‘administrator’ (this person’s colleague) has ‘tweeted’ something.

 

The SharePoint team have made this whole status/activity feed quite extensible, the first step is to define your own activity application

Setup:

	private ActivityApplication actApplication = null;
	private ActivityManager actMgr = null;
	private UserProfile currentUserProfile = null;
	private ActivityType tweetActivityType = null;
	private ActivityTemplate tweetActTemplate = null;

	private void SetupActivities()
         {
            Microsoft.Office.Server.UserProfiles.UserProfileManager pm = new UserProfileManager(SPServiceContext.Current);
            currentUserProfile = pm.GetUserProfile(Page.User.Identity.Name);
            actMgr = new ActivityManager(currentUserProfile);
            tweetActivityType = null;

            if (actMgr.PrepareToAllowSchemaChanges())
            {
                if (actMgr.ActivityApplications["SP2010Twitter"] == null)
                {

                    actApplication = actMgr.ActivityApplications.Create("SP2010Twitter");
                    actApplication.Commit();
                }
                else
                {
                    actApplication = actMgr.ActivityApplications["SP2010Twitter"];
                }

                tweetActivityType = actApplication.ActivityTypes["SP2010Tweeting"];
                if (tweetActivityType == null)
                {
                    tweetActivityType = actApplication.ActivityTypes.Create("SP2010Tweeting");
                    tweetActivityType.ActivityTypeNameLocStringName = "ActivityName";
                    tweetActivityType.ActivityTypeNameLocStringResourceFile = "SP2010Twitter";
                    tweetActivityType.IsPublished = true;
                    tweetActivityType.IsConsolidated = true;
                    tweetActivityType.AllowRollup = true;
                    tweetActivityType.Commit();
                }

                tweetActTemplate = tweetActivityType.ActivityTemplates[ActivityTemplatesCollection.CreateKey(false)];
                if (tweetActTemplate == null)
                {
                    tweetActTemplate = tweetActivityType.ActivityTemplates.Create(false);
                    tweetActTemplate.TitleFormatLocStringResourceFile = "SP2010Twitter";
                    tweetActTemplate.TitleFormatLocStringName = "Activity_Created";
                    tweetActTemplate.Commit();
                }
            }
            else
            {
                //not a profile admin

                var activities = actMgr.GetActivitiesForMe();
                foreach (var act in activities)
                {
                    if (act.Name == "SP2010Twitter")
                    {
                        actID = act.ActivityTypeId;
                    }
                }
            }
            
            

        }

The code above needs to be run once, to setup the application and to define the templates etc.

Creating an event:

	public ActivityEvent GenerateActivityEvent(string tweet, long activityId)
	{
            Entity owner = new MinimalPerson(CurrentUserProfile).CreateEntity(ActManager);
            Entity publisher = new MinimalPerson(CurrentUserProfile).CreateEntity(ActManager);
            
            ActivityEvent activityEvent = ActivityEvent.CreateActivityEvent(ActManager, activityId, owner, publisher);
            activityEvent.Name = "SP2010Twitter";
            activityEvent.ItemPrivacy = (int)Privacy.Public;
            activityEvent.Owner = owner;
            activityEvent.Publisher = publisher;            
            
            activityEvent.Value = tweet;
            activityEvent.Commit();

            
            return activityEvent;
        }

Publishing the event to the user's colleagues:

 

	public void MulticastPublishedEvents()
	{
            if (activityEvents.Count == 0)
                return;

            List publishers = new List();
            foreach (ActivityEvent activityEvent in activityEvents)
            {
                if (!publishers.Contains(activityEvent.Owner.Id))
                    publishers.Add(activityEvent.Owner.Id);
            }

            Dictionary owners;
            Dictionary> colleaguesOfOwners;
            ActivityFeedGatherer.GetUsersColleaguesAndRights(ActManager, publishers, out owners, out colleaguesOfOwners);

            Dictionary> eventsPerOwner;
            ActivityFeedGatherer.MulticastActivityEvents(ActManager, activityEvents, colleaguesOfOwners, out eventsPerOwner);

            List eventsToMulticast;
            ActivityFeedGatherer.CollectActivityEventsToConsolidate(eventsPerOwner, out eventsToMulticast);
            WriteEvents(eventsToMulticast);
	}

	private void WriteEvents(List events)
	{
            int startIndex = 0;
            while (startIndex + ActManager.MaxEventsPerBatch < events.Count)
            {
                ActivityFeedGatherer.BatchWriteActivityEvents(events, startIndex, ActManager.MaxEventsPerBatch);
                startIndex += ActManager.MaxEventsPerBatch;
            }
            ActivityFeedGatherer.BatchWriteActivityEvents(events, startIndex, events.Count - startIndex);
        }
            

Publishing an event is a little more involved and this code was drawn heavily from the SDK sample found here.

The main classes that get used here are:

 

ActivityApplication

An activity application is simply the name of the application, the ‘NewsFeed Settings’ on the my site gives the end user the ability to participate with your application:

 

 image

 

ActivityManager

Simple manager class that gets initialised with the current user profile, this class gets passed around to the other classes that create events.

 

ActivityType

This class provides us with the ability to actually define the activity, things like the time to live, is rolled up? and the template to use. This class also requires the developer to define the resources file that will be used to display the activity name.

 

ActivityTemplate

The display of the activity event gets managed by this class, you need to specify a resources file and key that contains the elements to create the display text. This class and associated resources file controls the UI presented to the user.

 

ActivityEvent

This is the actual event, here we define the publisher and other things like the item privacy level, it’s possible to create public and private events.

 

ActivityFeedGatherer

The gatherer is probably the least intuitive class, it provides the ability to batch write the events, this is handy because often we will want to publish the events to all of the colleagues of the current user.

 

Resources File:

Like I mentioned above, all of the templates and UI elements must be in a resx file. This file must be deployed to:

 

{SharePointRoot}\Resources\SP2010Twitter.resx

 

And will contain something like (note how the ActivityName and Activity_Created are used in the code above):

 

<data name="ActivityName" xml:space="preserve">
    <value>SP2010Twitter</value>
</data>
<data name="Activity_Created" xml:space="preserve">
    <value>{Publisher} tweeted: {Value}</value>
</data>

 

Some of the Default Template Variables:

{Link}

{Value}

{Publisher}

{Size}

 

So above I made use of the {Value} variable and populated the value property of the event object with the tweet value. I could change the resx definition which would change the way the event is shown to the user.

 

Problems:

 

I’ve come across a few little problems, firstly it seems like the user creating the event needs to be a profile admin, even a call to ActivityEvent.CreateActivityEvent seems to require the profile admin property. Unless I’m missing something, I hope this changes in the RTM environment.

 

You can download the sample code here

Monday, November 23, 2009 4:24:00 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [1] - Trackback
code | SharePoint 2010
# Sunday, November 22, 2009

I’ve been playing around with the new document set feature of SharePoint 2010, the idea is that you can group a bunch of documents or digital assets and control the metadata for all of them in one location.

When I first saw the demo around the document sets it reminded me of a piece of work I did for a client, they had a legacy system which would group a number of digital assets into a single entity and the search would return this one single entity if the search keyword was found in any of the documents. At the time we came to the conclusion that MOSS wasn’t a suitable replacement because of the way the search subsystem worked. Even with a custom protocol handler applying the correct metadata to each document/asset, at the point you start trying to invoke the various IFilters (doc, pdf, ppt etc) to index the contents of the documents you needed hand off a discrete url with the file extension of the document (the IFilters are invoked based on the  url), what would result was a disjointed search experience because the documents would be displayed outside of the set.

 

A document set is displayed in the document library as a single entity:

 

DocSet

 

When you select the document set, the ribbon changes so you can manage it or you can select the edit properties to manage the common meta-data:

 

docset2

 

The next thing to look at is the search experience, performing a search for a term that is in one of the documents of the set returns:

 

image

 

In this case I searched for a keyword that was specific to a document, the search results returned a just the single document, no reference to a document set.

 

Searching for a keyword that is contained in both the document set and document returns:

 

image

 

In this case we get the document set as a result and the document, rather than just a document set, the user has no idea that the document (Internet SharePoint Governance Plan) was apart of a document set.

 

I’m not saying the document set feature is useless, I think it has some valid scenario’s, I just would have liked it to work a little differently, but I guess the SharePoint team hasn’t changed the search engine under the covers and are constrained in the same way I was. I think the FAST pipeline offers some other alternatives, but I haven’t had a chance to look at that yet.

Sunday, November 22, 2009 8:02:00 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
SharePoint 2010
# Wednesday, November 18, 2009

Ever since the sneak peak developer videos were released months ago I’ve been wondering about the implementation of SharePoint 2010’s visual web parts. If your not sure what I mean, with SP 2010 and Visual Studio 2010 you can now create a web part with a design time experience, so you can drag and drop controls etc:

 

VS2010Designer

 

Now that the beta is upon us I can finally take a look under the covers at a visual web part, the default project structure looks like:

VS2010WebPart

 

The project contains a number of new items: Features and Package both relate to the deployment features of Visual Studio 2010 in that you can create SharePoint solutions (aka the Package) and features which can be activated, visual studio will automatically deploy and activate your web parts using this solution and features.

Next we move on to the VisualWebPart1.cs file which contains the secret sauce:

 

    public class VisualWebPart1 : WebPart
    {
        

        // Visual Studio might automatically update this path when you change the Visual Web Part project item.
        private const string _ascxPath = @"~/_CONTROLTEMPLATES/TestVisualWebPart/VisualWebPart1/VisualWebPart1UserControl.ascx";

        public VisualWebPart1()
        {
        }

        protected override void CreateChildControls()
        {
            Control control = this.Page.LoadControl(_ascxPath);
            Controls.Add(control);
            base.CreateChildControls();
        }

        protected override void RenderContents(HtmlTextWriter writer)
        {
            base.RenderContents(writer);
        }

 

As you can see, the web part still derives from WebPart, no special VisualWebPart base class, nothing special going on here.

In fact we are using the same techniques and approach that would have worked in Visual Studio 2008, the only difference now is that Visual Studio 2010 has better tooling support for SharePoint 2010 and will deploy the ascx file automatically for us to the _CONTROLTEMPLATES directory as part of the solution. 

 

There are still a few things a web part developer should know, lets look at the case where we want to expose some custom properties on a web part that we want a user to configure via the web interface:

 

      [System.Web.UI.WebControls.WebParts.WebBrowsable(true),
       System.Web.UI.WebControls.WebParts.WebDisplayName("Custom Prop"),
       System.Web.UI.WebControls.WebParts.WebDescription(""),
       System.Web.UI.WebControls.WebParts.Personalizable(
       System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared),
       System.ComponentModel.Category("Settings"),
       System.ComponentModel.DefaultValue("")
       ]        
        public string CustomProp
        {
            get { return customProp; }
            set { customProp = value; }
        }

 

Now if we put this property and attributes on the VisualWebPart1UserControl (in VisualWebPart1UserControl.ascx.cs) we will find that the custom property builder won’t appear (the web interface that lets us set a value to this property).

 

We have to add the custom property on the VisualWebPart1 class (in VisualWebPart1.cs) :

 

    public class VisualWebPart1 : WebPart
    {      

        // Visual Studio might automatically update this path when you change the Visual Web Part project item.
        private const string _ascxPath = @"~/_CONTROLTEMPLATES/TestVisualWebPart/VisualWebPart1/VisualWebPart1UserControl.ascx";

        protected override void CreateChildControls()
        {
            Control control = this.Page.LoadControl(_ascxPath);
            Controls.Add(control);
            base.CreateChildControls();
        }

        protected override void RenderContents(HtmlTextWriter writer)
        {
            base.RenderContents(writer);
        }

        [System.Web.UI.WebControls.WebParts.WebBrowsable(true),
       System.Web.UI.WebControls.WebParts.WebDisplayName("Custom Prop"),
       System.Web.UI.WebControls.WebParts.WebDescription(""),
       System.Web.UI.WebControls.WebParts.Personalizable(
       System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared),
       System.ComponentModel.Category("Settings"),
       System.ComponentModel.DefaultValue("")
       ]
        public string CustomProp
        {
            get { return customProp; }
            set { customProp = value; }
        }
    }

Now we get our custom property builder:

WebPartSettings

 

Lets assume that we want to pass the user entered value to the Visual component (the usercontrol) we now need to change the visual studio generated code to cast the user control to our visual user control class, rather than the more generic base Control:

       protected override void CreateChildControls()
        {
            //user control is of type VisualWebPart1UserControl and defined with private scope
            userControl = (VisualWebPart1UserControl)this.Page.LoadControl(_ascxPath);
            Controls.Add(control);
            base.CreateChildControls();
        }

 

From here we can set properties on the userControl variable as normal.

 

The same principles apply to web part connections, so the connection points need to be defined on the web part class (not the usercontrol). Visual Studio will take care of deploying the ascx file which is still a big win.

 

I doubt an experienced web part developer would have any issues, but I wonder how many new web part developers will not know that they can make there web parts configurable and connectable given that they will likely only use the Visual Studio lie presented to them?

Wednesday, November 18, 2009 10:30:00 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
code | SharePoint 2010
Statistics
Total Posts: 190
This Year: 3
This Month: 0
This Week: 0
Comments: 38