I have a site that features some pages which do not require any post-back functionality. They simply display static HTML and don't even have any associated code. However, since the Master Page has a <form runat="server"> tag which wraps all ContentPlaceHolders, the resulting HTML always contains the ViewState field, i.e:
<input
type="hidden"
id="__VIEWSTATE"
value="/wEPDwUKMjEwNDQyMTMxM2Rk0XhpfvawD3g+fsmZqmeRoPnb9kI="
/>
EDIT: I tried both variants of setting EnableViewState on page level with no luck at all:
<%# Page Language="C#" EnableViewState="false" %>
<%# Page Language="C#" EnableViewState="true" %>
I realize, that when decrypted, this value of the input field corresponds to the <form> tag which I cannot remove because it is on my master page. However, I would still like to remove the ViewState field for pages that only display static HTML. Is it possible?
You could override Render and strip it out with a Regex.
Sample as requested. (NB: Overhead of doing this would almost certainly be greater than any possible benefit though!)
[edit: this function was also useful for stripping all hidden input boxes for using the HTML output as a word doc by changing the MIMEType and file extension]
protected override void Render(HtmlTextWriter output)
{
StringWriter stringWriter = new StringWriter();
HtmlTextWriter textWriter = new HtmlTextWriter(stringWriter);
base.Render(textWriter);
textWriter.Close();
string strOutput = stringWriter.GetStringBuilder().ToString();
strOutput = Regex.Replace(strOutput, "<input[^>]*id=\"__VIEWSTATE\"[^>]*>", "", RegexOptions.Singleline);
output.Write(strOutput);
}
Add following methods to the page:
protected override void SavePageStateToPersistenceMedium(object state)
{
//base.SavePageStateToPersistenceMedium(state);
}
protected override object LoadPageStateFromPersistenceMedium()
{
return null; //return base.LoadPageStateFromPersistenceMedium();
}
protected override object SaveViewState()
{
return null;// base.SaveViewState();
}
Result :
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="" />
In the <% #page... directive at the top of the page, add EnableViewState="False". That will prevent the ViewState for that particular page.
The method suggested by Martin must be used very carefully; because it may cause unexpected behaviors in your pages as Martin pointed in parenthesis. I've actually experienced it. But there is another option to remove viewstate content from page safely.
This option gives you the ability to use viewstate without setting false, it also allows you to remove it from your pages. Please check the articles below:
1- http://www.eggheadcafe.com/articles/20040613.asp
2- http://aspalliance.com/72
There is a solution file zipped under the Peter's article [1] you can download. I recommend that you read the second article also referenced by Peter. This is a perfect solution to remove viewstate content from your page while using its capabilities.
There will always be a ViewState. See this related question:
Why does __VIEWSTATE hidden field gets rendered even when I have the EnableViewState set to false
in .net4 you can just remove the runat="server" from the form tag. But you can't use server controls inside the form tag once you remove it.
ViewState is added only if an asp:Form is present in the page. Remove the Form, and the hidden field will not be rendered.
Beware: By doing this, you are also renouncing to have server-side event handlers, or any kind of PostBack events.
Or just use a simple jQuery line to remove the fields, if you're using AJAX-style postback requests...
$(".aspNetHidden").remove();
This removes the DIV encasing the hidden __VIEWSTATE fields.
Related
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
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.
I need to develop a template-like user control, which would accept any arbitrary content, including other controls.
WmlCard.ascx
<asp:PlaceHolder ID="phContent" runat="server">
<card id="<%= Me.CardId %>" title="<%= Me.Title %>">
<p>
<%= Me.InnerText %>
</p>
</card>
</asp:PlaceHolder>
It would be used this way:
ListaTelefonica.aspx
<%# Register TagPrefix="sic" TagName="Card" Src="~/path/to/WmlCard.ascx"%>
<sic:Card ID="BuscaCard" runat="server" CardId="busca" title="Lista TelefĂ´nica" Visible="false">
<asp:Literal ID="SomeLiteral" runat="server" Visible="false">Some text<br /></asp:Literal>
<asp:Literal ID="RuntimeFilledLiteral" runat="server" Visible="false" /><br /></asp:Literal>
Any text.
</sic:Card>
It is unfortunate that Ascx user controls have to inherit System.Web.UI.UserControl. If I could inherit System.Web.UI.WebControls.WebControl, for example, it would be a piece of cake.
My problem is similar to this one, but instead of just text the control should accept any other control inside it.
I tried <ParseChildren(False)> and overriding AddParsedSubObject(Object) in WmlCard, but it is not a solution because the html is rendered before the Page Load, making it pointless to change the value of RuntimeFilledLiteral.Text in a page's Page_Load, for example.
First error I got:
Parser Error Message: Type 'ASP.sic_sicphone_usercontrol_wmlcard_ascx' does not have a public property named 'Literal'.
After adding <PersistenceMode(PersistenceMode.InnerDefaultProperty), ParseChildren(True, "InnerText")>:
The 'InnerText' property of 'sic:Card' does not allow child objects.
Same as above but changing WmlCard's property InnerText type from String to List(Of Object):
Literal content ('Any text.') is not allowed within a 'System.Collections.IList'.
After adding <ParseChildren(False)> and overriding AddParsedSubObject:
No error message, but WmlCard's content is rendered to html before I have a chance to change its inner controls runtime properties.
If I change WmlCard to inherit System.Web.UI.WebControls.PlaceHolder instead of System.Web.UI.UserControl:
'...UserControl.WmlCard' is not allowed here because it does not extend class 'System.Web.UI.UserControl'.
Found a valid way:
1) Put nothing inside my control (WmlCard.ascx).
2) Added the attribute <ParseChildren(False)> to WmlCard.
3) Added a handler to the event PreRender on WmlCard:
Private Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender
Dim litBefore As New Literal()
litBefore.Text = String.Format("<card id=""{0}"" title=""{1}""><p>", Me.CardId, Me.Title)
Dim litAfter As New Literal()
litAfter.Text = "</p></card>"
Me.Controls.AddAt(0, litBefore)
Me.Controls.Add(litAfter)
End Sub
And that's it. Everything is working. Thanks for your help thinking thru this!
What you need is a templated user control.
Take a look at this article for a detailed how-to: http://msdn.microsoft.com/en-us/library/36574bf6.aspx.
Actually you could create a custom user control, inherit from placeholder and your problem should be solved?
You can go the templated control way as suggested but I don't see the reason why you would want to do that.
[SupportsEventValidation, DefaultEvent("YourEventName")]
[ParseChildren(true)]
[PersistChildren(false)]
[ToolboxBitmap(typeof(System.Web.UI.WebControls.Panel))]
public class MyCustomControl : System.Web.UI.WebControls.PlaceHolder, INamingContainer, IPostBackDataHandler, IPostBackEventHandler
you can override the Render method to control the rendered HTML. I am struggling to see youru actual problem.
Got this working simply enough.
[ParseChildren(false)]
public class CssControl : System.Web.UI.UserControl
{
protected override void Render(HtmlTextWriter writer)
{
string html = null;
using(var innerWriter = new System.IO.StringWriter())
using(var htmlWriter = new HtmlTextWriter(innerWriter))
{
base.Render(htmlWriter);
html = innerWriter.GetStringBuilder().ToString();
}
var min = ScriptAndCssParser.MinifyCssFromHtml(html);
writer.Write(min);
}
}
In my case my goal was allow a master page level control to have child HTML and server-controls, like asp:content blocks. That way Viewpages could pass stylesheets up the chain to the master page.
Then I wanted to intercept all the generated HTML, and minify the CSS files. The minification part is out of scope for this question, but the above ParseChildren(false) prevents the "element can't contain..." error in the .aspx, and then calling base.Render() on an inner writer allows you to use the existing Web Forms pipeline to do all the rendering for you of both server-controls and plain HTML content.
I am struggling with something that I guess should be standard practice really. I have a number of user controls that use some JQuery plugins. I do not really want to link to the extra CSS and JS files from my main masterpage as this would cause extra load to the user the first time they hit the site, (admittedly it would only be the once), so I was just putting them links into the top of the user control. Then I looked at my source HTML, not nice! Even worse for controls that repeat multiple times on a page.
So I was thinking is there a way of injecting them into the Head of the page when they are needed from the User Control. For that matter is there a way of doing it to the footer for JS stuff?
To dynamically register a script (and ensure that duplicates are merged) in ASP.NET you can call:
Page.ClientScript.RegisterClientScriptInclude(
"mykey", "~/scripts/jquery-1.3.2.js");
And read the full details on this method on MSDN.
To add CSS dynamically you can do something like this:
HtmlLink cssLink = new HtmlLink();
cssLink.Href = "path to CSS";
cssLink.Attributes["some attr1"] = "some value1";
cssLink.Attributes["some attr2"] = "some value2";
Page.Header.Controls.Add(cssLink);
This example of injecting CSS will not merge duplicate entries. To avoid duplication you'll have to keep track of duplicates yourself. One place you can store a list of scripts you've already registered is in HttpContext.Items. Stick a HashSet in there that keeps a list of all registered scripts so that you don't register the same CSS file twice (which is generally harmless, but something to avoid anyway).
I followed a similar approach, but I use CSS directly in the user control so I don't have to import a CSS file. The following is code entirely from a sample user control:
<style id="style1" type="text/css" visible="false" runat="server">
td { font-family: Tahoma; font-size: 8pt; }
</style>
In code-behind:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
HtmlGenericControl style = new HtmlGenericControl("style");
style.Attributes.Add("type", "text/css");
style.InnerHtml = style1.InnerHtml;
Page.Header.Controls.Add(style);
}
You'll notice that the CSS is rendered in the head tag and not inside the body tag.
You can use ClientScript.RegisterClientScriptInclude() for the JavaScript.
For the CSS, one trick is to include them in your Master page, but with Visible="false", so that they aren't rendered into the markup by default.
Then, in your user controls, set a flag in the Items collection, from an early event, such as OnLoad(). For example, this.Context.Items["mycss"] = true;
Finally, in your Master page, from a later event, such as OnPreRender(), check to see if those flags are set. If they are, then set the Visible property to true for the corresponding CSS.
This also allows you to use the control with Master pages that don't use the CSS, since the Items entries could simply be ignored. If you have many Master pages that need the same behavior, you could put this code in a base class or use nested Master pages.
I assume you're using Asp.NET.
Try putting a content placeholder in the of the MasterPage...
<head>
<asp:ContentPlaceHolder ID="AdditionalPageHeader" />
</head>
If you're working in an aspx file or an ascx you need only define a content control...
<asp:Content ContentPlaceHolderID="AdditionalPageHeader" />
If you're working on a code-behind only type of server control, you can can get a pointer to that content place holder:
this.Page.Master.FindControl("AdditionalPageHeader")
... and manipulate it's contents programatically.
To add stylesheets or javascript (inline or not) dynamical I wrote these three functions:
Public Function addScript(ByVal path2js As String) As System.Web.UI.Control
Dim si As New HtmlGenericControl
si.TagName = "script"
si.Attributes.Add("type", "text/javascript")
si.Attributes.Add("src", path2js)
Return si
End Function
Public Function addScript_inline(ByVal js As String) As System.Web.UI.Control
Dim si As New HtmlGenericControl
si.TagName = "script"
si.Attributes.Add("type", "text/javascript")
si.InnerHtml = js
Return si
End Function
Public Function addStyle(ByVal path2css As String) As System.Web.UI.Control
Dim css As New HtmlLink
css.Href = path2css
css.Attributes.Add("rel", "stylesheet")
css.Attributes.Add("type", "text/css")
css.Attributes.Add("media", "all")
Return css
End Function
I call them in page_load on my masterpage, like this:
Me.Page.Header.Controls.Add(modGlobal.addScript("script/json/json2.js"))
or
Me.Page.Header.Controls.Add(modGlobal.addStyle("style/flexigrid/flexigrid.css"))
Regards
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...