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

No comments: