Saturday, October 24, 2009

FAST Search Whitepapers

Here are some great whitepapers you should read if you want to start learning about FAST. I know there is a lot of buzz around it with its integration with SharePoint 2010 and finally providing SharePoint with a robust search engine. This is a great starting point for starting to understand what Enterprise Search is and how it can be strategically introduced and aligned with your Enterprise Architecture.

http://www.microsoft.com/enterprisesearch/en/us/FAST-technical.aspx

Tuesday, October 20, 2009

FAST Introduction and SharePoint Search Evolution

There is a lot of information that is coming out from the SharePoint 2010 conference and one of the biggest ones is the integration of FAST into SharePoint 2010. What is FAST? FAST is an enterprise search engine that Microsoft acquired and they have placed a significant investment into. The most important thing you should know right off the bat is FAST does not equal SharePoint. FAST is an enterprise search platform which can be used as the search engine for SharePoint. Up to this point Microsoft has not provided a way to search for content across the enterprise. What we have done to compensate for this is build custom applications or purchase products like FAST and Google Appliances to do enterprise search.

This is what I have seen with the evolution of search solutions in the context of SharePoint. SharePoint 2001, nothing to really discuss but with SharePoint 2003 we started to get a taste of what we wanted for Search. We found that the search did not really work well in SharePoint 2003 (cross site searching did not work) and many customers who were using SharePoint 2003 said it simply did not work. It did basic text searching of content within SharePoint but it was missing key things like relevancy. This created a small market of third party vendors who creates search solutions for SharePoint. Remember, at this time Google had become the search engine of choice, as every day business users would just say go Google something and get the answer. Problem was we did not have the same search engine that we could use internally with a company, organization or enterprise. As result FAST, Google, Autonomy, etc. created enterprise search solutions that could be used within a company enterprise and that many these features that were required by the business user.

Then SharePoint 2007 came out with Enterprise Search. It was a significant improvement over what we had with SharePoint 2003 but it was still far off from being an enterprise search solution. They improved the user interface, allowed for target content taxonomy searching, they added a relevancy model, best bests, synonyms, administrative features, reporting, an API we can build customizations to, added security using an access control list (ACL), and business data search using the business data catalog (BDC). All the stuff needed when creating an enterprise search platform. We now had the ability to search for data inside and outside of SharePoint, we could rank the search results based on who you were, we could analyze searches to improve the user experience, etc. however it still seemed to fall short. The core problem I go back to is users are expecting that Google experience; and not just doing text searching. SharePoint tried to solve some of that but in the end it fell short.

One thing that had always been the most interesting is the introduction of the business data catalog (BDC) to provide a single result set of data from multiple disparate data sources. This was the most interesting search feature for me when SharePoint 2007 came out. This is where they tried to become an enterprise search engine because you go to one place, you enter something to search on, and you query against many different places but get back a single result set. I personally was able to use it successfully to index custom SQL databases of HR related data for several clients. So when they searched for a person, they were able to get more information about that person other than just information stored in Active Directory. Now the BDC had lots of limitations including only able to call databases, stored procedures and web services, no ability to do data transformation, an API that was very hard to develop with and had limitied scalability.

With the introduction of FAST as part of the Microsoft stack, they really have a true enterprise search engine. FAST has a significant amount of features and functionality, which I have not even touched upon. In my next blog, I intend to write about some of these core features and capabilities that are needed for an enterprise search solution and how they are used to meet your business users needs to find the data.

For more information on the value proposition of FAST, I have written the following two blogs:

Friday, October 16, 2009

SharePoint GB 2057 Localization

I was recently asked to dig around into an issue with an international SharePoint site we are setting up. I personally have little experience with globalization other having to read about it to pass a MS certification test.

There are language packs for SharePoint which are used to support configurable text for globalization. Well the issue was how is LCID 2057 for England handled? The English language pack supports 1033, which is for US English. LCID 2057 is a considered a sub language of 1033. So, would it be possible to create a unique resx file for GB that maps to 2057? After digging and stumbling around, the answer is it is not possible.

The only resolution would be in the web application set the regional settings to LCID 2057 (GB), and then modify the resx for US English (1033) in that specific web application.

This is what I was able to find out:

  • There is only a language pack for English (1033).
  • It is possible to have formatted text, like dates, formatted to 2057. It is possible to change the locale to 2057 by accessing the SPWeb.Locale. You can try to change the locale through the SharePoint Regional Settings screen in Site Settings, but you will not see a GB option, only US. Another way to change the locale is to go to Webs table in the site collection database; HOWEVER that is not supported by Microsoft.
  • In the Webs table you will see another column called language. What I was able to find out is that the value in this column MUST correspond to a language pack that has been installed. Otherwise SharePoint will bomb. So setting Language = 2057 and Locale = 2057 will not work. However Language = 1033 and Locale = 2057 will work. What this will do is make sure that things like dates are formatted correctly. The reason why it fails is because in several places, including the 12 hive, SharePoint is building a relative path to resources installed when the Language Pack was installed. You will 1033 folders throughout the 12 hive. So if the Language is set to 2057, it will start looking for a 2057 folders and things will start breaking. At this point I said, it would not be possible to create a dedicated unique resx file for GB. Bummer.

Here are some references:

Wednesday, October 14, 2009

Copy SPListItem.Version (SPListItemVersion) Part 3

Background and Considerations

A while back I wrote a blog that discussed the issues with copying SPListItems from one list to another. However I recently needed to create a utility and thought my old blog would solve the problem – I am unhappy to say it did not. It definitely unlocks the issue with copying SPListItems with versions however I just found a couple shortcomings of what I wrote. Let’s try again.

Here are some considerations I had to understand before starting to build this.

  • The SPListItem CopyTo() and CopyFrom() methods do not work after doing some research with Reflector.
  • You will need to need to loop over the versions backwards and add the versions of the list items in the destination list.
  • Moving documents is different than moving list items.
  • Recursively looping over items within a SPList or SPDocumentLibrary is not straight forward. You usually want to maintain the folder structure when moving items from one list to another. You cannot simply loop over all items in the SPList nor does a SPFolder object have a collect of items within it. Only easy way of achieving this is to use a CAML query to get all the items for a specific folder.
  • If you need to preserve the Created and Modified time stamps on the version items, you need to set the times correctly because they are stored as GMT in the SharePoint database.
  • If you want to move items cleanly into a new or existing list, I recommend writing code that will first remove all the items from the destination list, then remove all the content types destination list and finally add the needed content types back into the destination list. There are numbers of reasons why to do this. It is possible to write a routine to reconcile the content types from the source list to the destination list however that can be come complicated. The important thing to know is that if a column is missing in the destination list, the movement of the SPListItem or SPDocument item will fail. The code I have written is not dependant on the content type ID which is a good thing. This is because if the content types are defined within the SharePoint UI a unique GUID is created for that content type. If you are moving items across SharePoint servers, you cannot be guaranteed that the Content Type ID will be the same, but the column names and types should be the same.

Create Copy Folders Structure

I created a method called MoveFolderItems which will recreate the folder structure in a new library. All you need to do initiate it is something like the following.

MoveFolderItems(sourceList, sourceList.RootFolder, destList, destList.RootFolder);

As you can see in this method, it first gets all the items for a specified folder. Then it checks to see if the item is another folder or not. If so, it will create a new folder, otherwise it will move over the item depending.

        private static void MoveFolderItems(SPList sourceList, SPFolder sourceFolder, SPList destList, SPFolder destFolder)
{
//Query for items in the source folder
SPQuery query = new SPQuery();
query.Folder = sourceFolder;
SPListItemCollection queryResults = sourceList.GetItems(query);

foreach (SPListItem existingItem in queryResults)
{
if (existingItem.FileSystemObjectType == SPFileSystemObjectType.Folder)
{
Console.WriteLine(existingItem.Name);

//Create new folder item
SPListItem newSubFolderItem = newSubFolderItem = destList.Items.Add(destFolder.ServerRelativeUrl,
SPFileSystemObjectType.Folder, null);

//Set folder fields
foreach (SPField sourceField in existingItem.Fields)
{
if ((!sourceField.ReadOnlyField) && (sourceField.Type != SPFieldType.Attachments))
{
newSubFolderItem[sourceField.Title] = existingItem[sourceField.Title];
}
}

//Save the new folder
newSubFolderItem.Update();

if (newSubFolderItem.ModerationInformation != null)
{
//Update Folder Status
newSubFolderItem.ModerationInformation.Status = SPModerationStatusType.Approved;
newSubFolderItem.Update();
}

//Get the source folder and the new folder created
SPFolder nextFolder = sourceList.ParentWeb.GetFolder(existingItem.UniqueId);
SPFolder newSubFolder = destList.ParentWeb.GetFolder(newSubFolderItem.UniqueId);

//Recursive call
MoveFolderItems(sourceList, nextFolder,
destList, newSubFolder);
}
else
{
//Move the item
Console.WriteLine(existingItem.Name);

if (sourceList.BaseTemplate == SPListTemplateType.DocumentLibrary)
{
MoveDocumentItem(existingItem, destFolder);
}
else {
MoveItem(existingItem, destFolder);
}
}
}
}

