Self closing Html Generic Control? - c#

I am writing a bit of code to add a link tag to the head tag in the code behind... i.e.
HtmlGenericControl css = new HtmlGenericControl("link");
css.Attributes["rel"] = "Stylesheet";
css.Attributes["type"] = "text/css";
css.Attributes["href"] = String.Format("/Assets/CSS/{0}", cssFile);
to try and achieve something like...
<link rel="Stylesheet" type="text/css" href="/CSS/Blah.css" />
I am using the HtmlGenericControl to achieve this... the issue I am having is that the control ultimatly gets rendered as...
<link rel="Stylesheet" type="text/css" href="/CSS/Blah.css"></link>
I cant seem to find what I am missing to not render the additional </link>, I assumed it should be a property on the object.
Am I missing something or is this just not possible with this control?

I think you'd have to derive from HtmlGenericControl, and override the Render method.
You'll then be able to write out the "/>" yourself (or you can use HtmlTextWriter's SelfClosingTagEnd constant).
Edit: Here's an example (in VB)

While trying to write a workaround for umbraco.library:RegisterStyleSheetFile(string key, string url) I ended up with the same question as the OP and found the following.
According to the specs, the link tag is a void element. It cannot have any content, but can be self closing. The W3C validator did not validate <link></link> as correct html5.
Apparently
HtmlGenericControl css = new HtmlGenericControl("link");
is rendered by default as <link></link>. Using the specific control for the link tag solved my problem:
HtmlLink css = new HtmlLink();
It produces the mark-up <link/> which was validated as correct xhtml and html5.
In addition to link, System.Web.UI.HtmlControls contains classes for other void element controls, such as img, input and meta.

Alternatively you can use Page.ParseControl(string), which gives you a control with the same contents as the string you pass.
I'm actually doing this exact same thing in my current project. Of course it requires a reference to the current page, (the handler), but that shouldn't pose any problems.
The only caveat in this method, as I see it, is that you don't get any "OO"-approach for creating your control (eg. control.Attributes.Add("href", theValue") etc.)

I just created a solution for this, based on Ragaraths comments in another forum:
http://forums.asp.net/p/1537143/3737667.aspx
Override the HtmlGenericControl with this
protected override void Render(HtmlTextWriter writer)
{
if (this.Controls.Count > 0)
base.Render(writer); // render in normal way
else
{
writer.Write(HtmlTextWriter.TagLeftChar + this.TagName); // render opening tag
Attributes.Render(writer); // Add the attributes.
writer.Write(HtmlTextWriter.SelfClosingTagEnd); // render closing tag
}
writer.Write(Environment.NewLine); // make it one per line
}

The slightly hacky way.
Put the control inside a PlaceHolder element.
In the code behind hijack the render method of the PlaceHolder.
Render the PlaceHolders content exactly as you wish.
This is page / control specific and does not require any overrides. So it has minimal impact on the rest of your system.
<asp:PlaceHolder ID="myPlaceHolder" runat="server">
<hr id="someElement" runat="server" />
</asp:PlaceHolder>
protected void Page_Init(object sender, EventArgs e)
{
myPlaceHolder.SetRenderMethodDelegate(ClosingRenderMethod);
}
protected void ClosingRenderMethod(HtmlTextWriter output, Control container)
{
var voidTags = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) { "br", "hr", "link", "img" };
foreach (Control child in container.Controls)
{
var generic = child as HtmlGenericControl;
if (generic != null && voidTags.Contains(generic.TagName))
{
output.WriteBeginTag(generic.TagName);
output.WriteAttribute("id", generic.ClientID);
generic.Attributes.Render(output);
output.Write(HtmlTextWriter.SelfClosingTagEnd);
}
else
{
child.RenderControl(output);
}
}
}

Related

How to find a HtmlGenericControl using a Class

