Thursday, January 29, 2009

Using ASCX in WebParts and UserControls in SharePoint

I was explaining my technique of using User Controls in MOSS to someone today and they said I should blog about it. I figured there would be many people who have but a quick Google search didn't reveal as many results as I thought. So here goes:

In order to get around the issue of not being able to have pages with code behind in them, I have been using User Controls. Over the years I have adopted one way of developing web parts and user controls for which the code lives in DLLs but the layout still exists in ascx files. This may not be the only or best way to accomplish this but it is a way that has always worked for me.

On many of my assignments I receive HTML from a web designer and my job is to implement the functionality. I like working this way. As long as I ensure that the HTML my code ends up rendering is the same as the HTML the designer delivered in her static page, I know the page will look great. This means I usually start by taking the HTML and translating it into ASP controls.

So for example I may receive the following snippet from the designer:

<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>


I know that item 1 and item 2 are just an example, and the values here will need to dynamically come from a SharePoint list for instance. So I translate this to a repeater (my favourite ASP.NET cotnrol):

<asp:Repeater ID="MyRepeater" runat="server">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li><asp:Literal ID="litItemValue" runat="server"></asp:Literal></li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>


This way I know that the rendered HTML will be exactly what the designer intended and thus the page rendering will not be broken.

Now that I have the ascx ready, I want to use it in a user control or a web part. My next step then is to create a class in a Class library and have this class dynamically load the ascx into a user control object. I do this by calling the Page.LoadControl() method as seen in this code:

public class MyWebPart : WebPart
{
private const string ControlPath = "/_controltemplates/MyApplication/";

private UserControl myControl;
private Repeater myRepeater;

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

myControl = (UserControl)Page.LoadControl(ControlPath + "MyControl.ascx");
Controls.Add(myControl);

myRepeater = (Repeater)myControl.FindControl("MyRepeater");
}

protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
EnsureChildControls();

myRepeater.ItemDataBound += new RepeaterItemEventHandler(myRepeater_ItemDataBound);
myRepeater.DataSource = GetData();
myRepeater.DataBind();
}

private object GetData()
{
// create a proper data source here
object myReturnValue = new object();
return myReturnValue;
}

void myRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
// do your stuff here
}
}


The Page.LoadControl is the crucial part, but there are a few other bits that I'd like to explain here.

First there is the issue of the path that the control is being loaded from. This path is a virtual directory path, and I like to use the already existing virtual directory created by MOSS called _controltemplates. Seems like the right place to put control templates. Just to keep things neat I like to have a folder here for each application. Note that this virtual directory corresponds to the physical directory {Location of 12 Hive}\TEMPLATE\CONTROLTEMPLATES on your MOSS server(s) so if you place you controls here, things will work. I like to deploy my control templates using MOSS solution files but that's another post.

The second thing to note is that you have to add the UserControl object to the this.Controls collection. If you forget this, your web part will not render anything and you may get null reference exceptions.

Third, make sure you get the ID correct when using the FindControl method. Doing the lookup based on a string ID works fine, but if you have the wrong ID in here you will not know until you get a nasty null reference exception at runtime.

Last, remember to call the EnsureChildControls() before you start using any of the controls in your template. Otherwise you may get yet another null reference exception.

To get this all finally working, build your class library, register is as safe in the web.config file and add it to the bin folder of the MOSS application or the GAC. You should then be able to use your web part or user control.

Good luck.

4 comments:

paul.marriott said...

superb post

I previously tried to add a Usercontrol to a webpart but the deployed ascx was not found. Your example and explanation fixed my problem, good work!

BPO Services said...

I think its a great blog.

Mehul Parekh said...

This solution is not working for me if I register the assembly in GAC. I need to put that in solution's bin folder of the solution. Following is the path of bin directory on my WSS server.

C:\Inetpub\wwwroot\wss\VirtualDirectories\2222\bin

Is the proposed solution works only with MOSS?

Please guide me.

Joe said...

@Mehul: This solution should work just fine when your assembly is in the GAC, what is the error that you get? Also, this technique works with 2010. If you use the "Visual Web Part" SPI in Visual Studio 2010, this is the technique used underwater.