Sunday, August 24, 2008

Porting YetAnotherForum.NET to SharePoint

Idea

YetAnotherForum.NET (YAF.NET) on SharePoint, sounds like a great idea!
(The main focus of this article is partitioning data between YAF.NET forums based on SharePoint site.)

It sounds like YAF.NET might already handle authentication of users arriving through Active Directory (ie: auto register), but I don't want just a YAF.NET install next to my SharePoint system. My needs are to have YAF.NET be a feature that can be deployed across hundreds (thousands?) of SharePoint sites without intermingling the content (Topics/Posts) between sites.

It seemed like it might be a weekend effort, but with family activities, other personal projects and the size of the effort, I've decided that I just won't have the time to port YetAnotherForum.NET to SharePoint 2007 (WSSv3). I will share my findings though.

Approach #1

Inversion of Control, decoupling the data access mechanism from the application so a new data store (ie: SharePoint) could be injected. The approach would be to take all the DB access methods in YAF.NET, and make them use an interface, and put the existing DB access code into a class to satisfy that data interface. The phase 1 result would be a code refactored YAF.NET that works exactly the same as it did.

The second phase of the port would be to create another implementation of the data access interface that utilizes SharePoint lists instead of the SQL database. Plug in that new interface, and the YAF.NET should work fine without knowing that its running on SharePoint. It should be possible to satisfy all the database needs of YAF.NET with SharePoint lists and CAML for access. The only need that might not work is YAF.NET's free-text search capabilities.

Data will need to be compartmentalized into the SharePoint sites that YAF.NET was enabled for. This will help application scaling and content ownership. There are some practical data limits to SharePoint's core list capabilities, but utilizing separate YAF.NET lists per SharePoint site avoid those issues. The YAF.NET SharePoint data access would have to utilize the lists for the current site to make this work, would be pretty easy once the rest was in place.

Example YAF.NET loading from SharePoint site context:
http://hostname/sites/TestSite/_layouts/YAF/default.aspx

The SharePoint site at "/sites/TestSite" would have the normal YAF.NET data sources:

[...]

The entire YAF.NET for SharePoint system would be packaged as a SharePoint site collection or web scoped feature in a SharePoint solution file. That means that once the solution has been added to your SharePoint system, site administrators could go to Site Features (or Site Collection Features) and enable that feature. The initialization of the feature would run, creating the proper lists and default data.

This approach sounds like the best SharePoint solution, but without in-depth knowledge of YAF.NET, it feels like 3-4 weeks worth of development and testing.

Approach #2

A compromise solution to porting YAF.NET to SharePoint would be to partition the data for SharePoint sites within the current YAF.NET SQL database. By adding a new "Context" column to all existing tables, then slightly modifying all the SQL in YAF.NET, the system could function within the context of the SharePoint site it was accessed from. This approach simulates different data stores for each SharePoint site, but does not change the native source YAF.NET already uses, so the approach is less complex.

This approach sounds like a reasonable SharePoint solution, and avoids some of the questions about SharePoint list data scalability issues by continuing to use native SQL Server. Because this approach is generally many simple modifications, it feels more like 1-3 weeks worth of development and testing.

I started some work down this approach to flush out the main issues. Some of these steps are required for either approach...

Live in SharePoint Layouts
YAF.NET would need to function in the common SharePoint "layouts" directory. This is where common SharePoint functions/systems need to live, and they are given the opportunity to be "aware" of the site context that they are invoked from. This part was quick and easy.

Misc:
In \web.config, I enabled session state, and switched trust level to full. Neither is a great idea for SharePoint, but worked for my development purposes...
pages enablesessionstate="true" ...
trust level="Full" originUrl=""

For this url to work:
http://hostname/sites/TestSite/_layouts/YAF/default.aspx

...YAF.NET needs to be copied to:
C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS\YAF
(I put the YAF.NET binaries into the webroot/bin for testing)

Config.cs needs to know about site context:


