ASP.NET Triggering server-side events with multiple arguments - asp.net

I've got a rather lengthy question I'm afraid. I'm fairly new to ASP.NET so please bear with me.
I have built a control for an ASP.NET page that lists a number of options. Each option has two clickable areas (call them buttons for the sake of simplicity). One to select the option and one to hide the option.
protected void Page_Load(object sender, EventArgs e)
{
RenderOptions();
}
public void RenderOptions()
{
for (int i = 0; i < 5; i++) {
HtmlGenericControl div1 = new HtmlGenericControl("div");
div1.Attributes.Add("onclick", ClientScript.GetPostBackEventReference(this, "option" + i));
m_TreeContainer.Controls.Add(div1);
HtmlGenericControl div2 = new HtmlGenericControl("div");
div2.Attributes.Add("onclick", ClientScript.GetPostBackEventReference(this, "option" + i));
m_TreeContainer.Controls.Add(div2);
}
}
public void RaisePostBackEvent(string arg)
{
//do something
}
This works fine (I do implement the IPostBackEventHandler interface). The problem here is that there doesn't seem to be a way for me to find which HTML element was clicked and thus which action should be performed in the RaisePostBackEvent method.
What I tried to do is create a new class (HtmlDivControl) which looks like this:
class HtmlDivControl : HtmlGenericControl, IPostBackEventHandler
{
#region Delegates
public delegate void ClickEventHandler(object sender, string eventArgument);
#endregion
#region Properties
private ClickEventHandler m_Click;
public ClickEventHandler Click
{
get { return m_Click; }
set { m_Click = value; }
}
#endregion
#region Constructors
public HtmlDivControl()
{
}
#endregion
public void RaisePostBackEvent(string eventArgument)
{
m_Click.Invoke(this, eventArgument);
}
}
Now I made div1 and div2 my HtmlDivControl rather than HtmlGenericControl, set the Click property to a method (delegate) and passed the div (div1 or div2) itself as control for the GetPostBackEventReference method. This time, I could not only differentiate between the divs but also pre-determine the action that should be performed. However, the RaisePostBackEvent for controls are called after PageLoad. So the problem I'm with now is that the whole options control is rendered before the events are handled (and thus, an option that should for instance be hidden isn't because the actual hiding happens after the rendering). Moving the RenderOptions() call to the PageLoadComplete method doesn't help either, since then the div controls won't exist yet.
I'm pretty sure I'm missing something quite fundamental here. But could someone please explain me how I should approach something like this?
p.s.
How am I supposed to write underscores here? They're used to make text italic? Is there some escape character?

For someone new to ASP.Net, you've done pretty well so far. Your roadblock here is actually the way you are thinking about the issue. You should get a good grasp of the ASP.Net Page Lifecycle - you are missing something very fundamental.
In a nutshell, you want your page to rebuild it's state to the same way it was before the postback. Then process your events. Then make any state changes.
You're thinking about it as if your html controls should know about their state change at the start of the request, which is incorrect. There has to be the rebuilding phase first. This is critical for ASP.Net to even figure out which events to raise.
What I would recommend:
move your "RenderOptions()" method to the Page_Init handler. This will save you lots of issues if you ever incorporate ViewState into your controls. (I would also rename it, as it's not truly rendering anything, it's just adding your controls to the page. Render has a specific context in ASP.Net).
Then, in your OnClick event handlers for your controls, simply set your controls visibility as necessary, rather than trying to control the way they are rendered. It is always much simpler to set controls to Visible=False rather than try to change the way the controls are being rendered to the page. Remember that if you set Visible=False, there will be zero html sent to the response for that control, but the server will still know it's on the page, so you can still deal with it.
Think about your event handlers as the place where you will change the state of the page. It's where your logic should be in this case, rather than in Page_Load.

Related

ASP HiddenField ValueChanged handler fires on every postback in custom control

