Monday, September 1, 2008

Uncustomized (Ghosted) Vs Customized (Unghosted) Files within SharePoint

I beleive that understanding the differences between Uncustomized and Customized files is the key to uncovering the power of the SharePoint platform.

It is what enables SharePoint to provide a rich configration interface for the "power user" but at the same time provide a mechanisim for a more traditional development approach.
It is also the most mysterious part of SharePoint to any new developer as this concept does not really exist in the standard .NET world.

Here is an attempt to explain the difference:

Both Uncustomized and Customized files live within the logical structure of your SharePoint site and hence in the Content Database(both types of files can be seen from within SharePoint designer). However the difference is that with a customized file the "complete file" exists within the content database whereas the uncustomized file the entry within the content database is really just a pointer to a "file" on the physical file system(under the /12 directory).

This "file" could also be thought of as a "template file" and you can have multiple Uncustomized files within your SharePoint site pointing at a single "template file".

This is the area where SharePoint comes to the party for developers and enables us to do things such as easily move content through environments(DEV-> UAT-> PROD as we can move physical files much more easily than the complex database entries of a customized file) and also make changes to SharePoint sites in a centralized place. Lets say you create a new uncustomized "template file" and users use this template to provision 100 pages. Now the business requirements change and we need an extra control on this page. If this were a customized page we would need to add the new control to all 100 pages that are stored within the database. This is a real nightmare. However with uncustomized pages all we need to do is add the control to the "template file" on the physical file system and we have 100 pages immediately satisfying the new business requirement.

The Customized file really tells the story for the "Power User" or "Information Worker". This is where the power of SharePoint Designer comes into play.
A customized file can be created in a couple of different ways.
Firstly you create a customized file by using SharePoint Desginer to modify a template file like the ones mentioned above. The changes you make are now stored in the database and SharePoint no longer looks at the file system based file.
Alternatively you can create new SharePoint content directly in SharePoint Designer, for example a new page layout. This new layout is stored directly in the database.

Obviously this is very powerful as we can now remove developers from the equation, and SharePoint gives the Information Worker the ability to rapidly and extensively change the makeup of the site.

Getting the balance right is one of the fundamentals to any good SharePoint project. Too much work with customized files excludes some the nice things such scalability inherent in the development approach, not enough work with customized files and we find that business owners are not getting all the promised benefits of the SharePoint platform.

Wednesday, August 20, 2008

A compliant navigation control for SharePoint.

Following on from my post about creating a MOSS WCM website using a div based layout, I am going to describe a simple way to create a web standards compliant navigation control.

One of the short comings of the sharepoint:aspmenu or the asp:menu control is that they render horrible table tags. By using CSS adapters (Check out CSS Friendly) we can still take advantage of the built in controls but render the output in div and ul, li tags to create our site navigation. I use the MOSS PortalSiteMapProvider as my site map provider.

1) Firstly I created an empty class definition (MyMenu) that inherits from System.Web.WebControls.Menu.

 
public class MyMenu : System.Web.WebControls.Menu
{
}


This is done so that we have explicit control over which controls are adapted. If we decided to adapt the System.Web.WebControls.Menu directly all controls of this type would get rewritten even if we didn't want them to. This happened to me the first time I tried it in SharePoint, I ended up modifying every menu in the site including the Site settings and Application pages, obviously these are will not be seen by the anonymous user, hence I am not concerned by the fact they render in table tags.

2) Download the CSS Friendly Adapters either download the source code and sign the assembly (we can now place the code in the GAC) or place the binary in the Web application /bin folder. You may have to raise the trust level in the Web.config also.

3) Create a .browser file that adapts our control in 1) using the adapter in 2) an example of this is shown in the CSS Friendly project.

4) finally I wrapped the MyMenu control and PortalSiteMapProvider into a SharePoint ControlTemplate ascx. This was done to a) to keep my master page clean and tidy, and b) to remove some of the complexity of configuring the site map provider for future reuse.

