Passing a web control reference to a User Control base class - asp.net

I created several user controls - most containing a single web control (text box, drop down, radio button etc) - along with one or more validation controls. The point being to combine control and validation in a single user control.
I created a base class for these user control with some common functionality - setters for several properties of a single web control, specifically CssClass and Style to be set in the control in the ascx.
Eg a single text box with a single required field validator.
Sample code for the base class:
public WebControl ctrl {get; set;} //allow derived class access to this
public string CssClass
{
set { ctrl.CssClass = value; } //allow CssClass to be set in the aspx page
}
Sample code for derived class:
(in constructor or control OnInit Event - or ?)
base.ctrl = txt; //tell the base class which web control to apply common properties to.
public string ErrorMessage
{
set { val.ErrorMessage = value;} //this works !
}
Sample code for ascx:
<asp:TextBox ID="txt" Cssclass="input-text-m" maxlength="50" runat="server" />
<asp:RequiredFieldValidator ID="val" runat="server" ControlToValidate="txt"
ErrorMessage="">*</asp:RequiredFieldValidator>
Sample code for aspx:
<uc:TextBox ID="ForeName" Cssclass="input-text-m" maxlength="50"
ErrorMessage="Forename" runat="server"/>
The problem I found was that I couldn't find a way for the derived class to set the base class web control reference before the base classes property setters are called.
If I set base.ctrl in the derived class constructor - then the derived class control reference (txt) is still null at this point.
If I set base.ctrl in any of the control events - eg OnInit - then this is too late.
So far I have got around the problem by simply not using a base class, and writing the property setter code in the user control class instead, however this means duplication of code, which I was trying to avoid.
Is there a way to inform the base class of the control I want it to set the properties for in advance of them being set - or am I going about things the wrong way...

What about calling EnsureChildControls before any get/set operations and including the set operation for ctrl = txt in EnsureChildControls? This is pretty standard practice for a normal servercontrol, I would think it would work for UserControls too.
public string CssClass { set { EnsureChildControls(); ctrl.CssClass = value; } }
Override EnsureChildControls, leaving in the call to base, and set ctrl = txt; here after the call to base.
More information: http://msdn.microsoft.com/en-us/library/system.web.ui.control.ensurechildcontrols.aspx

Related

How do I declaratively bind 'SelectedValue' to datasource field?

I want to drive a RadioButtonLists SelectedValue property from a property in my datasource, but I'm not managing to make it work.
I have a <asp:RadioButtonList on my page bound to an <asp:ObjectDataSource. This datasource in turn provides the following model class:
public class CollectionWithDefault : Collection<string>
{
CollectionWithDefault(IEnumerable<string> items, string defaultItem)
{
foreach (var item in items)
Add(item);
DefaultItem = defaultItem;
}
public string DefaultItem { get; }
}
Notice that this class is a standard collection of strings that also exposes which one of them is the default option.
Consider that I have the following implementation for a value provider. This is a simple in-memory implementation, but keep in mind that this could be coming from a database or any other source:
public static class ItemProvider
{
public static CollectionWithDefault GetAvailableItems()
{
var items = new [] { "option1", "option2", "option3" };
return new CollectionWithDefault(items, items[1]);
}
}
I tried the following:
<asp:ObjectDataSource runat="server"
ID="ItemSource"
TypeName="MyNamespace.ItemProvider"
SelectMethod="GetAvailableItems" />
<asp:RadioButtonList runat="server"
DataSourceID="ItemSource"
SelectedValue='<%# Eval("DefaultItem") #>' />
I'm getting the following exception in the Eval call:
System.InvalidOperationException: 'Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control.'
How can I ensure that the correct radio is preselected based on the field coming from my datasource?
Changing the collection model itself to make it work is acceptable, but I can't set the SelectedValue from codebehind. I wanted to rely on the datasource control to do the heavy lifting.
I managed to make this work seamlessly without requiring manual assignments in codebehind by extending the original RadioButtonList control and modifying the core databinding method to honor ListItem objects.
It goes something like this:
public class MyRadioButtonList : RadioButtonList
{
protected override void PerformDataBinding(IEnumerable dataSource)
{
if (dataSource is IEnumerable<ListItem> listItems)
{
...
foreach (var listItem in listItems)
Items.Add(listItem);
...
}
else
{
base.PerformDataBinding(dataSource);
}
}
}
With this in place, it was just a matter of converting my source model into a IEnumerable<ListItem> on the presentation layer (easy to accomplish with an adapter/proxy implementation) and then feed these ListItems to the control.
Once I got this in place, I could see my selected items reflected correctly in the UI based on the datasource field. Considering how trivial the extension is, I feel it was quite worth it :)
The same inheritance approach can probably be used for similar controls like CheckBoxList, which suffers from the very same limitation.
For the more adventurous folks, one could also make this work by introducing extra DataSelectedField and DataEnabledField properties in the control and using Eval on top of them as part of the original databinding algorithm (which already does this with DataTextField and DataValueField). I felt this would be a little bit more involved for my use case and decided to go with a simpler override, but it is definitely a valid approach that could even live along my proposed solution for an even more robust RadioButtonList.

