How to check if ContentPlaceHolder is empty? - c#

How to check if ContentPlaceHolder is absolutely empty?
In the ContentPlaceHolder have text only, without tags and controls.
Example Page.Master:
<asp:ContentPlaceHolder runat="server" ID="Content" />
Example Test.aspx:
<asp:Content runat="server" ContentPlaceHolderID="Content">
Custom text without controls. Content.Controls.Count is 0 and Content.HasControls is false.
</asp:Content>
What I need to do is that when the placeholder is empty put a default content is in another control.
Overwrite tried twice for the same placeholder but I get error when dynamic load.

You can implement a method that will render the content control into a string, then check the string to find wheahter it contains any non-white space chars:
private bool HasContent(Control ctrl)
{
var sb = new System.Text.StringBuilder();
using (var sw = new System.IO.StringWriter(sb))
{
using(var tw = new HtmlTextWriter(sw))
{
ctrl.RenderControl(tw);
}
}
var output = sb.ToString().Trim();
return !String.IsNullOrEmpty(output);
}
protected void Page_PreRender(object sender, EventArgs e)
{
var placeholder = Master.FindControl("FeaturedContent");
var hasContent = HasContent(placeholder);
}

You need to find the ContentPLaceHolder on the master page first. Then you can cast the first control(which always exists) to LiteralControl and use it's Text property.
So this works as expected from Page_Load of the content-page:
protected void Page_Load(object sender, EventArgs e)
{
var cph = Page.Master.FindControl("Content") as ContentPlaceHolder;
if (contentPlaceHolder != null)
{
string textualContent = ((LiteralControl) cph.Controls[0]).Text;
if (string.IsNullOrEmpty(textualContent))
{
// ...
}
}
}

This seems to have changed, because I am seeing in 4.5 that HasControls DOES return true when there is only literal text in the Content, even a single whitespace. I do something like this in my master page:
<asp:Panel id="SidebarPanel" CssClass="Sidebar" runat="server">
<asp:ContentPlaceHolder id="SidebarContent" runat="server" />
</asp:Panel>
Sub Page_Load(...)
SidebarPanel.Visible = SidebarContent.HasControls
End Sub
This renders the sidebar content, if there is any, inside a <div class="Sidebar"> -- and avoids creating an empty div on the page when there's no content.

I really didn't want to run all the code for a render or risk that maybe some controls might have states that change after being rendered. So I came up with another approach.
public static int ChildrenCount(ContentPlaceHolder placeholder)
{
int total = 0;
total += placeholder.Controls.OfType<Control>().Where(x =>
(!(x is ContentPlaceHolder) && !(x is LiteralControl)) ||
(x is LiteralControl && !string.IsNullOrWhiteSpace(((LiteralControl)x).Text))
).Count();
foreach (var child in placeholder.Controls.OfType<ContentPlaceHolder>())
total += ChildrenCount(child);
return total;
}
For me the text I'd place directly into a Content element would be returned by OfType as a LiteralControl with the appropriate contents. Not only this but my formatting ("\r\n\t") would also be returned the same way. I'd also get ContentPlaceholders for subsequent master pages as they passed the slot in my web pages to the next master page or actual page.
So the task now is to get a count of controls that excludes these ContentPlaceholders and also excludes LiteralControls which are whitespace. This is pretty easy using the is operator. We'll just make sure a given control is neither of those types and then count it, or if it is a Literal we check if the contents are all whitespace or not. The last step is to recursively add the results of the same operation for all child ContentPlaceholders so nested master pages work as expected.
And then finally:
if (ChildrenCount(MyContentPlaceholder) == 0)
MyContentPlaceholder.Controls.Add(new LiteralControl("My default content!"));

My 2 cents:
If it's a constant content you'll have to insert AND there will be no <Content> at all:
<asp:ContentPlaceHolder>
<!-- Anything here will be inserted if there's no Content -->
</asp:ContentPlaceHolder>

Related

getting error 'must be placed inside a form tag with runat=server', but it is