5) Add the control template to the masterpage.

6) We can now style the control as normal using our custom .css file.

For more information and a tidy implementation of adapting a "wizard" control check out Toke's blog post here.

Sorting Generic List<> using Lambda expressions.

I have been having a play with C#3.0, specifically around sorting collections using Lambda expressions. i.e. a collection of navigation items. Here is a great blog post that clearly explains the basics. Using lambda expressions and linq for manipulation of collections.

Saturday, August 16, 2008

A pattern for creating a MOSS WCM website

Recently I have been working on MOSS WCM websites. I have developed a successful pattern to create branded websites where the design team doesn't need any specific SharePoint knowledge or experience to create great looking layer(<'div'>) based sites.

1) Created a Site Defintion based on either the STS Blank Site Definition or the base Publishing Site Definition.

2) Create a feature stapler to staple new fuctionality to the new Site Definition. By using a feature staple we can remove the complexity associated with creating Custom Site definitions.

3) Create a "Masterpage Feature"; this feature adds a new (<'div'>) based masterpage extending minimal.master, and a alternate .CSS file to the _catalogs/masterpage library as "ghostable" files.
The feature assigns the .master and .css to the site.
Because the files are set as ghostable all changes affecting sites using this def can be made to a single instance of the .master and .css file in the Feature folder. (This alternate .css file is the last .css rendered in the CSS hierarchy; this means the creative designer can create all their own classes that relate to the .master and effectively ignore all of the OOB SharePoint styling)
This feature also removes the default Page Layouts and stylesheets that get put in the "Style Library" library by default.

4) All creative resources can now be deployed using a Solution to the 12/TEMPLATE/IMAGES/ folder and referenced in the .CSS file


Result; we now a have beautiful a div based site, and the creative designer doesn't need any SharePoint specific knowledge.

Friday, May 16, 2008

Add SPLookup column declaratively through CAML XML

Up until a day ago, I thought that adding a SharePoint lookup column through CAML XML was impossible, and that you could only do it programmatically using a feature receiver. It made sense; how can you add a lookup to a list that doesn't exist yet?

A colleague of mine sent through this link to Josh Gaffey's Blog.

Check out how easy it is. I bet there are a lot of smart SharePoint dev's out there saying "I wish I thought of that".

Monday, May 12, 2008

Deploy a user control(.ascx) as a SharePoint web part

Frustrated by the lack of control over the UI when creating SharePoint web parts, want something more lightweight than SmartPart. The following code snippet illustrates how you can deploy a user control as a SharePoint web part.

1) Place your complied code in either the GAC, or web application \bin folder.
2) Save your user control to the file system
(usually: "12\TEMPLATE\CONTROLTEMPLATES").
3) Replace the string "MyUserControlPath" with the location of the usercontrol.
4) Deploy web part as normal.

 
[Guid("9c974b27-d41a-4053-8eb2-76c25e762f1b")]
public class MyWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
public MyWebPart()
{
this.ExportMode = WebPartExportMode.All;
}

protected override void CreateChildControls()
{
try
{
base.CreateChildControls();

this.Controls.Add((System.Web.UI.UserControl)
Page.LoadControl("MyUserControlPath"));
}
catch (Exception ex)
{
EventLog.WriteEntry("WebParts", ex.Message, EventLogEntryType.Error);
}
}
}

Wednesday, May 7, 2008

"sitedefinitiontemplate.wsp" has an unsupported extension, and cannot be added to the solution store.

I came across this problem when I was using STSADM to add my solution to SharePoint.
I used the command stsadm.exe -o addsolution -name sitedefinitiontemplate.WSP and couldn't figure out what was wrong?

It turns out that stsadm doesn't recognise the capitalised .WSP, when I used stsadm.exe -o addsolution -name sitedefinitiontemplate.wsp the "opperation completed sucessfully"

How to programatically associate a workflow with a list

I needed to do this in a previous project where we created a custom workflow and needed to associate that workflow with a list that existed within a SharePoint site.

