views:

761

answers:

1

Hello!

I'm trying to re-use my ASP User Controls in other projects, and have hit upon a problem. I've read ScottGu's post on http://weblogs.asp.net/scottgu/archive/2005/08/28/423888.aspx and tried to follow his patterns but run-time the child controls of my controls are not being initialised.

My setup: I'm using Visual Studio 2008 I've created a solution to hold the controls I want to distribute to several other solutions. This solution contains:

  • library projects (for model code, etc.), let's call this "ClassLibrary"
  • a WebApp project (with my User Controls), let's call this "Innermost"
  • a number of other WebApp projects acting as Shells for the Innermost project, let's regard one of them and call it "Outer"

The thought behind this is that Innermost contains the functionality and Outer contains any special modifications needed to make Innermost -controls work in a particular framework.

The ClassLibrary has code like this (this is all from a demo-setup I made to make sure it was an ASP issue, not something else I was doing):

using System;

namespace ClassLibrary {
    public class LibraryClass {
     public long getLong() {
      return DateTime.Now.Ticks;
     }

     public string getString() {
      return DateTime.Now.ToString();
     }
    }
}

Innermost references the library project and works fine. This is an example of a UserControl in this project:

<%@ Control Language="C#" AutoEventWireup="true" 
    CodeBehind="WebUserControl1.ascx.cs" 
    Inherits="Innermost.UserControls.WebUserControl1" %>
<asp:Label ID="testLabel" runat="server"/>

with code-behind:

using System;
using System.Web.UI;
using ClassLibrary;

namespace Innermost.UserControls {
    public partial class WebUserControl1 : UserControl {
     protected void Page_Load(object sender, EventArgs e) {
      LibraryClass c = new LibraryClass();
      testLabel.Text = "Your number is " + c.getLong();
     }
    }
}

I have a test page in the Innermost project which uses the User Controls of the Innermost project by registering them with the following syntax:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Test1.aspx.cs" 
    Inherits="Innermost.Test1" %>

<%@ Register TagPrefix="inner" TagName="Control1" 
    Src="~/UserControls/WebUserControl1.ascx" %>
<%@ Register TagPrefix="inner" TagName="Control2"
    Src="~/UserControls/WebUserControl2.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"&gt;
<head runat="server">
    <title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
 Control 1:
 <inner:Control1 ID="control1" runat="server" />
 <br />
 Control 2:
 <inner:Control2 ID="control2" runat="server" />
</div>
</form>
</body>
</html>

The Outer project references Innermost and ClassLibrary; and contains UserControls with the following registering of the Innermost controls:

<%@ Control Language="C#" AutoEventWireup="true" 
    CodeBehind="TopControl.ascx.cs" Inherits="Outer.Control.TopControl" %>
<%@ Register TagPrefix="inner" Assembly="Innermost"
    Namespace="Innermost.UserControls" %>
<inner:WebUserControl1 ID="Control1" runat="server"/>
<inner:WebUserControl2 ID="Control2" runat="server"/>

With a simple code-behind: using System; using System.Web.UI;

namespace Outer.Control {
    public partial class TopControl : UserControl {
     public bool ShowType1 { get; set; }

     protected void Page_Load(object sender, EventArgs e) {
      Control1.Visible = ShowType1;
      Control2.Visible = !ShowType1;
     }
    }
}

When I run the test-page in the Innermost project I get no errors, and everything works. When I run the Outer project I get NullReferenceExceptions telling me that the child controls of the Innermost.UserControls.WebUserControlX have not been set.

StackTrace:

[NullReferenceException: Object reference not set to an instance of an object.]
   Innermost.UserControls.WebUserControl1.Page_Load(Object sender, EventArgs e) in c:\Test\ASP\Innermost\Innermost\UserControls\WebUserControl1.ascx.cs:9
   System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +14
   System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +35
   System.Web.UI.Control.OnLoad(EventArgs e) +99
   System.Web.UI.Control.LoadRecursive() +50
   System.Web.UI.Control.LoadRecursive() +141
   System.Web.UI.Control.LoadRecursive() +141
   System.Web.UI.Control.LoadRecursive() +141
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +627

I can provide the test setup as solutions if anyone wants to have a closer look at this.

So, finally, my question is: Why do the ChildControls of the controls not get set up as expected? Even if I run CreateChildControls() in the Init of the outer controls they aren't being created. Is there some particular way I need to compile the Innermost project for use in the Outer project?

Any help would be much appreciated!

+2  A: 

I haven't every tried this setup before, but I can take a guess at what is going on. UserCotnrols are meant to be loaded by path, not by reference. You are attempting to create them by reference. Notice the difference between how you refer to them in the inner project and how it differs from the outer project. I believe to get this to work you will need to refer to the path location of the .ascx file.

A quick search shows how this can be done in VS 2005 but the concept should be the same in VS 2008. You can find that information here.

Another option would be to not use the design surface and instead load the controls using the LoadControl method, passing in the path to the .ascx files.

Edit The important thing to take from the link I mention above is that you must make the .ascx files accessable for your outer app. The above link does this by using a pre-build command to copy the .ascx files to a sub folder in the outer project. Think of the inner project as if you were going to redistribute it. You would have to give away not only the assembly, but also the .ascx files. For users to consume it they must reference the .ascx files and have their project reference your assembly. You must do the same thing as well.

I hope that is a little clearer.

Daniel
If controls live in different applications, as it appears to be, the LoadControl will throw an error: The virtual path ... maps to another application, which is not allowed.
Ruslan