I volunteer for INETA as the user group mentor for the Midwest Region (IL, IN, and WI). One of the things I'm planning to do differently this year is to better help spread the word on my blog about events happening in the region, whether they're Microsoft or user group organized and hosted.
If your user group is hosting an event such as a code camp, send me a short write up and some photos and I'll make sure it gets published in the INETA newsletter.
Back to business ... Here's some info about some Azure and Visual Studio 2010 events that are coming up:
TechNet Events Present:
Windows Azure, Hyper-V and
Windows 7 Deployment - 8:30am – 12:00pm
Get the inside track on new tips, tools and technologies for IT pros. Join your TechNet Events team for a look at Windows Azure™ and learn the basics of this new online service computing platform. Next, we’ll explore how to build a great virtual environment with Windows Server® 2008 R2 and Hyper-V™ version 2.0. We’ll wrap this free, half-day of live learning with a tour of easy deployment strategies for Windows® 7.
TOPICS INCLUDE:
• The Next Wave: Windows Azure
• Hyper-V: Tools to Build the Ultimate Virtual Test Network
• Automating Your Windows 7 Deployment with MDT 2010
MSDN Events Presents:
Cloud Computing and Azure - 1:00pm – 4:30pm
Join your local MSDN Events as we take a deep dive into cloud computing and the Windows Azure Platform.
TOPICS INCLUDE:
• What is cloud computing?
• Running web and web service applications in the cloud
• Using the Windows Azure and local developer cloud fabric
• Getting started – tools, SDKs and accounts
• Writing applications for Windows Azure
MSDN Events Presents:
Visual Studio 2010, .NET Framework 4.0 and SharePoint Development - 1:00pm – 4:30pm
Join your local MSDN Events team for a lively tour our latest version of Visual Studio and the .NET Framework. We’ll start with an overview the IDE and explore how you can use the latest features to develop great applications in a flash. Next, we’ll look at the newest changes to the .NET Framework and how you can leverage them today. Finally, we end the day by looking at the new tools in Visual Studio that will supercharge your SharePoint development.
TOPICS INCLUDE:
• What’s new in Visual Studio 2010
• What’s new in .NET Framework 4.0
• SharePoint Development using Visual Studio 2010
I'll be speaking at the Central Illinois SharePoint User Group in Springfield, IL at 9:00AM on Thursday, January 21st 2010.
The topic will be Building Communications Enabled Applications with OCS and Exchange:
Office Communications Server 2007 R2 and Exchange 2010 enable the development of communication enabled applications more quickly and easily than ever before. In this session you will learn about the core features included in the new platform SDKs and see a demo application built around a retail enterprise that utilizes each of the scenarios the platform enables. You will also learn about opportunities for integrating OCS and Exchange into your SharePoint applications.
More details on location and registration areavailable at https://www.clicktoattend.com/invitation.aspx?code=144377
This week at TechEd Europe 2009, Microsoft officially launched Exchange Server 2010 and the Exchange Web Services Managed API 1.0 SDK. This API provides a managed wrapper around Exchange Web Services, eliminating the need to work directly with SOAP or with proxy classes generated when adding a web reference to your environment’s Exchange Web Services url.
The release version of EWSMA includes improved support for Exchange Web Services features such as:
- Free/Busy
- Autodiscover
- Timezones
- Impersonation
- Search
In this post, I’ll demonstrate using EWSMA to query the Exchange free/busy service to find a meeting time that is most suitable for a set of participants.
Referencing the Exchange Web Services Managed API 1.0 SDK
The Exchange Web Services Managed API 1.0 SDK is installed by default to C:\Program Files\Microsoft\Exchange\Web Services\1.0, you need to reference Microsoft.Exchange.WebServices.dll.
Improved Autodiscover
The Exchange Autodiscover service provides clients with a way to retrieve the url to use to connect to Exchange Web Services. For a majority of deployments, there is one url for Exchange Web Services. However, consider large, global Exchange deployments…the Autodiscover server can for example “auto discover” the most geographically appropriate web service endpoint for a client such as Outlook to connect to.
If you’ve worked with Exchange Web Services in the past, you know that autodiscover’ing the url for Exchange Web Services involved a lot of code, this is what it looks like now:
1: ExchangeService service = new ExchangeService();
2: service.AutodiscoverUrl(String.Format(
3: "{0}@{1}.com",
4: Environment.UserName,
5: Environment.UserDomainName));
Set up Availability Options
Before querying the free/busy service, you need to specify some options that the service will use to retrieve a list of meeting time suggestions – this is represented by a class called AvailabilityOptions.
1: AvailabilityOptions availabilityOptions = new AvailabilityOptions();
2: availabilityOptions.MeetingDuration = 60;
3: availabilityOptions.MaximumNonWorkHoursSuggestionsPerDay = 4;
4: availabilityOptions.MinimumSuggestionQuality = SuggestionQuality.Good;
5: availabilityOptions.RequestedFreeBusyView = FreeBusyViewType.FreeBusy;
Check out the SDK documentation to explore other options you can set when creating an instance of AvailabilityOptions. In this case, we’re looking for a 60 minute time slot, allowing some flexibility to schedule the meeting outside of working hours, setting the minimum suggestion quality to Good, and only requesting the FreeBusy view after querying the service.
Get Availability Results
First, create a List<AttendeeInfo> to store the list of contacts to include in the request to the free/busy service. In this case, you can see how to add different attendee types to the list, e.g. the organizer, a required attendee, and a room resource.
1: List<AttendeeInfo> attendees = new List<AttendeeInfo>();
2:
3: attendees.Add(
4: new AttendeeInfo()
5: {
6: SmtpAddress = "rl@fabrikam.com",
7: AttendeeType = MeetingAttendeeType.Organizer
8: });
9:
10: attendees.Add(
11: new AttendeeInfo()
12: {
13: SmtpAddress = "sc@fabrikam.com",
14: AttendeeType = MeetingAttendeeType.Required
15: });
16:
17: attendees.Add(
18: new AttendeeInfo()
19: {
20: SmtpAddress = "boardroom@fabrikam.com",
21: AttendeeType = MeetingAttendeeType.Room
22: });
With the attendee list set up, create an instance of GetUserAvailabilityResults to store the meeting suggestions returned by the free/busy service.
1: GetUserAvailabilityResults availabilityResults =
2: service.GetUserAvailability(
3: attendees,
4: new TimeWindow(DateTime.Now, DateTime.Now.AddDays(1)),
5: AvailabilityData.FreeBusyAndSuggestions,
6: availabilityOptions);
I want to point out a gotcha here. When AvailabilityOptions.RequestedFreeBusyView is set to FreeBusyViewType.FreeBusy, the time window for the call to GetUserAvailability needs to be at least 24 hours. I’m not sure of the reasoning for this but if its set to a shorter time window, the call sometimes throws an exception about an invalid time window.
Explore Suggested Meeting Times
You can now explore the suggested meeting times returned by the free/busy service.
The results are in the form of a List<Suggestion> where Suggestion describes the date and quality of the suggestion, and a collection of individual TimeSuggestions. A TimeSuggestion describes any conflicts at the suggested time, whether or not the suggestion occurs during working hours, the suggested time and quality of the meeting.
Unless you set a time window of more than 1 day, you’ll probably just want to grab availabilityResults.Suggestions[0] and pick the most suitable TimeSuggestion in availabilityResults.Suggestions[0].TimeSuggestions according to whatever criteria are suitable for your application.
Book the Meeting
It’s just as easy to book the appointment directly from your application. Assuming that suggestion is an instance of a TimeSuggestion that you want to book:
1: // Create the appointment and set its properties
2: Appointment appointment = new Appointment(service);
3: // Set the appointment title
4: appointment.Subject = "Team Meeting";
5: // Set the appointment start and end time
6: appointment.Start = suggestion.MeetingTime;
7: appointment.End = suggestion.MeetingTime.AddMinutes(availabilityOptions.MeetingDuration);
8: // Add the conference room as a meeting resource
9: appointment.Resources.Add("Board Room", boardroom@fabrikam.com);
10: // Set the meeting location to display name of the conference room
11: appointment.Location = "Board Room";
12: // Save the appointment
13: appointment.Save();
Summary
You’ve seen how easy it is to include Outlook-style functionality into your custom applications. This didn’t use to be as simple, unless you loved working with WebDAV or CDO, or writing some sweet asynchronous WebRequest code…
I spoke today at the Chicago SharePoint User Group about Building Public Facing SharePoint sites. Thanks to Michael and Asif for inviting me, I'm looking forward to doing this again in the future.
You can download the presentation slides and the source code for the sample site I demo'd here.
Thanks to everybody who attended and asked great questions.
I recently got a chance to help out on Gadfly, a twitter client that @SteveHolstad, @leeiroth, and @eklimcz from Clarity are working on. If you haven’t had a chance to check it out, I definitely recommend it, I’ve been waiting for a long while for a great web-based twitter client to show up!
Gadfly allows you to rate individual tweets from 1 to 5 stars. Using this ratings data, we started looking into building a recommendation engine that would suggest other tweeple that you might find interesting.
![1[1] 1[1]](http://blogs.claritycon.com/blogs/george_durzi/11_thumb_6D48B093.png)
How a Twitter Reputation Algorithm Needs to Work
As I was researching how I would build such a recommendation engine, @goodoldschu forwarded me an article titled How a Twitter Reputation Algorithm Needs to Work. The author does a great job of describing what he thinks should and should not matter in a twitter reputation algorithm:
Should Matter:
- Co-follower rate: when two people follow the same people, you can numerically represent how similar their tastes are. This is a crucial concept for most recommendation algorithms.
- What your followers follow, and what they tweet about: if you index the text of individual tweets, you can do some very interesting stuff such as clustering similar tweeple into groups, e.g. people who tweet about a certain technology.
Shouldn’t Matter:
- Follower counts
- When you started tweeting
- Frequency of tweets
Pretty straightforward… Your follower count, when you started tweeting, and how often you tweet don’t represent the quality of your tweets. These metrics have no place in a twitter recommendation algorithm.
Practical Challenges
There are some practical challenges, however, when trying to incorporate the “should matter” metrics into a twitter recommendation algorithm; I’ll explain those as they relate to Gadfly in particular.
Gadfly is a web based application build in Silverlight; it uses isolated storage as a virtual file system to store application data on your machine. We’re somewhat limited in how much data we can store in isolated storage, e.g. 1MB for an in-browser Silverlight application.
So why not store followers and tweet content in a central database? If storage space weren’t an issue, this would certainly be feasible. However, we didn’t want to get into the business of constantly querying follower and following lists and storing them in our database, let alone storing the content (raw or indexed) of tweets. There’s also the matter of how quickly we’d eat into the twitter API rate limit with these operations.
Enter Ratings
The best way to explain this is to use the good old Netflix movie ratings example.
Netflix allows users to rate movies that they have watched. When new users join Netflix and begin rating movies, you can compare their preferences to existing users that have rated those same movies. Based on this data, you can identify a set of existing users who are most “similar” to the new users and use these users’ ratings for other movies to fill in the gaps in the new user’s profile. For example, a recommendation engine can predict that you would give The Bourne Supremacy a rating of 4 stars and place it on your recommended list.
This describes the KNN (K-Nearest Neighbor) algorithm in its simplest form. This is the algorithm that Netflix uses (albeit with a ton of tweaks and optimizations) to recommend movies to users.
How does this apply to a twitter recommendation engine? Instead of rating movies, you rate tweets. Since you’re rating the same twitter user multiple times, we average out this rating to come up with a single rating. This gives us a single figure that represents a Gadfly user rating a twitter user. Over time, we’ll probably calculate the average based on more recent tweets, otherwise new ratings will have little to no effect on the calculation.
When other Gadfly users rate the same twitter user, we can compute how similar these users’ tastes are. We can then predict what rating users will give to twitter users that they haven’t rated. If the predicted rating is above an acceptable threshold that we set, we can recommend that twitter user as a Gadfly Pick.
Computing Similarity
The process that powers a KNN-based recommendation engine is always run in batch mode due to the computational intensity that is involved.
The first step is to calculate the similarity (or correlation) between every set of two users using a coefficient such as Pearson. This is a number between 0 and 1, with 0 meaning that the users are not similar at all, and 1 meaning that they are as similar as they can be.
The key here though is that you can’t measure correlation between two users unless they have some ratings in common – we define this parameter to the algorithm as the Minimum Number of Ratings in Common. Realizing that at first our data will be very sparse, we set this to 1 - we can tweak this as more Gadfly users enter ratings. We can increase this to make the engine more selective as we get more ratings data.
To the left is example of some of the ratings data, this is all sample data so the similarity numbers are artificially high.
Generating Picks
Now that the system has computed all possible combinations of similarity among users with a minimum number of ratings in common, the next step is to generate the Gadfly Picks for each user.
When computing the picks for a particular user, e.g. UserId 67 in the above sample data, the K from KNN refers to the number of most similar users that the algorithm will use to predict ratings for twitter users that User 67 hasn’t rated. If the predicted rating is greather than a specified threshold, we recommend that twitter user to User 67 and they appear in their Gadfly Picks timeline.
So if we’re using a value of K=3 and the above sample data to put together the Gadfly Picks for User 67, the top 3 similar users are 68, 71, and 74 with Pearson values of 1.0, 1.0, and 0.992583334. Let’s use those numbers to predict how User 67 would rate a particular twitter user, assuming the following information on how User 67’s top 3 most similar users have rated that twitter user:
| User | Similarity | Rating |
| 68 | 1 | 2 |
| 71 | 1 | 3 |
| 74 | 0.992583334 | 2 |
Predicted Rating = (2*1 + 3*1 + 2*0.992583334) / (1 + 1 + 0.992583334) =6.985166668 / 2.992583334 = ~2.33
The predicted rating should be pretty self explanatory. If the three Gadfly users that User 67 is most similar to gave this twitter user ratings of 2, 3, and 2, a predicted rating of 2.33 is right in line.
We currently have the value of Predicted Rating Threshold set to 3.5 – this user wouldn’t make the cut.
Right now we’re generating each user’s Gadfly Picks once a day, we can make this more frequent as usage increases. It will be very interesting to see how much we have to tweak the backend SQL when it has to deal with much larger datasets.
Limitations and Opportunities for Improvement
I’m excited to get this rolled out in the next couple of days, but I can already identify several areas which I want to work on improving after we iron out the initial kinks.
Twitter Users in a Search Timeline have a Twitter User Id = 0
As I understand it, the twitter search API hasn’t yet been integrated into twitter’s core API. The implication of this for us is that the Twitter User Id of a twitter user in a search timeline is always 0. We’re thus not able to relate that user to an actual twitter user and can’t use that piece of ratings data. Hopefully this will be a non-issue as the APIs continue to come together.
The Engine Recommends Me to Me
I thought it was funny when I saw myself as a Gadfly Pick, we need to tweak the algorithm so that I’m not recommended to myself!
Ability to Rate the Recommendations
The only way to truly measure the effectiveness of a recommendation algorithm is to compare the predicted rating to the actual rating provided by the user. The functionality is currently there to rate the recommended picks. We haven’t proved this out yet, but we suspect that the algorithm will automagically take care of things.
We’ll be rolling this out in the next day or two, I’m really interested in taking a look at the quality of the ratings as we get more realistic data.
I’ll be speaking at the Chicago SharePoint User Group’s monthly meeting on September 10th 2009. The meeting is scheduled from 1pm to 4pm at the Microsoft office in Downers Grove, IL.
Interested in seeing what it takes to build out a public facing site using SharePoint? My talk will cover areas such as:
- Publishing workflow
- Server and network topology of a public facing SharePoint site
- Organizing your SharePoint solutions
- Publishing site definitions
- Branding
- Packaging the site’s assets
- Configuring content deployment
- Setting up anonymous access
I’ll also go over some special considerations for public facing SharePoint sites such as page payload size, accessibility, and search engine optimization.
More details and registration information should be available soon on the CSPUG site, hope to see you there!
This one’s for you @SteveHolstad!
I was helping one of our teams ease the deployment of a Silverlight project into various server environments. The project contained a ServiceReferences.ClientConfig file to reference some WCF services in the solution. When deploying the project to a Development, Staging, or Production environment, the team had to:
- Un-XAP the Silverlight XAP
- Copy in an environment specific ServiceReferences.ClientConfig file (or edit)
- Re-XAP the Silverlight XAP
Here’s how to do this pretty easily with TFS Team Build.
I like to store environment specific settings in the source control branch that corresponds to that environment. In the Dev branch (which translates to our Development Integration environment), I created a version of ServiceReferences.ClientConfig with the appropriate environment specific service bindings and placed it at. $\MyTeamProject\Dev\Env\Config\Services\ServiceReferences.ClientConfig.
In the Team Project’s build definition, you can now tap into the BeforeCompile target and do the following:
<Target Name="BeforeCompile">
<CreateItem Include="$(SolutionRoot)\Env\Config\Services\ServiceReferences.ClientConfig">
<Output ItemName="ServiceReferences" TaskParameter="Include"/>
</CreateItem>
<Copy SourceFiles="@(ServiceReferences)"
DestinationFolder="$(SolutionRoot)\Source\MySilverlightProject"
OverwriteReadOnlyFiles="True"/>
</Target>
The first thing that a Team Build does is get the latest code from source control, and place it in a working directory on the build agent. The new BeforeCompile target replaces the ServiceReferences.ClientConfig in the working directory with the environment specific one.
And it does it BEFORE COMPILE! When TFS then compiles the MySilverlightProject project, the resulting XAP will contain the environment specific version of ServiceReferences.ClientConfig.
This of course assumes that you’re structured your source control in such a way where you can point different Team Build definitions at different branches or directories in your source tree.
This probably falls under SharePoint Administration 101, but as a developer, I was pleasantly surprised at how easy it was to move our MOSS 2007 intranet to a new web front end.
Our environment is very simple: 1 SharePoint WFE and a separate SQL Server which is shared by several other applications. Our old WFE recently started acting up and we figured that we better get a new server up and running before this one GPF’d. It also ran a 32-bit version of Windows Server 2003, and we wanted to upgrade to Windows Server 2008 x64.
It was also important for us to not have to do any DNS changes. If that doesn’t work for you, you’ll have to adjust these instructions slightly.
The whole process was pretty simple, here are the steps I followed:
- Install Windows 2008 Server x64 on the new server
- Install MOSS but do not run the SharePoint Products and Technologies Configuration Wizard
- Bring up the new server to the same patch level as the farm
- In this case, I took the opportunity to bring everything up to SP2
- Run the SharePoint Products and Technologies Configuration Wizard on the new server
- Connect the server to the farm
- Host the Central Administration site on the server
- Let the wizard do its thing
At this point, you have a another fully functioning WFE in your farm. However, it won’t be able to serve any requests because as far as DNS is concerned, it doesn’t exist. You can browse to your site at the server’s IP address to make sure that everything is working.
- Send a nice email to your users to let them know that the farm will be down while you do this
- Run the SharePoint Products and Technologies Configuration Wizard on the old server
- Disconnect the server from the farm
- Change the old server’s IP address to something else
Your farm is now inaccessible until you assign the old server’s IP address to the new server.
- Assign the old server’s IP address to the new server
- Install your SSL certificate (if applicable) on the new server
- Set your host headers in IIS on the new server
That’s it! Go through Central Administration and make sure all your services are started, you should be good to go.
A couple of interesting things I learned while doing this:
- Don’t know why you’d want to do this, but it looks like your WFEs can be running different version of Windows Server
- You have to apply your host headers manually in IIS. Not surprising because the wizard probably has no clue what you have going on for DNS and load balancing.
Now I need to work on getting Windows Rights Management Services off the old server!
SharePoint Saturday Chicago was this past Saturday, June 13th at the Tribune Tower in Chicago.
Kudos to Kris Wagner and the rest of the organizing committee for pulling off a great event, I’m already looking forward to next year.
My session was titled: Building Public Facing SharePoint Sites. Using a SharePoint publishing site that I built out for the presentation, I went over the following:
- Publishing workflow
- Topology of the solution
- Tips for organizing your Visual Studio solutions
- Using publishing site definitions
- Packaging your run-once assets
- Configuring and running content deployment
- Setting up anonymous access
- Special considerations for public sites
As promised, here is my presentation, and the complete source code for the publishing site that I demo’d.
There was some great discussion, it was helpful to hear about the experiences of other people working on SharePoint publishing sites – it was comforting to see that we all also suffered over the same issues!
I recently took on the role of INETA User Group Mentor for Illinois, Wisconsin, and Indiana. As a user group mentor, my responsibilities include being the groups’ primary INETA contact, as well as being among the first to hear about exciting programs coming out of INETA.
INETA recently announced the Regional Speakers Program to help formalize the process around identifying local speakers.
Some excerpts from the announcement:
This new initiative is designed to help user groups coordinate speaking engagements for meetings and community events such as code camps connect with local and regional speakers. Long term, we (INETA) hope to use this as a staging ground to evaluate regional speakers for the national Speakers Bureau. The first phase of the program, registering speakers and connecting user groups with Regional Speakers, is now available.
We invite you to register as a Regional Speaker on the INETA web site and enroll in the program. The registration is open to all, so please feel free to forward.
User Group leaders are encouraged to search for regional speakers and contact them directly via the website. The link will launch a peer-to-peer email conversation for you to make appropriate arrangements. The program is designed such that a user group could potentially schedule all of its speakers.
If you’re looking for the definitive reference for programming against Microsoft Office Communications Server 2007 R2, be sure to check out Programming for Unified Communications with Microsoft® Office Communications Server 2007 R2, published by Microsoft Press and now available at your favorite retailer of books!
Go get it at Amazon.com.
I helped one of the authors – Chris Mayo – review his chapters on programming against the Office Communicator SDK, so I’ve seen first hand the quality of the content in the book.
Chris will soon be publishing a code library that encapsulates the Office Communicator SDK and extends it to enable you to build Contextual Collaboration functionality into your UC-enabled applications. I’ll be sure to write about it when it becomes available.
I'll be speaking at SharePoint Saturday on June 13th at the Tribune Tower in Chicago.
For more more information, check out http://www.sharepointsaturday.org/chicago or follow @SPSaturdayChi on twitter. The event is free and is open to the public. Registration hasn't been opened yet, but keep an eye on the site for more information.
I'm really looking forward to my talk, it's titled Best Practices for Building Public Facing SharePoint WCM Sites.
Using a completely custom built SharePoint WCM site, I'm going to cover topics such as:
- Topology of a public facing SharePoint WCM site
- Solution architecture
- Organizing your Visual Studio and SharePoint solutions
- Using a custom publishing site definition
- Branding
- Considerations for public facing sites
- Content authoring
- Content deployment
- Authentication
I'll also make my slides and source code available immediately after my talk. Looking forward to seeing everyone there!
When creating a build definition in TFS, you can set the option to build each check-in, meaning that a team build will get queued up every time a developer checks in a changeset.
When you have multiple developers working on a project, your build drop location might get cluttered. It's even worse if you have different build definitions for various branches of your source tree, e.g. Dev, Main, and Prod.
My colleague @pwalke suggested that we create a CurrentBuild folder that would always contain the most current build for every build definitions. If you browse to the team project's build drop location, you can open the folder and grab the build output.
This was pretty easy to do in the build definition by overriding the AfterDropBuild build target:
<Target Name="AfterDropBuild" Condition="'$(BuildBreak)'!='true'">
<CreateItem Include="$(DropLocation)\$(BuildNumber)\**\*.*">
<Output ItemName="BuildOutput" TaskParameter="Include"/>
</CreateItem>
<RemoveDir Directories="$(DropLocation)\CurrentBuild\$(BuildDefinition)" />
<Copy
SourceFiles="@(BuildOutput)"
DestinationFolder="$(DropLocation)\CurrentBuild\$(BuildDefinition)\%(RecursiveDir)"
SkipUnchangedFiles="false"/>
</Target>
This clears out the directory, and then recursively copies the build output to a subfolder in the CurrentBuild folder.
Update: Added a condition on the build target to verify if the build was successful, thanks @sajo!
I'm a big fan of packaging "run once assets" into the deployment process of a SharePoint WCM site - I believe that this type of content should be deployed using Features until content authors ultimately assume responsibility for maintaining it.
The reason I insist on this is that I think it is critical for developers to always be working with the whole site, and not in a sandbox. Get developers seeing the big picture early on in a SharePoint WCM project, and you're much more likely to avoid integration issues down the line.
So when it came time to provision a hierarchy of sites as part of a custom site definition, I explored different ways to automate this.
One way of implementing custom provisioning logic is by including an implementation of SPWebProvisioningProvider in your site definition. You can tap into SPWebProvisioningProvider's InitializePortal method and execute custom logic, e.g., creating webs, or setting custom navigation properties.
When exploring Andrew Connell's Minimal Publishing Portal site definition (download the code for AC's Professional SharePoint 2007 WCM Development book), I noticed the ProvisionData attribute of a template in the site definition that pointed to a file called PortalConfig.xml. This looked promising, it seemed like a way to define your custom provisioning logic in markup instead of using the SharePoint object model.
I couldn't find any good examples of anybody using this in a site definition, so I fired up Reflector to see how SharePoint was using it when provisioning sites based on the Publishing Portal site definition.
Take a minute to check out the Publishing Portal template - you can find this in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\XML\webtempsps.xml:
<Template Name="BLANKINTERNETCONTAINER" ID="52">
<Configuration ID="0"
Title="Publishing Portal"
Hidden="FALSE"
ImageUrl="/_layouts/1033/images/IPPT.gif"
Description="... omitted ..."
ProvisionAssembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c"
ProvisionClass="Microsoft.SharePoint.Publishing.PortalProvisioningProvider"
ProvisionData="xml\\InternetBlank.xml"
RootWebOnly="TRUE"
DisplayCategory="Publishing"
VisibilityFeatureDependency="97A2485F-EF4B-401f-9167-FA4FE177C6F6">
</Configuration>
</Template>
The template defines a ProvisionClass of Microsoft.SharePoint.Publishing.PortalProvisioningProvider (which is simply an implementation of SPWebProvisioningProvider) and points to xml\\InternetBlank.xml in its ProvisionData attribute.
If you examine the Provision method Microsoft.SharePoint.Publishing.PortalProvisioningProvider in Reflector, you can follow the logic that SharePoint uses to provision a Publishing site.
The Provision method calls CreatePortal, which loads and validates InternetBlank.xml against the PortalTemplate.xsd schema. CreatePortal calls CreateChildWebs which recursively creates the site hierarchy as defined in InternetBlank.xml.

