PDA

View Full Version : [CLOSED] Page.FindControl cannot find parent by id in Page_Load of UserControl that invoked by ResourceManger.RaisePostbackEvent



michaeld
Dec 19, 2013, 3:49 AM
The reason I believe this doesn't work is that Page in this condition is a Page that is instantiated from a new SelfRenderingPage. Issue appears when I call UserControlRenderer.Render( cfg );

To solve this problem, you need to override SelfRenderingPage.FindControls to use the original Page. You might want to override many of the default Page methods and properties (Form for example is not available either).

At a minimum, one has every reason to believe that while within the UserControl's Page_Load, they still should have access to find controls.

Vladimir
Dec 19, 2013, 6:30 AM
UserControl is rendered by UserControlRenderer is not related with main page at all
I am not sure why do you expect to get references to the main page.
UserControl is rendered in separate page and not involved to page life cycle of main page



To solve this problem, you need to override SelfRenderingPage.FindControls to use the original Page. You might want to override many of the default Page methods and properties (Form for example is not available either).

In this case, we need to change methods of parent page also to find user control also (find user control from parent page)

It is not good idea because as I mentioned before, UserControlRenderer renders user control in another page context (you can render it anywhere, in http handler, web service and etc)

If you need parent page context then you need to add user control to the page hierarchy (and do not use UserControlRenderer) or pass page reference to user control and use it as required

michaeld
Dec 19, 2013, 7:39 AM
UserControl is rendered by UserControlRenderer is not related with main page at all
I am not sure why do you expect to get references to the main page.
UserControl is rendered in separate page and not involved to page life cycle of main page

In this case, we need to change methods of parent page also to find user control also (find user control from parent page)

If you need parent page context then you need to add user control to the page hierarchy (and do not use UserControlRenderer) or pass page reference to user control and use it as required

I have had to revert all DirectEvent/Method UserControl creation to using UserControlRender because UserControlLoader, Items.Add, Controls.Add destroy it's parent client data in DirectEvents. I would love to create UserControls reliably in the standard page-life cycle but that just has not been plausible in testing. UserControlRender has the ability to overcome this deficit and send the client new controls and attach to a parent element without breaking the parent.

If you're curious what I'm building and you think you have a better way, I'm open. Please advise a better strategy here: http://forums.ext.net/showthread.php?27618-Add-UserControls-in-DirectEvents-Methods-and-have-them-automatically-Reload-on-Post-back-through-messanging-in-Hidden-Fields

I'm switching to a full ajax model without frequent Page refreshes which means I need to be able to create reusable UserControls reliably that can be rendered in Page_Load or DirectEvent/Method's with heavy nesting (e.g. that have their own controls with DirectEvents/Methods). So far I have found no better way than with UserControlRender so until I've been offered a better path, all I can do is test nuances and post the issues/limits I find. Passing in the Page from the lifecycle would defeat the reusable controls requirement.


It is not good idea because as I mentioned before, UserControlRenderer renders user control in another page context (you can render it anywhere, in http handler, web service and etc)

I'm not suggesting you break anything with regard to how UserControlRender presently works, but extend it so it is more reusable (as you say) anywhere, i.e. not just an httphandler or webservice, etc. So by anywhere, I'm expecting it to work within the page-lifecycle as well, and I've had great success with doing so too. I'm assuming a webservice or handler wouldn't call FindControl, but in a page life-cycle, you would, so it seems reasonable to me that if you are using it in the page life-cycle case, you should have access to the full Page tree. For the meanwhile, this has seemed to do the trick...



public partial class SelfRenderingPage {
public override Control FindControl( string id ) {
var ctl = base.FindControl( id );
if( ctl != null )
return ctl;
var Page = HttpContext.Current.Handler as Page;
return ( Page != null ) ? Page.FindControl( id ) : null;
}
}

Baidaly
Dec 19, 2013, 9:41 PM
Hello!
I saw that you created a lot of things to improve UserControls .

I'm just curious, what kind of scenario are you trying to implement?

michaeld
Dec 20, 2013, 3:05 AM
I saw that you created a lot of things to improve UserControls .

I'm just curious, what kind of scenario are you trying to implement?

Please follow the link above which contains the implementation of what I'm trying to create. That thread contains a link to the original feature request.

But in a nutshell, a reliable ajax standard with extnet to render reusable UserControls to the page without needing to refresh the page or writing alternate code to handle first page initialization versus DirectEvent/Methods. I want to be able to use the same UserControl initialization in both Page_Load or DirectEvents/Methods.


Refreshes are expensive because of the size of the extjs/extnet js client libraries. Even on the fastest machines, client load times on complex pages are in the seconds. Mobile devices are even worse because of their lower processing power. Most of the time, it is unnecessary to reload an entire page, especially when the client already has the runtime loaded and only regions of the page need to be updated. That's where loading UserControls reliably come in. IFrames have been significantly more reliable, but as noted in other threads, they have major memory problems and also require duplication of loading the client js libraries. That's why I'm focusing on DirectEvent/Method UserControls in the same page scope in all my test.

