ASP: Extend control (ASCX) and access base markup file from subclass code - c#

I'm building form validation controls for our C# ASP application. The bulk of the work is handled by a BaseValidator control (subclassing System.Web.UI.UserControl), which also has the markup for the validation output. This is then extended by subcontrols like PasswordValidator, that provides the Validate method and any extra fields needed by that validator control.
(The end goal is to have controls like <uc1:PasswordValidator ControlId="txtPassword" /> which we can plop into any form with minimum duplication.)
However, PasswordValidator.ascx.cs cannot access the form elements defined in BaseValidator.ascx; the only way I've found to do so is to duplicate the markup in each subcontrol's *.ascx file. How can I extend BaseValidator.ascx.cs and access BaseValidator.ascx's markup in the subclass?

I'm pretty sure you'll have to create Server Controls to accomplish this. Meaning, you'll need to generate the outputted Markup from code in the control rather than in the .ascx file.

If you have a true baseclass for your BaseValidator control which your PasswordValidator extends, then any markup/controls in the baseclass should be available through protected properties. I would even go so far as to argue that a true base should not have an ascx portion. The base class should provide methods and properties that expose controls to built on the fly (probably during Page_Init to maintain viewstate).
If you have 2 separate controls on the same page, your parent page can be modified to provide brokerage methods to allow such communication.
If you have the PasswordValidator and you just need the controls/markup, you can use the LoadControl method to create an instance of the BaseControl in memory, access its controls/markup programmatically and either add it or destroy it depending on what you want to do with it.
Barring any of that, it would be just as #Shawn said. Server controls.

