Validator.RenderControl returns an empty string - asp.net

I've faced with following problem and fighting with it almost 2 days.
I have the following code.
protected override void Render(HtmlTextWriter writer)
{
string controlOutput;
string validatorOutput;
string allOutput;
StringWriter controlStringWriter = new StringWriter();
HtmlTextWriter controlHtmlWriter = new HtmlTextWriter(controlStringWriter);
StringWriter validatorsStringWriter = new StringWriter();
HtmlTextWriter validatorsHtmlWriter = new HtmlTextWriter(validatorsStringWriter);
base.Render(controlHtmlWriter);
controlOutput = controlStringWriter.ToString();
this.renderValidators(validatorsHtmlWriter);
validatorOutput = validatorsStringWriter.ToString();
allOutput = String.Format("{0} {1}", controlOutput, validatorOutput);
writer.Write(allOutput);
}
The renderValidators function is :
private void renderValidators(HtmlTextWriter writer)
{
foreach (BaseValidator validator in this.ValidatorsCollection)
{
validator.RenderControl(writer);
}
}
The Render Function return an Empty String.
The Visible property of Validator that is in collection is set to true.
I am adding the validators in OnInit function. During first rendering validator.RenderControl(writer); return an empty string , after postback it starts to return an html input....
Maybe someone has faced with such problem ???

To my understanding you need to check if the control has been added to a page before rendering the validator.
Page page = Page;
if (page == null || page.Request == null) {
return false;
}
A very helpful example can be found in the MSDN

Related

How can I programmatically build a System.Web.UI.Page with a Form and a UserControl?