How to Best Intercept Parsing of all Elements in the Sub-tree of an ASP.NET User Control

I'd like to intercept parsing of each control in the entire sub-tree of my own User Control.
Currently I've overridden the protected method Control.AddParsedSubObject in my user control. It's limited to interception of parsing on the immediate child controls in the declarative syntax because each of those controls has its own AddParsedSubObject method to further parse its own child controls.
From the User Control I can't get into the childrens' children to intercept those parsing calls.
In the following declarative example of my user control I can access the tv object from inside the User Control's AddParsedSubObject override.
<asp:TreeView runat="server" id="tv" />
However I cannot access the tv object in the following example (or the other children of the first Panel) because that parsing is handled by the Panel instead or its children.
<asp:Panel runat="server">
<asp:TreeView runat="server" id="tv" />
<asp:Panel runat="server">
<asp:TextBox runat="server" />
</asp:Panel>
</asp:Panel>
My code-behind in the user control looks like this
// User control interception of its parsed children
protected override void AddParsedSubObject(object obj) {
// Do some custom work with the control object.
if (obj is Control && ((Control)obj).ID == "tv") {
TreeView tv = (TreeView)obj;
DoSomethingWithParsedObject(tv);
}
// Let ASP.NET continue and put the control in the page hierarchy
base.AddParsedSubObject(obj);
}
Looking for ideas about how I can intercept parsing of each control in the entire sub-tree of my user control. For example, I want to write out custom information at each parse step.
If you are using .Net 4.5, there is a good way to achieve it. Asp.Net uses ControlBuilder to build temporary cs files from aspx layouts. Before .Net 4.5 you can intercept it only by reflection and switching some internal static variables in ControlBuilder class.
But in .Net 4.5 they added new class ControlBuilderInterceptor. Then you can write such code:
namespace TestInterceptApp
{
public class BuilderInterceptor : ControlBuilderInterceptor
{
public override void OnProcessGeneratedCode(ControlBuilder controlBuilder, CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType,
CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod,
CodeMemberMethod dataBindingMethod, IDictionary additionalState)
{
base.OnProcessGeneratedCode(controlBuilder, codeCompileUnit, baseType, derivedType, buildMethod, dataBindingMethod, additionalState);
buildMethod.Statements.Insert(
buildMethod.Statements.Count - 1,
new CodeSnippetStatement("TestInterceptApp.ControlInterceptor.Intercept(#__ctrl);"));
}
}
}
Then you need to modify compilation section in web.config to register this class to Asp.Net:
<system.web>
<compilation debug="true" targetFramework="4.5" controlBuilderInterceptorType="TestInterceptApp.BuilderInterceptor"/>
</system.web>
And then you can write this interceptor class like this:
namespace TestInterceptApp
{
public static class ControlInterceptor
{
public static void Intercept(TreeView control)
{
// Do some custom work with the control object.
if (control.ID == "tv")
{
control.Nodes.Add(new TreeNode("Test"));
}
}
public static void Intercept(object obj)
{
// just ignore all others controls
}
}
}
Basically when ControlBuilder creates new cs files, it will insert this line
"TestInterceptApp.ControlInterceptor.Intercept(#__ctrl);"
to the end of each control generation block. You can define one method in ControlInterceptor class with object parameter, or you can make a couple of needed overloads with specific parameters. And plus one base method with object parameter. When CLR will execute it, if control is TreeView, then it will send it to correct method. For all other controls like Literal, Button, HtmlHead, Page, etc CLR will use method with object signature.
My sample will just add new TestNode to each TreeView in current application that has ID="tv". And code generated by ControlBuilder is such:
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
private global::System.Web.UI.WebControls.TreeView #__BuildControltv() {
global::System.Web.UI.WebControls.TreeView #__ctrl;
#line 32 "c:\users\someuser\documents\visual studio 11\Projects\TestInterceptApp\TestInterceptApp\Default.aspx"
#__ctrl = new global::System.Web.UI.WebControls.TreeView();
#line default
#line hidden
this.tv = #__ctrl;
#__ctrl.TemplateControl = this;
#__ctrl.ApplyStyleSheetSkin(this);
#line 32 "c:\users\someuser\documents\visual studio 11\Projects\TestInterceptApp\TestInterceptApp\Default.aspx"
#__ctrl.ID = "tv";
#line default
#line hidden
#line 32 "c:\users\someuser\documents\visual studio 11\Projects\TestInterceptApp\TestInterceptApp\Default.aspx"
this.#__BuildControl__control3(#__ctrl.Nodes);
#line default
#line hidden
// here is that line that we added via ControlBuilderInterceptor
TestInterceptApp.ControlInterceptor.Intercept(#__ctrl);
this.#__PageInspector_SetTraceData(new object[] {
#__ctrl,
null,
1838,
368,
false});
return #__ctrl;
}

