Access dynamically generated control from code behind - asp.net

I load a piece of html which contains something like:
<em> < input type="text" value="Untitled" name="ViewTitle" id="ViewTitle" runat="server"> </em>
into my control. The html is user defined, do please do not ask me to add them statically on the aspx page.
On my page, I have a placeholder and I can use
LiteralControl target = new LiteralControl ();
// html string contains user-defined controls
target.text = htmlstring
to render it property. My problem is, since its a html piece, even if i know the input box's id, i cannot access it using FindControl("ViewTitle") (it will just return null) because its rendered as a text into a Literal control and all the input controls were not added to the container's control collections. I definitely can use Request.Form["ViewTitle"] to access its value, but how can I set its value?

Jupaol's method is the prefer way of adding dynamic control to a page.
If you want to insert string, you can use ParseControl.
However, it doesn't cause compilation for some controls such as PlaceHolder.

Your process is wrong, you are rendering a control to the client with the attribute: runat="server"
This attribute only works if the control was processed by the server, you are just rendering as is
Since your goal is to add a TextBox (correct me if I'm wrong), then why don't you just add a new TextBox to the form's controls collection???
Something like this:
protected void Page_Init(object sender, EventArgs e)
{
var textbox = new TextBox { ID="myTextBoxID", Text="Some initial value" };
this.myPlaceHolder.Controls.Add(textbox);
}
And to retrieve it:
var myDynamicTextBox = this.FindControl("myTextBoxID") as TextBox;
I have created several working examples and they are online on my GitHub site, feel free to browse the code

Related

Switcher<Placeholder, PlaceholderSwitcher>.GetStack returns null How to implement dynamic placeholders in sitecore

I'm trying to implement Nick Wesselman's Dynamic Place Holder technique in Sitecore. I'm using sitecore 6.5 and asp.net.
http://www.techphoria414.com/Blog/2011/August/Dynamic_Placeholder_Keys_Prototype
http://www.techphoria414.com/Blog/2012/May/Sitecore_Page_Editor_Unleashed
I used the sourcecode I Found in the Sitecore_Page_Editor_Unleashed BLOG
All the pipelines seem to be in place and working. But in the Dynamic Placeholder control the following bit of code returns 0 (zero) although there are five dynamic placeholders on the control
Stack<Placeholder> stack = Switcher<Placeholder, PlaceholderSwitcher>.GetStack(false);
To Isolate the problem I created a very simple sitecore instance. 1 layout and 1 sublayout.
In the codebehind sublayout I have the following code for demo purposes:
var list = new List<int>();
for (int i = 0; i < 5; i++)
{
list.Add(i);
}
Repeater.DataSource = list;
DataBind();
This is the source of the ascx/sublayout
<asp:Repeater runat="server" ID="Repeater">
<ItemTemplate>
<mi:DynamicKeyPlaceholder runat="server" ID="pl" Key="place"></mi:DynamicKeyPlaceholder>
</ItemTemplate>
</asp:Repeater>
Result is that all five placeholders still have the same key.
What to do?
I've never tested this solution with multiple dynamically created placeholder controls inside the same sublayout. It's designed to solve the problem of the same sublayout (with a placeholder) being placed on a page, multiple times. It works by grabbing the ID of the containing rendering and appending it to the placeholder key. Steps I would take:
Ensure that the sublayout containing your repeater is placed in the layout dynamically, through a placeholder. You should not be using <sc:Sublayout/> to put the sublayout on the page. This may explain why the stack is empty.
You will also need to databind the "Key" field on the DynamicKeyPlaceholder. Within the same sublayout, all the DynamicKeyPlaceholder controls need to have a unique Key.
Of course, the danger here is that if the data driving your repeater changes, the Key could change. You might consider re-evaluating your architecture and driving it based on placing the same sublayout multiple times (with datasources), instead of a repeater.
What I have done is similar what you are requesting...I created a reference of another sublayout (which implements dynamic placeholders) from the calling control (which want to have dynamic ph).
So suppose my calling control is ControlA and implementing control is ControlB.
This is the markup of ControlA:
<ControlB ID="SectionItems" runat="server" CurrentItem="<%# Container.DataItem %>" />
And this is the code behind for it:
private static void SetIndexForControlBItems(RepeaterItemEventArgs e)
{
var controlBItems = e.Item.FindControl("ControlB") as ControlB;
if (controlBItems != null)
controlBItems.PropertyName = e.Item.ItemIndex.ToString();
}
When you assign the property name (which is going to be present in ControlB) then you will get the value of which index you are assigning dynamic PH to. And in ControlB you can do something like this:
private void SetPlaceholderKeys(RepeaterItemEventArgs e)
{
Placeholder phPreColumnContentTopLeft = e.Item.FindControl("phColumnContentTopLeft") as Placeholder;
if (phPreColumnContentTopLeft != null)
phPreColumnContentTopLeft.Key = "topLeftColumnSectionItems" + e.Item.ItemIndex + PropertyName;
Hope this helps!

How to find the HtmlInputCheckBox of a repeater control in code behind file without using runat ="server" tag

Inside repeater i have a html checkbox , now i want to access this checkbox in code behind file to check whether the checkbox is checked or not. but i dont want to user runat="server" tag , how can i do this my code as above
<input id="cbfdgroup" class="checkitem" type="checkbox" name="fd_cb_group[]" value='<%#Eval("FoodItemsUid") %>'>
in code behind file i am trying to access like this
foreach (RepeaterItem ri in rptMenu.Items)
{
if (ri.ItemType == ListItemType.Item || ri.ItemType == ListItemType.AlternatingItem)
{
HtmlInputCheckBox chk = (HtmlInputCheckBox)ri.FindControl("cbfdgroup");
if (chk.Checked)
{
}
}
}
but this is giving error as object referrence is not set to instance of an object.. how should i get this control in code behind file without using runat = "server" tag
It is not possible. To clarify, the runat="server" portion is doing just what it says. It is saying that this control should be made available and accessible to the server.
Code which is in the code-behind is code which is running on/executed by the server. So logistically, if the control is not made available to the server, the it cannot be manipulated by code (hence when it will not show up in intellisense either.)
I don't believe what you are asking is possible. runat=server is what makes controls available to the code behind. If you remove that attribute, your code behind is simply not aware of the control in any way.
A little bit more explanation:
The codebehind executes on the server. Therefore, any control you want to access in your codebehind must have runat=server in order to be available. The two are inseparable.
you don't need to read the items from repeater
assuming that you are sending a postback to the server (http post)
you can read the selected checkbox by simply:
string x = Request["fd_cb_group[]"];
it is more than 1 it will separate by comma, just use split the get a list of string of selected values.
the whole thing would be something like that:
protected void Button1_Click(object sender, EventArgs e)
{
string x = Request["fd_cb_group[]"];
string[] allSelectedOnes = x.Split(',');
foreach(string item in allSelectedOnes)
{
//your custom code for the selected checkboxes
}
}

ASP.NET: How to use contentplaceholder in attribute values without editor/designer errors?

I am using a contentplaceholder control in a master page to allow the content editor to specify the URL of an image used as a background to a div.
<div id="content-left-column"
style="background-image: url('<wc:UrlContentPlaceHolder runat='server' ID='leftContentBackgroundUrl'></wc:UrlContentPlaceHolder>');">
The placeholder is referenced on the content page like:
<asp:Content ID="Content1" ContentPlaceHolderID="leftContentBackgroundUrl" runat="server">/img/left-content.jpg</asp:Content>
The page renders just fine using this approach. However, when I look at the content page in source view, the ContentPlaceHolderId attribute value is underlined and there is a warning "Could not find 'leftContentBackgroundUrl' in the current master page or pages."
Design view will not render the content page due to this error.
Is there a way to use ContentPlaceHolder for attribute values such that no errors are reported in the Visual Studio editor and design surface?
*Note. I am aware of the issues with this approach. If the content editor puts in spaces, carriage returns or performs a document format in visual studio, the rendered attribute value is broken. I have created a subclass of ContentPlaceHolder that trims its values and uses ResolveClientUrl to address these issues. For the sake of discussion I have described the issue which affects a normal ContentPlaceHolder control.
The following stack overflow question addresses the fact that ContentPlaceHolder can be used with attribute values but does not address the design surface issues.
Why can't I use a ContentPlaceholder inside HTML attributes in ASP.NET?
I don't believe that's how ContentPlaceHolders where meant to be used. I would strongly advise you to use inline code for this.
Main.master:
<div id="content-left-column"
style="background-image: url(<%: LeftContentBackgroundURL %>);">
Main.master.cs:
public string LeftContentBackgroundURL { get; set; }
In the ContentPage you then just use the #MasterType directive and set the Property in Codebehind.
Content.aspx:
<%# MasterType VirtualPath="~/Main.master" %>
Content.aspx.cs:
protected void Page_Load(object sender, EventArgs e)
{
this.Master.LeftContentBackgroundURL = "/img/left-content.jpg";
}
This solution was inspired by #atticae's response.
On the master page, I included a normal ContentPlaceholder control with visible="false". Where I was previously using that ContentPlaceholder as an attribute value, I instead reference a property of the MasterPage, LeftBackgroundImageUrl.
<asp:ContentPlaceHolder runat='server' ID='leftContentBackgroundUrl' Visible="false"/>
<div id="content-left-column" style="background-image: url('<%: LeftBackgroundImageUrl%>');">
Using a subclass of ContentPlaceholder causes errors in the design surface of content pages which is why I went back to using a normal ContentPlaceholder.
The LeftBackgroundImageUrl property code looks like:
ReadOnly Property LeftBackgroundImageUrl As String
Get
Return RenderResolvedUrl(leftContentBackgroundUrl)
End Get
End Property
Private Function RenderedResolvedUrl(control As Control) As String
Dim visible As Boolean = control.Visible
control.Visible = True
Dim result As String = Nothing
Using writer As New System.IO.StringWriter()
Using htmlWriter As New System.Web.UI.HtmlTextWriter(writer)
control.RenderControl(htmlWriter)
htmlWriter.Flush()
End Using
result = Page.ResolveClientUrl(writer.ToString.Trim).Trim
End Using
control.Visible = visible
Return result
End Function
This solution allows the image url to be specified declaratively, and without the user having to add a MasterType directive. This is not perfect in the sense that it does not, at edit/design time, validate that the content the editor provides is just a URL or application relative URL. But, it does keep the user from having to write code.

ASP.Net - repeating input boxes on the client side using Repeater

I have the following requirement for creating a user profile in my application:
User should be able to enter multiple phone numbers/email addresses in his profile.
The screen looks somewhat like this:
- By default, on page load a single textbox for phone and email are shown.
- User can click a "+" button to add additional numbers/addresses.
- On clicking the "+" button we need to add another textbox just below the first one. User can add as many numbers/addresses as he wants. On submit, the server should collect all numbers/emails and save it in DB.
I tried using the Repeater control to do this. On page_load I bind the repeater to a "new arraylist" object of size 1. So, this renders fine - user sees a single textbox with no value in it.
When he clicks the "+" button, I ideally want to use javascript to create more textboxes with similar mark-up as the first.
My questions are these:
Can I render the new textboxes anyway using js? I notice that the HTML rendered by the repeater control is somewhat complex (names/ids) etc. and it might not be possible to correctly create those controls on client-side.
If there is a way to do #1, will the server understand that these additional inputs are items in the repeater control? Say, I want to get all the phone numbers that the user entered by iterating over Repeater.DataItems.
Conceptually, is my approach correct or is it wrong to use the Repeater for this? Would you suggest any other approach that might handle this requirement?
Coming from a Struts/JSP background, I am still struggling to get a grip on the .NET way of doing things - so any help would be appreciated.
The repeater control may be a bit of overkill for what you're trying to accomplish. It is mainly meant as a databound control for presenting rows of data.
What you can do is to dynamically create the boxes as part of the Page_Load event (C#):
TestInput.aspx :
<form id="form1" runat="server">
<asp:HiddenField ID="hdnAddInput" runat="server" />
<asp:Button ID="btnPlus" OnClientClick="setAdd()" Text="Plus" runat="server" />
<asp:PlaceHolder ID="phInputs" runat="server" />
</form>
<script type="text/javascript">
function setAdd() {
var add = document.getElementById('<%=hdnAddInput.ClientID%>');
add.value = '1';
return true;
}
</script>
TestInput.aspx.cs:
protected void Page_Load(object sender, EventArgs e)
{
if (ViewState["inputs"] == null)
ViewState["inputs"] = 1;
if (hdnAddInput.Value == "1")
{
ViewState["inputs"] = int.Parse(ViewState["inputs"].ToString()) + 1;
hdnAddInput.Value = "";
}
for (int loop = 0; loop < int.Parse(ViewState["inputs"].ToString()); loop++)
phInputs.Controls.Add(new TextBox() { ID = "phone" + loop });
}
I ended up using a PlaceHolder to dynamically add the text boxes and a HiddenField to flag when another TextBox needed to be added. Since the IDs were matching, it maintains the ViewState of the controls during each postback.
Welcome to the hairball that is dynamically-added controls in ASP.NET. It's not pretty but it can be done.
You cannot add new fields dynamically using javascript because the new field would have no representation in the server-side controls collection of the page.
Given that the requirements are that there is no limit to the number of addresses a user can add to the page, your only option is to do "traditional" dynamic ASP.NET controls. This means that you must handle the adding of the control server-side by new-ing a new object to represent the control:
private ArrayList _dynamicControls = new ArrayList();
public void Page_Init()
{
foreach (string c in _dynamicControls)
{
TextBox txtDynamicBox = new TextBox();
txtDynamicBox.ID = c;
Controls.Add(txtDynamicBox);
}
}
public void AddNewTextBox()
{
TextBox txtNewBox = new TextBox();
txtNewBox.ID = [uniqueID] // Give the textbox a unique name
Controls.Add(txtNewBox);
_dynamicControls.Add([uniqueID]);
}
You can see here that the object that backs each dynamically-added field has to be added back to the Controls collection of the Page on each postback. If you don't do this, data POSTed back from the field has nowhere to go.
If you want to user the repeater, I think the easiest way is to put the repeater in a ASP.Net AJAX update panel, add the extra textbox on the sever side.
There are definitely other way to implement this without using repeater, and it maybe much easier to add the textbox using js.
No, but you can create input elements similar to what TextBox controls would render.
No. ASP.NET protects itself from phony data posted to the server. You can't make the server code think that it created a TextBox earlier by just adding data that it would return.
The approach is wrong. You are trying to go a middle way that doesn't work. You have to go all the way in either direction. Either you make a postback and add the TextBox on the server side, or you do it completely on the client side and use the Request.Form collection to receive the data on the server side.

Email content of Asp.Net page

I have two pages on my site which are populated with content from a database as well as having user-entered fields (don't ask!). The pages also contain a ListView with a nested DataList. There are buttons on these pages which when clicked grab the html content of the page, write it to a HtmlTextWriter then get the text and put it into an email.
What I need to do is replace any TextBox / DropDownLists in the html source with string literal equivalents before putting into the email.
My aspx code so far looks something like this:
<div id="mailableContent" runat="server">
<asp:TextBox ID="txtMessage" runat="server"/>
<asp:Label ID="lblContentFromDb" runat="server"/>
<asp:ListView ID="lvwOffices" runat="server">
//loads of stuff here including more textboxes for the user to fill in
</asp:ListView>
</div>
and the codebehind is something like this:
StringBuilder stringBuilder = new StringBuilder();
StringWriter writer = new StringWriter(stringBuilder);
HtmlTextWriter htmlWriter = new HtmlTextWriter(writer);
mailableContent.RenderControl(htmlWriter);
MailMessage message = new MailMessage();
//do more stuff to set up the message object
message.Body = stringBuilder.ToString();
//send the message
My ideas so far are to 1. manually set any textboxes to Visible=false then populate literal controls with corresponding textbox values which is rather messy and tedious. Note I have to strip out input controls otherwise html for the email needs to be wrapped with a form elemen which I don't really want to do.
Is there a better way to do all this, I'm thinking that perhaps doing some .Net1.1 style xslt transforms with page content defined in xml files might be a better way to approach this, but am unsure if this will handle my requirement where I'm currently using a ListView with a nested DataList.
I find what you are describing to have a lot of overhead. Does your email template stay prety much the same? if so why not simply have a simple html template with html 3 code. Then simply read this file from disk and replace specific peices (ie. ##Name##) with the dynamic content. This way you have complete control over the html being sent via email and you can control what users input.
this would also limit the amout of work to make the html compatible with email clients.
Clarification: In the preceding suggestion, I propose that the UI implementation and the Email implementation be distinct, this in turn allows to to compose the email with more flexibility. Without using
mailableContent.RenderControl(htmlWriter);
this also allows you to compose the contents of the ListView to your specifications.
Couple of ideas:
Use RegEx to replace the html output of the textboxes and dropdownlists with plaintext. (Tricky, with the complication of finding the <option selected="selected"> stuff.
Do what I do:
Create an interface e.g. IPlainTextable
Make the interface enforce a boolean property called PlainTextMode (set false by default)
Extend TextBox and DropDownList with your own controls that implement IPlainTextable
In the Render section of your extended webcontrols, render out the plaintext value if PlainTextMode is true. e.g for your subclass of TextBox
protected override void Render(HtmlTextWriter writer)
{
if (PlainTextMode)
writer.WriteLine(this.Text);
else
base.Render(writer);
}
Before rendering out your page, run through all the IPlainTextable controls and set the PlainTextMode to true.
I have written a nifty little method for iterating through a nested control set:
public static List<T> FindControlsOfType<T>(Control ctlRoot)
{
List<T> controlsFound = new List<T>();
if (typeof(T).IsInstanceOfType(ctlRoot))
controlsFound.Add((T)(object)ctlRoot);
foreach (Control ctlTemp in ctlRoot.Controls)
{
controlsFound.AddRange(FindControlsOfType<T>(ctlTemp));
}
return controlsFound;
}
So you would just do something like:
foreach (IPlainTextable ctl in FindControlsOfType<IPlainTextable>(this))
{
ctl.PlainTextMode = true;
}
and then do your render to string after that...

Resources