I'm getting the above error message. However, the page in question does indeed have a tag with runat="server" attribute on it (at design time) and the control is inside it. If I run the project, and view source, that attribute appears to be gone (not sure if that part is normal or what's causing it if it's not).
The error pops when I try to run RenderControl method. The page loads fine to begin with. Any ideas?
<form id="form1" runat="server">
<div id="hiddenMVR" runat="server" style="display:block;">
// lots of other controls in here removed for brevity
</div>
</form>
Code behind:
StringBuilder stringBuilder = new StringBuilder();
StringWriter stringWriter = new StringWriter(stringBuilder);
HtmlTextWriter htmlTextWriter = new HtmlTextWriter(stringWriter);
//error occurs on RenderControl
this.hiddenMVR.RenderControl(htmlTextWriter);
Apparently the below code fixes the issue, although honestly I'm not sure why, since the original error message isn't accurate:
public override void VerifyRenderingInServerForm(Control control)
{
/* Confirms that an HtmlForm control is rendered for the specified ASP.NET server
control at run time. Used to avoid issue using RenderControl above */
}
Apparently, this overrides some built-in method that I'm not aware of, which is doing something that causes the error. Overriding it with no code prevents whatever is happening by default and thus eliminates the error.
This "fix" was mentioned in some other posts, but I didn't think it applied because the error message doesn't line up with the code.
I haven't noticed any detrimental issues from doing this.
Before RenderControl you need to change the type of Button, LinkButton or any other asp.net component you are using to Literal.
Simplest way to do the same is to remove that control and add Literal at that place.
A sample code to do the same is as below.
private void DisableControls(Control gv)
{
Literal l = new Literal();
for (int i = 0; i < gv.Controls.Count; i++)
{
if (gv.Controls[i].GetType() == typeof(Button))
{
l.Text = (gv.Controls[i] as Button).Text;
gv.Controls.Remove(gv.Controls[i]);
gv.Controls.AddAt(i, l);
}
if (gv.Controls[i].GetType() == typeof(LinkButton))
{
l.Text = (gv.Controls[i] as LinkButton).Text;
gv.Controls.Remove(gv.Controls[i]);
gv.Controls.AddAt(i, l);
}
if (gv.Controls[i].GetType() == typeof(CheckBox))
{
l.Text = (gv.Controls[i] as CheckBox).Text;
gv.Controls.Remove(gv.Controls[i]);
gv.Controls.AddAt(i, l);
}
if (gv.Controls[i].HasControls())
{
DisableControls(gv.Controls[i]);
}
}
}
and you can call the same using
DisableControls(listView);
before RenderControl

Ghost cell in IE9 with Repeater Control

