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

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 %>');
}

Related

extending ASP.NET Page using MasterPage attributes as properties

I have an authentication roles-based system: different roles are redirected to different folders and in each folder there is a web.config that allows the access only to a particular username.
Few roles have the default page in common with a gridview that react in different ways depending on the role(different columns are shown, events trigger different methods, etc.).
so my problem is that everytime I need to make minor changes to a page I need to copy/paste the same changes to all the others default pages in the other folders.
In terms of code I solved by creating a DefaultFather class which extends System.Web.UI.Page and every other Default class inherits from DefaultFather. In this way, if I dont declare a Page-life-method, the DefaultFather method will be triggered.
but what about the graphic part(html, javascript, asp components, etc...)??
I created a NestedMasterPage just for the Default pages but everytime I need to change the appearance/behaviour of controls(gridview, buttons, linkbuttons) I must use the FindControl() method.
there isnt really another way to solve this problem?
Im thinking of using the Page_Load() method to search for each control with FindControl() and save them into attributes for later usage but it doesnt really look like a good solution.
It would be nice if I could use the masterpage components as properties but I think that in order to do that I should create public properties and I dont know if it will cause some kind of security problem.
any suggestion?
btw, if masterpage is the solution, should I remove the DefaultFather class and place the code directly into the masterpage? or is it a good idea to have another class just for the code?
I'd say there's nothing wrong with having both a master page and a base class for your page. They serve different purposes. The master page is generally all about layout, and the base class would be about page functionality.
If you want to manipulate the markup on your master page, rather than accessing the fields directly, I'd say create a logical function which does what you need it to do, and let the master page do it.
// Site.Master.cs
public void HideSubmitButton()
{
btnSubmit.Visible = false;
}
// Default.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
((SiteMaster)Master).HideSubmitButton();
}
I'd probably wrap that cast so you can use it more easily - that is something that would belong in your base class:
// DefaultFather.cs
protected new SiteMaster Master { get { return (SiteMaster)base.Master; } }
// Default.aspx.cs
Master.HideSubmitButton();
EDIT
Per your comment about attaching event handlers - if you need to attach events to objects that live on the master (which may not be a good idea - ideally the event handler for something living on the master lives on the master - but if you really need it) you can expose methods to do that as well, like:
// Site.Master.cs
public void AttachEventHandlerToGoButton(EventHandler eventHandler)
{
btnGo.Click += eventHandler;
}
// Default.aspx.cs
Master.AttachEventHandlerToGoButton(DoMyThing);
private void DoMyThing(object sender, EventArgs e) { }
or if you want to get fancy, write a wrapper event:
// Site.Master
<asp:Button ID="btnGo" runat="server" OnClick="btnGo_Click" />
// Site.Master.cs
public event EventHandler GoButtonClick;
protected void btnGo_Click(object sender, EventArgs e) {
if (GoButtonClick != null) {
GoButtonClick(sender, e);
}
}
// Default.aspx.cs
Master.GoButtonClick += DoMyThing;
private void DoMyThing(object sender, EventArgs e) { }
Also see my edit on the Master wrapper - you need the base. there to avoid a stack overflow.

Propagation of events to User Controls in ASP.NET