We used a site definition that provisioned a pages library, and activated the workflow feature each time a site was created.

However it was still a manual step to associate the workflow with the pages library. To solve this we created a site feature receiver that ran when the site was created and called the following code.


 
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
using (SPWeb siteWeb = (SPWeb)properties.Feature.Parent)
{
SPList list = siteWeb.Lists["Name here"];
SPWorkflowTemplate baseTemplate =
siteWeb.WorkflowTemplates["Workflow Guid"];

SPWorkflowAssociation assoc = SPWorkflowAssociation.CreateListAssociation(
baseTemplate
, "NameOfWorkflow"
, "SPList task list"
, "SPList history list");

list.AddWorkflowAssociation(assoc);
}
}

Thursday, May 1, 2008

Adding a Custom web part to SharePoint and MOSS

Below is a method that shows how you can add a custom web part to SharePoint
 
public static void AddCustomWebPart(SPWeb web
, System.Web.UI.WebControls.WebParts.WebPart webPart
, string fullOrRelativeUrl
, PersonalizationScope scope
, string webPartZoneID
, int webPartZoneIndex)
{
SPLimitedWebPartManager wpManager =
web.GetLimitedWebPartManager(fullOrRelativeUrl, scope);

wpManager.AddWebPart(webPart, webPartZoneID, webPartZoneIndex);
}


The code looks pretty easy but it wasn't immediately obvious to me at the beginning. I struggled to find the second param webPart. Initially I looked within SharePoint to see if I could find it, you would think that you could look in the web part gallery find the web part and add it. This was not the case.

The easiest way I found was to deploy the web part as standard to your site. Then include the complied .dll for your custom web part as reference in your deployment console or feature receiver.

A sample usage is below. Notice I have included the "using SampleSharePointSolution.WebPart;" now I can pass new HelloWebPart() as the web part param.

 
using SampleSharePointSolution.WebPart;

namespace DeploymentConsole
{
class Program
{
static void Main(string[] args)
{
using (SPSite site = new SPSite("http://vm-spdev-7497:1000/"))
{
using (SPWeb web = site.RootWeb)
{
Deploy.AddCustomWebPart(web
, new HelloWebPart()
, "Default.aspx"
, PersonalizationScope.Shared
, "Left"
, 0);
}
}
}
}
}

Adding a ListView web part to SharePoint and MOSS

At the moment I'm pretty interested in looking at ways we can manage an existing SharePoint or MOSS infrastructure.

For example say you had a MOSS installation with 200 client colaboration sites and somebody wants to add a new web part to all these sites? Short of making the web part available to all sites and then asking the clients to add the web part themselves what can we do?

Below is a code snippet that shows how to add a new list view web part.

 
public static void AddListViewWebPart(SPWeb web
, SPList list
, SPView listView
, string fullOrRelativeUrl
, PersonalizationScope scope
, string webPartZoneID
, int webPartZoneIndex)
{
ListViewWebPart listViewWebPart = new ListViewWebPart();

listViewWebPart.ListName = list.ID.ToString("B").ToUpper();
listViewWebPart.ViewGuid = listView.ID.ToString("B").ToUpper();

SPLimitedWebPartManager wpManager =
web.GetLimitedWebPartManager(fullOrRelativeUrl, scope);

wpManager.AddWebPart(listViewWebPart as
System.Web.UI.WebControls.WebParts.WebPart
, webPartZoneID
, webPartZoneIndex);
}


Example usage from a console app. It adds a list view webpart using the default view to the top of the left webpart zone on the default.aspx page.

This method could also be called from a feature receiver.

Try it out.

 
namespace DeploymentConsole
{
class Program
{
static void Main(string[] args)
{
using (SPSite site = new SPSite("http://vm-spdev-7497:1000/"))
{
using (SPWeb web = site.RootWeb)
{
SPList list = web.Lists["New Library"];

Deploy.AddListViewWebPart(web
, list
, list.Views[0]
, "Default.aspx"
, PersonalizationScope.Shared
, "Left"
, 0);
}
}
}
}
}