Move SPListItem

Here is the code for the SPList item with its history. First we create the list item. Then we loop over the versions backwards and add each version into the destination list.

            private static void MoveItem(SPListItem sourceItem, SPFolder destinationFolder) {
//Create a new item
SPListItem newItem;

if (destinationFolder.Item != null)
{
newItem = destinationFolder.Item.ListItems.Add(
destinationFolder.ServerRelativeUrl,
sourceItem.FileSystemObjectType);
}
else {
SPList destinationList = destinationFolder.ParentWeb.Lists[destinationFolder.ParentListId];
newItem = destinationList.Items.Add(
destinationFolder.ServerRelativeUrl,
sourceItem.FileSystemObjectType);
}

//loop over the soureitem, restore it
for (int i = sourceItem.Versions.Count - 1; i >= 0; i--) {
//set the values into the new item
foreach (SPField sourceField in sourceItem.Fields) {
SPListItemVersion version = sourceItem.Versions[i];

if ((!sourceField.ReadOnlyField) && (sourceField.Type != SPFieldType.Attachments))
{
newItem[sourceField.Title] = version[sourceField.Title];
}
else if (sourceField.Title == "Created"
sourceField.Title == "Modified")
{
DateTime date = Convert.ToDateTime(version[sourceField.Title]);
newItem[sourceField.Title] = sourceItem.Web.RegionalSettings.TimeZone.UTCToLocalTime(date);
}
else if (sourceField.Title == "Created By"
sourceField.Title == "Modified By")
{
newItem[sourceField.Title] = version[sourceField.Title];
}
}

//update the new item with version data
newItem.Update();
}

//Get the new item again
SPList list = destinationFolder.ParentWeb.Lists[destinationFolder.ParentListId];
newItem = list.GetItemByUniqueId(newItem.UniqueId);
newItem["Title"] = sourceItem["Title"];
newItem.SystemUpdate(false);

if (sourceItem.Attachments.Count > 0)
{
//now get the attachments, they are not versioned
foreach (string attachmentName in sourceItem.Attachments)
{
SPFile file = sourceItem.ParentList.ParentWeb.GetFile(
sourceItem.Attachments.UrlPrefix + attachmentName);

newItem.Attachments.Add(attachmentName, file.OpenBinary());
}

newItem.Update();
}
}

Move Document