I'm trying to figure out how to find a "div" which is in my main .aspx page from a class, is this possible? If so how? I've tried the code below but is not working
HtmlGenericControl step1 = (HtmlGenericControl)Page.FindControl("step1")
I know this is the way I would do it in the code behind file but in this case I want to do it from a class file.
Thank you in advance.
Basically what I'm trying to accomplish is this, I have multiple divs in my page all of the have runtat="server" visible="false" when certain criteria is met I want to be able to change the visible="true". I have this scenario in multiple pages so I want to be able to create a Class and check for the conditions there and through this way make the div visible or un-visible
The div element must have the runat="server" attribute:
<body>
<form id="form1" runat="server">
<div id="step1" runat="server"></div>
</form>
</body>
Now, you can find the control with a method in a separated class, which is called from the code behind class of the page:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Class1 class1 = new Class1();
class1.FindDiv(this);
}
}
Separated class:
public class Class1
{
public HtmlGenericControl FindDiv(Page page)
{
HtmlGenericControl step1 = (HtmlGenericControl)page.FindControl("step1");
return step1;
}
}
If you use ASP Panel instead of a div u can access it and Panel renders as div. If u give runat="server" to a div u should be able to access it.
You cannot do that if it is not a server side control..
If it is a HTML control you need to set runat="server" attribute to the div to find it in code behind..
Try
HtmlGenericControl div = (HtmlGenericControl)this.FindControl("div1");
Note : In order to make this code work, you need to have runat="server" property at aspx page.

Change CSS class programmatically on load?