In asp.net how to reference page controls in custom base page class

I have a large number of .aspx pages (asp.net 4, c#) that all inherit from a custom base page class (that inherits from System.Web.UI.Page class).
By convention all these pages have the same set of controls on them (for example a couple of textboxes with the same ID).
I'd like to put some generic code in the custom base page class that retrieves the .Text values from these controls on the page.
Note this is not a MasterPage set up. I have a custom base page class, then a series of pages that inherit from that base page class.
How do I reference the textboxes on the page from the base class?
You should be able to access the controls using Page.FindControl("ControlID").
From your base class:
var txt = Page.FindControl("TextBox1") as TextBox;
if (txt != null)
{
//found the textbox
//...
}
Depending where on the form the controls are located, specifically if they're located in a container that implements the INamingContainer interface, you may need to make a recursive FindControl() method that can traverse the control hierarchy.
public Control FindControlRecursive(Control root, string id)
{
if (root.ID == id)
return root;
foreach (Control control in root.Controls)
{
Control foundControl = FindControlRecursive(control, id);
if (foundControl != null)
return foundControl;
}
return null;
}
One thing you could do is put an abstract method or property on the base class. This would force the inheritors to implement this method and the base could reliably call it.
protected abstract TextBox MyTextBox { get; }
then all your inherited pages would have to implement this method and ideally would return their MyTextBox.
You could create an interface as defined above or just make another PageBase that inherits from your other Base that represents pages with that set of controls.
EDIT:
As an example of implementation. Assuming the base class is called MyPageBase and HomePage.aspx has a Textbox on it with ID="TextBox1"
On the base define the abstract property
public abstract class MyPageBase : Page
{
protected abstract TextBox MyTextBox { get; }
}
On the page:
public partial class HomePage : MyPageBase
{
protected override TextBox MyTextBox
{
get
{
return this.TextBox1;
}
}
}
Within the base you can access the property since its abstract it works like an interface and MUST be implemented. Then the base can access this property assuming the inheritor honors the contract.
this.MyTextBox.Text = "Change the text";
If all you want to do is modify the text or another specific property it would be more ideal to encapsulate it better and provide a getter/setter to only the text property on the specific textbox. The sample doesn't let you change the actual instance of the textbox but it lets you access and modify any of its properties.
If they all have the same set of controls on them by convention, why not move the controls to the base class?
Alternatively, you could create an interface that includes the common set of controls, and then implement that interface in all your aspx.cs codebehinds. This would allow you to have some aspx pages that defy the convention. You can cast "this" as the interface, in the base class and if its not null, modify the controls. For example:
IControlSet controlSet = this as IControlSet;
if(controlSet != null)
{
controlSet.Name.Text = "someName";
}

ASP.NET ASCX Use of Instance Variable

Let's say I have an ASCX user control that requires access to the current user's full name. An ASPX page contains this line at the top
<%# Register src="top.ascx" tagprefix="custom" tagname="top" %>
and this line in the body:
<custom:top runat="server" />
The ASPX file knows the user ID of the current user and could determine his full name. So how can I use the code run by the ASPX file to provide its information to the ASCX file?
Declare a property on the UserControl and have the parent page set it.
On your usercontrol:
public string FullName { get; set; }
On the aspx page either set it in the code behind
YourUserControl.FullName = FullName
or through markup
<custom:top runat="server" FullName="<%= SomeProperty %>" />
You could use the Page property of the user control and cast it to the instance of your page. Then, call the method of your page class to get the user name.
To make this work in a dynamically compiled project, you have to do a little more work to have the control recognize the data type of the dynamically compiled page. Here is a short tutorial on how to do.
Or, as Brandon outlines, do the opposite and let your page tell your user control the information.
This sounds like you might be mistaken about how the page lifecycle works and how you can expose data across your controls. For example, lets say you have this code in your ASPX:
public override void OnLoad(EventArgs e)
{
string userName = "Bob";
}
In your ASPX file, you can reference the control and set a property on it to pass the data along:
<custom:top ID="someControl" runat="server" />
You expose a property in your top control like so:
public string UserName { get; set; }
You could then add this code to your OnLoad method:
someControl.UserName = userName;
Then your control will have access to that data. Alternatively, you can stick things in the Request cache if you dont have a direct line to the control:
HttpContext.Current.Items["key"] = userName;
And then pull the data from your control via the same fashion:
string fromCache = HttpContext.Current.Items["key"];
You could go about this in several ways. I typically use a session variable, since the user will be bound to the session.
In the ASPX (or when the user logs in):
Session["UserFullName"] = GetFullName(); //Code to get full name here
In the ASMX:
this.FullName = Session["UserFullName"]; //TODO: Check for null values

Find control in usercontrol from a Page ASP.NET

I am loading a control to a page dynamically with LoadControl("src to file").
In the usercontrol i have a validator and some other controls that i would like to access from my page. I canät get it to work, null pointer exception.
Scenario is like this. I have a Edit.aspx page which loads the EditTemplate.ascx usercontroll. I would like to get information or find the controls in the EditTemplate from the Edit.aspx site.
I have tried exposing the controls and validators as properties but how do i access them from my Edit.aspx?
Example code:
Edit.aspx, the control is later added into a
Control control = LoadControl("src to ascx");
TemplatePlaceHolder.Controls.Add(control);
EditTemplate.ascx
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="CompanyImageFile" ErrorMessage="RequiredFieldValidator"></asp:RequiredFieldValidator>
CodeBehind
public partial class EditTemplate : System.Web.UI.UserControl, IEditTemplate {
public RequiredFieldValidator Validator {
get { return this.RequiredFieldValidator1; }
set { this.RequiredFieldValidator1 = value; }
}
From the Edit.aspx site i would like to check the validators isValid property. Isvalid is set in a Save method.
The save button that saves the template is located in edit.aspx, so the post in done from that page.
So the question is how to get a hold of the property from the usercontrol in the edit.aspx page, where and how should this be done?
Thanks again.
Easiest way is to have the user control define properties like:
public IValidator SomeValidator {
get { return this.cuvValidator; }
set { this.cuvValidator = value; }
}
public string Text {
get { return this.txtText.Text; }
set { this.txtText.Text = value; }
}
Which your edit page can use.
HTH.
You can always use recursive approach. Check the solution on Steve Smith's blog:
Recursive-FindControl.
As mentioned in previous answers, I would expose any validators you must access from the parent ASPX page as properties in the user control.
public RequiredFieldValidator ValidatorToCheck
{
get { return this.rfvMyField; }
}
Then, you can dynamically add your user control to some placeholder (being sure to assign an ID to the user control).
// In my example, this is occurring in the Page_Load event
Control control = LoadControl("~/Controls/EditTemplate.ascx");
control.ID = "ucEditTemplate";
pnlControlHolder.Controls.Add(control); // the placeholder in my example is a panel
When you want to access the IsValid property on the given validator (presumably in your save action) you can do so as follows (being sure to cast the control to the appropriate type and using the ID you originally assigned to the user control):
EditTemplate control = (EditTemplate)pnlControlHolder.FindControl("ucEditTemplate");
if (control.ValidatorToCheck.IsValid)
{
// Some action
}

Resources