InternetBlank.xml can be found at C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\XML\InternetBlank.xml, let's take a look at its contents:
<?xml version="1.0" encoding="utf-8" ?>
<portal xmlns="PortalTemplate.xsd">
<web name="Home"
siteDefinition="BLANKINTERNET#0"
displayName="$Resources:cmscore,IPPT_Portal_Root_DisplayName;"
description="$Resources:cmscore,IPPT_Portal_Root_Description;" >
<webs>
<web name="PressReleases"
siteDefinition="BLANKINTERNET#1"
displayName="$Resources:cmscore,IPPT_Portal_PressRelease_DisplayName;"
description="" />
<web name="Search"
siteDefinition="SRCHCENTERLITE#1"
displayName="$Resources:cmscore,IPPT_Portal_SearchCenterLite_DisplayName;"
description="" />
</webs>
</web>
</portal>
If you've created an out of the box Publishing Portal, this should be familiar - InternetBlank.xml defines the Press Releases and Search Center sites that are created.
There are a couple of possible approaches you can take if you'd like to integrate this functionality into your custom site definition:
- Have your site definition use Microsoft.SharePoint.Publishing.PortalProvisioningProvider instead of writing your own SPWebProvisioningProvider
- Implement some of the logic in Microsoft.SharePoint.Publishing.PortalProvisioningProvider into your own implementation of SPWebProvisioningProvider
The approach you take really depends on your requirements. If you simply need to provision a hierarchy of sites as part of your site definition, go with #1. If you need to create those sites, but also execute more custom logic, go with #2.
I'm planning to explore a third option which involves extending Microsoft.SharePoint.Publishing.PortalProvisioningProvider to account for some more custom logic - we'll leave that for a future post though :)
Let me now demonstrate modifying AC's Minimal Publishing Portal site definition to use Microsoft.SharePoint.Publishing.PortalProvisioningProvider instead of a custom SPWebProvisioningProvider - you can download the code for AC's Professional SharePoint 2007 WCM Development book, the Minimal Publishing Portal site definition is in Chapter 5.
Let's first take a look at the WEBTEMP.PublishingMinimal.xml file for the site definition:
<?xml version="1.0" encoding="utf-8" ?>
<Templates xmlns:ows="Microsoft SharePoint">
<Template Name="PublishingMinimal" ID="10001">
<Configuration ID="0"
Title="Minimal Publishing Site"
DisplayCategory="Publishing"
Hidden="FALSE"
ImageUrl="/_layouts/images/PublishingMinimal/Preview.png"
RootWebOnly="false"
SubWebOnly="true" />
<Configuration ID="1"
Title="Minimal Publishing Portal"
DisplayCategory="Publishing"
Hidden="FALSE"
ImageUrl="/_layouts/images/PublishingMinimal/Preview.png"
ProvisionAssembly="Chapter05MinimalSiteDefinition, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=c591e70cfdf9ce4f"
ProvisionClass="WROX.ProMossWcm.Chapter05.ProvisioningEngine"
ProvisionData="SiteTemplates\\PublishingMinimal\\XML\\PortalConfig.xml"
RootWebOnly="true"
SubWebOnly="false" />
<Configuration ID="2"
Title="Minimal Publishing Site with Workflow"
DisplayCategory="Publishing"
Hidden="FALSE"
ImageUrl="/_layouts/1033/images/PublishingSite.gif"
SubWebOnly="true"
VisibilityFeatureDependency="54A92CA1-4E7C-4B73-B03A-E93955E4E560"
Description="... omitted ... "/>
</Template>
</Templates>
The site definition includes three configurations:
- Minimal Publishing Portal - the "provisioner" for the site definition
- Minimal Publishing Site - a template for a web without workflow
- Minimal Publishing Site with Workflow - a template for a web with workflow
Let's start by pasting the contents of C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\XML\InternetBlank.xml into PortalConfig.xml and editing the siteDefinition property of the root web to provision the appropriate configuration of the PublishingMinimal site definition:
<?xml version="1.0" encoding="utf-8" ?>
<portal xmlns="PortalTemplate.xsd">
<web name="Home"
siteDefinition="PublishingMinimal#0"
displayName="Home"
description="">
<webs>
<web name="PressReleases"
siteDefinition="BLANKINTERNET#1"
displayName="Press Releases"
description="" />
<web name="Search"
siteDefinition="SRCHCENTERLITE#1"
displayName="Search"
description="" />
</webs>
</web>
</portal>
We'll also modify WEBTEMP.PublishingMinimal.xml to use Microsoft.SharePoint.Publishing.PortalProvisioningProvider instead:
<?xml version="1.0" encoding="utf-8" ?>
<Templates xmlns:ows="Microsoft SharePoint">
<Template Name="PublishingMinimal" ID="10001">
<Configuration ID="0"
Title="Minimal Publishing Site"
DisplayCategory="Publishing"
Hidden="FALSE"
ImageUrl="/_layouts/images/PublishingMinimal/Preview.png"
RootWebOnly="false"
SubWebOnly="true" />
<Configuration ID="1"
Title="Minimal Publishing Portal"
DisplayCategory="Publishing"
Hidden="FALSE"
ImageUrl="/_layouts/images/PublishingMinimal/Preview.png"
ProvisionAssembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c"
ProvisionClass="Microsoft.SharePoint.Publishing.PortalProvisioningProvider"
ProvisionData="SiteTemplates\\PublishingMinimal\\XML\\PortalConfig.xml"
RootWebOnly="true"
SubWebOnly="false" />
<Configuration ID="2"
Title="Minimal Publishing Site with Workflow"
DisplayCategory="Publishing"
Hidden="FALSE"
ImageUrl="/_layouts/1033/images/PublishingSite.gif"
SubWebOnly="true"
VisibilityFeatureDependency="54A92CA1-4E7C-4B73-B03A-E93955E4E560"
Description="... omitted ... "/>
</Template>
</Templates>
While doing this, I discovered that I had to ensure that PublishingMinimal#0 and PublishingMinimal#2 also activated the SharePoint PublishingSite and PublishingWeb features. For each configuration in the site definition's ONET.xml, ensure that the features are activated.
The PublishingSite feature is scoped at the Site level and should be activated in <SiteFeatures>:
<SiteFeatures>
<!-- Feature: PublishingSite -->
<Feature ID="f6924d36-2fa8-4f0b-b16d-06b7250180fa" />
The PublishingWeb feature is scoped at the Web level and should be activated in <WebFeatures>:
<WebFeatures>
<!-- Feature: PublishingWeb -->
<Feature ID="94c94ca6-b32f-4da9-a9e3-1f3d343d7ecb" />
That's it!
Go ahead and create a site collection based on the Minimal Publishing Portal site definition, and you'll see the Press Releases and Search sites created automatically.
Creating custom site definitions is painful enough ... Simplifying it slightly by leveraging existing SharePoint Publishing functionality to provision a portal's hierarchy will hopefully make this a little easier.
As I mentioned, I'll be exploring extending the functionality in Microsoft.SharePoint.Publishing.PortalProvisioningProvider in order to still use it, but also inject some custom functionality which is often required during provisioning - stay tuned.
We recently wrapped up a project where we migrated a .com site built using Microsoft Content Management Server (MCMS) 2002 to take advantage of the web content management features available in SharePoint 2007.
Microsoft is no longer offering MCMS as a standalone product; all enterprise and web content management is now part of the MOSS platform. I'm not a Microsoft licensing expert (is anyone?), but customers with active Software Assurance for MCMS as of November 2006 are eligible for heavily discounted (or free) licenses for SharePoint 2007. At $50K a pop per server for a SharePoint Internet license, there's money to be saved here! Check out http://www.microsoft.com/cmserver/roadmap.mspx for some more information.
Anyway ... we really learned a lot of good stuff on this project, but also suffered through some other things the hard way. Together with the help of the rest of the team, I put together a list of the top ten things we learned on this SharePoint WCM project:
10 - CMS Assessment Tool - inventory your migration
Microsoft provides a tool for MCMS 2002 sites to "determine the level of work needed to migrate to SharePoint 2007". In our experience, this tool was really only good for one thing: getting an inventory of the assets in the MCMS 2002 site, e.g. the number of page templates, etc.
I wouldn't recommend using any output of this tool as input into the development process, the source code for your MCMS templates (if you have access to it) is more valuable than anything this tool puts out.
One thing I highly recommend is to store your old site's content in Xml files using the technique I described in my blog post Bulk Importing Content into a SharePoint Publishing Site to import it into the new site.
9 - Continuous Integration - how far to take it
The benefits of true continuous integration on a SharePoint project are debatable. By no means am I saying that continuous integration isn't valuable, it's just that the effort involved in actually making it work in SharePoint might just not be worth it.
We found a great middle ground: we have TFS configured to automatically build our source on every check in. After every successful build, all of our compiled WSPs are neatly arranged in our build drop location.
If you broke the build, you got yelled at over the next cube row ;)
8 - SSL - don't wait until the site is live
SSL is one of those things that developers tend to leave to the infrastructure and network dudes.
On this project, we implemented an HTTP module that would switch the site in and out of SSL depending on the area of the site that the user was visiting. We tried to fake it out in dev, but only saw the real issues once we had gone live with the site.
As cumbersome as this seems, get a real SSL certificate and make it available in an environment where developers can test any of the site's functionality related to SSL.
VeriSign even offers free trial SSL certificates (their sales reps won't stop calling you though) that developers can install in their individual environments.
7 - FireBug and Fiddler - the two amigos
Two development tools that we couldn't have gone without are FireBug and Fiddler.
The most visible part of the site migration process was to replicate the site's look and feel within SharePoint; this wasn't as straightforward as we initially thought. Forget the IE Developer Toolbar, FireBug is where it's at. It was invaluable when we were debugging CSS issues, or looking for the proper SharePoint style to override.
Oh yeah, and Heather Solomon rocks. Anyone that suffered through documenting every single SharePoint CSS style deserves some props.
Ah Fiddler, how I love thee. When we were knee deep in SSL issues, it was Fiddler that showed us the HTTP 301s that were causing FireFox and IE8 to complain about mixed content on the page.
6 - IETester - test your site on crap browsers (ahem, IE6)
Unfortunately, there are still quite a few IE6 installs out there, I wish it would just die already.
However, that meant that we couldn't ignore IE6 in our testing. IETester allowed us to easily test our site in different versions of IE without having to actually install any of those versions anywhere.
I don't run IE8 anywhere either, I'm really gun shy when it comes to installing beta versions of IE. IETester helped us spot any issues that came up when using different versions of IE, such as warnings about mixed content on a page when we were troubleshooting SSL issues. It's also worth nothing that IETester can also be configured to proxy through Fiddler for HTTP debugging.
5 - SharePoint Content Deployment - works great but tough to troubleshoot when things go wrong
Microsoft's recommended topology for an internet publishing web site is one where authors create content in a dedicated Authoring environment, and scheduled Content Deployment Jobs copy approved and published content to a Staging (optional) and Production (live) environment.
For the most part, this worked out pretty well for us. Here are a few things to watch out for:
Your content deployment destination site collection is NOT a Publishing site
When first setting up content deployment, you always deploy to a site collection that was created using the Blank Site site definition. No big deal, except that you need to understand that this site isn't truly a Publishing site. This makes sense in some ways - you don't want content authors to edit content on the live site, but it's confusing in some other ways.
For example, there are several options you would usually see under the site settings of a Publishing site, e.g. Caching, and Site Usage Reports. You won't see these options in your content deployment destination site, although this is where you would actually need to configure caching!!
The workaround here is that you have to know the urls to those options, they will work if you type them manually into the browser.
Troubleshooting failed content deployment jobs
Content deployment jobs fail for the damndest reasons, and when they do fail they're nearly impossible to troubleshoot. Maxime Bombardier has a great blog post called Debugging Content Deployment Issues, worth a look.
One technique for debugging failed jobs is to examine the exported package, and look in the manifest.xml for the item that failed. This will be the item which has a +1 count from the Number of Objects Imported. The problem with this technique is that it might sometimes point to an item that doesn't necessarily make sense, e.g. /Pages/Forms/AllItems.aspx - what on earth are you supposed to do here?
So if all else fails, what do you do?
One thing that worked consistently for us is the following:
- Switch the failed job to deploy ALL content (if it was initially set to incrementally deploy content)
- Run the full content deployment job
- This might take a while, make sure there's enough space for the SQL transaction logs to grow
- Verify that the full content deployment job succeeded
- Reset the job back to incremental
- Run all the other jobs you have configured along this content deployment path at least once (more on why next)
A side effect of running a FULL content deployment job is that it might reset any custom permissions you have implemented on sub sites in your site collection - even though you configure the job to not deploy any security settings.
Additionally, any incremental content deployment jobs that run after the full job might do the same.
We never figured out why exactly this happens, which is why we run all the incremental jobs again, and then correct any of the security settings that were messed up.
Yay for lame workarounds!
4 - Andrew Connell's WCM Book - worth its weight in gold
Professional SharePoint 2007 Web Content Management Development, really the best SharePoint book out there.
Don't let the title fool you, this is a must for every SharePoint developer - WCM or not.
I love the approach that Connell uses when demonstrating how to build a piece of functionality. He shows you how do it in the SharePoint UI, how to do it in SharePoint Designer, and how to do it using SharePoint Features and Solutions.
Just packed with awesomeness, and small enough that you can always carry it around.
While you're at it, check out @andrewconnell on twitter. Really easy to get in touch with, and always more than happy to answer questions.
3 - CSSRegistration - order matters
Typically, you will link to one or more CSS files from within your master page. You might have special taken care to order your CSSRegistration controls - after all, the order of CSS declarations matters.
Well, don't bother. SharePoint will render them alphabetically... !@#!#@$
2 - The F5 Experience - not
Simply put, there is no F5 experience in SharePoint. If you use tools such as the Visual Studio Extensions for WSS, you're going to make tradeoffs in some other places.
So your head doesn't explode a few weeks into the project, take the time up front to optimize your team's edit-compile-deploy process.
For example, use MSBuild targets to automatically build your WSP and generate your install/deploy/retract/remove/upgrade scripts. Instead of manually doing everything via Central Administration and Site Collection Features, compile and run one or more of your scripts, or have a script that does a retract/remove/install/deploy in one step!
Work smarter, not harder!
1 - Feature Receivers and HTTP Modules - the control you need
Feature Receivers
If you find yourself manually pasting content in your web.config file as part of your deployment process, step back and look at automating the process.
A few of our features depended on the existence of certain sections and items in a SharePoint site's web.config file. We wanted to have these sections automatically created and deleted as part of our feature activation and deactivation process.
For example, if a feature depended on the existence of a custom web.config section, we would store the text for the section in a resx file, and then create it using the SPWebConfigModification class as part of our feature activation process. Taking this a step further, you could use a different version of the resx file depending on which source control repository you were building out of.
There are a lot of uses for feature receivers, we just found this one particularly useful.
HTTP Modules
Before this project, I hadn't written a single HTTP module. I can't say that anymore, here are some examples of functionality we implemented using HTTP modules:
- Override Layouts pages master page
- Custom 500 error page
- Custom authentication for different sub sites in a site collection
- Switch site in and out of SSL depending on the site or page being requested
You might be intimated by the idea of writing an HTTP module, but they're actually no big deal since they're pretty easy to implement. Just remember though, that the order that you list them in your web.config actually matters.
Also keep in mind that each module will execute for every request, there will be performance implications if you go nuts and implement a lot of these.
Hopefully you find this list somewhat useful, and it saves you some head banging on your next SharePoint project. Would love to hear your suggestions in the comments, or catch me on twitter @gdurzi.
More Posts
Next page »