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:  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.
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.
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: 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: 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”
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: Now if your a colleague, you can visit your ‘MySite’ and view the aggregated activities of your colleagues: 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:
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
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: 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: 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: 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: 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.
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:  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: 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:
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?
I’ve been playing around with VS 2010 beta 2, I’ve got it installed in one of my development VM’s which is also running the SharePoint application that I’ve been working on for the past few months. This SharePoint application is using the 3.5 SP1 version of the .NET framework and we haven’t yet migrated the solution and projects to VS 2010, they are all in the 2008 format. I bring this point up, because the profiling tools in VS 2010 don’t need a solution to be open in order to profile an application. If your not familiar with the profiling features in VS 2010 then you should take a read of both the Profiler team blog post and also the post from John Robbins (debugging and profiling god). The first step to be able to profile any .net application is: 1. Open the visual studio 2010 command prompt 2. Run the following command: vsperfclrenv /globalsampleon 3. Restart IIS 4. Now from VS you can select the Profiler –> Attach/Detach option from the Analyze menu option 5. Now you can run through your application and the profiling information will be captured. 6. Once your done you’ll be taken to the main overview screen: 7. Now you can drill into the hot paths etc … The blog posts I’ve listed above can help you drill into this in more detail. 8. To turn off the profiling switch just run vsperfclrenv /globaloff and restart IIS The best bit is, if you have debugging symbols all the features will work, that is you can right click on the hot paths and the offending line of code will be shown and highlighted. So you don’t need to convert your project to VS 2010 to get this great profiling feature, it works with previous versions of the .NET framework.
Nice message after installing Visual Studio 2010 Beta 2: Everything is OK? … must be an error :)
I came across a rather interesting problem the other day (interesting enough to blog anyway). We have a development server that we are building some SharePoint web parts on, all of the developers have local admin rights to the box. We asked a normal user to have a look at some functionality that should be available to them, but when they browsed to the site they got a 403 forbidden error message. However when the developer requested that very same page it rendered fine. The weird thing was that if the original user (not the dev) then requested the page once again, it all rendered fine. We had a look in the SharePoint Logs and found that the assembly in the bin folder was denying access to the non developer, but once a dev hit the box the assembly was loaded fine and stayed in memory until the app pool was recycled. So the solution was to grant all users access to the bin directory. This wouldn’t be an issue in production because we will eventually GAC deploy these assemblies. But I did think it was a fun little exercise to track down the root cause.
Windows 7 contains a really nice tool called the Problem Steps Recorder: It can be found via the Windows 7 start menu by typing ‘problem step’  Once it’s running you get the following UI:  From here you can record all the clicks and keystrokes that your undertaking, you can even add comments to parts of your display. It finally saves the file as a zip file, which contains a .mht file that can be viewed in your browser. This output file will list things like version numbers of applications running as well as a range of screenshots of each major activity recorded. Now this is all well and good for sorting your mum’s computer problems, but as an IT pro who has had to document some awfully boring processes, I really think this tool will help me the next time I need to document the install of some software, or some mundane configuration change.
I’ve been working on a project that involves implementing a blogging system inside SharePoint, the out of the box SharePoint blogging system was unsuitable and the Community Kit for SharePoint didn’t solve our issues. So the system we developed was lightly based on the open source .NET Blog Engine which includes comprehensive support for the MetaWebBlog API. This API allows a interaction with a blogging system so that blog posts can be added, deleted and updated, but it also provides provisions for managing things like categories and comments. The main motivation for supporting the MetaWebBlog API is Windows Live Writer (WLW) this great tool really makes publishing to a blog a simple task. So I happily added the MetaWebBlog API and quickly found that windows live writer doesn’t support windows authentication. WLW supports a nice feature called Really Simple Discovery (RSD) where it can just be pointed to a blog’s home page and it can get all the configuration needs. By not supporting windows authentication we lost the auto discovery features. So I could still enable anonymous access to the MetaWebBlog.axd and wlwriter.xml (a file that tells WLW what capabilities your blog has), so now with some manual steps in WLW I could select the MetaWebBlog API and give it the url to use. My next problem was that my blogging system was all based around windows authentication. Every user has a blog that is based around their network credentials, I needed my metawebblog API implementation to validate the user to the windows network: With the following P/Invoke definitions: [DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
Now in all of my metawebblog API calls I could use a method like:
string username = string.Empty;
string password = input.Password;
string domain = string.Empty;
if (input.UserName.Contains(\\))
{
string[] usernameParts = input.UserName.Split('\\');
if (usernameParts.Length > 1)
{
domain = usernameParts[0];
username = usernameParts[1];
}
}
else
{
username = input.UserName;
}
IntPtr tokenHandle = IntPtr.Zero;
bool returnValue = LogonUser(username, domain, password,3, 0, ref tokenHandle);
if (returnValue == false)
{
//error not a valid user .. return
throw new MetaWeblogException("11", "User authentication failed");
}
if (tokenHandle != IntPtr.Zero)
CloseHandle(tokenHandle);
This code will return an invalid authentication message to WLW if the user doesn’t provide the correct domain, username and password (you could easily remove the need for a domain, I needed to keep it).
I’d like to say that its all good but really end users won’t go to the effort of following steps to configure WLW, the RSD is a killer feature in making web blogs accessible to end users. Fingers crossed that WLW will support windows authentication in a future version.
We’ve all probably worked on projects in the last 5 years or so that have involved reworking applications that were once built in MS access, in my case it was an access system that stored important safety testing information that was captured in an engineering workshop. It was originally designed and built by a mechanical engineer, who didn’t know much about database design. The system was redeveloped for a number of reasons including: - The data couldn’t be shared, it was locked away on a PC in the workshop, no analysis of the data could be performed.
- It didn’t scale well, only one user at a time could access it.
There are lots of other reasons why MS access shouldn’t be used for this type of information, but my point was that Access is a pretty poor tool for critical business information because it was difficult for the business to access this information. I think most people would share this view. Lets compare a couple of scenarios with SharePoint as the tool, so all the information would be stored in a list: - The data can be shared by webservices and RSS, with effort.
- It can scale, multiple users can access it at the same time.
But is the business data in a SharePoint list really easier to work with? Can it: - Be used in SQL Server analysis cube?
- Easily used in Reporting Services?
- Joined with other business data to see correlations?
- Perform complex real world queries?
- Do you really want to model your business data based on the limitations of SharePoint?
The answer is no. I still think that business data needs to live in a system designed for business data, i.e. A database: SQL Server. From here it can be queried, joined and more importantly shared, whether that be back into SharePoint or any other tool that supports a database (Reporting Services, Performance Point, Analysis Services etc). So now that brings me back to my comparison with MS Access, we are now doing lots of work moving systems away from MS Access, will we be doing the same thing in 5 years time, moving our SharePoint lists away from SharePoint? I think SharePoint lists have their place, no question, but not for line of business data. Keep the SP lists for trivial data that is not important to the overall operation of your business.
With the upcoming SharePoint conference releasing information about SharePoint 2010, it won’t be too long before everyone will be looking at the product. Interestingly SharePoint 2010 is 64 bit only, this will have an impact on developers using Virtual PC 2007, which only supports 32 bit guest OS’s. However windows 7 and Server 2008 R2 supports boot to VHD, this provides the ability to create a single VHD file that can booted. Once you’ve booted up the VHD your computer has access to all of it’s CPU cores, which is a major win. I’ve read that the performance hit of virtualising the file system to write to the VHD is around 5%, so it’s barely noticeable. Most importantly you can boot into a 64 bit OS where SharePoint 2010 can be installed. To recap, the advantages of boot to VHD: - Can run 64 bit machines
- Access to all CPU cores
- Still keep portability by way of VHD files
Disadvantages - Can’t multitask with the primary OS, the VHD OS is the primary OS
- Need to upgrade to windows 7 or server 2008 R2 (Not really a disadvantage, but might be an issue in corporate environments)
|