Thanks for the suggestions.
I solved this using a service and the strategy pattern. There is a central FieldValidator class which provides validation for any user interface. For the web interface, these methods are exposed through a WebService and (through the WebService) a UserControl. There are no issues with accessing the page elements, because there is now only one UserControl class that stands between the form and the WebService.
For example, <uc1:FieldValidator ControlType="Password" ControlToValidate="txtPassword" runat="server" > will plop down fully-functional clientside and serverside validation, with no need for code behind. Great! :)
(I mentioned resolution a while back in a reply to my question, but I can't mark that as answered.)

Related

User Control embedded on a page changes ClientID of controls

I have a user control(.ascx), which I am embedding in my page(.aspx), as below:
<%# Register TagName="UI" TagPrefix="my" Src="~/userControls/UI.ascx" %>
<my:UI runat="server" ID="mytestui" />
When page is loaded the controls(textboxes, dropdowns etc), inside the user control, changes automatically. Say I have a textbox called txtTest it changes to mytestui_txtTest.
As you can see it takes the ID of user control above and appends it to control.
Is there any way to prevent this from happening?
This happens because your control is a subclass of UserControl class, which implements INamingContainer interface. By default all controls that implement this interface prepend children's IDs with their own IDs, thus insuring that every ID on the page is unique.
To aviod this behavior you have two options:
Create server control, inheriting not from UserControl but from Control class directly. This one does not implement INamingContainer. However you won't have markup file, and have to generate all controls programmatically.
[Edit: ASP.NET 4 only] Use ClientIDMode="Static" for child controls. This mode insures that server and client IDs of the control are exactly the same. In this case this is a job of a developer to make sure that this control is used only once on the page to avoid ID duplicates.
What version of .Net are you using? If >= 4.0 you can use ClientIDMode="Static" as Andrei said. You can set it at control or page level as well.

How can I easily create templates for a custom asp.net Web Forms control?

I am trying to make it so we have one code behind for a user control but different sites (and quite possibly pages) will need their own HTML template of that HTML. The html also needs to be modifyable by front-end developers without a dll push.
Previously, I have done this by creating a standard ascx with code behind that binds all the controls. I then have the front end developers make a copy of the ascx and change the markup of the new ascx (leaving the code behind definitions exactly matching from the original). Then have a setting that tells the control which actual ascx to use, and everything hooks up correctly.
However, I am wondering if there are better ways to achieve this, especially since something as simple as deleting an asp control (like a label) that's not needed for one site will cause an exception)
In an ideal world I would prefer something more akin to MVC or MVP type of system, but I am stuck in pure, original webforms system that I need to add custom controls to.
Is there a better way to achieve templating than creating multiple ascx's sharing the same code behind?
Note that physical files for the templates are a must, so that we can track the templates in source control.
Given your constraints, I think you're on the right track with sharing the code-behind. One thing that sticks out in your requirements is the ability delete a control without breaking the code-behind. You could achieve this by adding an intermediate code-behind between the base class (your current code-behind) and the ascx.
Base Class (your refactored code-behind)
Public MustInherit Class BaseUC
Inherits System.Web.UI.UserControl
' Define all page controls here
Public MustOverride Property lblPageTitle As Label
Private Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
' Check if control has been initialized by inheriting class
If lblPageTitle IsNot Nothing Then
lblPageTitle.Text = "Hooray"
End If
End Sub
End Class
New site-specific code behind
Partial Class controls_Sample
Inherits BaseUC
Public Overrides Property lblPageTitle As System.Web.UI.WebControls.Label
Get
Return title
End Get
Set(value As System.Web.UI.WebControls.Label)
title = value
End Set
End Property
End Class
Site specific ASCX
<%# Control Language="VB" AutoEventWireup="false" CodeFile="Sample.ascx.vb" Inherits="controls_Sample" %>
<asp:Label runat="server" ID="title"></asp:Label>

How to use a web user control in code only (no rendering)?

I have a user control which has panels and other gui controls. It's used like any user control in rendering a web page. However I have a need to use a new instance of it and use it in code only without rendering it as it has business logic which I want to use. However it gets object null reference errors during run whenever a gui control is referenced in code, for example: pnlSomePanel.Visible = true;
How do I use a user control in code only (without it being rendered)? Using .NET 4.0.
Technically, you should just be able to create a new instance of the code-behind class (assuming an asp.net web application and not a web site).
However, if there is business logic embedded in that user control, I would suggest moving it into a separate class or exposing the business logic as a static method with an appropriate set of parameters.
How about this...
Add a reference to your module on the page.
<%# Reference Control="~/modules/MyModule.ascx" %>
Then, you can create an instance of that module in your code behind using the class name of the module.
modules_MyModule ctrl = LoadControl("~/modules/MyModule.ascx") as modules_MyModule;
ctrl.SomeMethod();
The next step would be to add it to a parent container, but if you skip that step, you should still have a functional object in the code behind. Not totally sure if it will work, but in theory it should.

Re-using another page section

I have created a web page that I use as a small dashboard to hold issue or no issue. It works great. The page uses an .aspx and .aspx.cs. I would like to be able to reuse the information on this page on other pages. My site already uses master pages and I have not been able to find an easy way to include this information.
How can I use an include from a page that has coding in the code behind easily?
Typically you use Web User Controls for this.
Web User Controls allow you to package up other controls into one that you can drop onto multiple pages. They are great for common UI items such as address entries, dashboards, etc. Basically anything that needs to be the same across multiple pages.
At the risk of seeming very obvious - do you mean usercontrols. These will allow you to reuse chunks of functionality across your site.
I guess this question falls into two categories: User Controls, and Code Reuse. Not sure which one you are after.
User Controls
If you are talking about the controls on your page you will want to create a common user control.
Code Reuse
You need to create a common class (whether it is static or not depends on how you intend to use it) and define functions within that class.
For instance, lets say you have a page that you want to print "Hello World!" on any aspx/.cs page.
You could do this
public static class MyClass
{
public string PrintHelloWorld()
{
return "Hello World!";
}
}
Then you call it from any of your pages like so:
MyClass.PrintHelloWorld();
Right click on the project > Add New Item...
Select User Control (.ascx)
Put your markup & code behind there.
Then you add that control in any other page (includding other controls [although I wouldn't recommend that])
It sounds like you may want to create an ascx User Control.
http://msdn.microsoft.com/en-us/library/ie/2x6sx01c.aspx

Failing to add controls to a page dynamically

I'm adding a User Control for each record pulled up in a data reader, here's the basic loop:
while (dr.Read())
{
ImageSelect imgSel = new ImageSelect(dr["Name"].ToString());
myPanel.Controls.Add(imgSel);
}
The problem is that there are no controls added to the page, I check the html output and there is my panel, with nothing in it.
I even stepped through the code in the debugger, and verified that myPanel.Controls gets a control added on each loop, with the count being 6, no errors, but then they dont show up on the page.
I've run the above code in the Page_Init and Page_Load events, both with the same result.
EDIT:
Ok so I've switched to using LoadControl("....ascx") to get my control instance, which is now working. But originally I was also passing in data via the controls constructor.. Is this still possible or do I just need to set them via get/sets?
EDIT 2:
Thanks to Freddy for pointing out that the LoadControl has an overload where you CAN pass in constructor params, see accepted answer.
EDIT 3:
After trying this method both with and without the constructor. I have found its better to just use setters for any properties I want the control to have versus trying to use the passed in object array for my constructor.
Update: As Steve pointed out, the overload of LoadControl that uses the type won't take into account the controls in the ascx. This is also mentioned in this answer: Dynamically Loading a UserControl with LoadControl Method (Type, object[]).
As I mentioned before, the get/set are more in line with the asp.net model, so I recommend using that with the LoadControl variation that receives the user control path. That said, the Steve's version is an interesting alternative: http://www.grumpydev.com/2009/01/05/passing-parameters-using-loadcontrol/.
My take is the LoadControl with type is meant to be used with web custom controls instead.
If it is an user control you should use LoadControl(usercontrolpath) to get the instance of the user control.
You can use a constructor by doing:
var name = dr["Name"].ToString();
var imgSel = LoadControl(typeof(ImageSelect), new object[]{ name });
myPanel.Controls.Add(imgSel);
Notice that depending on the project model you are using, you need to add a Reference to the aspx to use it with the typeof variation:
<%# Reference Control="~/somepath/myusercontrol.ascx" %>
Ps. I usually use the set/get for controls as I find them more in line with the asp.net model
To add UserControls you must call the LoadControl method passing in the path to the .ascx file. You can not create them by just instantiating the object the .ascx file inherits from.
A UserControl consists of both the markup and the class in the code behind. The markup contains a link to the class behind, but the class behind does not know where the markup lives and therefore can not be created on it's own.

Categories