How to update design-time UserControl interface based on property value? - asp.net

I've created a UserControl with the following public property:
[Browsable(true)]
public string Text
{
get { return pnlLookupTable.GroupingText; }
set { pnlLookupTable.GroupingText = value; }
}
pnlLookupTable is, as you may have guessed, a Panel control. I can change the value of the Text property in the Properties window and it's reflected in the markup like it should be. However, the design view of the UserControl inside a page does not show updated GroupingText for the Panel. How can I get this to happen?
EDIT:
By request, here is the entire class to which that property belongs. You can see there's nothing special going on:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class LookupTable : System.Web.UI.UserControl
{
[Browsable(true)]
public string Text
{
get { return pnlLookupTable.GroupingText; }
set { pnlLookupTable.GroupingText = value; }
}
}
And here's the relevant part of the .ascx file:
<%# Control Language="C#" AutoEventWireup="true" CodeFile="LookupTable.ascx.cs" Inherits="LookupTable" %>
<asp:Panel ID="pnlLookupTable" runat="server" GroupingText="Lookup Table">
<%-- Irrelevant content here. --%>
</asp:Panel>
I have changed some identifiers and other trivial things to protect the proprietary nature of the code.
I should also reiterate that I'm looking at a Web form in design mode that has my control added, and I'm changing the Text property. I want to see the change to the Panel's GroupingText visually in the designer.

I'd have to think back a bit to the UserControlDesigner code...
Short story: I don't think it's possible.
And here's the long story:
From what I recall, User Controls located in ASCX files are never run in the designer. That is, the code inside the ASCX or ASCX.CS file is never compiled or run in Visual Studio. This is to prevent memory leaks caused by the fact that in the CLR you cannot unload assemblies that you have loaded. In order to run the code in your User Control, Visual Studio would have to compile your ASCX into a DLL, then load it, and then run the code. Every time you make a change to the ASCX, it would have to perform this operation again. Every time this operation happens more memory will be consumed by the additionally loaded DLL generated from your ASCX.
Because of this limitation in the CLR the User Control designer doesn't actually compile or run the ASCX file. Instead, it parses the ASCX file and looks for controls inside it and it loads those controls instead. For each control it finds in the ASCX file it will create the associated control designer and render that control's design time HTML.
There are a couple of ways to work around this:
Instead of using an ASCX user control you can write a regular custom control that derives from Control and the code is written in a CS or VB file.
Compile the ASCX into a DLL. David Ebbo wrote a blog post on how to do this.
The reason that these two solutions should work is that they both involve having the code compiled into a DLL. The idea is that the DLL doesn't change very often so it is safe for Visual Studio to load the DLL without risk of having to reload it for each time the DLL changes (and leak memory).

I thought for sure I had an answer for this, and wrote it all out, but something was bugging me about it so I ended up testing this out for a few hours.
Turns out that (just like Eilon said), I don't think you can do this.
ASCX controls completely ignore the DesignerAttribute, so you can't specify a custom design time renderer for them. I thought "fine, I can subclass the Panel control and specify a new designer on it that will get properties from the parent control". Guess what? No chance. If you access the Panel's parent control in the designer, it will not cast to the custom usercontrol type. I can see that it's a UserControl, and it can't possibly be any other UserControl except my TestUserControl, but the designer class throws an exception if I try to cast it!
This blows my mind to be honest. It's basically exactly what Eilon is saying - there is an intentional restriction on web usercontrols in design mode.
Here's a vague post from Steven Cheng suggesting that design time support is just a no-go for web usercontrols.
While this was a fun learning opportunity for me, I'm sorry to report that I don't think an .ascx will cut it. I'm pretty sure that even compiling it to a DLL like Eilon suggested won't even do it. You'll probably have to go the custom control route.

Related

How are custom controls instantiated in asp.net?