I'm having problems with event handlers always firing on HiddenFields despite no changes to the field that I'm aware of.
I have an ASCX user control where I statically declare some elements:
MyControl.ascx:
<div id="AnItem" runat="server">
<asp:Textbox id="TextBox1" runat="server" />
<asp:HiddenField id="HiddenField1" runat="server" />
</div>
(There's obviously a lot more there, but this is the markup of note)
MyControl.ascx.cs:
protected override void OnInit(EventArgs e){
base.OnInit(e);
if (SelectedValue != null){
TextBox1.Text = SelectedValue.Text;
HiddenField1.Value = SelectedValue.ID.ToString();
}
}
protected override void OnLoad(EventArgs e){
base.OnLoad(e);
if (String.IsNullOrEmpty(TextBox1.Text))
TextBox1.Attributes["style"] = "display:none";
TextBox1.TextChanged += ItemTouched;
HiddenField1.ValueChanged += ItemTouched;
}
protected void ItemTouched(object sender, EventArgs e){
// process changed values
}
The code seems unconventional because I've omitted items unrelated (I assume) to my question.
My control is actually created dynamically using a wrapper class that I can serialize:
[Serializable]
public class ListControl{
public void GenerateControl(TemplateControl parent){
var control = parent.LoadControl("~/MyControl.ascx") as MyControl;
control.Options = _options;
control.SelectedValue = _selectedValue;
return control;
}
private IList<SelectableOption> _options;
private SelectionOption _selectedValue;
}
(The reasons for this wrapper are due to a large legacy code base that is too slow in creating the list of selectable values. The _options list is generated once and then kept in the session to speed up postback execution.)
ItemTouched is attached to every element that may be touched by the user (or manipulated by JavaScript). My problem is that it's being fired on every postback - even when HiddenField1 wasn't modified on the client side (I've confirmed this by removing all JavaScript that touched it).
I'm at a loss as to why the handler is being fired is the value isn't being touched. It does not fire when the control values aren't set (e.g. in my OnInit method), but always does if they are "pre-set". I don't expect the change handlers to fire if I attach the event handlers after setting the default values, but this doesn't seem to be the case. Am I making a fundamentally bad assumptions about ASP.NET events?
It's been a while, but if I recall correctly HiddenField or Textbox controls fire ValueChanged based on comparing their current value with the value stored in ViewState. Since you're dynamically creating these controls, I'm guessing their viewstate isn't getting rehydrated properly (or early enough) - which then triggers the ValueChanged event handler to fire (since they have a value that differs from that stored in ViewState).
Your best shot at really understanding what's going on is to enable debugging on the .NET Framework: http://msdn.microsoft.com/en-us/library/cc667410.aspx and set some breakpoints around the area where ValueChanged is fired. Then you can see what condition is causing it, and that should hopefully help you figure out how to work around it.

ASP.NET find all Controls on a page and hide them

I'm trying to on Page_Load hide all my RadioButtonLists but I can't seem to get the syntax quite right
I'm guessing I've got to use the FindControl syntax something like this
CType(FindControl, RadioButtonList)
And then I'm guessing I will have to loop through each RadioButtonList and set the Visible = False attribute on it.
I seem to be getting an error with the code above.
Any ideas what I can try?
Thanks
Try this:
protected void Page_Load(object sender, EventArgs e)
{
HideRadioButtonLists(Page.Controls);
}
private void HideRadioButtonLists(ControlCollection controls)
{
foreach (WebControl control in controls.OfType<WebControl>())
{
if (control is RadioButtonList)
control.Visible = false;
else if (control.HasControls())
HideRadioButtonLists(control.Controls);
}
}
FindControl only works if you know the name of the control you're looking for, and more than that it is not a recursive call. Unless you can guarantee that your control will be in the specific container you're searching in, you won't find it. If you want to find all of the radiobutton lists, you'll need to write a method that cycles through all control sets in the parent/child relationship and sets the radiobuttonlist visible to false.
Just pass Page.Controls to this function (untested, may need tweaking):
public void HideRadioButtonLists(System.Web.UI.ControlCollection controls)
{
foreach(Control ctrl in controls)
{
if(ctrl.Controls.Count > 0) HideRadioButtonLists(ctrl.Controls);
if("RadioButtonList".Equals(ctrl.GetType().Name, StringComparison.OrdinalIgnoreCase))
((RadioButtonList)ctrl).Visible = false;
}
}
Why not use a ASP.Net skin page to set the default values for all RadioButtonLists to visible = false.
I would def look into using a skin page here.
Doing a foreach on the Controls property and checking the type is going to be slow. What you should be doing, in my opinion and depending on your requirements, is using CSS / skins to hide the unwanted buttons or simply adding them to a List<T> so you can loop through only those that you need to modify.
Worst case scenario the foreach will work but it’s a bit slow and undesirable.

Loading of controls outside of Page_Load is a no-no?

My apologies in advance for posting such a lengthy question. Believe it or not, what you see here actually represents a fairly condensed version of the problem/code at hand. And while I would appreciate any pointers on a better or different approach, I would also very much like to get the bottom of this so that I can sleep at night :)
I came across a requirement to pass confirmation messages between distinct aspx pages. I opted against using a query string variable since query string values "are" sticky (i.e. they persist on all subsequent postbacks) and I didn't want to deal with adding a bunch of conditional logic around this.
Anyway, I came up with a very simple class that uses Session to associate notifications with specific URLs. I then hooked my master page Page_Load event to query this class for any notifications that should be displayed for the current URL. If it finds any, it dynamically loads a NotificationMessage user control and displays the message content.
Everything works as expected when trying to pass Notifications between different aspx pages. Predictably, things don't work when a content page attempts to add a notification to itself (i.e. "The data you entered is not valid, try again"). The reason is pretty clear: by the time a content page adds a Notification for itself, the Page_Load event of the master page has already fired, so it's too late in the page lifecycle to do any good. The relevant code is pasted below.
public class MyMasterPage:MasterPage{
protected void Page_Load(object sender, EventArgs e)
{
LoadNotifications(this.Request.Url.ToString());
}
private void LoadNotifications(string url)
{
//look for a notification
Notification? notification = NotificationManager.Instance.RetrieveNotification(url);
//there are no notifications, nothing to see here
if (!notification.HasValue)
{
return;
}
//there is a Notification for this url, so load it into a user control
NotificationMessage notificationMessageControl = (NotificationMessage)LoadControl("~/App_UserControls/NotificationMessage.ascx");
notificationMessageControl.ID = "notificationMessage";
notificationMessageControl.Notification = notification;
notificationMessageControl.Visible = true;
//find the placeholder on the master page
PlaceHolder placeHolder = (PlaceHolder)PageUtils.FindControlRecursive(this, "NotificationPlaceholder");
if (placeHolder == null)
{
throw new ApplicationException("NotificationPlaceholder control not found.");
}
//insert into control
placeHolder.Controls.Add(notificationMessageControl);
placeHolder.Visible = true;
//remove the notification so it doesn't show up next time
NotificationManager.Instance.RemoveNotification(url);
}
}
Given the lifecycles issued alluded to above, I modified the NotificationManager class so that it raises an event whenever a notification has been added for the current page. The master page intercepts that event, and if the Page_Load has already fired, it kicks off the LoadNotifications method all over again.
//bind the event on the page constructor
public MyMasterPage()
{
NotificationManager.Instance.NotificationAdded += this.NotificationAdded;
}
private void NotificationAdded(string forUrl)
{
if (_pageLoaded){
LoadNotifications(forUrl);
}
}
Unfortunately, this doesn't work. I have stepped through this code numerous times, and despite the fact that the master page loads the NotificationMessage UserControl and adds it to the appropriate placeholder without incident, the final aspx HTML never includes the markup for that UserControl. I've put breakpoints inside the Page_Load of the UserControl and verified that they are indeed being hit during execution.
If I dynamically load the UserControl from inside the content page and bypass the Master page altogether, it renders without a hitch:
public partial class MyContentPage:Page
{
public void DoSomethingCool(object sender, EventArgs e)
{
if (MyServiceLayer.Save(foo)==false){
Notification notification = new Notification(NotificationType.Error, "We’re sorry, your document was not saved.");
NotificationMessage notificationMessage = (NotificationMessage)LoadControl("~/App_UserControls/NotificationMessage.ascx");
notificationMessage.Notification = notification;
notificationMessage.Visible = true;
PlaceHolder holder = (PlaceHolder)PageUtils.FindControlRecursive(this, "NotificationPlaceholder");
holder.Controls.Add(notificationMessage);
}
}
}
For the record, I stripped out the dynamic loading of the UserControl, opting instead for a a static declaration in the master page markup and a code based toggle of the control's Visible property; still no dice!
If someone could shed some light on this conundrum, I would be much obliged.
I've tried this sort of thing before and I was never fully comfortable with it. What I did instead, was put my ASCX on every page (or on the masterpage), and let the ASCX control its state rather than letting the ASPX control my ASCX.
I'm not sure this will help for your situation, though.
It seems like you want your information to show up after your control events fire. You might consider letting those messages aggregate until all control events have fired and then pull out all of the messages from your NotificationManager in OnPreRender, rather than Page_Load. That way you can get rid of the events (like NotificationAdded), etc that are probably complicating matters.
Not 100% what the problem is, though. It sometimes helps to know that the MasterPage is actually a control on the Page, rather than the other way around like you would think. It is going to be subject to the limitations any control would have on a Page.
HTH, Anderson

ASP.net Server controls: How to handle ID of dynamic controls [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
.Net Changes the element IDs
I have the following problem (I'll explain it simplified because otherwise it would get too complicated/long).
I need to create an ASP.net server control that is inherited from Panel. Simple enough. When the custom control renders, it should dynamically create a Button, associate an event handler to it (which will also be defined inside the server control) and add this button to the rendered control. The click event handler defined inside the server control does some job which for the moment isn't interesting.
I already coded an example and that works fine. In the constructor of the server control I create a new button control, give it an ID, associate an event handler, on the OnInit of the server control I add the button to the Panel controls (remember, my control inherits from Panel) and then everything gets rendered. This looks something like the following:
public class MyCustomControl: Panel
{
private Button myButton;
public MyCustomControl()
{
myButton = new Button();
myButton.ID = "btnTest";
myButton.Click += new EventHandler(btnTest_Click);
}
protected void btnTest_Click(object sender, EventArgs e)
{
//do something...
}
//...
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
this.Controls.AddAt(0, myButton);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
//...
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter output)
{
base.RenderContents(output);
}
}
This works quite fine. The button gets rendered correctly and when I click on it, the appropriate handler inside this customer server control is invoked and executed. Now the problem however starts when I would like to add multiple instances of this server control to my page because all of my generated buttons will have the same id "btnTest" which results in a conflict. So what I tried is to associate a random number to the id of the button, s.t. it end up being "btnTest1235512" or something similar. This solves my problem with the multiple IDs, but results in the problem that my event handler when clicking on the button is no more called correctly. I guess this is due to the problem that my button always gets another id when entering in the constructor and so the appropriate callback (btnTest_Click event handler) isn't found.
Can someone give me a suggestion how I could handle the problem. Is there some way for remembering the ID of the button and re-associating it. As far as I know however this has to happen in the OnInit, where the ViewState isn't yet available. So storing the id in the ViewState wouldn't work.
Any suggestions??
Implementing INamingContainer should fix the problem. You can then keep naming all your buttons btnTest.
This is just a marker interface so you don't have to implement any methods or properties.
Inherit from INamingContainer like:
public class MyCustomControl : Panel, INamingContainer
{
}
This is a marker interface and requires no implementation. I just verified with your code and it solves your problem.
Implementing INamingContainer will do it for you. It will automatically name the buttons with unique ID's. Just a note, if you need to use any of those buttons in any JavaScript, you'll need to use the ClientID property:
function getButton() {
var myButton = document.getElementById('<%=btnTest.ClientID %>');
}

Server Controls and View State

Ok time to show my complete lack of knowladge for all things web forms but here goes. I am extending the Panel control and OnPreRender sticking some additional controls inside of it (lets just say 1 textbox for simplicity). From here I am just letting the Panels Render method do its thing.
The issue I am having is that obviously every time this control is rerendered it is just sticks that same TextBox in the panel again with the value I am coding in the OnPreRender method. Now I dont actually want to repopulate the panel every time,
I want to stick the textbox contorl in there on first load and have them reloaded from the control/viewstate caches. In this case with my example of just sticking a single textbox in the panel, if the value of the textbox changes and a postback occurs I want that value to to remain the changed value.
Really basic webforms stuff I know, but I have never had to create custom controls in my time. ANy help appreciated.
Chris.
You need to (re)create the child control (the textbox) in OnInit - so that it's there when LoadViewState and ProcessPostBackData is called.
See the server control lifecycle for more info.
Dynamic controls in ASP.NET are tricky, especially if you are new to webforms and the page lifecycle. If you can avoid dynamic controls, do so. Use controlName.Visible=false, and other tricks instead.
If you must then read this article. Rule of thumb,add controls early in the page life cycle, reference them later in the page lifecycle. PreRender is almost the very end, an uncommon place to be adding and using controls.
Not sure if this applies to all versions of .Net, (I think 2.0 or later) but there is a method called CreateChildControls that isn't really a part of the lifecycle exactly, it's basically called anytime the EnsureChildControls method is called. By default it is called before PreRender if it's not a postback. So basically your code would look like this:
public class SomeControl : WebControl, INamingContainer
{
private TextBox someTextBox;
protected override void CreateChildControls()
{
base.CreateChildControls();
someTextBox= new TextBox();
someTextBox.ID = "tbxMain";
Controls.Add(textboxToCheck);
}
}
Now the part to not is that unless you call EnsureChildControls, you can't be 100% sure that the controls exist before the Public Properties on your control are filled by the ViewState load. What does this mean? Well take the code from before and add a property for the CssClass:
public class SomeControl : WebControl, INamingContainer
{
private TextBox someTextBox;
protected override void CreateChildControls()
{
base.CreateChildControls();
someTextBox= new TextBox();
someTextBox.ID = "tbxMain";
Controls.Add(textboxToCheck);
}
public String CssClass { get; set; }
}
In CreateChildControls you won't want this:
someTextBox.CssClass = CssClass;
Since there is no way to be sure the control exists yet. There's a couple ways you can handle this:
public String CssClass
{
get
{
EnsureChildControls();
return someTextbox.CssClass;
}
set
{
EnsureChildControls();
someTextbox.CssClass = value;
}
In this example I am calling EnsureChildControls (Assuming you are setting the CssValue on the textbox in the CreateChildControls method) and setting or getting from the textbox.
Another way is putting anything that depends on the control's public properties in the OnPreRender method:
protected override void OnPreRender(EventArgs e)
{
someTextbox.CssClass = CssClass;
}
Thus avoiding the mess of worrying about the property being filled already during the ViewState load.
One Note:
The use of INamingContainer can be important. Basically all that does is makes sure the controls on the parent control have an id that is unique on the page by applying the parent's name (And maybe more) to the id. Basically if the parent ID is Parent and the child control ID is Child the ID might show up as Parent_Child. This will solve problems with ViewState not populating the properties correctly or not at all.
Inside your code you will need to manage the restore of viewstate information should you need the services of viewstate.
A good example here is this View State example by Microsoft. There are a few other items referenced in the code sample, but it should get you along the right path.

Resources