As I mentioned earlier, moving a document is a little bit different. Here is the code that will copy a document, metadata and versions over to a new library.

       private static void MoveDocumentItem(SPListItem sourceItem, SPFolder destinationFolder)
{
//loop over the soureitem, restore it
for (int i = sourceItem.Versions.Count - 1; i >= 0; i--)
{
Hashtable htProperties = new Hashtable();

//set the values into the new item
foreach (SPField sourceField in sourceItem.Fields)
{
SPListItemVersion version = sourceItem.Versions[i];

if (version[sourceField.Title] != null)
{
if ((!sourceField.ReadOnlyField) && (sourceField.Type != SPFieldType.Attachments))
{
htProperties[sourceField.Title] = Convert.ToString(version[sourceField.Title]);
}
else if (sourceField.Title == "Created"
sourceField.Title == "Modified")
{
DateTime date = Convert.ToDateTime(version[sourceField.Title]);
htProperties[sourceField.Title] = sourceItem.Web.RegionalSettings.TimeZone.UTCToLocalTime(date);
}
else if (sourceField.Title == "Created By"
sourceField.Title == "Modified By")
{
htProperties[sourceField.Title] = Convert.ToString(version[sourceField.Title]);
}
}
}

//Get the version of the document
byte[] document;
if (i == 0)
{
document = sourceItem.File.OpenBinary();
}
else
{
document = sourceItem.File.Versions.GetVersionFromLabel(
sourceItem.Versions[i].VersionLabel).OpenBinary();
}

//Create the new item. Overwriting it will treat is as a
//new item.
SPFile newFile = destinationFolder.Files.Add(
destinationFolder.Url + "/" + sourceItem.File.Name,
document,
htProperties,
true);

newFile.Item["Created"] = htProperties["Created"];
newFile.Item["Modified"] = htProperties["Modified"];
newFile.Item.UpdateOverwriteVersion();
}

}

Wednesday, October 7, 2009

.NET 4.0 WF Initial Impressions

A couple months ago I was asked some very direct questions about the viability of K2 and other such tools with .NET 4.0 and Dublin. I personally have just not have had lots of time to do go off and research this. However I attended a quick one hour virtual session put on by Microsoft for WF in .NET 4.0.

The big thing I found out is that the State Machine workflow will not available in the initial release of .NET 4.0. That was a big surprise to me. All you will have is Sequential and Flow Chart workflows. The presenter said that you can achieve something similar to a State Machine workflow by doing a Flow Chart workflow. This would lead me to believe that many of the workflow challenges we had with WF in MOSS 2007 have not been resolved.

They talked a little about Workflow Services and I found out that you cannot do as much with Workflow Services than what you can do with WF. I did not get any details on what those specifics were.

A lot of the discussion was about how ISV can use WF to augment their frameworks and even provide the ability to allow customizations into their products using visual tools. This is what I have been preaching for a while now. You cannot adopt WF as the business process automation platform for a company. It does not come anywhere close. It is a framework to build business process automation frameworks.

I have had conversations think where companies believe that since they have SharePoint to host their WF workflows and they believe that is all they need. In the long run your costs will be significantly hirer to maintain, extend upon and manage. I have a personal thing with WF in SharePoint because I do not like the fact that the workflows can only be tied to a piece of content. If a company wanted to do finance or accounting process automation (that would span across enterprise systems) the workflow instance would have to be tied to a SharePoint list item which is not even actor in the process itself. So ask, why do we need this SharePoint list item, it serves no real purpose in the process. Plus if someone deletes the item or the associated task, the process will just end. There is no reporting, and the list goes on.

The point is that WF in MOSS should be used to just manage content in SharePoint. It is not a good platform for human workflow – you really need to look at other tools if you need human workflow. Plus it really does not look like Microsoft is chasing after companies like K2 and Nintex and they should have a healthy future.

Monday, October 5, 2009

IIS 7 Kerberos Configuration

I have seen several questions come up on projects in the past three weeks where teams are trying to configure Kerberos with IIS 7. With IIS 6 we were used to just setting up the SPNs. Now with IIS 7 we have to configure the <windowsAuthententication> node in the applicationHost.config file. If not, it will seem as if Kerberos is just flat out not working.

