Exception thrown when rendering a dynamic label WebControl with the AssociateControlID - c#

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.

Related

Selecting a Revit Element by the ID taken from WPF DataGrid

Before jumping straight to the problem, I will shortly describe what I am trying to code. Basically, I am trying to do a Revit Add-In which opens a WPF Dialog, shows the id and categoryName of the elements in the active view and when double-clicked on a cell containing the id, the dialog closes and the element with that id is selected.
The problem arises after I double-click. Because the Execute() command ends after the dialog is shown, I am no longer able to perform the Selection.
Command.cs
[Transaction(TransactionMode.Manual)]
public class Command : IExternalCommand
{
static List<Element> elemList = new List<Element>();
public Result Execute(
ExternalCommandData commandData,
ref string message,
ElementSet elements)
{
UIApplication uiapp = commandData.Application;
UIDocument uidoc = uiapp.ActiveUIDocument;
Application app = uiapp.Application;
Document doc = uidoc.Document;
//List<Element> elemList = new List<Element>();
FilteredElementCollector allElementsInView = new FilteredElementCollector(doc, doc.ActiveView.Id);
IList elementsInView = (IList)allElementsInView.ToElements();
foreach (Element elem in elementsInView)
{
elemList.Add(elem);
}
OpenDialog();
return Result.Succeeded;
}
public static void SelectElementById(ElementId id)
{
}
public static List<Element> SendElements()
{
return elemList;
}
public void OpenDialog()
{
ElementListDialog dialog = new ElementListDialog();
dialog.Show();
}
}
}
As you can see above, I also have a SelectElementById(...) method which is called from the ElementListDialog.xaml.cs class. Below you will see the functionality for populating the DataGrid and the MouseButtonDoubleClick method which gets the cell's value and passes the id of type ElementId back to Command.cs
ElementListDialog.xaml.cs
private List<Element> elemList;
public ElementListDialog()
{
InitializeComponent();
Lab8.Command.SendElements();
elemList = Lab8.Command.SendElements();
//elemList = _elemList;
Content content = new Content();
foreach (var elem in elemList)
{
content = new Content();
content.id = elem.Id;
content.categName = elem.Category.Name;
var finalList = new Content
{
id = content.id,
categName = content.categName
};
ElementDataGrid.Items.Add(finalList);
}
}
private void ElementDataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var grid = sender as DataGrid;
var cellValue = grid.SelectedValue;
Content content = new Content();
content = (Content)cellValue;
Command.SelectElementById(content.id);
}
Therefore; how should I design the SelectElementById method such that I am able to select a certain element by the id? Or does the current code not allow this to be performed?
Current code is fine. You need to get the active view and use a transaction to set the element in the current view
public static void SelectElementById(ElementId id)
{
ICollection<ElementId> to_showids = new List<ElementId>();
to_showids.Add(id);
//RESET ELEMENTS IN VIEW
var itrans = new Transaction(YOURDOCUMENT, "Show Element");
using(itrans)
{
itrans.Start();
//The below step is to make sure that any previous selection is removed and only your new selection is set.
YOURDOC_UI.Selection.SetElementIds(to_showids);
itrans.Commit();
}
//SHOW ELEMENTS
YOURDOC_UI.ShowElements(to_showids);
}
Selecting an element or some UI interactions do not require transactions.
If you however want to change an element property in datagrid and apply changes in revit then you will need transaction.
You can run your WPF window on a different thread in order not to block Revit UI.
Then you can use IExternalEventHandler to execute code in the valid Revit API context
class DoSomethingOnEventRaised : IExternalEventHandler
{
public void Execute(UIApplication uiapp)
{
//code to be executed in API context
}
}
IExternalEventHandler doSomethingEvHandler = new DoSomethingOnEventRaised();
ExternalEvent doSomethingEvent = ExternalEvent.Create(calculationSupport);
doSomethingEvent.Raise() will execute the code

Changing Xamarin Label in ListCell does not work

