Microsoft does not support TreeView Control inside AJAX:UpdatePanel. So there are lots of issues if one need to do the same. One of my teams needed to use the TreeView inside the UpdatePanel. The core of the application is around some hierarchical documentation. As the user interaction with the system mostly through this hierarchical data, TreeView with check boxes is the most obvious selection. However, the pain began as the development progressed and changes in requirements started pouring in. One option was for clientside population of treeview. But some dynamic changes happening to the underlying hierarchical data (by concurrent users), we needed to repopulate the tree from the database. With growing data volume, clientside population had to be abandoned. But the TreeView started giving endless sitting with the code as it started behaving weirdly at most of the times.
Here are some of the key issues and resolutions of some of them. Some are yet to be resolved, however.
1. The SelectedNode property is lost in successive postbacks. The scenario is easy. Click any TreeNode. In the coddebehind, you get the TreeView.SelectedNode. But on any successive postback where the postback is caused by anything other than a TreeNode click, the SelectedNode property is NULL.
Resolution: Use your own ViewState to manage. Following is the code segment:
#region ViewState handling for selected node
//Added by Kangkan - on June 04 2009
// As the selected Node is lost on second or more postback
// if the treeview is inside the update panel.
protected override object SaveViewState()
{
if (TViewDeviceHeirarchy.SelectedNode != null)
{
ViewState["SelectedNodePath"] = TViewDeviceHeirarchy.SelectedNode.ValuePath;
}
return base.SaveViewState();
}
protected void Page_PreLoad(object sender, EventArgs e)
{
GetSelectedNodeFromViewState();
}
private void GetSelectedNodeFromViewState()
{
if (ViewState["SelectedNodePath"] != null)
{
TreeNode node = TViewDeviceHeirarchy.FindNode(ViewState["SelectedNodePath"].ToString());
if (node != null)
node.Select();
else
ViewState["SelectedNodePath"] = null;
}
}
public void ClearSelectedNodeViewState()
{
ViewState.Remove("SelectedNodePath");
if (TViewDeviceHeirarchy.SelectedNode != null)
TViewDeviceHeirarchy.SelectedNode.Selected = false;
}
#endregion ViewState handling for selected node
2. The CheckedNodes collection is null in successive postbacks. The scenario is easy. Check any checkbox associated with a TreeNode. In the coddebehind, you get the TreeView.CheckedNodes collection. But on any successive postback where there is no change in the checkedNodes, the CheckedNodes collection returns 0 count. And in this point of time, though the TreeNodes are checked at the client (browser), a FindNode at times fail to locate the node and if it locates, the TreeNode.Checked property is also at times false. It was a nightmare to continue with such a piece of code. Thought of using the same method as that used for persisting the SelectedNode. There are issues with that. So we relied upon Session variable. Here is the code segment for the same:
#region CheckNodes handling
//To handle loss of checked Nodes on repeatitive postbacks
// SetCheckedNodesToSession sets the nodes'ValuePath to session
// GetCheckedNodesFromSession gets the TreeNode Collection from the session
//The session variable Session["CheckedNode"] is used.
//The same need to be cleared (set to null) once the usage is over.
//-----------------------------------------------------------------
/// <summary>
/// SetCheckedNodesToSession: Sets the CheckedNodes' ValuePath to a Session Object.
/// ValuePath of each CheckedNode is concatenated to a string and the string is saved.
/// Session["CheckedNode"] is used for saving the ValuePath string.
/// </summary>
private void SetCheckedNodesToSession()
{
if (TViewDeviceHeirarchy.CheckedNodes.Count > 0)
{
StringBuilder checks = new StringBuilder();
for (int i = 0; i < TViewDeviceHeirarchy.CheckedNodes.Count; i++)
{
checks.Append(TViewDeviceHeirarchy.CheckedNodes[i].ValuePath + "|");
}
Session["CheckedNode"] = checks.ToString().Remove(checks.ToString().Length - 1, 1);
}
}
/// <summary>
/// Return TreeNodeCollection from the Session
/// Session["CheckedNode"] is used for saving the ValuePath string.
/// </summary>
/// <returns>TreeNodeCollection as saved into the session.</returns>
private TreeNodeCollection GetCheckedNodesFromSession()
{
TreeNodeCollection ColNodes = new TreeNodeCollection();
if (Session["CheckedNode"] != null)
{
string checks = Session["CheckedNode"].ToString();
string[] CheckArray = checks.Split(new char[] { '|' });
TreeNode node;
foreach (string s in CheckArray)
{
node = TViewDeviceHeirarchy.FindNode(s);
if (node != null)
{
ColNodes.Add(node);
//node.Checked = true;
}
}
}
return ColNodes;
}
#endregion CheckNodes handling
[/code]
3. SelectedNodeChanged event does not fire! The usage scenario include change of one node from one parent to another parent node. So what happens is that once we complete the task, we clear the nodes of the tree [TreeView.Nodes.clear()] and rebind the tree. We bind the tree to the first level only and set the nodes to PopulateOnDemand=true for the nodes that have child nodes. At this moment, if we again click on any tree node, at times it does not fire any postback. Rather the element just vanishes(?) from the treeview. If I reload the page, we can see the element at it works fine. To get rid of this issue, I use to reload the page at the end of certain usage scenario. But the pain is how to let the user know the satus of what happened at the end of the usage? Again the use of Session variable. What we have done is shown below:
At the end of any such usage scenario, put the message to the user in a session variable and reload the page:
[code:c#]
public void SetReloadStatus(string message, bool IsError)
{
Session["ReloadMessage"] = message;
Session["ReloadStatus"] = IsError.ToString();
}
[/code]
At the load of the page, check for the existence of any session variable for pending tasks and complete accordingly:
public void ShowReloadStatus()
{
if (Session["ReloadStatus"] != null)
{
bool boolVal = Convert.ToBoolean(Session["ReloadStatus"].ToString());
if (boolVal)
Infobar1.error = Session["ReloadMessage"].ToString();
else
Infobar1.info = Session["ReloadMessage"].ToString();
Session["ReloadStatus"] = null;
Session["CheckedNode"] = null;
}
}
[/code]
I am aware that the methods applied are not good, rather I shall say not at all meaningful from programming point of view. But till Microsoft does not provide better solutions or I migrate to some other wiser control that works properly, these are some work arounds that we are sticking to. I shall request you to provide your valuable feedback and further suggestions on this.
Problem: In ASP.NET tree view, user wish to select one or many treenodes and wish to have options for further working depending upon the selection. The selection is done using a checkbox. On checking, the server side code need to know what has been checked and provides further options for the same. The checkbox check/uncheck does not fire a postback. Though we tried to imitate the same using javascript, the page gets refreshed.
Findings:
Microsoft does not support this. An issue/request for the feature was put on Microsoft feedback site and the same has been rejected. The status of the request is “Closed (Won’t fix)”. For details see [http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=103865]
I tried following options:
1. Add a ajvascript to do a postback: I added a onClick script to the tree as follows:
function TreeClicked()
{
var curevent = event;
if(curevent.srcElement.type=="checkbox")
{
//__doPostBack('','');
//__doPostBack('mytree','');
//__doPostBack('LinkButton1','');
}
}
It works in normal scenario. But it refreshes/reloads the page all the time. I needed a flicker free refresh using AJAX. But could not get it. This can be used if you have no issue with the page reload.
2. Derve your own treeview: I derived the treeview and created my own treeview control where I added the postback event and override the render method. This behaves very weird in Visual Studio 2005. However I got partial success in Visual Studio 2008.
The cod e for deriving is as follows:
using System;
using System.Text;
using System.Data;
using ASP = System.Web.UI.WebControls;
using System.Web.UI;
using System.IO;
namespace MyTreeView
{
[ToolboxData("<{0}:MyTreeView runat=server></{0}:MyTreeView>")]
public class MyTreeView : ASP.TreeView, IPostBackEventHandler
{
public event EventHandler CheckClick;
protected override void Render(HtmlTextWriter writer)
{
StringBuilder builder = new StringBuilder();
using(StringWriter stringWriter = new StringWriter(builder))
{
HtmlTextWriter tempWriter = new HtmlTextWriter(stringWriter);
base.Render(tempWriter);
}
string find = "<input type=\"checkbox\"";
string replace = "<input type=\"checkbox\" onclick=" + getPostBack() + " \"";
writer.Write(builder.ToString().Replace(find, replace));
}
protected override void RenderChildren(HtmlTextWriter writer)
{
StringBuilder builder = new StringBuilder();
using (StringWriter stringWriter = new StringWriter(builder))
{
HtmlTextWriter tempWriter = new HtmlTextWriter(stringWriter);
base.RenderChildren(tempWriter);
}
string find = "<input type=\"checkbox\"";
string replace = "<input type=\"checkbox\" onclick=" + getPostBack() + " \"";
writer.Write(builder.ToString().Replace(find, replace));
}
protected string getPostBack()
{
return this.Page.ClientScript.GetPostBackEventReference(this, "@CheckPostBack");
}
protected virtual void OnCheckClick(EventArgs e)
{
if (CheckClick != null) CheckClick(this, e);
}
void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)
{
OnCheckClick(new EventArgs());
}
}
}
Work around: I thought of a work around. The user needs the checkbox only when the user needs to select more than one element. So, the user will select the element (while selecting a single one) by clicking the tree node and NOT BY CHECKING the checkbox. The options for single element selection will be provided on selection of the element by clicking the tree node. The options now being provided on multiple selection will be available all the time. If the user selects any such option where one or more check box need to be checked, the system will give a message to the user.
Resolution: Looking at the findings, I thought of taking up the work around as of now.
Another point to share. Of course I delayed the publishing.
While I was migrating the YAFNET forum from my machine (WinXP with SQL Server 2005 Express), to a machine with Win2K3 and SQL Server 2000, I faced another issue. The first issue was of migrating SQL Server 2005 Express database to SQL Server 2000 database. The next issue is of conflict of versions of .NET in IIS. The target machine was hosting few web applications developed on .NET 1.1 and was running fine. I created a site for the YAFNET application and in the properties, set the .NET version as 2.0. So far so good. The site started up with ease, no problem reported and started running.
Next day I saw a weird issue. The site reported that the application have failed. Soon I realised that there is an issue with the versions. I created a new application pool on the IIS and moved the YAFNET site from the existing pool to the new one. The site came back to life and running!
Beyond general concept of upgrading, I faced a challenge of downgrade. I needed to migrate a database in SQL Server 2005 Express edition to SQL Server 2000 standard edition. I thought of how to do the same. Time permitted was just few hours.
I scratched on my head and thought of possible ways:
1. Detach from 2005 and attach in SQL 2000: Can not be done. It is not allowed, even if you set the compatibility level of the database to SQL server 2000. This just sets the way how sql queries are parsed and processed.
2. Import from SQL Server 2000 Enterprise Manager: Not possible, the Enterprise Manager does not connectthe SQL Server 2005 Express edition instance.
3. Connect to SQL Server 2000 from SQL Server management Studio: But there is no option for export.
4. I tried creating script. It creates the database, not the data.
So, started a google search. Got a good link for Moving SQL Server 2005 Express databases to SQL Server 2000 and read about using Microsoft SQL Server Database Publishing Wizard. I used the steps in the post:
- Ran the Database Publishing Wizard against my SQL Server 2005 Express database.
- Created a SQL Server 2000-compliant SQL script that contained all
the SQL statements required to create the database. The SQL script also
created all the INSERT statements required to populate the tables in
the database.
- Created a new blank database in SQL Server 2000.
- Ran the SQL script from step 2 against the SQL Server 2000 database - using the Query Analyser.
But there were few issues. I tried running the script from the Enterprise Manager on the SQL Server 2000 instance. The Query Analyser did not allow me to open the script file, saying it has long lines. So I got worried. Then finally I could run it from SQL Server management Studio from which I connected to the 2000 server.
Happy downgrade!
The following table highlights the main state management
issues:
|
State Management Issues
|
Implications
|
|
Stateful components
|
Holds server resources and can cause server affinity,
which reduces scalability options.
|
|
Use of an in-memory state store
|
Limits scalability due to server affinity.
|
|
Storing state in the database or server when the client
is a better choice
|
Increased server resource utilization; limited server
scalability.
|
|
Storing state on the server when a database is a better
choice
|
In-process and local state stored on the Web server
limits the ability of the Web application to run in a Web farm. Large amounts of
state maintained in memory also create memory pressure on the server.
|
|
Storing more state than you need
|
Increased server resource utilization, and increased time
for state storage and retrieval.
|
|
Prolonged sessions
|
Inappropriate timeout values result in sessions consuming
and holding server resources for longer than necessary.
|
Some of the best practices adopted for effective state
management are as under:
Minimize Session
Data
Keep the amount of session data
stored for a specific user to a minimum to reduce the storage and retrieval
performance overheads. The total size of session data for the targeted load of
concurrent users may result in increased memory pressure when the session state
is stored on the server, or increased network congestion if the data is held in
a remote store.
If you use session state, there are
two situations you should avoid:
●
Avoid storing any shared
resources. These are required by multiple requests and may result in
contention because the resource is not released until the session times out.
●
Avoid storing large collections and
objects in session stores. Consider caching them if they are required
by multiple clients.
Free Session
Resources As Soon As Possible
Sessions continue
to hold server resources until the data is explicitly cleaned up or until the
session times out.
You can follow a two-pronged
strategy to minimize this overhead. At design time, you should ensure that the
session state is released as soon as possible. For example, in a Web
application, you may temporarily store a dataset in a session variable so that
the data is available across pages. This data should be removed as soon as
possible to reduce load. One way to achieve this is to release all session
variables containing objects as soon as the user clicks on a menu item.
Avoid Accessing
Session Variables from Business Logic
Accessing session
variables from business logic makes sense only when the business logic is
interspersed along with presentation code as a result of tight coupling.
However, in the majority of cases,
you benefit from loosely coupled presentation and business logic, partitioned in
separate logical layers. This provides better maintainability and improved
scalability options. It is most frequently user interface-related state that
needs to be persisted across calls. Therefore, session-related state should be
part of the presentation layer. In this way, if the workflows of the user
interface change, it is only the presentation layer code that is affected.
Do You Know the
Number of Concurrent Sessions and Average Session Data per User?
Knowing the number
of concurrent sessions and the average session data per user enables you to
decide the session store. If the total amount of session data accounts for a
significant portion of the memory allocated for the ASP.NET worker process, you
should consider an out-of-process store.
Using an out-of-process state store
increases network round trips and serialization costs, so this needs to be
evaluated. Storing many custom objects in session state or storing a lot of
small values increases overhead. Consider combining the values in a type before
adding them to the session store.
HTTP compression
- is a simple way to improve site performance
and decrease bandwidth, with no configuration required on the client
side.
- is a capability built into both web servers and web browsers, to make better use of available bandwidth.
- HTTP protocol data is compressed before it is sent from the server.
How HTTP Compression Works
When
IIS receives a request, it checks whether the browser that sent the
request is compression-enabled. (Recent versions of Microsoft® Internet
Explorer and most other browsers typically send the following header if
they are compression-enabled: Accept-Encoding: gzip, deflate.) IIS then
determines whether the request is for a static file or for dynamic
content.
If the content of the file is static, IIS checks
whether the file has previously been requested and is already stored in
a compressed format in the temporary compression directory. If a
compressed version of the requested file is not found, IIS sends an
uncompressed version of the requested file to the client browser while
a background thread compresses the requested file. The newly compressed
file is then stored in the compression directory, and subsequent
requests for that file are serviced directly from the compression
directory. In other words, an uncompressed version of the file is
returned to the client unless a compressed version of the file already
exists in the compression directory.
If the file contains
dynamic content, IIS compresses the response as it is generated and
sends the compressed response to the browser. No copy of the file is
cached by the Web server.
The performance cost of compressing a
static file is modest and is typically incurred only once, because the
file is then stored in the temporary compression directory. The cost of
compressing dynamically generated files is somewhat higher because the
files are not cached and must be regenerated with each request. The
cost of expanding the file at the browser is minimal. Compressed files
download faster, which makes them particularly beneficial to the
performance of any browser that uses a network connection with
restricted bandwidth (a modem connection, for example).
If your
Web sites use large amounts of bandwidth or if you want to use
bandwidth more effectively, consider enabling HTTP compression, which
provides faster transmission times between IIS and compression-enabled
browsers regardless of whether your content is served from local
storage or a UNC resource. If your network bandwidth is restricted,
HTTP compression can be beneficial unless your processor usage is
already very high.
IIS provides the following compression options:
• Static files only.
• Dynamic application responses only.
• Both static files and dynamic application responses.
Dynamic
processing can affect CPU resources because IIS does not cache
compressed versions of dynamic output. If compression is enabled for
dynamic responses and IIS receives a request for a file that contains
dynamic content, the response that IIS sends is compressed every time
it is requested. Because dynamic compression consumes considerable CPU
time and memory resources, use it only on servers that have slow
network connections but CPU time to spare.
Compressed static responses can be cached and therefore do not affect CPU resources like dynamic responses do.
Compressing application response files is usually called dynamic compression.
Using HTTP Compression for Faster Downloads (IIS 6.0)
http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/25d2170b-09c0-45fd-8da4-898cf9a7d568.mspx?mfr=true
Enabling HTTP Compression (IIS 6.0)
http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/502ef631-3695-4616-b268-cbe7cf1351ce.mspx?mfr=true
Customizing the File Types IIS Compresses (IIS 6.0)
http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/5bce429d-c4a7-4f9e-a619-5972497b932a.mspx?mfr=true
http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/d52ff289-94d3-4085-bc4e-24eb4f312e0e.mspx?mfr=true
Speed Web delivery with HTTP compression
http://www.ibm.com/developerworks/web/library/wa-httpcomp/
Problem – While reading a CSV file using Microsoft Text Driver,
It interprets CSV file columns in it’s own way like if one the column
contains data specific to a date, then it only keep data which is in
proper format otherwise it put null against that.
Solution
– Create a schema.ini file which defines the structure of a CSV file in
turn gets interpreted automatically by driver itself in a desired
format.
Details -
Schema.ini files provide schema information about the records in a text file.
If
you create a DSN, the schema.ini file gets created automatically in the
folder where all your CSV files reside. But if you use connection
string, you have to create schema.ini file on your own.
When
the Text driver is used, the format of the text file is determined by
using a schema information file. The schema information file, which is
always named schema.ini and always kept in the same directory as the
text data source, provides the IISAM with information about the general
format of the file
Each Schema.ini entry specifies one of five characteristics of the table:
· The text file name
· The file format
· The field names, widths, and types
· The character set
· Special data type conversions
Example of a schema.ini file
Code:
[data.txt]
ColNameHeader=FALSE
Col1=Name Char Width 255
Col2=Company Char Width 255
Below are the links for detail information on this topic
http://msdn.microsoft.com/en-us/library/ms709353.aspx
http://www.codeproject.com/KB/database/FinalCSVReader.aspx
Code:
[data.txt]
ColNameHeader=FALSE
Col1=Name Char Width 255
Col2=Company Char Width 255
Below are the links for detail information on this topic
http://msdn.microsoft.com/en-us/library/ms709353.aspx
http://www.codeproject.com/KB/database/FinalCSVReader.aspx
While working on a web application, one of my team presented some weird behaviour on ASP.NET
FIrst observation: Session times out randomly while working on the application.
So we started observing the application closely to see what might be causing this. We came out with some more observations:
Detailed observation: Session times out when any of the user participates in certain scenario. Also, not only the specific session expires, but all the use sessions are expired. Further study shown that actually the app domain itself recycles.
We studied the activities in these specific scenario. We found that a certain implementation use to create folder, put some temporrary files inside it and once done, the folder and the files were deleted. The application recycles whenever the folder is deleted.
The technical reason: ASP.NET runs a File Monitor (FCN) that observes any change to the structure of the Virtual Directory. In case of any change the application is recycled.
Some forums said that tghe app_data folder within the application folder is immune to the condition. But upon testing, we found it otherwise.
Analysis: Lots of analysis:
Also we were observing session expiration initially. All session expires on application recycle unless the state is not in-process. So moving state in-proc to out-proc is a solution.
We found some other approaches as well.
Resolution: There are multiple solutions that can be taken up for this:
Compiled Solutions / workarounds so for
Sol 1: Use out of process session state.
Sol 2: Use Directory Junction between seperate Web folder and content folders. A directory Junction is a pointer to an external folder (outside the application folder/sub folder.
See http://blogs.msdn.com/toddca/archive/2005/12/01/499144.aspx for more details
Sol 3: Disable FCNotifications in ASP.NET2.0 by adding DWORD FCNMode =1 under HKLM\Software\Microsoft\ASP.NET key
Registry information
loadTOCNode(3, 'resolution'); To enable this hotfix, you must add the following DWORD value at the following registry key:
HKLM\Software\Microsoft\ASP.NET\FCNMode
The following table lists possible values for the FCNMode DWORD value and the behavior that is associated with each value.
Value Behavior
Does not exist This is the default behavior. For each subdirectory, the application will create an object that will monitor the subdirectory.
0 or greater than 2 This is the default behavior. For each subdirectory, the application will create an object that will monitor the subdirectory.
1 The application will disable File Change Notifications (FCNs). Smile
2 The application will create one object to monitor the main directory. The application will use this object to monitor each subdirectory.
More info at: http://support.microsoft.com/kb/911272/en-us
Sol 4: Do not delete any folder.
Most of the time solution 4 seems to be easy way out till MS releases fix for this problem. So we are following solution no 4 for the time being.
Further reading:
http://www.eggheadcafe.com/software/aspnet/32318159/modifying-application-fol.aspx
http://blogs.msdn.com/toddca/archive/2005/12/01/499144.aspx
http://forums.asp.net/p/966593/1209642.aspx
http://weblogs.asp.net/owscott/archive/2006/02/21/438678.aspx
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=240686
http://connect.microsoft.com/VisualStudio/feedback/Workaround.aspx?FeedbackID=240686