I have this code:
public static string RenderView(string path)
{
Page pageHolder = new Page();
UserControl viewControl = (UserControl)pageHolder.LoadControl(path);
pageHolder.Controls.Add(viewControl);
StringWriter output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
Which is run from:
[WebMethod]
public string GetReportsHTML()
{
string output = "";
output = ViewManager.RenderView("ReportsControl.ascx");
return output;
}
This is to test rendering ASCX files and spitting them out of a SOAP/REST service.
Problem is, some controls (runat=server ones) fail if they are not encapsulated in a tag with runat=server.
The solution to that is here, but the solution assumes being inside an ASPX file where I can just edit the markup.
How would I programmatically build a Page, add a Form, set runat=server so that I can follow that solution and add my control to the Form Control?
Have you tried something like this ?
public static string RenderView(string path)
{
Page pageHolder = new Page();
System.Web.UI.HtmlControls.HtmlForm formHolder = new System.Web.UI.HtmlControls.HtmlForm();
pageHolder.Controls.Add(formHolder );
UserControl viewControl = (UserControl)pageHolder.LoadControl(path);
formHolder.Controls.Add(viewControl);
StringWriter output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
Hope this will help

Render a user control ascx

I want to use an ascx as a template, and render it programatically, using the resulting html as the return value of an ajax method.
Page pageHolder = new Page();
MyUserControl ctlRender = (MyUserControl)pageHolder.LoadControl(typeof(MyUserControl),null);
pageHolder.Controls.Add(ctlRender);
System.IO.StringWriter swOutput = new System.IO.StringWriter();
HttpContext.Current.Server.Execute(pageHolder, swOutput, false);
return swOutput.ToString();
This all executes, and the Page Load event of the user control fires, but the StringWriter is always empty. I've seen similar code to this in other examples, what am I missing?
Have you tried this:
public string RenderControl(Control ctrl)
{
StringBuilder sb = new StringBuilder();
StringWriter tw = new StringWriter(sb);
HtmlTextWriter hw = new HtmlTextWriter(tw);
ctrl.RenderControl(hw);
return sb.ToString();
}
Taken directly from the article here:
ASP.NET Tip: Render Control into HTML String
You always have to use something like this.Controls.Add(TemplateControl.LoadControl("~/PathTo/YourControl.ascx")
The reason is, that there is no internal mapping of Types to there ascx-file (only the other way arround). So this means if you initialize new YourControl(), it will not do anything you defined in the ascx part. If you would have
protected override void protected override void Render(HtmlTextWriter output) {
output.WriteLine("Test");
}
this would give you "Test" in the place you rendered your control to.

When does a page get rendered in ASP.NET?

I'm writing am ASP.NET/C# project, it's a simple blog page with commnents.
Problem I'm having when button click you see comments load original blogload plus blogs and comments, trying to get it to load blog/comment selected only.
If I try not to load blog in page_load or have it only do if not postback nothing is displayed. Any help would be appreciated.
PS I know there are many blog engines out there but have specific reason.
protected void Page_Init(object sender, EventArgs e)
{
//ParseControls(GlobalVar.pathxsltver);
// BindInfo();
}
private void ParseControls(string myxslt)
{
//load the data
FileStream fs = new FileStream(Server.MapPath ( GlobalVar.compathver), FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
DataSet dset = new DataSet();
dset.ReadXml(fs);
fs.Close();
XPathDocument xdoc = new XPathDocument(Server.MapPath(GlobalVar.pathver ));
XmlDocument mydoc = new XmlDocument();
XPathNavigator navigator = xdoc.CreateNavigator();
XPathExpression expression = navigator.Compile("BlogItems/Blog");
expression.AddSort("ID", XmlSortOrder.Descending, XmlCaseOrder.UpperFirst, string.Empty, XmlDataType.Text);
XPathNodeIterator iterator = navigator.Select(expression);
int TheCnt = 0;
int cnt = GlobalVar.BlogCntDisplay;
string st = "<BlogItems>";
foreach (XPathNavigator item in iterator)
{
TheCnt++;
string sid = item.SelectSingleNode("ID").Value;
st = st + "<Blog id=\"" + sid + "\">" + item.InnerXml;
st = st + "<ComCnt>" + MyFunc.CountComments (sid,dset) + "</ComCnt></Blog>";
if (TheCnt == cnt) { break; }
}
st = st + "</BlogItems>";
mydoc.LoadXml(st);
XslCompiledTransform transform = new XslCompiledTransform();
XsltSettings settings = new XsltSettings(true,true);
transform.Load(Server.MapPath(myxslt),settings,null);
StringWriter sw = new StringWriter();
transform.Transform(mydoc, null, sw);
string result = sw.ToString();
//remove namespace
result = result.Replace("xmlns:asp=\"remove\"", "");
//parse control
Control ctrl = Page.ParseControl(result);
//find control to add event handler
//Boolean test = phBlog.FindControl("btnComment2").i;
phBlog.Controls.Add(ctrl);
XmlNodeList nList = mydoc.SelectNodes("//BlogItems/Blog/ID");
foreach (XmlNode objNode in nList)
{
Button btnComment = (Button) phBlog.FindControl("btnComment"+objNode.InnerText );
btnComment.CommandArgument = objNode.InnerText ;
btnComment.BorderWidth = 0 ;
btnComment.Command += new CommandEventHandler(Button1_Click);
}
}
protected void Page_Load(object sender, EventArgs e)
{
//if (!Page.IsPostBack )
//{ParseControls(GlobalVar.pathxsltver);}
ParseControls(GlobalVar.pathxsltver);
}
protected void Button1_Click(object sender, CommandEventArgs e)
{
Label1.Text = "Comm hit : " + e.CommandArgument.ToString();
ParseControls(GlobalVar.blogcommentsver );
}
You're question is kind of vague, but if I understand you correctly, you're wondering why the entire page refreshes when you just want to handle the button click?
Whenever you do any kind of postback, and that includes handling any events, the entire page is re-rendered. More than that, you're working with a brand new instance of your page class. The old one is dead and gone. That's just the way the web normally works.
If you only want to reload a part of the page, you need to use ajax. In ASP.Net land, that means placing your comments section inside an UpdatePanel control that can be refreshed.

ASP.NET: Shortest way to render DataTable to a string (HTML)?

What is the shortest way to convert a DataTable into a string (formatted in HTML)?
Programmatically binding to a UI control and rendering to an ASP.NET page is not acceptable. Can't depend on the ASP.NET page lifecycle.
The purpose of this shouldn't matter, but to satisfy curiosity: This is for logging/debugging/dump purposes in algorithms that do a lot of DataTable processing.
Thanks!
You can use the ASP.net controls such as GridView, DataGrid and point them render into StringBuilder using StringWriter, No need to use ASP.net page for this, this is a simple example in Console
class Program
{
static void Main(string[] args)
{
IList<Person> persons = new List<Person>()
{
new Person{Id = 1, Name="Test Name 1"},
new Person{Id = 2, Name="Test Name 2"}
};
GridView gridView = new GridView();
StringBuilder result = new StringBuilder();
StringWriter writer = new StringWriter(result);
HtmlTextWriter htmlWriter = new HtmlTextWriter(writer);
gridView.DataSource = persons;
gridView.DataBind();
gridView.RenderControl(htmlWriter);
Console.WriteLine(result);
}
}
class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
I use this function through my application. It's pretty straightforward
static public string ConvertDataTableToHTMLString(System.Data.DataTable dt, string filter, string sort, string fontsize, string border, bool headers, bool useCaptionForHeaders)
{
StringBuilder sb = new StringBuilder();
sb.Append("<table border='" + border + "'b>");
if (headers)
{
//write column headings
sb.Append("<tr>");
foreach (System.Data.DataColumn dc in dt.Columns)
{
if (useCaptionForHeaders)
sb.Append("<td><b><font face=Arial size=2>" + dc.Caption + "</font></b></td>");
else
sb.Append("<td><b><font face=Arial size=2>" + dc.ColumnName + "</font></b></td>");
}
sb.Append("</tr>");
}
//write table data
foreach (System.Data.DataRow dr in dt.Select(filter,sort))
{
sb.Append("<tr>");
foreach (System.Data.DataColumn dc in dt.Columns)
{
sb.Append("<td><font face=Arial size=" + fontsize + ">" + dr[dc].ToString() + "</font></td>");
}
sb.Append("</tr>");
}
sb.Append("</table>");
return sb.ToString();
}
If this is just for purposes of logging, might it not make more sense to log them out as XML - easier to manipulate if you need to. You just need to call the WriteXml method.
Create the control, create an HTML Writer, set any settings or databind the control, then call the render method, using the HTML Writer.
You can then get the string out of the writer.
Edit: I initially misread the question and thought you wanted to render a datagrid.
A Datatable can easily be rendered to its XML.
you asked for HTML.
here is a console app code that will render a datatable using a datagrid control.
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("Column1");
dt.Columns.Add("Column2");
dt.Rows.Add("RowValue1", "Field2RowValue1");
dt.Rows.Add("RowValue2", "Field2RowValue2");
DataGrid dg = new DataGrid();
dg.DataSource = dt;
dg.DataBind();
StringWriter sw = new StringWriter();
HtmlTextWriter w = new HtmlTextWriter(sw);
dg.RenderControl(w);
Console.Write(sw.ToString());
Console.ReadLine();
}
}

ListItems attributes in a DropDownList are lost on postback?

A coworker showed me this:
He has a DropDownList and a button on a web page. Here's the code behind:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
ListItem item = new ListItem("1");
item.Attributes.Add("title", "A");
ListItem item2 = new ListItem("2");
item2.Attributes.Add("title", "B");
DropDownList1.Items.AddRange(new[] {item, item2});
string s = DropDownList1.Items[0].Attributes["title"];
}
}
protected void Button1_Click(object sender, EventArgs e)
{
DropDownList1.Visible = !DropDownList1.Visible;
}
On the page load, the items' tooltips are showing, but on the first postback, the attributes are lost. Why is this the case, and are there any workarounds?
I had the same problem and wanted to contribute this resource where the author created an inherited ListItem Consumer to persist attributes to ViewState. Hopefully it will save someone the time I wasted until I stumbled on it.
protected override object SaveViewState()
{
// create object array for Item count + 1
object[] allStates = new object[this.Items.Count + 1];
// the +1 is to hold the base info
object baseState = base.SaveViewState();
allStates[0] = baseState;
Int32 i = 1;
// now loop through and save each Style attribute for the List
foreach (ListItem li in this.Items)
{
Int32 j = 0;
string[][] attributes = new string[li.Attributes.Count][];
foreach (string attribute in li.Attributes.Keys)
{
attributes[j++] = new string[] {attribute, li.Attributes[attribute]};
}
allStates[i++] = attributes;
}
return allStates;
}
protected override void LoadViewState(object savedState)
{
if (savedState != null)
{
object[] myState = (object[])savedState;
// restore base first
if (myState[0] != null)
base.LoadViewState(myState[0]);
Int32 i = 1;
foreach (ListItem li in this.Items)
{
// loop through and restore each style attribute
foreach (string[] attribute in (string[][])myState[i++])
{
li.Attributes[attribute[0]] = attribute[1];
}
}
}
}
Thanks, Laramie. Just what I was looking for. It keeps the attributes perfectly.
To expand, below is a class file I created using Laramie's code to create a dropdownlist in VS2008. Create the class in the App_Code folder. After you create the class, use this line on the aspx page to register it:
<%# Register TagPrefix="aspNewControls" Namespace="NewControls"%>
You can then put the control on your webform with this
<aspNewControls:NewDropDownList ID="ddlWhatever" runat="server">
</aspNewControls:NewDropDownList>
Ok, here's the class...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Security.Permissions;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace NewControls
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")]
public class NewDropDownList : DropDownList
{
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
protected override object SaveViewState()
{
// create object array for Item count + 1
object[] allStates = new object[this.Items.Count + 1];
// the +1 is to hold the base info
object baseState = base.SaveViewState();
allStates[0] = baseState;
Int32 i = 1;
// now loop through and save each Style attribute for the List
foreach (ListItem li in this.Items)
{
Int32 j = 0;
string[][] attributes = new string[li.Attributes.Count][];
foreach (string attribute in li.Attributes.Keys)
{
attributes[j++] = new string[] { attribute, li.Attributes[attribute] };
}
allStates[i++] = attributes;
}
return allStates;
}
protected override void LoadViewState(object savedState)
{
if (savedState != null)
{
object[] myState = (object[])savedState;
// restore base first
if (myState[0] != null)
base.LoadViewState(myState[0]);
Int32 i = 1;
foreach (ListItem li in this.Items)
{
// loop through and restore each style attribute
foreach (string[] attribute in (string[][])myState[i++])
{
li.Attributes[attribute[0]] = attribute[1];
}
}
}
}
}
}
Simple solution is to add the tooltip attributes in the pre-render event of the dropdown. Any changes to the state should be done at pre-render event.
sample code :
protected void drpBrand_PreRender(object sender, EventArgs e)
{
foreach (ListItem _listItem in drpBrand.Items)
{
_listItem.Attributes.Add("title", _listItem.Text);
}
drpBrand.Attributes.Add("onmouseover", "this.title=this.options[this.selectedIndex].title");
}
If you only want to load the listitems on the first load of the page then you will need to enable ViewState so that the control can serialize its state there and reload it when the page posts back.
There are several places where ViewState can be enabled - check the <pages/> node in the web.config and also in the <%# page %> directive at the top of the aspx file itself for the EnableViewState property. This setting will need to be true for ViewState to work.
If you don't want to use ViewState, simply remove the if (!IsPostBack) { ... } from around the code that adds the ListItems and the items will be recreated on each postback.
Edit: I apologize - I misread your question. You are correct that the attributes do no survive postback as they are not serialized in ViewState. You must re-add those attributes on each postback.
One simple solution- Call your drop down loading function on the click event where you request for post back.
Here's the VB.Net code of the solution proposed by Laramie and refined by gleapman.
Update: The code I posted below is actually for the ListBox control. Just change the inheritance to DropDownList and rename the class.
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Security.Permissions
Imports System.Linq
Imports System.Text
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Namespace CustomControls
<DefaultProperty("Text")> _
<ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")>
Public Class PersistentListBox
Inherits ListBox
<Bindable(True)> _
<Category("Appearance")> _
<DefaultValue("")> _
<Localizable(True)> _
Protected Overrides Function SaveViewState() As Object
' Create object array for Item count + 1
Dim allStates As Object() = New Object(Me.Items.Count + 1) {}
' The +1 is to hold the base info
Dim baseState As Object = MyBase.SaveViewState()
allStates(0) = baseState
Dim i As Int32 = 1
' Now loop through and save each attribute for the List
For Each li As ListItem In Me.Items
Dim j As Int32 = 0
Dim attributes As String()() = New String(li.Attributes.Count - 1)() {}
For Each attribute As String In li.Attributes.Keys
attributes(j) = New String() {attribute, li.Attributes(attribute)}
j += 1
Next
allStates(i) = attributes
i += 1
Next
Return allStates
End Function
Protected Overrides Sub LoadViewState(savedState As Object)
If savedState IsNot Nothing Then
Dim myState As Object() = DirectCast(savedState, Object())
' Restore base first
If myState(0) IsNot Nothing Then
MyBase.LoadViewState(myState(0))
End If
Dim i As Int32 = 0
For Each li As ListItem In Me.Items
' Loop through and restore each attribute
' NOTE: Ignore the first item as that is the base state and is represented by a Triplet struct
i += 1
For Each attribute As String() In DirectCast(myState(i), String()())
li.Attributes(attribute(0)) = attribute(1)
Next
Next
End If
End Sub
End Class
End Namespace
Typical solutions to this problem involves creating new controls that are not quite feasible in normal circumstances. There is a simple yet trivial solution to this problem.
The issue is that the ListItem loses its attributes on postback. However, the List itself never loses any custom attributes. One can take advantage of this in a simple yet effective manner thus.
Steps:
Serialize your attributes using the code in the answer above (https://stackoverflow.com/a/3099755/3624833)
Store it to a custom attribute of the ListControl (dropdownlist, checklistbox, whatever).
On post back, read back the custom attribute from the ListControl and then deserialize it back as attributes.
Here is the code I used to (de)serialize attributes (What I needed to do was to keep track of what items of the list were originally rendered as selected when retrieved from the backend and then save or delete rows as per the changes made by the user on the UI):
string[] selections = new string[Users.Items.Count];
for(int i = 0; i < Users.Items.Count; i++)
{
selections[i] = string.Format("{0};{1}", Users.Items[i].Value, Users.Items[i].Selected);
}
Users.Attributes["data-item-previous-states"] = string.Join("|", selections);
(above, "Users" is a CheckboxList control).
On post back (in my case a Submit button Click event), I use the below code to retrieve the same and store them into a Dictionary for post processing:
Dictionary<Guid, bool> previousStates = new Dictionary<Guid, bool>();
string[] state = Users.Attributes["data-item-previous-states"].Split(new char[] {'|'}, StringSplitOptions.RemoveEmptyEntries);
foreach(string obj in state)
{
string[] kv = obj.Split(new char[] { ';' }, StringSplitOptions.None);
previousStates.Add(kv[0], kv[1]);
}
(PS: I have a library funcs that perform error handling and data conversions, omitting the same here for brevity).
Simple solution without ViewState, creating new server control or smth complex:
Creating:
public void AddItemList(DropDownList list, string text, string value, string group = null, string type = null)
{
var item = new ListItem(text, value);
if (!string.IsNullOrEmpty(group))
{
if (string.IsNullOrEmpty(type)) type = "group";
item.Attributes["data-" + type] = group;
}
list.Items.Add(item);
}
Updating:
public void ChangeItemList(DropDownList list, string eq, string group = null, string type = null)
{
var listItem = list.Items.Cast<ListItem>().First(item => item.Value == eq);
if (!string.IsNullOrEmpty(group))
{
if (string.IsNullOrEmpty(type)) type = "group";
listItem.Attributes["data-" + type] = group;
}
}
Example:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
using (var context = new WOContext())
{
context.Report_Types.ToList().ForEach(types => AddItemList(DropDownList1, types.Name, types.ID.ToString(), types.ReportBaseTypes.Name));
DropDownList1.DataBind();
}
}
else
{
using (var context = new WOContext())
{
context.Report_Types.ToList().ForEach(types => ChangeItemList(DropDownList1, types.ID.ToString(), types.ReportBaseTypes.Name));
}
}
}
#Sujay
You could add a semi-colon separated text into the dropdown's value attribute (like csv style), and use String.Split(';') to get 2 "values" out of the one value, as a workaround to get away with not having to create anew user control. Especially if you only have few extra attributes, and if it is not too long. You could also use a JSON value into the dropdown's value attribute and then parse out whatever you need from there.
//In the same block where the ddl is loaded (assuming the dataview is retrieved whether postback or not), search for the listitem and re-apply the attribute
if(IsPostBack)
foreach (DataRow dr in dvFacility.Table.Rows)
{
//search the listitem
ListItem li = ddl_FacilityFilter.Items.FindByValue(dr["FACILITY_CD"].ToString());
if (li!=null)
{
li.Attributes.Add("Title", dr["Facility_Description"].ToString());
}
} //end for each
I managed to achieve that using Session Variables, in my case my list is not going to contain many elements so it works pretty well, this is how I did it:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
string[] elems;//Array with values to add to the list
for (int q = 0; q < elems.Length; q++)
{
ListItem li = new ListItem() { Value = "text", Text = "text" };
li.Attributes["data-image"] = elems[q];
myList.Items.Add(li);
HttpContext.Current.Session.Add("attr" + q, elems[q]);
}
}
else
{
for (int o = 0; o < webmenu.Items.Count; o++)
{
myList.Items[o].Attributes["data-image"] = HttpContext.Current.Session["attr" + o].ToString();
}
}
}
When the Page is loaded first time the list is populated and I add an Image attribute which is lost after postback :( so at the time I add the elements with its attributes I create one Session variable "attr" plus the number of the element taken from the "for" cycle (it will be like attr0, attr1, attr2, etc...) and in them I save the value of the attribute (a path to an image in my case), when postback occurs (inside the "else") I just loop the list and add the attribute taken from the Session variable using the "int" of the "for" loop that is the same as when the page was loaded (this is because in this page I do not add elements to the list just selecting so they have always the same index) and the attributes are set again, I hope this helps someone in the future, greetings!

Resources