I'm trying to create a user control template that I can send as email.
In a utility class I have a method that contains this code:
StringBuilder sb = new StringBuilder();
Page p = new Page();
UserControl ctrl = (UserControl)p.LoadControl("~/EmailTemplates/OrderConfirmation.ascx");
StringWriter sw = new StringWriter(sb);
Html32TextWriter htw = new Html32TextWriter(sw);
ctrl.RenderControl(htw);
This correctly writes the user controls text, but if I want to use a server control such as a listview inside of the controls page, the listview is never evaluated. It seems that only inline code blocks are evaluated. How can I get around this?
You should actually add the control to a page and execute the page:
var page = new FormlessPage();
var ctrl = (UserControl)page.LoadControl("~/EmailTemplates/OrderConfirmation.ascx");
page.Controls.Add(ctl);
StringWriter writer = new StringWriter();
HttpContext.Current.Server.Execute(page, writer, false);
return writer.ToString();
Formless page simply looks like:
public class FormlessPage : Page
{
public override void VerifyRenderingInServerForm(Control control)
{
}
}
It allows your control to have input elements without a <form> wrapper.
This method will call your page lifecycle methods and bind up your form elements nicely.
You could have a Public function (aka ProcessLoad), instead of Page_Load function and call it immediately after loading the control. This way you can pass parameters to your UserControl as well.
Related
I get an exception when calling a web service which dynamically builds HTML elements at run-time and returns the rendered elements as a string.
The following error occurs on the RenderControl() method call:
System.Web.HttpException: Unable to find control with id 'txtCertificate' that is associated with the Label 'lblCertificate'.
StringWriter stringWriter = new StringWriter();
HtmlTextWriter writer = new HtmlTextWriter(stringWriter);
// Create System.Web.UI.WebControls.Panel (container)
Panel pnlFRow1 = new Panel();
pnlFRow1.CssClass = "f-row";
// Create System.Web.UI.WebControls.Textbox
TextBox txtCertificate = new TextBox();
txtCertificate.ID = "txtCertificate";
txtCertificate.CssClass = "f-input f-input-full";
// Create System.Web.UI.WebControls.Label
Label lblCertificate = new Label();
lblCertificate.ID = "lblCertificate";
lblCertificate.CssClass = "f-label f-label-full";
lblCertificate.AssociatedControlID = txtCertificate.ID;
lblCertificate.Text = "Certificate:";
Panel pnlCertificate = new Panel();
pnlCertificate.CssClass = "f-label f-label-full";
// Binding child controls to parent controls
pnlFRow1.Controls.Add(lblCertificate);
pnlFRow1.Controls.Add(pnlCertificate);
pnlCertificate.Controls.Add(txtCertificate);
// Render control
pnlContent.RenderControl(writer);
// Return rendered HTML
return writer.InnerWriter.ToString();
I tried placing the line pnlFRow1.Controls.Add(lblCertificate); after the line pnlCertificate.Controls.Add(txtCertificate); thinking that this might be an issue where the order matters, however this causes the same error.
The AssociatedControlID attribute is a must have in order to have the Label WebControl render as an actual <label> element and must be displayed before the input control.
One way of doing it would be to inherit the Label class, and replace the RenderBeginTag method with something that's more to your liking. If you nest the Label class it should keep in the local context and stop it leaking out into the rest of your application
For example:
public partial class _Default : Page
{
public class Label : System.Web.UI.WebControls.Label {
public string For = null;
public override void RenderBeginTag(HtmlTextWriter writer) {
AddAttributeIfExists(writer, "ID", ID);
AddAttributeIfExists(writer, "For", For);
writer.RenderBeginTag("Label");
}
private void AddAttributeIfExists(HtmlTextWriter writer, string name, string value) {
if (!string.IsNullOrWhiteSpace(value))
writer.AddAttribute(name, value);
}
}
protected void Page_Load(object sender, EventArgs e) {
StringWriter stringWriter = new StringWriter();
HtmlTextWriter writer = new HtmlTextWriter(stringWriter);
Panel test = new Panel() { ID = "PanelTest" };
TextBox txtCertificate = new TextBox() { ID = "txtCertificate" };
Label lblCertificate = new Label() { ID = "lblCertificate", Text = "Certificate", For = txtCertificate.ClientID };
test.Controls.Add(lblCertificate);
test.Controls.Add(txtCertificate);
test.RenderControl(writer);
string teststring = writer.InnerWriter.ToString();
}
}
Note: Instead of using AssociatedControlId, you can now use a custom For property which will do the same thing. Also note how the Label class is nested inside your page class, so it doesn't interfere with other pages.
Unfortunately, as we discovered in the first version of this answer, if you try to use .Attributes["for"] without the AssociatedControlId, .NET will convert the <Label> to a <span>, so the above example should get around this.
There might be a better way to get the result I'm looking for. Basically I have a page that dynamically loads content into a div using jquery.load().
So far it works great for static content, but now I have a repeater that I want to be able to include in this content. So I wanted to use a generic handler (maybe a webservice) to dynamically generate the rendered html of the user control and insert (or append, or load) it into the div.
So far I've had no luck.
Initially I tried this method
UCPeople ucPeople = new UCPeople { RoleType = (roleType.ToLower() == "board" ? RoleTypes.Board : RoleTypes.Executive) };
TextWriter myTextWriter = new StringWriter();
HtmlTextWriter myWriter = new HtmlTextWriter(myTextWriter);
ucPeople.RenderControl(myWriter);
return myTextWriter.ToString();
Which just returns an empty string.
So then I tried adding the control to a page instance, and it still came out blank.
Then I tried to load the userconrol into the new page instance with this.
var page = new Page(){ViewStateMode = ViewStateMode.Disabled };
UCPeople ucPeople = (UCPeople)page.LoadControl("~/our-company/people/UCPeople.ascx");
That seemed to get further, but the usercontrol throw an error because it's not in a form.
When I checked the form was null for that page. So I figured I needed to create an instance of the page, and I tried it two different ways.
var p2 = (Page)PageParser.GetCompiledPageInstance("~/our-company/people/peopleTest.aspx",
"~/our-company/people/peopleTest.aspx", context);
var p = (Page)System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath("~/our-company/people/peopleTest.aspx", typeof(Page));
Both still yielded a null form.
I'm out of ideas. Any suggestions?
Thanks.
I would use this Browser Framework in my Winform Application c#.
I just Saw the Documentation HERE
So i would use this Method
I just create a new Class and a new Awesomium.Windows.Forms.WebControl Object.
Now if i use it without any particulary method (just the ones to create object and Load the Url Source it works. But when i want use This method :
browser.SetHeaderDefinition("MyHeader", myCol); //myCol is a NameValueCollection
i recive this error The control is disabled either manually or it has been destroyed.
On the first page that i linked there is wrote :
In addition to its regular meaning, the Enabled property has a special meaning in WebControl: it also indicates if the underlying view is valid and enabled.
A WebControl is considered invalid when it has been destroyed (by either calling Close() or Shutdown()) or was never properly instantiated.
Manually setting the Enabled property to true, will temporarily render the control disabled.
....
....
While disabled (either because the view is destroyed or because you manually set this property) attempting to access members of this control, may cause a InvalidOperationException (see the documentation of each member).
Now i tried to play with the ENABLED property but i still get this error. What i have to do to resolve this problem? I really didn't understand.
Awesomium.Windows.Forms.WebControl browser =
new Awesomium.Windows.Forms.WebControl();
this.SuspendLayout();
browser.Location = new System.Drawing.Point(1, 12);
browser.Name = "webControl1";
browser.Size = new System.Drawing.Size(624, 442);
browser.Source = new System.Uri("http://www.google.it", System.UriKind.Absolute);
browser.TabIndex = 0;
**** This below is the code that i cant use cause i get the error control
// System.Collections.Specialized.NameValueCollection myCol =
// new System.Collections.Specialized.NameValueCollection();
// myCol.Add("Referer", "http://www.yahoo.com");
// browser.SetHeaderDefinition("MyHeader", myCol);
// browser.AddHeaderRewriteRule("http://*", "MyHeader");
The issue is that you cannot set the header definition until the control has finished being created. You just need to delay when you're setting the header definition until the control is ready. I'm not a Winforms expert, so there may be a better event to use to determine where a control is in its lifecycle, but here's a working modification of what you posted that just uses the control's Paint event to defer the problematic method calls:
public partial class Form1 : Form
{
private Awesomium.Windows.Forms.WebControl browser;
public Form1()
{
InitializeComponent();
browser = new Awesomium.Windows.Forms.WebControl();
//delay until control is ready
browser.Paint += browser_Paint;
Controls.Add(browser);
browser.Location = new System.Drawing.Point(1, 12);
browser.Name = "webControl1";
browser.Size = new System.Drawing.Size(624, 442);
browser.Source = new System.Uri("http://www.google.it", System.UriKind.Absolute);
browser.TabIndex = 0;
}
void browser_Paint(object sender, PaintEventArgs e)
{
browser.Paint -= browser_Paint;
System.Collections.Specialized.NameValueCollection myCol =
new System.Collections.Specialized.NameValueCollection();
myCol.Add("Referer", "http://www.yahoo.com");
browser.SetHeaderDefinition("MyHeader", myCol);
browser.AddHeaderRewriteRule("http://*", "MyHeader");
}
}
I am trying to add a ConfirmButtonExtender to my controls collection within a custom control at runtime but can not figure out why the extender will not wire to the button that is being added to the controls collection in the same CreateChildControls method. I did a simple test and added a button explicitly to an aspx page and then creating the extender dynamically in the PreRender of the the .cs file of that page and it still did not work. It seems that the only way to get this to work is to have the actual tags on the .aspx page.
Am I missing something?
protected virtual void CreateChildControls(System.Resources.ResourceManager rm)
{
valValidationSummary = new ValidationSummary();
valValidationSummary.ID = "valValidationSummary";
valValidationSummary.ShowSummary = true;
valValidationSummary.HeaderText = rm.GetString("ValidationSummary");
valValidationSummary.CssClass = "error";
btnGetRates = new LocalizedButton();
btnGetRates.ID = "btnGetStats";
btnGetRates.TextResource = rm.GetString("SubmitButton");
btnGetRates.Text = rm.GetString("SubmitButton");
btnGetRates.CssClass = "inputfield";
btnGetRates.Click += new System.EventHandler(OnSubmitButton_Click);
btnConfirmation = new ConfirmButtonExtender();
btnConfirmation.ID = "rfBtnSubmit_Confirm";
btnConfirmation.ConfirmText = rm.GetString("BAUConfrimation");
btnConfirmation.TargetControlID = "btnGetStats";
this.Controls.Add(btnConfirmation);
this.Controls.Add(valValidationSummary);
this.Controls.Add(btnGetRates);
}
Dumb mistake, I was not rendering the control.
I asked how to render a UserControl's HTML and got the code working for a dynamically generated UserControl.
Now I'm trying to use LoadControl to load a previously generated Control and spit out its HTML, but it's giving me this:
Control of type 'TextBox' must be placed inside a form tag with runat=server.
I'm not actually adding the control to the page, I'm simply trying to grab its HTML. Any ideas?
Here's some code I'm playing with:
TextWriter myTextWriter = new StringWriter();
HtmlTextWriter myWriter = new HtmlTextWriter(myTextWriter);
UserControl myControl = (UserControl)LoadControl("newUserControl.ascx");
myControl.RenderControl(myWriter);
return myTextWriter.ToString();
Alternatively you could disable the ServerForm/Event-validation on the page that is rendering the control to a string.
The following example illustrates how to do this.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string rawHtml = RenderUserControlToString();
}
private string RenderUserControlToString()
{
UserControl myControl = (UserControl)LoadControl("WebUserControl1.ascx");
using (TextWriter myTextWriter = new StringWriter())
using (HtmlTextWriter myWriter = new HtmlTextWriter(myTextWriter))
{
myControl.RenderControl(myWriter);
return myTextWriter.ToString();
}
}
public override void VerifyRenderingInServerForm(Control control)
{ /* Do nothing */ }
public override bool EnableEventValidation
{
get { return false; }
set { /* Do nothing */}
}
}
This is a dirty solution I used for the moment (get it working then get it right, right?).
I had already created a new class that inherits the UserControl class and from which all other "UserControls" I created were derived. I called it formPartial (nod to Rails), and this is going inside the public string renderMyHTML() method:
TextWriter myTextWriter = new StringWriter();
HtmlTextWriter myWriter = new HtmlTextWriter(myTextWriter);
UserControl myDuplicate = new UserControl();
TextBox blankTextBox;
foreach (Control tmpControl in this.Controls)
{
switch (tmpControl.GetType().ToString())
{
case "System.Web.UI.LiteralControl":
blankLiteral = new LiteralControl();
blankLiteral.Text = ((LiteralControl)tmpControl).Text;
myDuplicate.Controls.Add(blankLiteral);
break;
case "System.Web.UI.WebControls.TextBox":
blankTextBox = new TextBox();
blankTextBox.ID = ((TextBox)tmpControl).ID;
blankTextBox.Text = ((TextBox)tmpControl).Text;
myDuplicate.Controls.Add(blankTextBox);
break;
// ...other types of controls (ddls, checkboxes, etc.)
}
}
myDuplicate.RenderControl(myWriter);
return myTextWriter.ToString();
Drawbacks off the top of my head:
You need a case statement with every
possible control (or controls you
expect).
You need to transfer all
the important attributes from the
existing control (textbox, etc) to
the new blank control.
Doesn't take full advantage of
Controls' RenderControl method.
It'd be easy to mess up 1 or 2. Hopefully, though, this helps someone else come up with a more elegant solution.
You can add the control into page, render html and then remove the control from page.
Or try this:
Page tmpPage = new TempPage(); // temporary page
Control tmpCtl = tmpPage.LoadControl( "~/UDynamicLogin.ascx" );
//the Form is null that's throws an exception
tmpPage.Form.Controls.Add( tmpCtl );
StringBuilder html = new StringBuilder();
using ( System.IO.StringWriter swr = new System.IO.StringWriter( html ) ) {
using ( HtmlTextWriter writer = new HtmlTextWriter( swr ) ) {
tmpPage.RenderControl( writer );
}
}
You can either add a form to your user control, or use a regular html input box
<input type="text" />
Edit: If you are trying to do something AJAXy, maybe you want something like this
http://aspadvice.com/blogs/ssmith/archive/2007/10/19/Render-User-Control-as-String-Template.aspx
public static string RenderView<D>(string path, D dataToBind)
{
Page pageHolder = new Page();
UserControl viewControl = (UserControl) pageHolder.LoadControl(path);
if(viewControl is IRenderable<D>)
{
if (dataToBind != null)
{
((IRenderable<D>) viewControl).PopulateData(dataToBind);
}
}
pageHolder.Controls.Add(viewControl);
StringWriter output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
You can remove the data binding part if not needed.
I was having the same problem using similar code to #TcKs and haven't been able to make any of these examples work for me. I got it working by using the LoadControl method of a UserControl as such:
UserControl uc = new UserControl();
Control c = uc.LoadControl("newUserControl.ascx");
c.RenderControl(myWriter);