I am developing an ASP.NET web application at work, and I keep running into the same issue:
Sometimes I want to write HTML to the page from the code-behind. For example, I have a page, Editor.aspx, with output which varies depending on the value of a GET variable, "view." In other words, "Editor.aspx?view=apples" outputs different HTML than "Editor.aspx?view=oranges".
I currently output this HTML with StringBuilder. For example, if I wanted to create a list, I might use the following syntax:
myStringBuilder.AppendLine("<ul id=\"editor-menu\">");
myStringBuilder.AppendLine("<li>List Item 1</li>");
myStringBuilder.AppendLine("</ul>");
The problem is that I would rather use ASP.NET's List control for this purpose, because several lines of StringBuilder syntax hamper my code's readability. However, ASP.NET controls tend to output convoluted HTML, with unique ID's, inline styles, and the occasional block of inline JavaScript.
My question is, is there an ASP.NET control which merely represents a generic HTML tag? In other words, is there a control like the following example:
HTMLTagControl list = new HTMLTagControl("ul");
list.ID = "editor-menu";
HTMLTagControl listItem = new HTMLTagControl("li");
listItem.Text = "List Item 1";
list.AppendChild(listItem);
I know there are wrappers and the such, but instead of taming ASP.NET complexity, I would rather start with simplicity from the get-go.
is there an ASP.NET control which
merely represents a generic HTML tag?
Yes, it's called the HtmlGenericControl :)
As far as exactly what you want no, but you can get around it easily:
HtmlGenericControl list = new HtmlGenericControl("ul");
list.ID = "editor-menu";
HtmlGenericControl listItem = new HtmlGenericControl("li");
listItem.InnerText = "List Item 1";
list.Controls.Add(listItem);
If you really need to get down to bare metal then you should use the HtmlTextWriter class instead of a StringBuilder as it is more custom tailored to pumping out raw HTML.
If you want to just assign the results of your stringbuilder to a blank control, you can use an <asp:Literal /> control for this.
LiteralControl has constractor that you can pass your html...
i think it is better.
new LiteralControl(sb.ToString());
Related
I am trying to come up with a neat solution to create automated json schema markup on my aspx pages. The markup in question is FAQPage, but that's irrelevant.
I decided that I needed to scrape the content of the current page to find questions and answers. After a few false starts I came across the HtmlAgilityPack plugin which enables me to achieve what I want, but I've come across some issues.
The HtmlAgililtyPack parser can be initiated in a number of ways, but the only one I could get to work for me and my scenario (scrape current page) was to feed in a string.
First, I created an asp ID with a runat="server" tag.
To get the string, I used HTMLTextWriter; here's the code:
static string ConvertControlToString(Control ctl)
{
string s = null;
var sw = new StringWriter();
using (var w = new HtmlTextWriter(sw))
{
ctl.RenderControl(w);
s = sw.ToString();
}
return s;
}
Now, all that works fine - in most cases.
However, I'm running into edge cases where I use scriptmanager and updatepanels. I suspect there will be more. The error is: ... must be inside a form control with a runat="server". Of course it is but the rendercontrol doesn't realise it.
So, two questions:
Is there a way to feed HtmlAgilityPack parser in another way that doesn't
require a string (and that won't loop)?
Is there a better way to scrape the text other than Control.RenderControl() that won't cause errors?
Incidentally, I've found a solution to the problem I'm having but it involves manipulating each affected page, and that's not great.
So, thought I'd throw it out there and see if there are better workarounds or a better solution.
You can load HTML in a few different ways but ultimately HTML is a string so this is what the parser will operate on. I'm not sure what you mean about looping.
Rather than rendering controls as HTML and then parsing them it might be better to let the entire page load and parse it after it has rendered, this allows your javascript/updatepanels to finish transforming the page before you parse the HTML.
The LoadFromBrowser method (I believe) loads the specified url in a headless browser, allows any javascript to run and then parses the resulting HTML: https://html-agility-pack.net/from-browser
If you need to attach authentication credentials there is a question addressing that here: HtmlAgilityPack and Authentication
Alternatively (keeping your existing code) you might try instantiating a new HtmlControl with the tag "form", adding the the control passed in to ConvertControlToString to it and then parsing that which may avoid your error. You may need to check the control doesn't already have a form tag, this approach doesn't address javascript/update panels and I'm not 100% sure it would work.
HtmlGenericControl form = new HtmlGenericControl("form");
Control ctl = new Control();
form.Controls.Add(ctl);
string s = string.Empty;
var sw = new System.IO.StringWriter();
using (var w = new HtmlTextWriter(sw))
{
form.RenderControl(w);
s = sw.ToString();
}
This seems really simple, but for some reason Im stumped..
Im dynamically generating an HTML Select Box, lets call it myselect. Im creating this select box based on some database values Im generating an HTML Select Box.. almost like a string that Im just spitting out to the page. So it's never a control in the codebehind, just part of a string thats rendered as HTML by the browser. If I submit my form, and in my codebehind I perform:
Dim myVal as String = Request.Form("myselect")
That code will give me the VALUE of the myselect select box. How can I refer to this control to cast it as a System.Web.UI.HtmlControls.HtmlSelect control? Request.Form seems to give me the value, but I want to reference the object itself..
If this select box isn't declared statically on your page (that is, you're adding it to the Control Collection), you'll have to do something like this: Dim MySelect as HtmlSelect = Page.FindControl("MySelect") as HtmlSelect
You'll have to forgive me if my casting syntax is a bit off -- I'm used to casting in C#.
If your dynamically generating the control in your code file, then it will not be available to you on post back. As long as you generate it again before the viewstate is processed (you can do it in your oninit), then you can access it as you would anything else.
MySelect.SelectedValue
In response to the comments above (thanks for your help), I found what Gabriel McAdams and jwiscarson had to say were true. In browsing the Request object, I found that its nothing more than a collection of key\value pairs. Performing Request.Form("myformobj") will return a value, because thats all thats available to the application. If necessary, I suppose I can whip up some nifty javascript to track form object types, but it's certainly not necessary in my case.
Thanks for the help
What are the advantages/disadvantages of using this model
<asp:GridView Id="grdEmployees" runat="server" DataSourceID="objEmployees">
...
</asp:GridView>
in comparison with programmatically creating controls and binding data to them, etc...?
In which cases should one use each method?
There's no right or wrong here - it depends on what you're trying to achieve.
You can safely use static controls when you know your layout is not subject to change (i.e. a grid with a fixed number of columns), or any future possible changes are likely to be of static nature (a new column in your grid). This approach is perfectly fine and obviously faster in the average real world case.
If you need flexibility then you need to generate the controls programmatically (i.e. you need to generate a variable number of grids - or any other controls). For flexibility we mean that your layout needs to take into account variable values you won't be able to know till runtime.
I find uses for both models, but tend to try to use the markup in the aspx pages where I can because it is easier to read and helps me separate my view code from my logic code. The places where I programatically create controls and bind data to them are when I need a dynamic number of controls. A good example might be when you are generating a set of drop-downs dynamically for user search criteria -- I would do something like this:
SqlDataReader dr;
// Set up database connection and set dr to search query.
while(dr.Read())
{
Literal name = new Literal();
name.Text = dr["Name"] + ": ";
Page.Controls.Add(name);
DropDownList ddl = new DropDownList();
ddl.ID = "Search_" + dr["ID"];
SqlDataReader dr2;
// Set up database connection and set dr2 to search items query.
while(dr2.Read())
{
ListItem li = new ListItem(dr2["Name"], dr2["Value"]);
ddl.Item.Add(li);
}
Page.Controls.Add(ddl);
}
One other thing to keep in mind is you can create markup controls in your aspx page and then bind them to custom DataSets that you populate in your code-behind.
UI state should only be affected the business process it's representing. You should therefore aspire to have your UI automatically update to changes in the business model. If you're programming the UI manually then you're more likely to have cases where the state of the business model is not accurately reflected. Programming the UI declaratively removes most of this concern. Where possible, use the declarative method.
On one of our pages, we've got a table that has rows generated at runtime. I want to move the content of those rows out into their own control and set properties on it at runtime, in order to separate the presentation from the code-behind. But it's not working the way I'm expecting.
If I have my row-like control inherit from UserControl, then I can do this to create the control at runtime:
MyControl row = (MyControl)LoadControl("~/controls/MyControl.ascx");
Then, I wanted to add that to my table:
MyTable.Rows.Add(row);
But row does not inherit from System.Web.UI.HtmlControls.HtmlTableRow or System.Web.UI.WebControls.TableRow, so you can't do that. However, if I make my control inherit from one of those two, then the LoadControl call above complains that the control doesn't inherit from UserControl.
What's the happy solution here? If additional information is needed, please ask.
I think what you're looking to do is this:
MyControl row = (MyControl)LoadControl("~/controls/MyControl.ascx");
TableCell newCell = new TableCell();
newCell.Controls.Add(row);
TableRow newRow = new TableRow();
newRow.Cells.Add(newCell);
MyTable.Rows.Add(newRow);
The problem as you stated is that your control cannot inherit from multiple base classes and you cannot add your control directly to the Rows collection. You need to first wrap it. There may be cleaner solutions, but this one does work.
I include a JS file in a user control. The host page has multiple instances of the user control.
The JS file has a global variable that is used as a flag for a JS function. I need the scope of this variable be restricted to the user control. Unfortunately, when I have multiple instances of the control, the variable value is overwritten.
What's the recommended approach in a situation like this?
Some options are to dynamically generate the javascript based on the ClientId of the User Control. You could dynamically generate the global variable for example.
Another option and one I would recommend is to encapsulate the global variable and function within an object, then your user control can emit the JS to create an instance of that object (Which can be dynamically named thus letting you scope the object as you see fit).
Edit
I don't have a working code sample that I can share but, I have done this in a couple different ways. the easiest method is to do this in the markup of your user control.
<script language='javascript'>
var <%=this.ClientID%>myObject=new myObject();
</script>
Assuming your control has a clientId of myControl this will create a variable myControlmyObject.
Another way to do this would be to generate the script in the code behind you could register it using: Page.ClientScript.RegisterStartupScript().
I would recommend refactoring your code such that all the common JS logic is stored in one place, not in every UserControl. This will reduce the size of your page by a good margin.
You can pass in the id of the UserControl to the common JS method(s) to differentiate between the UserControls.
For the issue of limiting the scope of your 'UserControl' variable, you could store some sort of a Key/Value structure to keep your UserControl-specific value - the Key would be the UserControl clientID, and the value would be the variable that you're interested in.
For example:
var UCFlags = new Object();
//set the flag for UserControl1:
UCFlags["UC1"] = true;
//set the flag for UserControl2:
UCFlags["UC2"] = false;
To access them, you simply pass the ClientID of the UserControl in to the UCFlags array:
myFlag = UCFlags["UC1"];
On the server-side, you can replace the constant strings "UC1" or "UC2" with
<%= this.ClientID %>
like this:
myFlag = UCFlags["<%= this.ClientID %>"];
You can still use the <%= this.ClientID %> syntax here even though the bulk of the JS is in a separate file; simply set
UCFlags["<%= this.ClientID %>"] = value;
before the call to embed the JS file.
Well, if you have to keep with the current solution, you could rename your global variable to something like the following code, which should be in the .ascx file for your control:
<script type='text/javascript'>
var <%= this.ClientID %>_name_of_global_variable;
</script>
Where "this" is the asp.net control. That way, each control has a unique variable name, based off the client id. Make sure you update the rest of your javascript to use this new naming convention. The problem, it looks messy, and the variable names will become very long depending on where the control is embedded in the page.
Does that make sense? It should take minimal javascript modification to get it working.
I ran into same issue and below blog post solved it. Solution is to take Object oriented way for javaScript
Adding multiple .NET User Controls that use JavaScript to the same page