///
/// Determine the site context from the http request.
/// /
/// /sites/TestSite
///
static public string UrlContextPath
{
get
{
string scriptUrl = HttpContext.Current.Request.ServerVariables["SCRIPT_NAME"];
string rawUrl = HttpContext.Current.Request.RawUrl;
int pos = rawUrl.ToUpper().IndexOf(scriptUrl.ToUpper());
if (pos <>
{
return "/";
}
return "/" + rawUrl.Substring(0, pos);
}
}


UrlBuilder.cs needs to build urls with site context:

public string BuildUrl(string url)
{
return string.Format("{0}{1}?{2}",
Config.UrlContextPath.Substring(1),
HttpContext.Current.Request.ServerVariables["SCRIPT_NAME"],
url
);

//return string.Format("{0}?{1}",HttpContext.Current.Request.ServerVariables["SCRIPT_NAME"],url);
}


Partition Data by Site Context

This is the BIGGEST porting task, requiring changes to all 31 database tables, 1 view, 162 stored procedures, and all corresponding code invocations of SQL and stored procedures.

Add the Context field to all database tables. For example:

IF NOT EXISTS (SELECT 1
FROM sysobjects
WHERE id = Object_id(N'yaf_AccessMask')
AND Objectproperty(id,N'IsUserTable') = 1)
CREATE TABLE dbo.yaf_AccessMask (
[AccessMaskID] INT IDENTITY NOT NULL,
[Context] NVARCHAR(255) NOT NULL,
[BoardID] INT NOT NULL,
[Name] NVARCHAR(50) NOT NULL,
[Flags] INT NOT NULL CONSTRAINT DF_yaf_AccessMask_Flags DEFAULT (0))
GO

IF NOT EXISTS (SELECT 1
FROM syscolumns
WHERE id = Object_id('yaf_AccessMask')
AND name = 'Context')
BEGIN
ALTER TABLE dbo.yaf_AccessMask
ADD Context NVARCHAR(255) NOT NULL DEFAULT '/'
END
GO



Modify all SQL and Stored Procedures to use the new Context field. For example:

CREATE PROCEDURE [dbo].[yaf_accessmask_delete](
@AccessMaskID INT,
@Context NVARCHAR(255) = '/')
AS
BEGIN
DECLARE @flag INT
SET @flag = 1
IF EXISTS (SELECT 1
FROM yaf_ForumAccess
WHERE AccessMaskID = @AccessMaskID)
OR EXISTS (SELECT 1
FROM yaf_UserForum
WHERE AccessMaskID = @AccessMaskID)
SET @flag = 0
ELSE
DELETE FROM yaf_AccessMask
WHERE AccessMaskID = @AccessMaskID
AND Context = @Context
SELECT @flag
END
GO



Modify all invocations to SQL and Stored Procedures to use the new Context field. For example:


static public bool accessmask_delete( object accessMaskID )
{
using ( SqlCommand cmd = new SqlCommand( "yaf_accessmask_delete" ) )
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue( "@AccessMaskID", accessMaskID );
cmd.Parameters.AddWithValue("@Context", Config.UrlContextPath);
return (int)ExecuteScalar(cmd) != 0;
}
}



With a few of these changes in effect, YAF.NET continued to function properly, which helped prove the viability of the approach, but also proved that it would take a non-trivial amount of time to complete. I did not continue the effort due to my lack of availability.

Additional Issues:

Web Part - These approaches are targeted at making YAF.NET work as a companion to SharePoint, not fully integrate it. An expectation of any SharePoint admin would be how to drop a dynamic view of the forum onto a SharePoint Web Part Page. Luckily YAF.NET is constructed with web user controls, which translate pretty easily into web parts. Look at SmartPart WebPart (or other sample code) for how to load a normal web user control in a Web Part container.

Isolation - Some elements of YAF.NET need to be shared rather than isolated between SharePoint web sites. Data such as Users, Permissions and Access Masks are some examples of data that might need to be shared across all SharePoint sites (especially users!)