I have a problem with a ListView. I want each Cell to have a label and a switch but the text of the label does not appear.
Here is my code:
public class FilterPage : ContentPage
{
public FilterPage()
{
List<FilterCell> listContent = new List<FilterCell>();
foreach(string type in Database.RestaurantTypes)
{
FilterCell fc = new FilterCell();
fc.Text = type;
listContent.Add(fc);
}
ListView types = new ListView();
types.ItemTemplate = new DataTemplate(typeof(FilterCell));
types.ItemsSource = listContent;
var layout = new StackLayout();
layout.Children.Add(types);
Content = layout;
}
}
public class FilterCell : ViewCell
{
private Label label;
public Switch CellSwitch { get; private set; }
public String Text{ get { return label.Text; } set { label.Text = value; } }
public FilterCell()
{
label = new Label();
CellSwitch = new Switch();
var layout = new StackLayout
{
Padding = new Thickness(20, 0, 0, 0),
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.FillAndExpand,
Children = { label, CellSwitch }
};
View = layout;
}
}
If I enter a fixed Text in the FilterCell-Constructor it works fine (e.g.: label.Text = "Hello World")
When I create a Method for the ItemSelected-Event and read out the SelectedItem.Text Property I get the text I assigned as Value but it's never displayed. Only the switch is displayed when I try to run this Code.
Thanks for your help
Niko
Ohh boy. This code looks like a rape (sorry I had to say this).
Now let's see what's wrong:
The reason is you are mixing up data and view heavily.
The line
types.ItemTemplate = new DataTemplate(typeof(FilterCell));
means: "For each item in the list (ItemsSource) create a new filter cell". The FilterCells that you create in the loop are never displayed.
The easy fix
public class FilterPage : ContentPage
{
public FilterPage()
{
var restaurantTypes = new[] {"Pizza", "China", "German"}; // Database.RestaurantTypes
ListView types = new ListView();
types.ItemTemplate = new DataTemplate(() =>
{
var cell = new SwitchCell();
cell.SetBinding(SwitchCell.TextProperty, ".");
return cell;
});
types.ItemsSource = restaurantTypes;
Content = types;
}
}
There is a standard cell type that contains a label and a switch SwitchCell, use it.
As ItemsSource of your list, you have to use your data. In your case the list of restaurant types. I just mocked them with a static list.
The DataTemplate creates the SwitchCell and sets the Databinding for the Text property. This is the magic glue between View and data. The "." binds it to the data item itself. We use it, because our list contains items of strings and the Text should be exactly the string. (read about Databinding: https://developer.xamarin.com/guides/xamarin-forms/getting-started/introduction-to-xamarin-forms/#Data_Binding )
I striped away the StackLayout that contained the list. You can directly set the list as Content of the page.
Lesson
use standard controls, if possible
You should always try to remember to keep data and view apart from each other and use data binding to connect to each other.
Try to avoid unnecessary views.

Get html of div in codebehind ASP.NET c# [duplicate]

<form runat="server" id="f1">
<div runat="server" id="d">
grid view:
<asp:GridView runat="server" ID="g">
</asp:GridView>
</div>
<asp:TextBox runat="server" ID="t" TextMode="MultiLine" Rows="20" Columns="50"></asp:TextBox>
</form>
Code behind:
public partial class ScriptTest : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
g.DataSource = new string[] { "a", "b", "c" };
g.DataBind();
TextWriter tw = new StringWriter();
HtmlTextWriter h = new HtmlTextWriter(tw);
d.RenderControl(h);
t.Text = tw.ToString();
}
}
Even the GridView is within a from tag with runat="server", still I am getting this error.
Any clues please ?
You are calling GridView.RenderControl(htmlTextWriter), hence the page raises an exception that a Server-Control was rendered outside of a Form.
You could avoid this execption by overriding VerifyRenderingInServerForm
public override void VerifyRenderingInServerForm(Control control)
{
/* Confirms that an HtmlForm control is rendered for the specified ASP.NET
server control at run time. */
}
See here and here.
An alternative to overriding VerifyRenderingInServerForm is to remove the grid from the controls collection while you do the render, and then add it back when you are finished before the page loads. This is helpful if you want to have some generic helper method to get grid html because you don't have to remember to add the override.
Control parent = grid.Parent;
int GridIndex = 0;
if (parent != null)
{
GridIndex = parent.Controls.IndexOf(grid);
parent.Controls.Remove(grid);
}
grid.RenderControl(hw);
if (parent != null)
{
parent.Controls.AddAt(GridIndex, grid);
}
Another alternative to avoid the override is to do this:
grid.RenderBeginTag(hw);
grid.HeaderRow.RenderControl(hw);
foreach (GridViewRow row in grid.Rows)
{
row.RenderControl(hw);
}
grid.FooterRow.RenderControl(hw);
grid.RenderEndTag(hw);
Just after your Page_Load add this:
public override void VerifyRenderingInServerForm(Control control)
{
//base.VerifyRenderingInServerForm(control);
}
Note that I don't do anything in the function.
EDIT: Tim answered the same thing. :)
You can also find the answer Here
Just want to add another way of doing this. I've seen multiple people on various related threads ask if you can use VerifyRenderingInServerForm without adding it to the parent page.
You actually can do this but it's a bit of a bodge.
First off create a new Page class which looks something like the following:
public partial class NoRenderPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{ }
public override void VerifyRenderingInServerForm(Control control)
{
//Allows for printing
}
public override bool EnableEventValidation
{
get { return false; }
set { /*Do nothing*/ }
}
}
Does not need to have an .ASPX associated with it.
Then in the control you wish to render you can do something like the following.
StringWriter tw = new StringWriter();
HtmlTextWriter hw = new HtmlTextWriter(tw);
var page = new NoRenderPage();
page.DesignerInitialize();
var form = new HtmlForm();
page.Controls.Add(form);
form.Controls.Add(pnl);
controlToRender.RenderControl(hw);
Now you've got your original control rendered as HTML. If you need to, add the control back into it's original position. You now have the HTML rendered, the page as normal and no changes to the page itself.
Here is My Code
protected void btnExcel_Click(object sender, ImageClickEventArgs e)
{
if (gvDetail.Rows.Count > 0)
{
System.IO.StringWriter stringWrite1 = new System.IO.StringWriter();
System.Web.UI.HtmlTextWriter htmlWrite1 = new HtmlTextWriter(stringWrite1);
gvDetail.RenderControl(htmlWrite1);
gvDetail.AllowPaging = false;
Search();
sh.ExportToExcel(gvDetail, "Report");
}
}
public override void VerifyRenderingInServerForm(Control control)
{
/* Confirms that an HtmlForm control is rendered for the specified ASP.NET
server control at run time. */
}
Tim Schmelter's answer helped me a lot, but I had to do one more thing to get it to work on my aspx page. I am using this code to email an embedded GridView control (as HTML), for report automation.
In addition to adding the override sub, I had to do the render() in Me.Handles.onunload, or else I got an error on the RenderControl line.
Protected Sub Page_After_load(sender As Object, e As EventArgs) Handles Me.Unload
If runningScheduledReport Then
Dim stringBuilder As StringBuilder = New StringBuilder()
Dim stringWriter As System.IO.StringWriter = New System.IO.StringWriter(stringBuilder)
Dim htmlWriter As HtmlTextWriter = New HtmlTextWriter(stringWriter)
GridView1.RenderControl(htmlWriter)
Dim htmlcode As String = stringBuilder.ToString()
Func.SendEmail(Context.Request.QueryString("email").ToString, htmlcode, "Auto Report - Agent Efficiency", Nothing)
End If
End Sub

Using LoadControl on pages with server controls

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.

UserControl's RenderControl is asking for a form tag in (C# .NET)

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);

Categories