Defining where usercontrol output will be - asp.net

I am new to ASP.NET and user controls. I am trying to generate a javascript array from my C# code.
On the main .aspx page I have this:
<%# Page Language="C#" AutoEventWireup="true" CodeFile="main.aspx.cs" Inherits="main" %>
<%# Register Src="~/table.ascx" TagPrefix="uc1" TagName="myTable" %>
Then on my table.asc.cs I have this:
protected void Page_Load(object sender, EventArgs e)
{
(...)
this.LoadDataFromDB();
(...)
}
private void LoadDataFromDB()
{
(...)
Response.Write(array);
(...)
}
My problem is that the array is being written before the <html> tags. It still works fine, but, how could I put it inside the <head> tags for instance?
Thank you
UPDATE:
I added this to my main.aspx
<asp:Literal ID="Literalarray" runat="server" Mode="PassThrough" Text="" />
and this to my ascx.cs:
Literal Literalarray= new Literal();
Literalarray.Text = output;
What am I missing?

Use a Literal control instead of Response.Write. Place it on your control somewhere and set its Text property.
You have to place it on your control, not on your page and you don't need to reinitalize it.

This code in the ascx.cs:
Literal Literalarray= new Literal();
Literalarray.Text = output;
should be:
Literalarray.Text = output;
Which should be in the Page_Load as a designer file will declare the literal type and allocate the space for it. By declaring a new one, the old one may be hidden. Also, be aware that if you are generating a JavaScript array that you also generate the script tags as part of the output as a literal doesn't do much decorating around the result.
I'd probably suggest putting a literal in the head on the main.aspx and load the data in there that way for one idea.
You could also do dynamic controls so that in the table.ascx.cs you create a Literal like you did previously and then add that to the head of the page assuming the head tag has a "runat=server" attribute so the code behind can use it. I'm pretty sure that in the code behind for the table you could do something like this:
Literal Literalarray= new Literal();
Literalarray.Text = output;
this.Page.head.AddControl(Literalarray);

Related

Alternative to ClientScript.RegisterClientScriptBlock?