I have two pages, let's call them "receipts.com" and "business.receipts.com". Both link to a page on a different domain via Response.Redirect("http://receipts2.com/default.aspx?mode=")
where the "mode"-parameter is the referring page.
The recieving page should look in the query string, and choose a different CSS class according to the "mode"-parameter.
How is this accomplished? And is this the right way to do it?
Instead of swapping class names you can use the same class and different stylesheets.
There are two ways to handle stylesheets: client side and server side.
On the client side, you can parse the query string and disable stylesheets using: (document.getElementsByTagName("link")[i]).disabled = true;
On the server side, you can use themes or simply add a placeholder around the style declarations and show/hide them using codebehind that looks at Response.QueryString["mode"]:
<asp:PlaceHolder ID="placeHolder1" runat="server" Visible="false">
<link rel="stylesheet" href="/alternate.css" media="all" />
</asp:PlaceHolder>
and code behind in a master page somewhere:
if(Response.QueryString["mode"] == "blah")
{
placeHolder1.Visible = true;
}
You could use different Page-Themes:
protected void Page_PreInit(object sender, EventArgs e)
{
switch (Request.QueryString["mode"])
{
case "receipts.com":
Page.Theme = "DefaultTheme";
break;
case "business.receipts.com":
Page.Theme = "BusinessTheme";
break;
}
}
Of course you can also use the code above to apply different Css-Classes to controls.
You can use document.referrer instead of passing in the page via querystring.
When you say "The receiving page should look in the query string, and choose a different CSS class", what is that class going to be set against, ie the body or an element like p?
Plain Javascript
document.getElementById("MyElement").className = " yourClass";
jQuery
$("p").addClass("yourClass");
Perhaps you meant a css theme?
and then you could try
if (document.referrer == "blahblah")
document.write("<link rel='stylesheet' type='text/css' href='one.css' />)
else
document.write("<link rel='stylesheet' type='text/css' href='two.css' />)
Although I would recommend looking into jQuery
$.get(stylesheet, function(contents){
$("<style type=\"text/css\">" + contents + "</style>").appendTo(document.head);
});
u can do something like this
{
string hrefstring = null;
string mode = this.Page.Request.QueryString.Item("mode");
if (mode == "a") {
hrefstring = ("~/yourcss/a.css");
} else if (mode == "b") {
hrefstring = ("~/yourcss/b.css");
}
css.Href = ResolveClientUrl(hrefstring);
css.Attributes("rel") = "stylesheet";
css.Attributes("type") = "text/css";
css.Attributes("media") = "all";
Page.Header.Controls.Add(css);
}

How to send an xml serialized object from ASP.NET to Silverlight

On the button click on an ASP.NET page, I need to load a silverlight application, passing a serialized object from ASP.NET codebehind to MainPage.xaml.cs. How to do this?
Why not use WCF? This is a perfect fit for sending serialized objects. Also, WCF hosts well on IIS, so it works great with ASP. Here is a tutorial to get you started. You should be able to see clearly how to define a simply API that you can call from Silverlight. You just need to make your object part of a DataContract.
Do either of these help - http://www.silverlight.net/archives/videos/using-startup-parameters-with-silverlight or http://forums.silverlight.net/t/183963.aspx/1 ?
Some options for you, may help. You could use Javascript - silverlight.net on scripting Silverlight to reach inside the Silverlight object from your page.
Another option is to have the Silverlight object access the AspNet page to ask for it's xml using PageMethods. ( System.Web.Services.WebMethod) once it's loaded.
One option is to configure a Silverlight onLoad event in the <object> tag for your app:
<param name="onLoad" value="setInfo" />
Then use script to push the XML into your app (dynamically insert the XML onto the page from ASP.NET):
<script type="text/javascript">
function setInfo(sender) {
var msg = '<yourtag>your info here</yourtag>';
sender.getHost().content.Page.SetInfo(msg);
}
</script>
To allow script to call your app, configure as follows:
public MainPage()
{
HtmlPage.RegisterScriptableObject("Page", this);
InitializeComponent();
}
[ScriptableMember]
public void SetInfo(string xml)
{
// do stuff
}
Register onLoad param in your Silverlight <Object> tag:
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2">
<param name="source" value="ClientBin/MySlApp.xap"/>
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="4.0.50826.0" />
<param name="autoUpgrade" value="true" />
<param name="onLoad" value="onSilverlightLoad" />
</object>
and register <Script>
<script type="text/javascript">
function onSilverlightLoad(sender, args)
{
var mySlControl = sender.getHost();
mySlControl.content.Communicator.MyDeserializer("SerializedObjectString")
}
</script>
In your Silverlight app register `Communicator' object so it is the app itself:
namespace MySilverlightApp
{
public MySilverlightApp()
{
InitializeComponent();
HtmlPage.RegisterScriptableObject("Communicator", this);
}
}
and create de-serialization function decorated with [ScriptableMember]:
[ScriptableMember]
public void MyDeserializer(string _stringPassedFromHtmlDocument)
{
//deserialize _stringPassedFromHtmlDocument
}
I have above working in one of Sharepoint projects utilising Silverlight webpart. Serialized object is however rendered into HTML so not sure if that will work for your Button.Click() requirement. One thing though should you go down this route: I encountered many many many issues when trying XML serialization and found JSON to be better alternative.
In your MainPage.xaml.cs, define a property getter/setter for whatever object type you need passed.
In your ASP.NET page button click handler, set the property to the serialized object.
If you need this to maintain the serialized object after the page lifecycle finishes, simply change the property setter in MainPage.xaml.cs to persist the serialized object across page lifecycles.
Hope this helps.
Pete
There are two possible paths here:
1) When the button is pressed, the page posts back to the server, gathers some information, serializes it into XML, shows the silverlight component, which then loads the serialized XML.
2) When the page is loaded, the XML data is available. Pressing the button simply shows the silverlight component and asks it to load the XML data.
Scenario 1
Here are the steps that you need to take for scenario 1.
1) Add the silverlight component to the page and embed it in a container (div, table, whatever you like) that is set to runat server. Note that we also specify an onload param to fire a specific event when the silverlight object has finished loading:
<div id="divDiagram" runat="server">
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
id="objDiagram">
<param name="onLoad" value="RefreshDiagram" />
</object>
</div>
2) In your code-behind, hide this container until the button is pressed. For example:
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
divDiagram.Visible = false;
}
}
void Button1_Click(object sender, EventArgs e)
{
divDiagram.Visible = true;
}
3) Add a hidden input to hold the serialized data:
<input type="hidden" id="txtSerializedData" runat="server" />
4) Set the contents of this input on the button click:
txtSerializedData.Value = "some serialized data";
5) Modify the silverlight components code to expose the control to javascript:
public MainPage()
{
InitializeComponent();
System.Windows.Browser.HtmlPage.RegisterScriptableObject("DiagramPage", this);
}
6) Add a method to the silverlight control that can be called from javascript (this is the ScriptableMember attribute) to get the serializable content and work with it:
[System.Windows.Browser.ScriptableMember()]
public void RefreshDiagram()
{
// Fetch the hidden input control from the page
var serializedElement = System.Windows.Browser.HtmlPage.Document.GetElementById("txtSerializedData");
// Then fetch its value attribute
var sSerializedData = serializedElement.GetAttribute("value");
// Finally, do something with sSerializedData
}
7) Finally, add the javascript method to the page that is fired when the silverlight control is loaded:
<script type="text/javascript">
function GetDiagramPageContent() {
/// <summary>This method retrieves the diagram page object content</summary>
// Exceptions are handled by the caller
var oObject = document.getElementById('objDiagram');
if (oObject) {
return oObject.Content;
} else {
return null;
}
}
function RefreshDiagram() {
try {
var oContent = GetDiagramPageContent();
try {
// If we don't have content or a diagram page, bail
if ((oContent == null) || (oContent.DiagramPage == null)) {
return;
}
} catch (ex) {
return;
}
// Now ask the control to refresh the diagram
oContent.DiagramPage.RefreshDiagram();
} catch (ex) {
alert('Javascript Error (RefreshDiagram)\r' + ex.message);
}
}
</script>
Scenario 2
Scenario 2 is very similar to scenario 1, with the following changes:
1) Do not include the onLoad param in the silverlight object. Instead, call the RefreshDiagram javascript method from the client-side button click.
2) Do not show or hide the containing div in code-behind. Instead, use the style attributes to control the visibility:
<div id="divDiagram" runat="server" style="visibility: hidden; visibility: visible">
and in the javascript button click event:
var oDiv = document.getElementById("divDiagram");
oDiv.style.visibility = "";
oDiv.style.display = "";
3) Load the hidden text box in pageload instead of on the server-side button click.
This might help
Passing Objects between ASP.NET ans Silverlight Controls
best regards
You can use this thing called SilverlightSerializer by Mike Talbot

Acess controls on ContentPages via Javascript by MasterPage

I need my MasterPage to be able to get ControlIDs of Controls on ContentPages, but I cannot
use <%= xxx.CLIENTID%> as it would return an error as the control(s) might not be loaded by the contentplaceholder.
Some controls have a so called BehaviourID, which is exactly what I would need as they can be directly accessed with the ID:
[Asp.net does always create unique IDs, thus modifies the ID I entered]
Unfortunately I need to access
e.g. ASP.NET Control with BehaviouraID="test"
....
document.getElementById("test")
if I were to use e.g. Label control with ID="asd"
....
document.getElementById('<%= asd.ClientID%>')
But if the Labelcontrol isn't present on the contentpage, I of course get an error on my masterpage.
I need a solution based on javascript. (server-side)
Thx :-)
You could use jQuery and access the controls via another attribute other than the ID of the control. e.g.
<asp:Label id="Label1" runat="server" bid="test" />
$('span[bid=test]')
The jQuery selector, will select the span tag with bid="test". (Label renders as span).
Best solution so far:
var HiddenButtonID = '<%= MainContent.FindControl("btnLoadGridview")!=null?
MainContent.FindControl("btnLoadGridview").ClientID:"" %>';
if (HiddenButtonID != "") {
var HiddenButton = document.getElementById(HiddenButtonID);
HiddenButton.click();
}
Where MainContent is the contentplace holder.
By http://forums.asp.net/members/sansan.aspx
You could write an json-object with all the control-ids which are present on the content-page and "register" that object in the global-scope of your page.
Some pseudo pseudo-code, because I can't test it at the moment...
void Page_Load(object sender,EventArgs e) {
System.Text.StringBuilder clientIDs = new System.Text.StringBuilder();
IEnumerator myEnumerator = Controls.GetEnumerator();
while(myEnumerator.MoveNext()) {
Control myControl = (Control) myEnumerator.Current;
clientIDs.AppendFormat("\t\"{0}\" : \"{1}\",\n", myControl.ID, myControl.ClientID);
}
page.ClientScript.RegisterStartupScript(page.GetType(),
"ClientId",
"window.ClientIDs = {" + clientIDs.ToString().Substring(0, clientIDs.ToString().Length - 2) + "};",
true);
}
It sounds like your issue is that you are using the master page for something it wasn't intended. The master page is a control just like any other control, and therefore cannot access any of the controls of its parent (the page). More info:
ASP.Net 2.0 - Master Pages: Tips, Tricks, and Traps
My suggestion is to inject the JavaScript from your page where the controls can actually be resolved. Here is a sample of how this can be done:
#Region " LoadJavaScript "
Private Sub LoadJavaScript()
Dim sb As New StringBuilder
'Build the JavaScript here...
sb.AppendFormat(" ctl = getObjectById('{0});", Me.asd.ClientID)
sb.AppendLine(" ctl.className = 'MyClass';")
'This line adds the javascript to the page including the script tags.
Page.ClientScript.RegisterClientScriptBlock(Me.GetType, "MyName", sb.ToString, True)
'Alternatively, you can add the code directly to the header, but
'you will need to add your own script tags to the StringBuilder before
'running this line. This works even if the header is in a Master Page.
'Page.Header.Controls.Add(New LiteralControl(sb.ToString))
End Sub
#End Region

Accessing user control on child master page from child master page code behind

Trying to set the value of a literal user control on my child master page via the code behind of the same master page.
Here is example of the code I am using:
Global.master
<form id="form1" runat="server">
<div>
<asp:ContentPlaceHolder id="GlobalContentPlaceHolderBody" runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
Template.master (child of Global.master)
<asp:Content ID="TemplateContentBody" ContentPlaceHolderID="GlobalContentPlaceHolderBody" Runat="Server">
<asp:Literal ID="MyLiteral1" runat="Server"></asp:Literal>
<p>This is template sample content!</p>
<asp:ContentPlaceHolder ID="TemplateContentPlaceHolderBody" runat="server">
</asp:ContentPlaceHolder>
Template.master.cs
protected void Page_Load(object sender, EventArgs e)
{
MyLiteral1.Text = "Test";
}
ContentPage.aspx
< asp:Content ID="ContentBody" ContentPlaceHolderID="TemplateContentPlaceHolderBody" Runat="Server">
</asp:Content>
Once I am able to achieve this, I will also need to be able to access content on the global and template master pages via content pages.
If I understand your scenario you want to have your content pages access items from your master pages. If so, you'll need to setup a property to expose them from your master page, and in your content page you can setup a MasterType directive.
Take a look at this post for an example.
Found a working solution. In the template.master (nested child master), I had to put the code in OnLoad event.
protected override void OnLoad(EventArgs e)
{
MyLiteral1.Text= "<p>MyLiteral1 Successfully updated from nested template!</p>";
base.OnLoad(e);
}
Very strange...
Basically, I am using the global master as the page that has code shared on every page, then I will have various nested pages to suit each website section. For the navigation nested template, I want to be able to show if the user is logged in and how many items in shopping cart.
If there is a better way to achieve this, I am open to suggestions.
EDIT: This is my answer when I thought you were trying to access a control on the child master from the parent master code behind.
You can use a recursive findControl function:
protected Control FindControlRecursive(string id, Control parent)
{
// If parent is the control we're looking for, return it
if (string.Compare(parent.ID, id, true) == 0)
return parent;
// Search through children
foreach (Control child in parent.Controls)
{
Control match = FindControlRecursive(id, child);
if (match != null)
return match;
}
// If we reach here then no control with id was found
return null;
}
Then use this code in your master page:
protected void Page_Load(object sender, EventArgs e)
{
//EDIT: if GlobalContentPlaceHolderBody isn't visible here, use this instead:
//Control c = FindControlRecursive("MyLiteral1", Page.FindControl("GlobalContentPlaceHolderBody"));
Control c = FindControlRecursive("MyLiteral1", GlobalContentPlaceHolderBody);
if(c != null)
((Literal)c).Text = "Test";
}

Categories