I will also give code to add a custom web part in a future post.

April was a big month

Well here is my first post about "Life and Other Stuff". Today is the first of May and I just wanted to take a moment to reflect on what a great month April has been for me personally.

On the 5th of April I competed in my first Half Iron Man triathlon. This consists of a 2km sea swim, a 90km solo bike ride, and a half marathon. This was a special day and marked the culmination of a lot of hard work. I started training around Christmas with the initial goal of completing the event in under 5h:30m.

I finished 13th in the open men's competition in a time of 4h:51m:48s. In the process I swam the fastest I have ever swam for 2km, biked faster over 90km than ever, and after all that ran my first half marathon. A big thanks to all my friends and family, I definitely could not have done it without your support.

Over the next two weekends, dad and I rode two 100km+ road races in the South Island which was a lot of fun.

This month starts with my first 12hour Adventure Race in the Coromandel, with my girlfriend. Should be fun :s

Tuesday, April 22, 2008

Failed to register ASP.NET client scripts on this site

I just tried to install an EPiServer 4.62 site on my new development machine, when I came across this error: "Failed to register ASP.NET client scripts on this site".

It turns out that this error message occurs when you have installed other .NET Frameworks (i.e. 3.0 and 3.5) where aspnet_regiis.exe does not exist. The fix for this is to temporarily move folder(s) from "C:\WINDOWS\Microsoft.NET\Framework\" and retry installation.

Friday, April 18, 2008

Formatting code segments in Blogger

I found this great post about adding custom style sheets to blogger. Check out this link if you want your code segments to look good.

Thursday, April 17, 2008

Programatically using the Business Data Catalog

Yesterday I had to convert an existing SharePoint WebPart from using Microsoft CRM webservices to use the MOSS Business Data Catalog (BDC) as the means of data connection.

I created the following class that may be of use to some people. It builds up a string dictionary of the entity you are searching for. For example if you need to find all the information about a particular organisation pass in the name of the entity "dboAccountBase", the field to search on "AccountID", and the Guid for the organisation. You will be returned a string dictionary containing all the fields for your particular organisation.

 
private Dictionary GetDictionary(string entityName, string filterKey, string filterValue)
{
Dictionary dict = new Dictionary();

Entity entity = BdcInstance.GetEntities()[entityName];
FilterCollection filters = entity.GetFinderFilters();
IEntityInstanceEnumerator enumerator = entity.FindFiltered(filters, BdcInstance);

while (enumerator.MoveNext())
{
IEntityInstance iEntity = enumerator.Current;
DataRow dr = iEntity.EntityAsDataTable.Rows[0];

if ((dr[filterKey] != null || dr[filterKey] != DBNull.Value)
&& (dr[filterKey].ToString().ToUpper() == filterValue.ToUpper()))
{
foreach (DataColumn dataColumn in dr.Table.Columns)
{
if (dr[dataColumn] == null || dr[dataColumn] == DBNull.Value)
{
dict[dataColumn.ColumnName] = string.Empty;
}
else
{
dict[dataColumn.ColumnName] = dr[dataColumn].ToString();
}
}
break;
}
}
return dict;
}




private LobSystemInstance _instance = null;


private LobSystemInstance BdcInstance
{
get
{
if (_instance == null)
{
NamedLobSystemInstanceDictionary sysInstances =
ApplicationRegistry.GetLobSystemInstances();

_instance = sysInstances[BdcInstanceName];
}
return _instance;
}
}

Welcome to my blog

Welcome to my blog, where I will be writing about my work, my life and other stuff.
Posts will include current projects and problems I am working on, with a particular focus on Microsoft Technologies; including SharePoint, WSS 3.0, and MOSS.
I will also keep everyone updated on my sporting exploits and any other things that I find interesting.
I hope you enjoy.