I have a user control (Navigation) nested within another user control (Header) that is dynamically loaded from a Control class (Standard).
The user controls, Navigation and Header have AutoEventWireup = false.
The control class Standard calls loads the Header user control from a configuration item.
private void layoutAndRender(HtmlTextWriter output, string UserControlKey, NameValueCollection UserControlsConfiguration)
{
if(UserControlsConfiguration[UserControlKey] != null && UserControlsConfiguration[UserControlKey].ToString() != "")
{
string suc = System.Web.HttpContext.Current.Request.ApplicationPath + UserControlsConfiguration[UserControlKey].ToString();
UserControl ucToRender = (UserControl)this.Page.LoadControl(suc);
ucToRender.RenderControl(output);
}
}
My problem is that I want to initialize an object in the Navigation user control that can accept Page.Request and Page.Response, but events don't seem to be firing in the Navigation code behind.
The code I'm using to initialize my object is:
this.browser = new Browser(this.Request, this.Response);
I tried doing this during the Navigation constructor but this.Request and this.Response are not set at that time.
I tried using the statement in a void Page_Load(object sender, System.EventArgs e) method, but this doesn't seem to be firing, even if I have this.Load += new System.EventHandler(this.Page_Load); in the Navigation constructor.
I've also tried similar statements for Page_Init and Page_PreRender, but none of these seem to be firing.
Is it that a control loaded with LoadControl does not fire Load or Init events, if loaded the way I have loaded them, and the same goes for any user controls that it may include?
If AutoEventWireup is set to false for the controls that you want to load, then you should override the OnInit method to wire up the Load event handler for the controls. The Request and Response properties should be available from within Page_Load.
For example:
public class Header : Control
{
private void Page_Load(object sender, EventArgs e)
{
}
override protected void OnInit(EventArgs e)
{
this.Load += new System.EventHandler(this.Page_Load);
}
}
See MSDN for more info on AutoEventWireup:
http://support.microsoft.com/kb/324151
Unfortunately, this is legacy code, and not always done the right way.
In particularly, because the user control is loaded and rendered manually, it probably skips most of the event model that I wanted to take advantage of. Ideally, it should have done an AddControl() to the page, rather than rendering the control to a HtmlTextWriter.
My work around is to override the RenderControl method, and initialize the browser property before passing RenderControl up the chain.

dropdown list in user control and auto postback after new item selected