I have sent this blog to a couple of co-workers (http://sharepointspot.blogspot.com/2008/12/sharepoint-kerberos-on-windows-2008.html) and this got them up and running immediately.

If you want a little background Kerberos configuration in general – read this blog I wrote - http://www.k2distillery.com/2009/04/k2-blackpearl-kerberos-configuration.html. Most of the content is slighted towards K2 configuration with Kerberos however it will help you if you never done it before.

This blog (http://blogs.msdn.com/martinkearn/archive/2007/04/23/configuring-kerberos-for-sharepoint-2007-part-1-base-configuration-for-sharepoint.aspx) is probably the most well known blog on Kerberos for MOSS. This guy basically shows you all of the Kerberos commands that you need to run for all the SharePoint service accounts that you may create for your SharePoint farm.

As well, Kerberos configuration comes up a lot with the configuration of SSRS and MOSS. Here is a good article that explains it (http://msdn.microsoft.com/en-us/library/bb283324.aspx).

Saturday, October 3, 2009

Embed and Deploy User Control in SharePoint Web Part

1.0 Introduction

Several months I go I had some colleagues mention to me that it is possible load an ASP.net user control into a SharePoint web part. You may be asking why would I want to or consider doing that. There are some important reasons.

  • Your company may already have a large investment in standard ASP.net user controls and you do not want to have to rewrite them as a SharePoint web part.
  • ASP.net user controls can be easily embedded into other custom web applications.
  • SharePoint web part development can be challenging at times to build up a rich user interface. Using an ASP.net user control, you can build and test that code outside of the SharePoint context. I believe that most of this has to do with short comings of Visual Studio as an Integrated Development Environment (IDE) for SharePoint. We expect great things soon…

A popular code project call SmartPart is out there which many people have used to load user controls into a web part. Greg Galipeau referred me to blog which discussed many short comings of that project. Upon reading that, I knew I would never us it for a client and that it is really not that hard to create your own SmartPart web parts.

As I have discussed in the past, I am a huge proponent of:

  1. Creating SharePoint deployment projects that deploy everything in a solution and Feature.
  2. Anything that is deployed to SharePoint runs under minimal trust.

In this article I plan to show you how to create a .Net project that builds a .NET user control and web part, how to deploy the solution and best practices I learned along the way.

2.0 Creating the SharePoint Projects

There are basically two projects we need to create. The first if for the .NET user control and the second is for the ASP.net web part which will load the user control. The process I am going to take you through is:

  1. Build the ASP.net Project by itself.
  2. Create a Web Part Project.
  3. Then show you the modifications to integrate the user control into the web part.

2.1 Creating the ASP.net Project

First create the project for the ASP.net control. This is as simple as creating an ASP.net project. Here is a screen shot of the project that I created.

image

There is really nothing special about it:

  • I left the Default.aspx so that I can use it for testing the SmartControl.
  • I please the SmartControl in a UserControls folder. No specific reason other than come practice.

Here is the code from SmartContro.ascx. Note I only have a simple label we will use for testing purposes.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="SmartControl.ascx.cs" Inherits="MOSSDistillery.SmartControl.UserControl.UserControls.SmartControl" %>
<asp:Label ID="lblHelloWorld" runat="server" Text=""></asp:Label>

Here is the code behind for SmartControl.ascx.cs. The only interesting thing I have done here is created a method for changing the color of the text of the label. In the example later on, I will show how this can be set from the web part’s configuration. The point of this is to show how to pass data into the user control.

public partial class SmartControl : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
lblHelloWorld.Text = "Hello World";
lblHelloWorld.ForeColor = System.Drawing.ColorTranslator.FromHtml("#FFFF00");
}
}

2.2 Creating the Web Part Project

Second create the project for the web part. I know that I could use WSPBuilder to make my life easier however I have found that it is really not that hard to build a Feature and web part. In my blog on how to create web part, I provided the exact steps on how to project for a web part project. Please go there and complete the instructions, as that is what I have done.

Here is my resulting project as well as the code for my web part project using the instructions I have in this blog.

image

SmartWebPart.cs

public class SmartWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
protected override void Render(HtmlTextWriter writer) {
base.Render(writer);
writer.WriteLine("Hello World");
}
}

MOSSDistillery.SmartControl.WebPart.SimpleWebPart.webpart

<webParts>
<webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
<metaData>
<type name="MOSSDistillery.SmartControl.WebPart.SmartWebPart, MOSSDistillery.SmartControl.WebPart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e652952dcf5e6363" />
<importErrorMessage>Cannot import MOSSDistillery.SmartControl.WebPart.SmartWebPart</importErrorMessage>
</metaData>
<data>
<properties>
<property name="Title" type="string">My Smart Web Part</property>
<property name="Description" type="string">My Smart Web Part Demonstration.</property>
</properties>
</data>
</webPart>
</webParts>

Feature.xml

