We are rendering usercontrols dynamically like this:
public string RenderControl(string pathcontrol)
{
string html;
var page = new Page();
var control = page.LoadControl(path);
page.Controls.Add(control);
// do stuff to the control (give it some data to work on)
using (var writer = new StringWriter())
{
HttpContext.Current.Server.Execute(page, writer, false);
html = writer.ToString();
}
return html;
}
This lets us the same user controls when rendering pages normally as we do when rendering responses to ajax calls. However, when adding controls which themselves contain a scriptmanagerProxy we run into the problem that the newed up Page object doesn't contain either a ScriptManager or the HtmlForm in which the ScriptManager needs to run.
Is there any way around this?
Yours
Andreas
As others have said you can add a ScriptManger dynamically easily enough [ Add ScriptManager to Page Programmatically? if your Page object is complete.
Can you try using BuildManager.CreateInstanceFromVirtualPath() to create the Page object instead? You issue may be how you create that object. There's a bit more to creating a new page than newing up the Page object.
Eg.
Page page
= BuildManager.CreateInstanceFromVirtualPath("~/Test.aspx", typeof(Page))
See also http://www.west-wind.com/weblog/posts/120530.aspx for a little more background.
Can you do something like this:
page.Form.Controls.AddAt(0, New ScriptManager())
Edit: I think you'd also need to add your control to the page's form, not just to the page itself, right? It's my understanding that the form is created with the page, but if not you should be able to just do:
page.Form = new HtmlForm()
You may also need to do something like:
page.Controls.Add(page.Form)
Sure, the trick is to add it in a page's Init event handler. You can use:
Page.Init += delegate {
// check for script manager
if( ScriptManager.GetCurrent(Page) == null ) {
ScriptManager m = new ScriptManager();
m.ScriptMode = ScriptMode.Release;
Page.Form.Controls.AddAt(0, m);
}
}
I'd recommend avoiding dynamically adding forms to your page if you can. For example, the above code snippet assumes a form is already present on the page.
Update
Sure, thanks for pointing that out Andreas. Here's an update. So, there is no setter for Page.Form - but you are correct in that you can add a new HtmlForm to the Controls collection. Once added, the Page.Form property is no longer null. That will allow you to add the ScriptManager dynamically as seen above. Here is a code sample that shows this working (ASPX file is a simple page without a server side form):
public partial class Pages_Test_DynamicFormSample : Page {
protected void Page_Init(object sender, EventArgs e) {
Controls.Add( new HtmlForm() );
ScriptManager m = new ScriptManager();
m.ScriptMode = ScriptMode.Release;
Form.Controls.AddAt(0, m);
}
protected void Page_Load(object sender, EventArgs e) {
// ScriptManager test
var t1 = new System.Web.UI.WebControls.TextBox();
var t2 = new System.Web.UI.WebControls.TextBox();
Form.Controls.Add( t1 );
Form.Controls.Add( t2 );
ScriptManager.GetCurrent(Page).SetFocus( t2 );
}
}
Enjoy - btw, setting the ScriptManager's ScriptMode to Release obviously isn't required. We do it just to avoid some JavaScript bugs found in the Debug version of the ASP.NET script runtime.
Related
I am using a jQuery editor and when the user hits the submit button i put the content into asp.net Panel control as html and then when i render this Panel the html i added is not
retrieved.
function MoveData() {
var sHTML = $('#summernote_1').code();
// dvFrontPageHtml is asp.net Panel
$('[id*=dvFrontPageHtml]').html(sHTML);
setTimeout(function () {
javascript: __doPostBack('ctl00$ctl00$ContentPlaceHolderBody$ContentPlaceHolderBody$lnkSave', '');
}, 10000);
return false;
}
System.Text.StringBuilder sb = new System.Text.StringBuilder();
System.IO.StringWriter stWriter = new System.IO.StringWriter(sb);
System.Web.UI.HtmlTextWriter htmlWriter = new System.Web.UI.HtmlTextWriter(stWriter);
dvFrontPageHtml.RenderControl(htmlWriter);
string Message = sb.ToString();
The message does not returning the html added.
I dont want to use jQuery ajax call as of now.
Any suggestions
without seeing all the relevant code its hard to pinpoint the problem.
but im pretty sure you are trying to find an ASP.net control by its serverside ID from clientside.
dvFrontPageHtml is the Controls ID by which asp.net identifies it, and unless you explicitly tell ASP.Net otherwise, it will generate a different ID for the control to be used by scripts at clientside
you need to retrieve the panel's clientside ID thats being generated for it by asp.net
you do it by a preprocessor directive <%=dvFrontPageHtml.ClientID%>:
$('[id*=<%=dvFrontPageHtml.ClientID%>]').html(sHTML);
alternatively, if you want the clientside ID to be same as the serverside ID, you can set the control's attribute ClientIDMode="Static".
UPDATE:
from your comment it seems the problem is elsewhere. what comes to mind, is that RenderControl() takes the control as it was when sent to the client in the Response. but the control is not being submitted to the server in next Request, so you will not be able to retrieve its altered html.
what you can do as a workaround, is hook into ASP.NET's build in postback mechanism, and submit the panel's html as a custom event argument:
for the example, lets assume this is our html:
<asp:Panel ID="dvFrontPageHtml" runat="server" ClientIDMode="Static">test</asp:Panel>
<asp:Button ID="BT_Test" runat="server" Text="Button"></asp:Button>
this will be our javascript:
$(function(){
// add custom event handler for the submit button
$("#<%=BT_Test.ClientID%>").click(function (ev) {
//prevent the default behavior and stop it from submitting the form
ev.preventDefault();
//alter the panels html as you require
var sHTML = $('#summernote_1').code();
$('[id*=dvFrontPageHtml]').html(sHTML);
//cause a postback manually, with target = BTCLICK and argument = panel's html
__doPostBack('BTCLICK', $('[id*=dvFrontPageHtml]').outerHTML());
});
});
and here we capture the postback on serverside:
//we monitor page load
protected void Page_Load(object sender, EventArgs e)
{
string Message;
//check if its a postback
if (IsPostBack)
{
//monitor for our custom target "BTCLICK"
if (Request.Form["__EVENTTARGET"].CompareTo("BTCLICK") == 0)
{
// retrieve the panels html from the event argument
Message = Request.Form["__EVENTARGUMENT"];
}
}
}
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
I have to add a dropdown control in the web part.
I am rendering the part using HTML Strings as follows...
StringBuilder sb = new StringBuilder();
sb.Append(div id="content); There are quotes in the string
sb.Append(div class=""hb"">");
*sb.Append(div class=""someclass"">");*
sb.Append(h2 id=""contentpage_title"">Title");
**sb.Append(div class=""ctn_conferences"">");**
writer.Write(sb.ToString());*
I have to add a loaded dropdown control...so I declared..
protected DropDownList ddMyDropDown = new DropDownList();
Then added the control in the middle of the render where I wanted as follows...
ddMyDropDown.RenderControl(writer);
Everything is fine....except... the post back does not work.
My event handlers are not getting executed.
When I add the control ...like Controls.Add(Control) then it adds at the bottom of the part. That is not what I want.
So how do I get the post back to work?
Thanks in advance.
-Satyen
You've got the right idea for the render method override but add the following:
protected DropDownList ddMyDropDown;
protected override void CreateChildControls()
{
base.CreateChildControls();
ddMyDropDown = new DropDownList();
ddMyDropDown.AutoPostBack = true;
Controls.Add(ddMyDropDown);
}
Also, in the render method override call EnsureChildControls() before you try and render any control setup through the CreateChildControls() method.
I'm trying to take an existing bunch of code that was previously on a full .aspx page, and do the same stuff in a .ashx handler.
The code created an HtmlTable object, added rows and cells to those rows, then added that html table the .aspx's controls collection, then added it to a div that was already on the page.
I am trying to keep the code in tact but instead of putting the control into a div, actually generate the html and I'll return that in a big chunk of text that can be called via AJAX client-side.
HtmlTable errors out when I try to use the InnerHtml property (says it isn't supported), and when I try RenderControl, after making first a TextWriter and next an HtmlTextWriter object, I get the error that Page cannot be null.
Has anyone done this before? Any suggestions?
*Most recent is above.
OK, even after Matt's update there is a workaround ;)
Firstly, we have to use a page with form inside. Otherwise we won't be able to add a ScriptManager control. One more thing: the ScriptManager control should be the first control in the form. Further is easier:
Page page = new Page();
Button button = new System.Web.UI.WebControls.Button
{
ID = "btnSumbit",
Text = "TextButton",
UseSubmitBehavior = true
};
HtmlForm form = new HtmlForm
{
ID="theForm"
};
ScriptManager scriptManager = new ScriptManager
{
ID = "ajaxScriptManager"
};
form.Controls.Add(scriptManager);
form.Controls.Add(button);
page.Controls.Add(form);
using (StringWriter output = new StringWriter())
{
HttpContext.Current.Server.Execute(page, output, false);
context.Response.ContentType = "text/plain";
context.Response.Write(output.ToString());
}
This works. The output is quite large so I decided not to include it into my answer :)
Actually, there is a workaround. Yep, we may render a control in handler.
Firstly, we need a formless page. Because without it we get:
Control 'btnSumbit' of type 'Button'
must be placed inside a form tag with
runat=server.
public class FormlessPage : Page
{
public override void VerifyRenderingInServerForm(Control control)
{
}
}
Secondly, nobody can prevent us from creating an instance of our FormlessPage page. And now let's add a control there (I decided to add a Button control as an example, but you could use any).
FormlessPage page = new FormlessPage();
Button button = new System.Web.UI.WebControls.Button
{
ID = "btnSumbit",
Text = "TextButton",
UseSubmitBehavior = true
};
page.Controls.Add(button);
Thirdly, let's capture the output. For this we use HttpServerUtility.Execute method:
Executes the handler for the specified
virtual path in the context of the
current request. A
System.IO.TextWriter captures output
from the executed handler and a
Boolean parameter specifies whether to
clear the
System.Web.HttpRequest.QueryString and
System.Web.HttpRequest.Form
collections.
Here is the code:
using (StringWriter output = new StringWriter())
{
HttpContext.Current.Server.Execute(page, output, false);
context.Response.ContentType = "text/plain";
context.Response.Write(output.ToString());
}
The result will be:
<input type="submit" name="btnSumbit" value="TextButton" id="btnSumbit" />
In addition I can recommend ScottGu's article Tip/Trick: Cool UI Templating Technique to use with ASP.NET AJAX for non-UpdatePanel scenarios. Hope, you could find a lot of useful there.
Another option is to host the ASP.NET HTTP pipeline in your process, render the page to a stream and read the HTML you need to send from the HttpListenerContext.Response.OutputStream stream after the page has been processed.
This article has details: http://msdn.microsoft.com/en-us/magazine/cc163879.aspx
I'm dynamically loading user controls adding them to the Controls collection of the web form.
I'd like to hide user controls if they cause a unhandled exception while rendering.
So, I tried hooking to the Error event of each UserControl but it seems that this event never fires for the UserControls as it does for Page class.
Did some googling around and it doesn't seem promising. Any ideas here?
mmilic, following on from your response to my previous idea..
No additional logic required! That's the point, your doing nothing to the classes in question, just wrapping them in some instantiation bubble-wrap! :)
OK, I was going to just bullet point but I wanted to see this work for myself, so I cobbled together some very rough code but the concept is there and it seems to work.
APOLOGIES FOR THE LONG POST
The SafeLoader
This will basically be the "bubble" I mentioned.. It will get the controls HTML, catching any errors that occur during Rendering.
public class SafeLoader
{
public static string LoadControl(Control ctl)
{
// In terms of what we could do here, its down
// to you, I will just return some basic HTML saying
// I screwed up.
try
{
// Get the Controls HTML (which may throw)
// And store it in our own writer away from the
// actual Live page.
StringWriter writer = new StringWriter();
HtmlTextWriter htmlWriter = new HtmlTextWriter(writer);
ctl.RenderControl(htmlWriter);
return writer.GetStringBuilder().ToString();
}
catch (Exception)
{
string ctlType = ctl.GetType().Name;
return "<span style=\"color: red; font-weight:bold; font-size: smaller;\">" +
"Rob + Controls = FAIL (" +
ctlType + " rendering failed) Sad face :(</span>";
}
}
}
And Some Controls..
Ok I just mocked together two controls here, one will throw the other will render junk. Point here, I don't give a crap. These will be replaced with your custom controls..
BadControl
public class BadControl : WebControl
{
protected override void Render(HtmlTextWriter writer)
{
throw new ApplicationException("Rob can't program controls");
}
}
GoodControl
public class GoodControl : WebControl
{
protected override void Render(HtmlTextWriter writer)
{
writer.Write("<b>Holy crap this control works</b>");
}
}
The Page
OK, so lets look at the "test" page.. Here I simply instantiate the controls, grab their html and output it, I will follow with thoughts on designer support etc..
Page Code-Behind
protected void Page_Load(object sender, EventArgs e)
{
// Create some controls (BadControl will throw)
string goodHtml = SafeLoader.LoadControl(new BadControl());
Response.Write(goodHtml);
string badHtml = SafeLoader.LoadControl(new GoodControl());
Response.Write(badHtml);
}
Thoughts
OK, I know what you are thinking, "these controls are instantiated programatically, what about designer support? I spent freaking hours getting these controls nice for the designer, now you're messing with my mojo".
OK, so I havent really tested this yet (probably will do in a min!) but the idea here is to override the CreateChildControls method for the page, and take the instance of each control added on the form and run it through the SafeLoader. If the code passes, you can add it to the Controls collection as normal, if not, then you can create erroneous literals or something, up to you my friend.
Finally..
Again, sorry for the long post, but I wanted to get the code here so we can discuss this :)
I hope this helps demonstrate my idea :)
Update
Tested by chucking a control in on the designer and overriding the CreateChildControls method with this, works fine, may need some clean up to make things better looking, but I'll leave that to you ;)
protected override void CreateChildControls()
{
// Pass each control through the Loader to check
// its not lame
foreach (Control ctl in Controls)
{
string s = SafeLoader.LoadControl(ctl);
// If its bad, smack it downnnn!
if (s == string.Empty)
{
ctl.Visible = false; // Prevent Rendering
string ctlType = ctl.GetType().Name;
Response.Write("<b>Problem Occurred Rendering " +
ctlType + " '" + ctl.ID + "'.</b>");
}
}
}
Enjoy!
This is an interesting problem.. I am still pretty fresh when it comes to custom controls etc, but here are my thoughts (feel free to comment/correct people!).. (I am kinda thinking/writing out loud here!)
If an error occurs during rendering, in some cases, would it not be too late? (since some of the controls HTML may have already been sent to the Writer and output).
Therefore, would it not be best to wrap the user control's Render method, but rather than passing it the reference to the "Live" HtmlTextWriter, you pass your own, trap any Exceptions raised in this little safety "bubble", if all goes well, you then pass your resultant HTML to the actual HtmlTextWriter?
This logic could probably be slung to a generic wrapper class which you would use to dynamically load/render the controls at run time..
If any errors do occur, you have all the information you need at your disposal! (i.e control references etc).
Just my thoughts, flame away! :D ;)
Depending on where your errors are occurring you can do something like...
public abstract class SilentErrorControl : UserControl
{
protected override void Render( HtmlTextWriter writer )
{
//call the base's render method, but with a try catch
try { base.Render( writer ); }
catch ( Exception ex ) { /*do nothing*/ }
}
}
Then inherit SilentErrorControl instead of UserControl.
Global.asax and Application_Error?
http://www.15seconds.com/issue/030102.htm
Or the Page_Error Event on an individual Page only:
http://support.microsoft.com/kb/306355
void Page_Load(object sender, System.EventArgs e)
{
throw(new ArgumentNullException());
}
public void Page_Error(object sender,EventArgs e)
{
Exception objErr = Server.GetLastError().GetBaseException();
string err = "<b>Error Caught in Page_Error event</b><hr><br>" +
"<br><b>Error in: </b>" + Request.Url.ToString() +
"<br><b>Error Message: </b>" + objErr.Message.ToString()+
"<br><b>Stack Trace:</b><br>" +
objErr.StackTrace.ToString();
Response.Write(err.ToString());
Server.ClearError();
}
Also, Karl Seguin (Hi Karl!) had a Post on using HttpHandler instead:
http://codebetter.com/blogs/karlseguin/archive/2006/06/12/146356.aspx
(Not sure what the permission to reproduce it, but if you want to write up an answer, you got my Upvote ☺)
How about adding a new sub-class of UserControl that error-handles its render and load methods (so that they hide as you wish) and then inheriting from that for your user controls?
I am not sure I understand your response.. How are you loading your controls and adding them to your controls collection?
That was the whole point of the bit added in the "Update" section.. You have the flexibility to use the SafeLoader wherever you please.
I am not sure why you feel you don't have access/control over the Html? The goal of the SafeLoader is that you dont care what the html is, you simply try and "output" the control (within the "bubble") and determine if it loads OK in its current state.
If it does (i.e. the html is returned) then you can do what you like with it, output the html, add the control to the controls collection, whatever!
If not, then again, you can do what you like, render an error message, throw a custom exception.. The choice is yours!
I hope this helps clarify things for you, if not, then please shout :)
I used #Keith's approach, but the problem is that the control is rendered up until the Exception is thrown, potentially resulting in open HTML tags. I'm also rendering the exception information in the Control if in Debug mode.
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
try
{
// Render the module to a local a temporary writer so that if an Exception occurs
// the control is not halfway rendered - "it is all or nothing" proposition
System.IO.StringWriter sw = new System.IO.StringWriter();
System.Web.UI.HtmlTextWriter htw = new System.Web.UI.HtmlTextWriter(sw);
base.Render(htw);
// We made it! Copy the Control Render over
writer.Write(sw.GetStringBuilder().ToString());
}
catch (System.Exception ex)
{
string message = string.Format("Error Rendering Control {0}\n", ID);
Log.Error(message, ex);
if (Page.IsDebug)
writer.Write(string.Format("{0}<br>Exception:<br><pre>{1}\n{2}</pre>", message, ex.Message, ex.StackTrace));
}
}