IE9 Generate blank cell or you can say Ghost Cell, with ASP.Net Repeater control.
I try javascript regural expression. Render function to run reg. exp. but the page holds few update controls and generate error.
Error: sys.webforms.pagerequestmanagerservererrorexception the message
received from the server could not be parsed. ScriptResource.axd
I try all the well known links for this error.
Please suggest me if you really have...
Thank You
protected override void Render(HtmlTextWriter writer)
{
using (HtmlTextWriter htmlwriter = new HtmlTextWriter(new System.IO.StringWriter()))
{
base.Render(htmlwriter);
string html = htmlwriter.InnerWriter.ToString();
if ((ConfigurationManager.AppSettings.Get("RemoveWhitespace") + string.Empty).Equals("true", StringComparison.OrdinalIgnoreCase))
{
//html = Regex.Replace(html, #"(?<=[^])\t{2,}|(?<=[>])\s{2,}(?=[<])|(?<=[>])\s{2,11}(?=[<])|(?=[\n])\s{2,}", string.Empty);
html = Regex.Replace(html, #"(?<=<td[^>]*>)(?>\s+)(?!<table)|(?<!</table>\s*)\s+(?=</td>)", string.Empty);
html = html.Replace(";\n", ";");
}
writer.Write(html.Trim());
}
another Solution is, but fail for Repeater
var expr = new RegExp('>[ \t\r\n\v\f]*<', 'g');
document.body.innerHTML = document.body.innerHTML.replace(expr, '><');
You can access the Repeater control directly (before it's written to the page and rendered by IE) and remove the cells based on their index.
Need to remove spaces between "< /td >" and "< td >".
Found a very useful script to prevent unwanted cells in your html table while rendering in IE9 browser.
function removeWhiteSpaces()
{
$('#myTable').html(function(i, el) {
return el.replace(/>\s*</g, '><');
});
}
This javascript function you should call when the page loads (i.e. onload event)

Programmatically adding a hyperlink to a bulleted list that IS NOT DisplayMode=Hyperlink

I have a ASP.NET bulleted list control that, until today, was created and used only for plain text. A new design request asks that I turn SOME of those items into hyperlinks. Therefore the bulleted list will ultimately need to contain some plain text items, and some hyperlinks. If I change it to DisplayMode=Hyperlink, even if I leave the value blank, the entries that should just be plain text still become clickable links.
One solution that I think I can make work, is to use a Literal control and use HTML (<a href...) on the lines that need to be links. That will entail a little bit of re-working some old code, so before I try that I really want to know if this is possible to do with the existing BulletedList.
EDIT:
I seriously couldn't find anything about this anywhere, and I generally consider myself a pretty good Googler. So for the one or two lost and confused souls who find themselves in the same scenario sometime in the next decade, here is my complete implementation of the excellent answer offered below:
In the page's code-behind:
foreach (SupportLog x in ordered)
{
blschedule.Items.Add(new ListItem(x.Headline, "http://mysite/Support/editsupportlog.aspx?SupportLogID=" + x.SupportLogID));
}
blschedule.DataBind();
Note the DataBind at the end --- this is necessary to fall into the DataBound event:
protected void blschedule_DataBound(object sender, EventArgs e)
{
foreach (ListItem x in blschedule.Items)
{
if (x.Value.Contains("http")) //an item that should be a link is gonna have http in it, so check for that
{
x.Attributes.Add("data-url", x.Value);
}
}
}
In the .aspx page's head:
<script src="<%# ResolveClientUrl("~/jquery/jquery141.js") %>" type="text/javascript"></script>
<script>
$(document).ready(function () {
$('#<%=blschedule.ClientID %> li').each(function () {
var $this = $(this);
var attr = $this.attr('data-url');
if (typeof attr !== 'undefined' && attr !== false) {
$this.html('' + $this.text() + '');
}
});
});
</script>
The if statement is required to make sure to only turn the items that have the "data-url" attribute into links, and not turn ALL items into links.
You may find it's easier to use an <asp:Repeater /> for that task.
Something like:
<asp:Repeater ID="Repeater1" runat="server">
<HeaderTemplate><ul></HeaderTemplate>
<ItemTemplate>
<li><%# string.IsNullOrEmpty(Eval("url").ToString()) ? Eval("text") : string.Format("{1}", Eval("url").ToString(), Eval("text").ToString()) %></li>
</ItemTemplate>
<FooterTemplate></ul></FooterTemplate>
</asp:Repeater>
Hackalicious Way
set the URL value to DataValueField when data binding the BulletedList
use the DataBound event to iterate through the items and add an attribute to each one with a URL value
protected void BulletedList1_DataBound(object sender, EventArgs e)
{
foreach (ListItem i in BulletedList1.Items)
{
if (i.Value.Length > 0)
{
i.Attributes.Add("data-url", i.Value);
}
}
}
use JavaScript/jQuery to apply the necessary markup:
$('[data-url]').each(function() {
var $this = $(this);
$this.html('' + $this.text() + '');
});
didn't test this jQuery but it should be close

Execute html from database in ASP.NET page

Any suggestions to load a html page saved in the database?
html:
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>xxx</title>
</head>
<body>
<form id="Form1" runat="server" method="post">
<ext:ResourceManager ID="ResourceManager1" runat="server" />
</form>
</body>
codebehind:
protected override void OnInit(EventArgs e)
{
this.IniciarFormulario();
using (ServicoECMClient proxy = new ServicoECMClient())
{
tipoDocumento = proxy.ObterTipoDocumento(int.Parse(tipoDocumentoID));
}
if (tipoDocumento == null)
{
throw new ApplicationException();
}
this.Page.Header.InnerHtml = tipoDocumento.estilo; //css
this.Page.Form.InnerHtml = tipoDocumento.form; // form
base.OnInit(e);
}
I can not retrieve the form values.
Look:
foreach (System.Web.UI.Control controle in this.Form1.Controls)
{
if (controle.GetType().Name == "HtmlInputText" || controle.GetType().Name == "HtmlInputSelect"
|| controle.GetType().Name == "HtmlInputRadio" || controle.GetType().Name == "HtmlInputTextCheckbox")
{
if (!string.IsNullOrEmpty(this.Request[controle.ClientID]))
{
documento_indice documentoIndice = new documento_indice();
documentoIndice.id_indice = int.Parse(controle.ClientID.Split('_')[1]);
documentoIndice.valor = this.Request[controle.ClientID];
documentoIndice.timestamp = DateTime.Now;
documentos_indices.Add(documentoIndice);
}
}
}
Controls is empty. => this.Form1.Controls
Any Suggestion?
There's another better way to do?
Thanks.
The short answer is, yes you can get this functionality to work. We provide all of our customer-specific customizations in a manner similar to this.
The long answer is that it will require some restructuring of your application and HTML.
The easiest way that we found to implement this is through UserControls. The basic approach is:
1) Create your page content that is stored in the DB as a UserControl, i.e.
<%# Control Language="vb" AutoEventWireup="false" %>
<input id="txtTest" type="text" runat="server" />
2) When you extract it from the DB, store it in a file on disk with an ascx extension (say content.ascx for now).
3) Modify your main page to add a div that runs at the server that the ascx will be loaded into:
<div id="divContent" runat="server">
</div>
4) In page init, load the control into the div and initialize it:
Dim oControl As Control
' Load a user control
oControl = Me.LoadControl("content.ascx")
If oControl IsNot Nothing Then
' Ensure viewstate is enabled
oControl.EnableViewState = True
' Set properties as required
oControl.ID = "ContentControl"
' Clear the existing control(s) from the content container
Me.divContent.Controls.Clear()
' And add the new one
Me.divContent.Controls.Add(oControl)
End If
5) Access the collection of controls contained in the div control.
Note that on a page postback the controls will not have content loaded into them until the page load event is fired due to the standard page lifecycle.
I have verified that codebehind is not required and this works correctly exactly as described above.
Unless you add the controls to the controls collection by hand (a tricky business) you're going to need to read your form values the old-fashioned way:
Request.Form["txtSomething"]
That means you can get the string values contained in your controls if you know their names, but not much else.