<?xml version="1.0" encoding="utf-8" ?>
<Feature Id="21E3F9D4-6DC2-4042-A873-C3440127476F"
Title="My Smart Web Part"
Description="My Smart Web Part Demonstration."
Version="1.0.0.0"
Scope="Site"
Hidden="FALSE"
DefaultResourceFile="core"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="elements.xml" />
<ElementFile Location="MOSSDistillery.SmartControl.WebPart.SimpleWebPart.webpart"/>
</ElementManifests>
</Feature>

elements.xml

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Module Name="WebParts" List="113" Url="_catalogs/wp">
<File Url="MOSSDistillery.SmartControl.WebPart.SimpleWebPart.webpart" Type="GhostableInLibrary" />
</Module>
</Elements>

manifest.xml

<Solution xmlns="http://schemas.microsoft.com/sharepoint/" SolutionId="1B69F425-BCC3-4de0-BC2F-A84B1168F84A">
<FeatureManifests>
<FeatureManifest Location="MOSSDistillery.SmartControl.WebPart\Feature.xml"/>
</FeatureManifests>
<Assemblies>
<Assembly Location="MOSSDistillery.SmartControl.WebPart\MOSSDistillery.SmartControl.WebPart.dll" DeploymentTarget="GlobalAssemblyCache" >
<SafeControls>
<SafeControl Assembly="MOSSDistillery.SmartControl.WebPart.SmartWebPart, MOSSDistillery.SmartControl.WebPart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e652952dcf5e6363"
Namespace="MOSSDistillery.SmartControl.WebPart"
Safe="True"
TypeName="*"/>
</SafeControls>
</Assembly>
</Assemblies>
<CodeAccessSecurity>
<PolicyItem>
<Assemblies>
<Assembly PublicKeyBlob="..." />
</Assemblies>
<PermissionSet class="NamedPermissionSet" Name="MOSSDistillery.SmartControl.WebPart" version="1" Description="MOSSDistillery.UserControl.WebPart">
<IPermission class="AspNetHostingPermission" version="1" Level="Minimal" />
<IPermission class="SecurityPermission" version="1" Unrestricted="true" />
<IPermission class="WebPartPermission" version="1" Connections="True" />
<IPermission class="Microsoft.SharePoint.Security.SharePointPermission, Microsoft.SharePoint.Security, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" version="1" Unrestricted="true" />
</PermissionSet>
</PolicyItem>
</CodeAccessSecurity>
</Solution>

WSP.ddf

.OPTION Explicit
.Set CabinetNameTemplate="MOSSDistillery.SmartControl.WebPart.wsp"
.Set DiskDirectory1="C:\MOSSDistillery\SmartControl\MOSSDistillery.SmartControl\MOSSDistillery.SmartControl.WebPart\Deployment"

manifest.xml

.Set DestinationDir="MOSSDistillery.SmartControl.WebPart"
%outputDir%MOSSDistillery.SmartControl.WebPart.dll
TEMPLATE\FEATURES\MOSSDistillery.SmartControl.WebPart\elements.xml
TEMPLATE\FEATURES\MOSSDistillery.SmartControl.WebPart\Feature.xml
TEMPLATE\FEATURES\MOSSDistillery.SmartControl.WebPart\MOSSDistillery.SmartControl.WebPart.SimpleWebPart.webpart

.Delete outputDir

2.3 Preparing for Project Deployment

Now that we have both the projects created, we need to complete the following steps to integrate them so that we can display the user control within the web part.

2.3.1 Sign the ASP.net Project

First we need to sign the ASP.net project with the user control because it will be deployed to the GAC.

2.3.2 Get the Public Key Token

Then you will need to run the following command like we did for the web part project so we can get the public key token.

sn -Tp "C:\MOSSDistillery\SmartControl\MOSSDistillery.SmartControl\MOSSDistillery.SmartControl.UserControl\bin\MOSSDistillery.SmartControl.UserControl.dll"

2.3.3 Modify SmartControl.ascx

First add the Assembly tag to the user control. This is so the web control can reference the dll that will be deployed to the GAC. Notice we used the public key token we created in the previous step. Second, I removed the CodeBehind attribute in the Control element.