My understanding is that when a component or custom control is defined in an aspx page using the <%Register%> tag, it is declared in an auto-generated designer.cs (C#) file by the compiler. If this custom control is never used in the aspx page, does this still happen in the designer.cs file?
Assuming the control is used in the aspx page, what mechanism then instantiates this control, how is it new-ed up behind the scenes? The designer file only declares it. Thanks much, and if there are good articles out there discussing this I’d be happy to read them.
This may not answer all your questions, but some of them. Rick Strahl wrote a great article a while back on compilation and deployment that describes how it works:
Compilation and Deployment in ASP.NET 2.0
I added part of the article that I think relates most to your question below:
Referencing other Pages and Controls
Remember that page and control compilation happens on a per directory basis! So referencing other pages and controls becomes a little more tricky for ASP.NET 2.0, because you can no longer assume that a CodeBeside class from another page or control is available in the current assembly. At best all pages and controls in the same directory end up in the same assembly, at worst each page or control gets its own assembly and they know nothing about each other.
If you need to reference another page from a control or another page you need to explicitly import it with the #Reference directive. Again this is different than ASP.NET 1.1 where all CodeBehind classes were immediately available to your entire Web application. In ASP.NET 2.0 an explicit assembly reference is required to load it.
Assume for a minute that you have the DataEntry.aspx page I showed earlier and you want to create a second page that uses the same CodeBeside class so you can reuse the page logic, but change the page layout in DataEntry2.aspx by changing a few colors and moving around the controls of the page. In essence you want to have two ASPX pages reference the same CodeBeside file.
Here’s how to do this:
<%# Reference Page="~/DataEntry.aspx" %>
<%# Page Language="C#" AutoEventWireup="true" Inherits="DataEntry" %>
I’m leaving out the CodeFile attribute reference the CodeBeside class of the DataEntry page, and add the #Reference tag to the page to force the CodeBeside class to be imported.
The same is true with any User Control definitions. To import a user control you need to use the #Register tag, which imports the assembly that the control lives in. ASP.NET is smart during compilation and figures out exactly where related assemblies live based on how the project is compiled. If the control or page lives in the same assembly no reference is actually added. But if it is external – in another directory for example, then the assembly reference is added.
Referencing problems
If you can explicitly reference other pages and controls in your markup pages, then all works well and as expected. But if you dynamically load controls or reference pages dynamically in your code, things get a lot more complicated.
The most common problem I run into is dynamic loading of controls. In ASP.NET 1.x you might have run code like this for dynamically loading controls into a page:
public partial class DynamicControlLoading : System.Web.UI.Page
{
protected CustomUserControl MessageDisplay = null;
protected void Page_Load(object sender, EventArgs e)
{
MessageDisplay = this.LoadControl( "~/UserControls/CustomUserControl.ascx") as CustomUserControl;
this.Controls.Add(MessageDisplay);
}
protected void btnSay_Click(object sender, EventArgs e)
{
this.MessageDisplay.ShowMessage(this.txtMessage.Text);
}
}
CustomUserControl in this case is a simple User Control that lives in another directory and is loaded dynamically at runtime. Further assume that you truly dynamically want to load this control so you may have a choice of several controls, or the end-user might even create a custom control that gets dropped into place instead.
If you run the code above in ASP.NET 2.0 it will likely fail. I say likely because there are some inconsistencies that will sometimes pick up control references automatically, for example if the user control lives in the same directory and gets compiled into the same assembly as the page, or if another page has the control referenced.
It should and usually will fail. Why? Because ASP.NET compiles on a directory level and the CustomUserControl lives in a separate directory and so goes into a separate assembly. It’s not visible to page class to get a strongly typed reference. Intellisense will show a big, fat and red exclamation point or nothing at all for the MessageDisplay control. When you run the page it will bomb.
You can reference the control as the Control type of course, but if you need to access any custom properties on the user control beyond Control properties you can’t unless you resort to Reflection. As far as I know there’s no way to add a reference to another user control or page programmatically because the reference needs to be available way earlier at compile time before your code ever runs.
Alternatives are to not load controls dynamically or at least provide some mechanism to load up any user controls beforehand on a page with the appropriate #Register tags. But that’s not always possible. The other option is to create a user control base class in APP_CODE and expose the public interface there. The main problem with this is that this base class will not have access to any internal controls of the user control and so the base class would have to use FindControl to reference any embedded controls. So this is inefficient as hell, and cumbersome to boot.
I’ve run into similar situations with inheritance scenarios. For example, inheriting one master page off another’s CodeBeside class. All works well, but the ASP.NET compiler complains that the Profile object is being overridden illegally (a compiler warning). Running with the inherited master page works, but there are quirks. User Controls added to the master page often fail with type conflicts as ASP.NET treats the user control added to the base page as a different type than the user control added to the second page.
It’s inconsistencies like these that deal with referencing other types that have made me waste an incredible amount of time, thinking I had something fixed only to find out later that it didn’t actually work consistently when I changed a completely different page. Worse you have to really understand the model to get your head around what might be wrong.
Bottom line: The overall ASP.NET 2.0 compilation model is internally complex. Most of the time you don’t need to understand it, but when you run into these boundary scenarios, you really DO have to understand what goes on behind the scenes to be able to work around the quirks.

Does ASP.NET use reflection to check for existance of event handlers?

I create an asp.net page e.g. default.aspx.
I then define a button...
<asp:Button ID="btnNew" runat="server" Text="New" OnClick="btnNew_OnClick" />
... however I do not define the handler for btnNew_OnClick in code.
ASP.Net will tell me this when I start the page and throw an exception.
Therefore, does it use reflection to check if the class that implements my page has this method ?
Is this efficient if it has to do this each time it parses a page's markup?
Not specifically. This happens when ASP.NET compiles your ASPX markup. ASPX markup is compiled the first time the page is hit on the fly, and stored somewhere in C:\WINDOWS\Microsoft.NET\Framework\vX\Temporary ASP.NET Files.
The exception to that is if you precompile your pages using aspnet_compiler.exe. However, if you were to pre-compile it you'd see there error there, not when you hit the site.
Is this efficient if it has to do this each time it parses a page's markup?
ASP.NET isn't parsing the markup on every page view and post back; it's only parsing it once when it's compiled. It stored a hash of the page (usually called hash.web somewhere in Temporary ASP.NET Files) and compares the hashes. If the hash is different (the page changed) then it recompiles it. Here is an example of what that compiled code may look like:
#line 58 "C:\X\UserControls\FilterControl.ascx"
#__ctrl.Click -= new System.EventHandler(this.btnApply_Click);
#line default
#line hidden
#line 58 "C:\X\UserControls\FilterControl.ascx"
#__ctrl.Click += new System.EventHandler(this.btnApply_Click);
This of course gets compiled into an executable assembly. Effectively, what the ASPX compiler is doing is compiling the server side markup into C# code, then compiler that into an assembly.
ASP.NET actually generates a class descending from your page using the ASPX markup as a template of sorts. You can find the generated classes source code in the folders under %WINDIR%\Microsoft.NET\v[FRAMEWORK VERSION]\Temporary ASP.NET File.
My understanding is it generates a class which instantiates the control and wires the event handlers to whatever method applies. This is why things break badly when you, say, make your event handler private -- the descendant class can't access it.
The code generation is pretty expensive up-front; it is partially responsible for that lengthy asp.net warm-up most of us asp.net developers suffer through. But it is very, very efficient once the app gets warmed up as everything is rendering via compiled code.
No it does not use reflection. As #Wyatt Barnett said, the compiler generates code for this. The generated code is the same as if you would register the event yourself.
btnNew.Click += new EventHandler(btnNew_Click);
As the code for the markup is generated it is the same regarding performance, maybe except for the first call.

User Controls Not seeing the Day of Light - Doesn't recognize code-behind methods

This is driving me absolutely nuts.
I created a new WAP project in VS 2008. Copied over the files in my Web Site Project. Added any required references. Tried to convert the Web Project to a Web Application using the "Convert to web application".
None of my user controls are able to see methods in their code behind. They don't even see them so I get errors everywhere saying it doesn't know what this or that method is.
Example:
<%=CreateMenu(xxx.WebMenuType.Occasion, "menuShopOccasion", "Occasion") %>;
That is in my Header.ascx
And so it errors out because it has no clue what CreateMenu is!
In my Header.ascx.cs it's there and was being referenced with no problem in my old Web Site Project:
protected string CreateMenu(xxx.WebMenuType menuType, string menuID, string title)
{
...
}
It's probably a namespace problem. Make sure that the Inherits attribute in your <%# Page ... %> declaration refers to the correct path to the code behind file, including the namespace. The designer file must also be in the same namespace as the code behind.
I am not entirely sure this is your problem but....
you may be missing the .designer.cs files. For your example above there would also be a Header.ascx.designer.cs which contains a partial class (Header) which has all the declarations of the controls in the Header.ascx file?

Getting namespace name not found for ASP.net user control

So I'm having problems when I try to publish the website.
I'm in visual studio 2008 sp1.
I've got a bunch of user controls and on a few pages I'm using them programatically.
I've got a reference on the aspx page
<%# Reference Control="~/UserControls/Foo.ascx" %>
Then on the code behing I use
ASP.usercontrols_foo newFoo control = (ASP.usercontrols_foo)Page.LoadControl("~/UserControls/Foo.ascx");
If I navigate to the page it works fine, but when I goto publish the website I get a compile time error.
Argh, I'm bleeding development hours on this same issue. Does anyone have a solution to this ?
BTW: It builds if you uncheck "Allow this precompiled site to be updatable" (Right-click on the website, choose Property Pages, MSBuild Option)
But I need the site to be updatable.
I had this same problem - actually, in my case I could get it to compile and build my website, but it would blow up spectacularly when doing an automated build.
Try replacing your code as follows:
ASP.usercontrols_foo newFoo control = (ASP.usercontrols_foo)Page.LoadControl("~/UserControls/Foo.ascx");
with
USERCONTROLS_Foo newFoo control = (USERCONTROLS_Foo)Page.LoadControl("~/UserControls/Foo.ascx");
(Capitalization will be based on how you capitalized your directory name/control name - but in either case should highlight appropriately).
Specify a namespace for user control (see Dynamically Load a user control (ascx) in a asp.net website ).
I've found a solution for it. If i'll declare controls in user defined namespace, then I can use controls directly without using ASP or referencing it into aspx page.
It may have something to do with the specific type not being available. Can you change the control reference so the type and cast just use the base Control class:
Control control = Page.LoadControl("~/UserControls/Foo.ascx");
Yes, I can cast it to Control. But then I lose direct access to the methods on the usercontrol.
I know that I can access the methods by reflecting into the control, and I've successfully done that but that's far from ideal to access all the other user controls on the site.
Also, the follow-up error is that it cant find the usercontrols that on the markup
<%# Register src="CategoryRows.ascx" tagname="CategoryRows" tagprefix="abc" %>
<abc:CategoryRows ID="CategoryRows" runat="server" />
Note that I can run the site successfully both locally and on the server if I essentially XCopy the entire site (source code and all). But Publish fails.
Casting the user control may create many problem .my approach is to create a class (say control Class) put all the properties and method you need after casting and inherit this class from System.Web.UI.UserControl .Then in your user cotrol code file instead of System.Web.UI.UserControl user this control class .
now when ever you need casting cast with this class only . it will be light casting as well.
Please check:
Do you have any website.publishproj file?
If it exists, then delete and try again and publish code.

ASP.NET Custom Controls

How do you create a custom control (not an ASCX control) and, more importantly, use it in your project? I'd prefer not to create a separate project for it or compile it as a DLL
Server controls should be compiled into a DLL. There should be no reason to be afraid of having an additional assembly in your project, and it helps create good project organization.
ASP.NET Server controls are actually custom classes in an assembly. They do not have an "ascx" markup file associated to them.
To use a ASP.NET Server Control, you need to tell ASP.NET where to look for its class.
First, create a Class Library project and add it to your solution, I'll call mine "CustomControls.dll".
Once there, add a simple class to be your webcontrol:
public class Hello : System.Web.UI.WebControl
{
public override Render(HtmlTextWriter writer)
{
writer.Write("Hello World");
base.Render(writer);
}
}
Build your project, and add it as a reference to your main web project.
Now, in the ASPX page that you want to use this control, you need to register it, so add this as the first line in the aspx AFTER the Page Directive:
<%# Register TagPrefix="Example" Namespace="CustomControls" Assembly = "CustomControls" %>
Now, you should have a control named <Example:Hello> available to you. It might take it a minute to show up in intellisense.
Create the class for the control and build the solution. If everything goes well the control should now be available on the toolbox.
Sometimes the VS doesn't update the toolbox. If that happens add the Register directive to the page:
<%# Register Assembly="NAME_OF_THE_ASSEMBLY" Namespace="NAMESPACE_OF_THE_CUSTOM_CONTROL" TagPrefix="cc1" %>
then just use the control on the page:
<cc1:mycustompanel id="MyCustomPanel1" runat="server"><asp:TextBox id="TextBox1" runat="server"></asp:TextBox></cc1:mycustompanel>
If you don't want a *.ascx file, you don't want a separate project, and you don't want a *.dll file, what do you think is left? The code has to live somewhere.
You mentioned that you wanted to avoid creating a separate project. As the other responses have indicated, it is far more common to simply create a new project to store your custom controls and reference it. It is possible, however, to register a control defined in the same project, using the regular syntax:
<%# Register TagPrefix="Example" Namespace="CustomControls" Assembly="CustomControls" %>
Where the "Assembly" is the assembly where both the control and the page is located. This works much easier if you are using a Web Application Project, rather than a Web Site.

Resources