Once all the variations and issues with rendering extnet UserControls are remedied, I can switch to creating pages with a minimum complexity and render new controls on-demand back to the client where everything else is already loaded. It means more round-trips back to the server but far less initial load-time cost.


Example: I load an admin page with a preview of their content available to edit. On it which is composed of UserControls, there are a few dozen menu options which launch windows with FormPanels so they can edit specifics. Each menu item click can call a DirectEvent to render only the window requested and bind the editable contents. Then the user can click save and its DirectEvent saves the content, tells the client to destroy the window and refresh only the user control that was affected or any other sections in the preview that might also be affected (that's where FindControl to the whole page becomes important). All of this is possible now with extnet, except managing the reload of the UserControls so their own DirectEvents/Methods can be called successfully. Managing reload of UserControls is at the onus of the developer. I'm trying to remedy this in that link. It's still very hard to ensure postback binds back to the proper functions because of bugs from nested UserControls variations and IDModes. I'm working through the hundred or so variations to weed out these.

Vladimir
Dec 20, 2013, 11:31 AM
Hi,

UserControlRenderer assumes that you render user control without binding to concrete page context
The fact that you call UserControlRenderer in the your page should not to be propagated to user control
So, your user control is not related with the page and I don't see any reason to make such hack for page hierarchy.
What if you need to find control in the user control (using FindControl), your code doesn't allow it

If need to get reference of main page then use 'HttpContext.Current.Handler'
imho, such overriding is not correct (in architecture term)

I will discuss your proposal with my colleguaes and i'll let you know them opinion

I guess it is better (if you need to persist page hierarchy) to render user control the same way


<%@ Page Language="C#" EnableViewState="false" ClassName="Test43" %>
<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>


<!DOCTYPE html>
<script runat="server">


protected void Render_DirectClick(object sender, DirectEventArgs e)
{
var ct = new Container();
ct.ContentControls.Add(this.Page.LoadControl("Child.ascx"));
this.Page.Controls.Add(ct);
ct.Render("Panel1", RenderMode.AddTo);
}
</script>


<html>
<head id="Head1" runat="server">
<title>Sample</title>
</head>
<body>
<form id="Form1" runat="server">
<ext:ResourceManager ID="ResourceManager1" runat="server" />

<ext:Button runat="server" Text="Render UserControl" OnDirectClick="Render_DirectClick" />
<ext:Panel ID="Panel1" runat="server" Width="300" Height="300">
</ext:Panel>
</form>
</body>
</html>

michaeld
Dec 20, 2013, 2:17 PM
UserControlRenderer assumes that you render user control without binding to concrete page context
The fact that you call UserControlRenderer in the your page should not to be propagated to user control
So, your user control is not related with the page and I don't see any reason to make such hack for page hierarchy.
What if you need to find control in the user control (using FindControl), your code doesn't allow it

If need to get reference of main page then use 'HttpContext.Current.Handler'
imho, such overriding is not correct (in architecture term)


I guess it is better (if you need to persist page hierarchy) to render user control the same way


protected void Render_DirectClick(object sender, DirectEventArgs e)
{
var ct = new Container();
ct.ContentControls.Add(this.Page.LoadControl("Child.ascx"));
this.Page.Controls.Add(ct);
ct.Render("Panel1", RenderMode.AddTo);
}
</script>


I agree with everything you're saying. UserControlRender was not my first choice. It became my last resort after trying a dozen or so strategies working with Daniil. This is the first alternate model from ones suggested to try, so thank you. I will try and report back.



I will discuss your proposal with my colleguaes and i'll let you know them opinion


I'm going to assume you mean the whole model I presented as a feature request here:
http://forums.ext.net/showthread.php?27574-UserControl-Auto-Loader-amp-Manager

I won't presume this is the only best way, but to me, it's a logical next step to a comprehensive ajax solution.

Check my implementation, to get the ordering right, I had to move final loading of the registered controls till after all controls Page_Load. I did this by overriding Page.RaisePostBackEvent. But it would be more natural in ResourceManager.RaisePostBackEvent.
Implementation here: http://forums.ext.net/showthread.php?27618-Add-UserControls-in-DirectEvents-Methods-and-have-them-automatically-Reload-on-Post-back-through-messanging-in-Hidden-Fields&p=122873#post122873

So far it just works. The reason is because each add of a registered control adds a hidden field and they return to the post in the same order they were created. It may be safer to just use numbering instead. Though I trust your skill to put the whole thing together even better than I have.

michaeld
Dec 21, 2013, 10:17 AM
I'm assuming this mode does not participate in layout; therefore it's like ContentControls. If the UserControl has asp WebControls, it needs to be based on Form. Also, I don't like the fact that it has the wrong parent, so instead, can't I change it to Add to the Controls of the Parent? And I think it's advisable to set the ID. Also wouldn't it be better to make ct a new Component() instead?


protected void Render_DirectClick(object sender, DirectEventArgs e)
{
var ct = new Container();
var uc = Page.LoadControl("Child.ascx");
uc.ID = "uc1";
ct.ContentControls.Add();
Panel1.Controls.Add(ct);
ct.Render("Panel1", RenderMode.AddTo);
}


How can I do the same AddTo with Items to participate in layout? I tried with UserControlLoader and it doesn't work.

michaeld
Dec 23, 2013, 4:13 AM
I've taken your suggestions and extended them to the latest implementation found here:
http://forums.ext.net/showthread.php?27618-Add-UserControls-in-DirectEvents-Methods-and-have-them-automatically-Reload-on-Post-back-through-messaging-in-Hidden-Fields&p=123026#post123026

Daniil
Dec 23, 2013, 5:53 AM
I'm assuming this mode does not participate in layout; therefore it's like ContentControls. If the UserControl has asp WebControls, it needs to be based on Form. Also, I don't like the fact that it has the wrong parent, so instead, can't I change it to Add to the Controls of the Parent? And I think it's advisable to set the ID. Also wouldn't it be better to make ct a new Component() instead?


protected void Render_DirectClick(object sender, DirectEventArgs e)
{
var ct = new Container();
var uc = Page.LoadControl("Child.ascx");
uc.ID = "uc1";
ct.ContentControls.Add();
Panel1.Controls.Add(ct);
ct.Render("Panel1", RenderMode.AddTo);
}


How can I do the same AddTo with Items to participate in layout? I tried with UserControlLoader and it doesn't work.

In this code the "ct" should participate in the Panel1's layout.


protected void Render_DirectClick(object sender, DirectEventArgs e)
{
var ct = new Container();
var uc = Page.LoadControl("Child.ascx");
uc.ID = "uc1";
ct.ContentControls.Add(uc);
Panel1.Items.Add(ct);
ct.Render();
}

michaeld
Dec 24, 2013, 2:11 AM
In this code the "ct" should participate in the Panel1's layout.


protected void Render_DirectClick(object sender, DirectEventArgs e)
{
var ct = new Container();
var uc = Page.LoadControl("Child.ascx");
uc.ID = "uc1";
ct.ContentControls.Add(uc);
Panel1.Items.Add(ct);
ct.Render();
}


The problem is the Child.ascx may contain controls that have their own flex that are supposed to be based off the Parent's Layout (in this case Panel1). If I generically render a Container, the children in the UserControl will not participate properly. The sample you provided is fine, but the api I'm trying to create looks as follows (presently updated to use your suggestion):


/// <summary>
/// Add a User Control during a Direct Event that needs to be Rendered to Page, like adding a Bin object.
/// To Replace the existing, use same CtlName; To add new each time, make ctlName unique
/// </summary>
public static void AddDirectControl( this AbstractContainer Parent, string ucPath, string ctlName, bool items = false, BindFn bindFn = null, bool Register = false ) {


var uc = Parent.Page.LoadControl( ucPath );
uc.ClientIDMode = System.Web.UI.ClientIDMode.Predictable;
uc.ID = ctlName;


var comp = new Container();
comp.ContentControls.Add( uc );


// Give a chance to bind before adding the control
if( bindFn != null )
bindFn( uc );
DirectRendering = true;
if(items)
Parent.Items.Add(comp);
else
Parent.ContentControls.Add(comp);
comp.Render();
DirectRendering = false;


if( Register )
RegisterUserControl( Parent, ucPath, uc.ClientID, ClientIDMode.Predictable );
}


The present challenge I'm trying to work out is this api above needs to support bool items in the same way UserControlRenderer does. That is, if true, Parent.Items.Add versus Parent.ContentControl.Add if false. The problem is a just adding a generic Container is prohibitive to the design goal.

UserControlLoader would be perfect because it handles Content or Items perfectly. That's why I'd hoped to the problem I showed here: http://forums.ext.net/showthread.php?27644-OPEN-409-Exception-in-Transformer-with-UserControlLoader-with-RenderMode-AddTo UserControlLoader RenderMode.AddTo would have been the end-all solution. But you told me, no, you may not support this. So I'm back to the drawing board. There's got to be some way to render a UserControlLoader to a Parent without introducing an arbitrary container but also without rerendering the parent, but how? Please advise.

Daniil
Dec 24, 2013, 9:18 AM
I responded here:
http://forums.ext.net/showthread.php?27644&p=123090&viewfull=1#post123090