<%@ Assembly Name="MOSSDistillery.SmartControl.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=367c68a97c663918"%>
<%@ Control Language="C#" AutoEventWireup="true" Inherits="MOSSDistillery.SmartControl.UserControl.SmartControl" %>
<asp:Label ID="lblHelloWorld" runat="server" Text=""></asp:Label>

2.3.4 Change the User Control Code

All I did was create some properties which set the text and the color. We will set these from the SharePoint web part.

public partial class SmartControl : System.Web.UI.UserControl
{
private string _text = string.Empty;
private string _color = string.Empty;

public string Color
{
get
{
return _color;
}
set
{
_color = value;
}
}

public string Text
{
get
{
return _text;
}
set
{
_text = value;
}
}

protected void Page_Load(object sender, EventArgs e)
{
lblHelloWorld.Text = _text;
lblHelloWorld.ForeColor = System.Drawing.ColorTranslator.FromHtml(_color);
}
}


2.3.5 Modify the Web Part

Now I have to modify the web part to set the properties of my user control. The core of this solution is the code I put in the CreateChildControls() method.

public class SmartWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
private string _error = string.Empty;
private string _color = string.Empty;
private string _text = string.Empty;

[Personalizable(PersonalizationScope.Shared),
WebBrowsable(true),
WebDisplayName("Text"),
WebDescription("Text"),
Category("Custom")]
public string Text
{
get
{
return _text;
}
set
{
_text = value;
}
}

[Personalizable(PersonalizationScope.Shared),
WebBrowsable(true),
WebDisplayName("Color"),
WebDescription("Color"),
Category("Custom")]
public string Color
{
get
{
return _color;
}
set
{
_color = value;
}
}

protected override void Render(HtmlTextWriter writer) {
base.Render(writer);
writer.WriteLine(_error);
}

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

if (string.IsNullOrEmpty(_text))
{
throw new Exception("No text has not been set");
}

if (string.IsNullOrEmpty(_color))
{
throw new Exception("Color has not been set");
}

string path = "~/_controltemplates/MOSSDistillery.SmartControl.UserControl/SmartControl.ascx";
MOSSDistillery.SmartControl.UserControl.SmartControl control =
(MOSSDistillery.SmartControl.UserControl.SmartControl)Page.LoadControl(path);

control.Text = _text;
control.Color = _color;

Controls.Add(control);
}
catch (Exception ex)
{
_error = ex.Message + " " + ex.InnerException;
}
}
}

One important note, notice that I hard coded the path to the user control. If I wanted to make this a generic web part that would have the ability to load any ASP.net user control, I would instead make the following changes. The problem with this approach is that configuration values from the web part could not be set into the user control. It possible to use the web.config but again in this case it did not make sense because a web part like this could be used in lots of places. In each of those places the color or text may be different so a web.config setting would not work. It could be possible to create some code that uses reflection to set the properties of ASP.net user control but I really did not want to create anything that complex yet.

string _path = "";

[Personalizable(PersonalizationScope.Shared),
WebBrowsable(true),
WebDisplayName("User Control Path"),
WebDescription("User Control Path"),
Category("Custom")]
public string UserControlPath {
get {
return _path;
}
set {
_path = value;
}
}
protected override void CreateChildControls()
{
try
{
base.CreateChildControls();

if (string.IsNullOrEmpty(_path))
{
throw new Exception("Path has not been set");
}

System.Web.UI.UserControl control = (System.Web.UI.UserControl)Page.LoadControl(_path);
Controls.Add(control);
}
catch (Exception ex)
{
_error = ex.Message + " " + ex.InnerException;
}
}

2.3.6 Change Manifest.xml

Next we need to make the following changes to the manifest.xml. Basically we need to incorporate the user control into the deployment.

  1. We add the UserControl as a new Assembly element. It is important to add the SafeControls element for the UserControl so that it will be marked as a safe control in the web.config.
  2. We add TemplateFiles element which will place the ascx control in the CONTROLTEMPLATES folder in the 12 hive. Notice that the location has “MOSSDistillery.SmartControl.UserControl”. This will create a folder called “MOSSDistillery.SmartControl.UserControl” and will place the ascx control in that folder. This is important so your user controls do not get intermingled with the out of the box MOSS user controls.