Overlapping domains - Some elements of YAF.NET need to pull data from SharePoint rather than handled by YAF.NET. For instance, Users and Permissions should come from SharePoint, otherwise SharePoint admins will need to setup users/permissions TWICE (once for SharePoint sites, and another time for YAF.NET).

Default data - Some data is provisioned during the one-time YAF.NET install. Some amount of data provisioning may need to occur once per SharePoint site. During setup/initialization. For instance, the AccessMask is setup with some default permissions. If data in YAFF.NET is merely partitioned between SharePoint sites, that one-time data initialization will only benefit one SharePoint site.

Monday, August 18, 2008

Microsoft Update - the following updates were not installed

I kept receiving "the following updates were not installed" when trying to use Microsoft Update on one of my plain Windows XP VMware machines. I have HEAVILY relied on Microsoft Update for a long time for many machines, and this is the first time updates just wouldn't install. In my scenario, I would choose "Custom" and anything I chose would go through the motions, but ultimately fail to install.

I followed the simple instructions here which solved my problem: Updates are not installed successfully from Windows Update, from Microsoft Update, or by using Automatic Updates after you perform a new Windows XP installation or you repair a Windows XP installation

Method 1: Register the Wups2.dll file in Windows

To register the Wups2.dll file in Windows, follow these steps:
1.Stop the Automatic Updates service. To do this, follow these steps:
a. Click Start, click Run, type cmd, and then click OK.
b. At the command prompt, type the following command, and then press ENTER:
net stop wuauserv
2.Register the Wups2.dll file. To do this, follow these steps:
a. At the command prompt, type the following command, and then press ENTER:
regsvr32 %windir%\system32\wups2.dll
Note For a computer that is running Windows XP Professional x64 Edition, type the following command, and then press ENTER:
regsvr32 %windir%\syswow64\wups2.dll
b. Click OK on each verification message that you receive.
3.Start the Automatic Updates service. To do this, type the following command at the command prompt, and then press ENTER:
net start wuauserv
4.Exit the command prompt. To do this type exit, and then press ENTER.

Sunday, August 10, 2008

Service manuals are very useful !

My printer broke and I was confident that I'd be able to fix the simple problem once I downloaded the service manual. I have to say it was very cool to be able to get a hold of that manual ! It has instructions on how to diagnose, disassemble and fix. The printer went through the motions, just didn't lay any ink... must be clogged, should be easy to fix.

The printer actually stopped printing about 2 years ago, and I had since abandoned the overly expensive and questionable ethics of that printer's manufacturer. Since I HAD purchased extra printer ink cartridges with that company's settlement to the class action lawsuit (pertaining to questionable ink practices)... I wanted to use up that ink, and spare some ink on my new Canon PIXMA MP780.

I have to applaud any company (even the maker of this printer and its expensive ink) that has a service manual that I can find online (I got it at this site). Kudos ! I also found community help for exactly the problem I had. Regardless of technical expertise or skills, I think everyone should have the chance to get the full manuals and self-help for all the products they own.

My project started following A FIX for Clogged Printheads which was VERY nice. The disassembly, construction of make-shift tools, and use of anti-freeze... all went great removing tons of gunk from my old printhead. Followed all instructions, didn't help in the end. I was still happy and empowered by the community help.

Then I found and dove into the service manual. I wouldn't gotten nearly as far carefully and gracefully locating and removing the proper screws without it. I usually break things as I'm being careful, which I didn't do as I followed the manual.

As the manual pointed me at screws that could only be reached with long narrow specialized screwdrivers I started to get frustrated, but still thinking there was SOME hope that I'd be able to reseat them all and get this thing back together.

Having to ultimately go for removing the printhead itself (ugh), I abandoned the manual, and honestly all hope of ever getting this darn thing back together. Just think though, without the manual I wouldn't have ended up with this, or potentially in the mood to buy a new printer. Too bad I already decided I didn't like this one's manufacturer, I might have bought another of their printers merely because I was empowered with the service manual to destroy my old one!


Care with the service manual lead to not caring and destruction.

Well, at least the 3 day project is now over, and I've got some additional neat parts for my Junk box.