The ASP.NET function ClientScript.RegisterClientScriptBlock can be used to register a chunk of JavaScript code that will be added to the page when it's rendered. The idea here is that you could have multiple instances of various user controls trying to register the same script over and over, but this ensures that it will only be included once.
The problem is, you don't really have any control over where the code is added to the page. This will insert the code inside the BODY tag of your page, but I need to add something (not limited to JavaScript code) into the HEAD block.
I'm well aware of various methods of adding something to the HEAD block via a ContentPlaceHolder block or by "Head.Controls.Add but these options do not address the problem of the same thing being added multiple times.
Is there an existing way to do this, or do I have to write a class that does something similar to ClientScript.RegisterClientScriptBlock except targeting the HEAD block?
I threw together a user control. There's nothing in the markup at all, so you can just add a new Web Forms User Control to your project and put this in the code behind:
public partial class ScriptControl : System.Web.UI.UserControl
{
private Dictionary<string, string> _scripts = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public void RegisterScript(string key, string script)
{
if(!_scripts.ContainsKey(key)) _scripts.Add(key, script);
}
protected override void Render(HtmlTextWriter writer)
{
writer.WriteFullBeginTag("script");
foreach(var script in _scripts.Values) writer.Write(script);
writer.WriteEndTag("script");
}
}
Just add the directive to your page markup:
<%# Register TagPrefix="uc" TagName="ScriptControl"
Src="ScriptControl.ascx" %>
(where "ScriptControl.ascx" is whatever you've named the control)
and then you can add it wherever you need to, including in the header.
<head runat="server">
<uc:ScriptControl id="HeaderScriptControl" runat="server"/>
</head>
The usage is pretty much the same:
HeaderScriptControl.RegisterScript("myScript",
#"alert(""hello, world!"")");

Response.write to <head>

Hopefully this won't be a difficult question for someone to answer, but I am having a lot of trouble finding the solution online. I am trying to add some HTML to my asp.net page from the code behind (It's VB.net). I would like to add the HTML into the head section of my page but can only add to the body currently.
You can put code in the head, just like the body. For example:
<%= CallAMethodThatReturnsAStringOfHtml() %>
You could try creating a property in your code behind and add your html in the Page_Load method:
Public MyHtml As String
then in the head section of your HTML just use the literal notation:
<%= MyHtml %>
Have runat attribute on your head element and you will be able to access it
<head id="someHead" runat="server">
</head>
Now in your codebehind, you can set it like
someHead.InnerHtml="<script src='somelibrary.js' ></script>";
I made this way, and it worked:
on the .aspx file:
...
<%
Response.Write(GetDisclosureText());
%>
...
on the aspx.cs file:
protected string GetDisclosureText()
{
string disclosure = "";
// ...Apply custom logic ...
if (!string.IsNullOrEmpty(disclosure))
{
return disclosure;
}
return "Error getting Disclosure Text";
}
Note the only difference is that I call Response.Write, not just the function.

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.

How to LoadControl a control that uses VaryByControl OutputCache, specifying values for properties

I've got a user control that should use caching, with VaryByControl. The .ascx file looks like this :
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="TestControl.ascx.cs" Inherits="mynamespace.TestControl" %>
<%# OutputCache Duration="10" Shared="true" VaryByControl="Test" %>
<p id="SomeText" runat="server">Nothing</p>
The TestControl class in the code-behind file has a int Test {...} property and an Page_Load() event handler that fills the SomeText paragraph with:
SomeText.InnerText = string.Format(#"Test={0} at {1}", Test, DateTime.Now)
I've got a .aspx file that looks like this:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="TestPage.aspx.cs" Inherits="mynamespace.TestPage" %>
<%# Register TagPrefix="xxx" TagName="TestControl" Src="Controls\TestControl.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<xxx:TestControl Test="6" runat="server" />
<xxx:TestControl Test="7" runat="server" />
<hr />
<asp:PlaceHolder ID="Suport" runat="server" />
</body>
</html>
The two <xxx:TestControl> tags properly load instances of TestControl with Test set to the expected value, I can refresh the browser a few times and I can see the cache properly doing it's job.
Now I'd like to fill the <asp:PlaceHolder ID="Suport" /> with some instances of TestControl, using varying Test values, that should all benefit from proper caching. I'm trying to use the LoadControl method, but I can't find a way to specify a value for the Test property. I expect such a method to exist, after all asp.net code loading the .aspx page manages to find the proper cached control. All I get is an instance of PartialCachingControl without CachedControl initialized and at runtime the rendered TestControl shows Test has the default value of 0.
This is how my .aspx Page_Load() event handler looks like:
protected void Page_Load(object sender, EventArgs e)
{
PartialCachingControl tc = (PartialCachingControl) LoadControl(#"Controls\TestControl.ascx");
if (tc.CachedControl != null)
((TestControl)tc.CachedControl).Test = 67;
Suport.Controls.Add(tc);
}
Edit
I could work around the problem by caching the whole page, but it just seems odd that I can't find a way to do it this way. Especially since invoking the control through the ASPX file works as expected (proving there's a way).
Edit 2
Hmm, no answers so far. I started a bounty, hopefully it gets a bit more attention.
To get a control participate in the full page life cycle it shall be added in the Init event or the CreateChildControls method rather than to add it on Load. Since VaryByControl needs fully qualified control identifiers to work it must be initialized before the page cycle begins.
Something similar to this:
protected override void OnInit(EventArgs e) {
var testControl = LoadControl(#"TestControl.ascx");
testControl.ID = "TestControl";
Suport.Controls.Add(testControl);
base.OnInit(e);
}
protected override void OnLoad(EventArgs e) {
TestControl testControl = GetTestControl("TestControl");
if(testControl != null){ //If it is null it is cached and can not be changed
testControl.Test = 242;
}
base.OnLoad(e);
}
private TestControl GetTestControl(string name) {
var control = this.Suport.FindControl(name);
var partialCachedControl = control as PartialCachingControl;
if(partialCachedControl != null) {
control = partialCachedControl.CachedControl;
}
return control as TestControl;
}
Since the output is cached per control you can not change the control until the cache is cleared. If you want to change the value and regenerate the content you either have to get the cache cleared or create a new control (with a new ID). One way to clear the cache is to use VaryByCustom instead and generates a cache key that changes if your Test-value is changing.
Also remember to implement INamingContainer interface on your test-control to avoid naming conflicts between the different objects. To do this, just add the interface to the control, like this:
public class TestControl: WebControl, INamingContainer {}
You have to swap 2 lines in order to make your code work :
PartialCachingControl tc = (PartialCachingControl) LoadControl(#"Controls\TestControl.ascx");
Suport.Controls.Add(tc);
if (tc.CachedControl != null)
((TestControl)tc.CachedControl).Test = 67;
As soon as you add the control, the cached control is initialized.
E.G.
I think you have misunderstood the VarByControl-property, it does not tell the cache to change upon a property on the control, but on the ID of controls on the page. Here is the text from MSDN:
The VaryByControl property is set to fully qualified control identifiers, where the identifier is a concatenation of control IDs starting from the top-level parent control and delimited with a dollar sign ($) character.
In your case you can maybe set VaryByCustom instead of VaryByControl and generate a cache key from the Test-property-value and vary it if it changes.

How can I replicate If...Then logic as a Web control?

I have a designer working at the ASPX level. He doesn't do C#, doesn't do code-behinds, doesn't compile, etc. He's a pure designer, working with HTML and server controls.
I need a conditional control -- an If...Then-ish type thing. Normally, I would do this:
<asp:Placeholder Visible='<%# DateTime.Now.Day == 1 %>' runat="server">
It's the first day of the month!
</asp:PlaceHolder>
Is there any way to do something like this without the databinding syntax? Something like:
<asp:If test="DateTime.Now.Day == 1" runat="server">
It's the first day of the month!
</asp:If>
Is there some kind of way to extend a placeholder to allow this? I've fiddled around a bit, but in the end, I have a conditional that I essentially have to compile.
Now, there's nothing wrong with the databinding syntax, but's just one more bit of...weirdness, the a designer is going to have to understand. Additionally, it doesn't give me "else" statements. Something like this would be great...
<asp:If test="DateTime.Now.Day == 1" runat="server">
It's the first day of the month!
<asp:Else>
It's not the first day of the month!
</asp:Else>
</asp:If>
Instead of writing control
asp:If
why not use:
<% if expression
{ %>
Yellow
<% } %>
<% else
{%>
Red
<% } %>
Taking into account that codebehind files are out as the designer probably not got VS, I think a simpler solution with less code may be more preferential:
<%# Page Language="C#"%>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
if (DateTime.Now.Day == 1)
{
DateTestPanelFirst.visible = true;
DateTestPanelOther.visible = false;
}
}
</script>
<html>
<body>
<asp:panel runat="server" id="DateTestPanelFirst" visible="false">
It's the first day of the month!
<asp:panel>
<asp:panel runat="server" id="DateTestPanelOther">
It's not the first day of the month!
<asp:panel>
</body>
</html>
the <asp:panel> could be changed to another type of web control, like <asp:label> etc. I think almost all dot net controls have the visible property, so you can hide/show them at any time.
The data-binding syntax has two problems: first it's a little weirder for your designer vs. using plain text, and second it requires the designer to remember to invert the "if" test in your "else" block.
The first problem may annoy your designer a little, but the second problem is much more severe, because it forces your designer to think like a programmer (inverting boolean logic!) and makes every if/else block into a possible bug you need to test for after your designer hands over a template.
My suggestion: use data-binding syntax, but fix the more severe problem by creating custom controls that only require data-binding test code on the If control, but not on the Else control. Sure your designers will have to type a few more characters, but the other more severe problems won't apply and your performance won't suffer as it would if you had to dynamically compile code each time your page ran.
Here's an example I coded up to illustrate:
<%# Page Language="C#"%>
<%# Register Assembly="ElseTest" TagPrefix="ElseTest" Namespace="ElseTest"%>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
DataBind();
}
</script>
<html>
<body>
<ElseTest:IfControl runat="server" visible="<%#1 == 1 %>">
This should be visible (in "if" area)
</ElseTest:IfControl>
<ElseTest:ElseControl runat="server">
This should not be visible (in "else" area)
</ElseTest:ElseControl>
<br /><br />
<ElseTest:IfControl runat="server" visible="<%#0 == 1 %>">
This should not be visible (in "if" area)
</ElseTest:IfControl>
<ElseTest:ElseControl runat="server">
This should be visible (in "else" area)
</ElseTest:ElseControl>
</body>
</html>
Here's the underlying controls, which are simply wrappers around asp:Literal:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
[assembly: TagPrefix("ElseTest", "ElseTest")]
namespace ElseTest
{
// simply renames a literal to allow blocks of data-bound-visibility
[ToolboxData("<{0}:IfControl runat=\"server\"></{0}:IfControl>")]
public class IfControl : Literal
{
}
[ToolboxData("<{0}:ElseControl runat=\"server\"></{0}:ElseControl>")]
public class ElseControl : Literal
{
public override bool Visible
{
get
{
// find previous control (which must be an IfControl).
// If it's visible, we're not (and vice versa)
for (int i = Parent.Controls.IndexOf(this)-1; i >= 0; i--)
{
Control c = Parent.Controls[i];
if (c is IfControl)
return !c.Visible; // found it! render children if the if control is not visible
else if (c is Literal)
{
// literals with only whitespace are OK. everything else is an error between if and then
Literal l = c as Literal;
string s = l.Text.Trim();
if (s.Length > 0)
throw new ArgumentException("ElseControl must be immediately after an IfControl");
}
}
throw new ArgumentException("ElseControl must be immediately after an IfControl");
}
set
{
throw new ArgumentException("Visible property of an ElseControl is read-only");
}
}
}
}
If you want it more concise, you can easily shorten the tag name (by changing the class names and/or tag prefix). You can also create a new property (e.g. "test") to use instead of "Visible".
If you really want to get rid of the <%# %>, there are likley many different tricks you can use to leverage CodeDOM or other ways to dynamically compile code, although performance will be a challenge since you'll probably end up dynamically compiling code each time the page runs, it may introduce pesky security issues, and more. I'd stay away from that.

Resources