<Solution xmlns="http://schemas.microsoft.com/sharepoint/" hSolutionId="1B69F425-BCC3-4de0-BC2F-A84B1168F84A">
<FeatureManifests>
<FeatureManifest Location="MOSSDistillery.SmartControl.WebPart\Feature.xml"/>
</FeatureManifests>
<Assemblies>
<Assembly Location="MOSSDistillery.SmartControl.WebPart\MOSSDistillery.SmartControl.WebPart.dll" DeploymentTarget="GlobalAssemblyCache" >
<SafeControls>
<SafeControl Assembly="MOSSDistillery.SmartControl.WebPart.SmartWebPart, MOSSDistillery.SmartControl.WebPart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e652952dcf5e6363"
Namespace="MOSSDistillery.SmartControl.WebPart"
Safe="True"
TypeName="*"/>
</SafeControls>
</Assembly>
<Assembly Location="MOSSDistillery.SmartControl.UserControl\MOSSDistillery.SmartControl.UserControl.dll" DeploymentTarget="GlobalAssemblyCache">
<SafeControls>
<SafeControl Assembly="MOSSDistillery.SmartControl.UserControl.SmartControl, MOSSDistillery.SmartControl.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=367c68a97c663918"
Namespace="MOSSDistillery.SmartControl.UserControl"
Safe="True"
TypeName="*"/>
</SafeControls>
</Assembly>
</Assemblies>
<TemplateFiles>
<TemplateFile Location="CONTROLTEMPLATES\MOSSDistillery.SmartControl.UserControl\SmartControl.ascx"/>
</TemplateFiles>
<CodeAccessSecurity>
<PolicyItem>
<Assemblies>
<Assembly PublicKeyBlob="..." />
</Assemblies>
<PermissionSet class="NamedPermissionSet" Name="MOSSDistillery.SmartControl.WebPart" version="1" Description="MOSSDistillery.UserControl.WebPart">
<IPermission class="AspNetHostingPermission" version="1" Level="Minimal" />
<IPermission class="SecurityPermission" version="1" Unrestricted="true" />
<IPermission class="WebPartPermission" version="1" Connections="True" />
<IPermission class="Microsoft.SharePoint.Security.SharePointPermission, Microsoft.SharePoint.Security, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" version="1" Unrestricted="true" />
</PermissionSet>
</PolicyItem>
</CodeAccessSecurity>
</Solution>


2.3.7 Modify the WSP.ddf

Finally we needed to make a couple modifications to bring in the ASP.net User Control. You can see that I have pulled in both SmartControl.dll and SmartControl.ascx. Both of the paths to those files match the paths specified in the manifest.xml.

.OPTION Explicit
.Set CabinetNameTemplate="MOSSDistillery.SmartControl.WebPart.wsp"
.Set DiskDirectory1="C:\MOSSDistillery\SmartControl\MOSSDistillery.SmartControl\MOSSDistillery.SmartControl.WebPart\Deployment"

manifest.xml

.Set DestinationDir="MOSSDistillery.SmartControl.WebPart"
%outputDir%MOSSDistillery.SmartControl.WebPart.dll
TEMPLATE\FEATURES\MOSSDistillery.SmartControl.WebPart\elements.xml
TEMPLATE\FEATURES\MOSSDistillery.SmartControl.WebPart\Feature.xml
TEMPLATE\FEATURES\MOSSDistillery.SmartControl.WebPart\MOSSDistillery.SmartControl.WebPart.SimpleWebPart.webpart

.Set DestinationDir="MOSSDistillery.SmartControl.UserControl"
..\MOSSDistillery.SmartControl.UserControl\bin\MOSSDistillery.SmartControl.UserControl.dll

.Set DestinationDir="CONTROLTEMPLATES\MOSSDistillery.SmartControl.UserControl"
..\MOSSDistillery.SmartControl.UserControl\UserControls\SmartControl.ascx

.Delete outputDir


3.0 Conclusions

That is it; you can now see how easy it is to deploy a user control to SharePoint and load it into a web part. The great thing about this is that you can do development of web parts significantly more quickly.

4.0 References