I have a dropdown list in User Control
How Can I get selected value of dropdown list of user control in page when user select another item (auto postback is true)?
I tried to store selected value of ddl in a public member from Selected Index Changed event handler. But this handler executes after page load of container page. I need to load data in page based on selected value in ddl of user control.
Thanks
User Control's code
protected void ddlPageSize_SelectedIndexChanged(object sender, EventArgs e)
{
_SelectedPageSize = Convert.ToInt32(ddlPageSize.SelectedValue);
}
int GetSelectedPageSize()
{
return _SelectedPageSize;
}
There are a number of ways to accomplish what you're looking for. The first is simply to re-order your events in the containing page. If you use the PreRender event rather than the PageLoad event, your drop down selection action will be complete and the information will be readily available.
A second method, which probably more extensible, would be to raise a custom event from your usercontrol that your page listens for and handles. Then the action would be taken directly at the point where the information is immediately available. This allows any containing structure (whether it's a page, usercontrol or something similar) to subscribe to the event and handle whatever is needed.
A third method, a little more rigid, would be to have a function in the containing page that is called by the usercontrol once the data is complete. This requires the usercontrol to have knowledge of the specific page type that it will be included in (making it less extensible) so I wouldn't recommend it.
Edit: Here's an idea for implement option #2 with a custom event:
public partial class MyUserControl: UserControl
{
//All of your existing code goes in here somewhere
//Declare an event that describes what happened. This is a delegate
public event EventHandler PageSizeSelected;
//Provide a method that properly raises the event
protected virtual void OnPageSizeSelected(EventArgs e)
{
// Here, you use the "this" so it's your own control. You can also
// customize the EventArgs to pass something you'd like.
if (PageSizeSelected!= null)
PageSizeSelected(this, e);
}
private void ddlPageSize_SelectedIndexChanged(object sender, EventArgs e)
{
_SelectedPageSize = Convert.ToInt32(ddlPageSize.SelectedValue);
OnPageSizeSelected(EventArgs.Empty);
}
}
Then in your page code you would listen for the event. Somewhere in the page load you would add:
myUserControlInstance.PageSizeSelected += MyHandinglingMethod;
And then provide the method that handles the event:
protected void MyHandlingMethod(object sender, EventArgs e)
{
// Do what you need to do here
}

Events between an ASPX and ASCX

i'm a beginner in .NET, and search since yesterday morning to resolve my problem without finding the solution.
Here is my problem :
I create dynamically some User Controls by this way, because I need to give parameters :
List<ANNOUNCEMENT> listAnnouncement = getAnnoucements();
foreach(ANNOUNCEMENT ann in listAnnouncement)
{
if(ann.IS_CURRENT_ANNOUNCEMENT && currentAnnouncement == null)
{
currentAnnouncement = ann;
}
List<Object> listParams = new List<Object>();
listParams.Add(ann);
AnnouncementPresentation ap = (AnnouncementPresentation)(Controller.LoadControl(Page, "~/UserControls/AnnouncementPresentation.ascx", listParams.ToArray()));
/* important for the end of the method */
ap.modifyAnnouncementButtonClick += new EventHandler(modifyAnnouncementButtonClick);
pnl_announcements.Controls.Add(ap);
}
In this ASCX, I have a button, and when user will click on it, I want to call a method contained in my ASPX, so I do this in the ASCX :
public event EventHandler modifyAnnouncementButtonClick;
protected void btn_modify_announcement_Click(object sender, EventArgs e)
{
PageAdminAnnonces.currentAnnouncement = annonce;
modifyAnnouncementButtonClick(sender, e);
}
And this in the ASPX :
protected void modifyAnnouncementButtonClick(object sender, EventArgs e)
{
initListOfAnnouncement();
lbl_errors.Text = currentAnnouncement.TITLE;
}
I think everything works, but there is the problem : It works once, and at the end of the method, I delete my ASCX as you can see, and create new ASCX. But they don't have the methods, and when I click again, nothing works, so the ASPX is reloaded. After reloading, it works again.
Do i do something wrong?
According to the information in the comments, I suppose that your solution does not work because you are recreating the controls in the Click event handling method, which is very late in the page's lifecycle and should not be used for adding controls.
As mentioned in the comments, I suggest you to create the controls in Page_Init or Page_Load and not recreate them in the button's Click handling method. You should also assign a unique ID to each of them. Then, in the Click handler, you can use FindControl method to acces the created controls. Alternatively you can just save the references to the controls upon creation, so you can access them later easily.
Useful links:
http://msdn.microsoft.com/en-us/library/ms178472.aspx
http://visualstudiomagazine.com/articles/2010/10/11/more-on-adding-controls-dynamically.aspx

User Control Page_Load event not reading variable passed from main page

I have problem passing a variable from a main page containing a user control to the user control itself. Although the passed variable is available generally in the code-behind of the user control the page_load event can't seem to read it.
My code -
In the main page code-behind:
protected void FindCCFsButton_Click(object sender, EventArgs e)
{
if (CustomerDropDown.SelectedIndex != 0)
{ SearchUcCCFList.SetCustID(CustomerDropDown.SelectedValue); }
}
(SearchUcCCFList is the instance of the user control in the main aspx page).
In the user control code behind:
public partial class ucCCFList : System.Web.UI.UserControl
{
public string srchCust { get; set; }
public void SetCustID(string custID)
{
srchCust = custID;
testCustLabel.Text = GetCustID(); //this works
}
public string GetCustID()
{
return srchCust;
}
protected void Page_Load(object sender, EventArgs e)
{
CCFGridView.DataSource = DAL.SearchCCFs(custID : GetCustID()); //doesn't work
CCFGridView.DataBind();
test2CustLabel.Text = GetCustID(); //doesn't work
}
In the Page_Load event GetCustId() doesn't return anything (so the records aren't filtered and all get returned) although it can be read in the methods outside the Page_Load.
I'm probably making a beginners error but any help would be appreciated.
Edit - following Alan's suggestion in the comments I stepped through the page loading sequence & it appears that the user control's Page_Load event is running BEFORE the code in the main page's button click so the variable is not yet available. The sequence after clicking the button is:
User control Page_Load runs
Code in button event on main page
Other code (outside Page_Load) in user control runs hence variable is available here.
This seems a bit weird, is there another way to pass the variable into the user controls Page_Load?
In this case, your click handling even on the main page is called after the user control page load call. Your variable is being set, but not until after your data binding in the user control.
Either switch the user control to declarative binding which will handle calling methods in the correct order for you. Or the easier fix in this case is to change the user control data binding from Page_Load to Page_PreRender, which is called later in the life cycle, after the main page click handling call.
protected void Page_PreRender(object sender, EventArgs e)
{
CCFGridView.DataSource = DAL.SearchCCFs(custID : GetCustID()); // will work now
CCFGridView.DataBind();
test2CustLabel.Text = GetCustID(); // will work now
}
For a more thorough answer, read up on the ASP.NET page life cycle including the interaction with user controls' life cycle.

Resources