Dynamically change MasterPage and ContentPlaceHolderID of asp content tag?

I have a page which initially inherited from MastePage.master . And I want to use the same page but with different masterpage(MasterPage2.master) at some other place in my project. For that I am using the following code.
private void Page_PreInit(object sender, EventArgs e)
{
if (Request.QueryString["Update"].ToString() == "New")
{
this.MasterPageFile = "MasterPage2.master";
Content con = new Content();
con = (Content)this.FindControl("Content1");
this.Content1.ContentPlaceHolderID = "ContentPlaceHolder2";
}
}
I am also trying to set the asp content tag's ContentPlaceHolderID to ContentPlaceHolder2 which is from MasterPage2.master. Initially it was ContentPlaceHolder1.
But I am getting null value at con = (Content)this.FindControl("Content1");
Thanks
Page internally stores in private '_contentTemplateCollection' hashtable. it uses ContentPlaceHolderID property as key and stores special class (that will be used to build/initialize Content tag) as a value
- so to change ContentPlaceHolderID value (defined within markup) you need to modify this hashtable, remove old entry linked with old Id and add other entry with new Id
- you need to change ContentPlaceHolderId before creating master page otherwise an exception will be thrown in runtime
- best place to change Ids is Page 'preinit' event and if it is better to change Ids before change master page (if you will change master page at runtime)
To change ContentPlaceHolderID of Content tag, you can use following function in Page PreInit event
public static void AssignContentToNewPlaceHoldersWithinPage(Page pPage, string pOldId, string pNewId)
{
if (pPage == null || string.IsNullOrEmpty(pOldId) || string.IsNullOrEmpty(pNewId))
{
return;
}
// Try to get a reference to private hashtable using fasterflect free reflection library in codeplex (http://fasterflect.codeplex.com/)
// you can replace following line with standard reflection APIs
var lTmpObj = pPage.TryGetFieldValue("_contentTemplateCollection");
if (lTmpObj != null && lTmpObj is Hashtable)
{
var _contentTemplateCollection = lTmpObj as Hashtable;
if (_contentTemplateCollection.ContainsKey(pOldId) && !_contentTemplateCollection.ContainsKey(pNewId))
{
var lTemplate = _contentTemplateCollection[pOldId];
_contentTemplateCollection.Add(pNewId, lTemplate);
_contentTemplateCollection.Remove(pOldId);
}
}
}
function parameter are
pPage is reference to page instance contains content tag
pOldId is ContentPlaceHolderId property value in markup - the Id you want to change
pNewId is the new Id you want to use
I hope that my answer will be useful and I am sorry if my English language is not good
You can dynamically change the Master Page at runtime, but you need to use the same ContentPlaceHolder IDs. That way, your pages will work with either Master Page without adding extra code to change the IDs at runtime.
private void Page_PreInit(object sender, EventArgs e)
{
if (Request.QueryString["Update"].ToString() == "New")
{
this.MasterPageFile = "MasterPage2.master";
}
}
You can even test that your page will work with either Master Page in the Visual Studio design/markup view by changing the MasterPageFile in the <% Page %> directive in the .aspx markup.
The Master Page can be changed by overriding OnPreInit.
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
MasterPageFile = "~/MasterPages/MyOther.Master";
}
But for the ContentPlaceHolders I would suggest to create new ContentPlaceHolders with the same name in both of your MasterPages.

Categories

Resources