I start & stop Visual Studio 2008 hundreds of times per day as part of the normal debugging cycle. In my case, it's running my ASP.net project in Internet Explorer 8. For some time now, there's been a delay of upwards of 10 seconds when stopping the project, before I could gain control of VS2008 again.
This finally got so annoying that I did some research and found this. I tried every single idea presented in that thread but nothing worked. Finally I got to the last item - May 7, 2010 - which suggested the culprit to the be the solution's "SUO" file. Rather than delete it outright, I instead moved it to another folder. Incidentally, in my case the file was over 11MB in size. I restarted VS2008 and voilĂ ... the delay disappeared!
I don't know all that the SUO file contains but I've suffered no adverse effects from deleting it. In fact, Visual Studio has already started rebuilding it!
Monday, August 2, 2010
Tuesday, July 27, 2010
Expanding the Usefulness of Global.asax
I've long utilized the 'Global.asax' file to run assorted startup methods, both from an Application and Session perspective. One limitation of this file is that you can't introduce "using" statements like you can in any normal C# file. This forces you to prefix every method and class with its full definition. This isn't so bad but I've reached a point where I have more complex code I need to run and have grown tired of all those prefixes. Frankly, I think they also make the code much less readable.
To resolve this issue I found this excellent blog post by Ross Nelson which explains how to modify things. Essentially Global.asax is altered to contain just this one line:
This then lets you create a new 'Global.cs' file in which all of the previous contents of 'Global.asax' are placed.
To resolve this issue I found this excellent blog post by Ross Nelson which explains how to modify things. Essentially Global.asax is altered to contain just this one line:
<%@ Application Language="C#" Inherits="Global" %>
Wednesday, July 14, 2010
Multiple Interchangeable User Controls on One Web Page
I'm building an ASP.Net web page in which there's a need to display any one of about twenty different user controls at any one time. The contents of each user control is varied, with each holding a different assortment of labels, textboxes, dropdown listboxes, etc. Assorted criteria on the page determines which user control is displayed. The image shown on the left shows a trio of examples representing the [blue] web page and different [orange] user controls.
I don't have a lot of experience with ASP.Net user controls but when I have used them there'd typically be 2 or 3 on every page in the project, representing such things as a toolbar, a pulldown menu, a navigation menu, etc. In those cases it was easy enough to just refer to the user control directly, performing a simple cast like this:
But the situation with this page is quite different. With it I have a PlaceHolder control called "placeHolderSubForm" into which one user control at a time will be loaded. Where the difficulty arises is when it's time to cast from a Control object to a specific User Control type. For example, if the first user control shown were loaded into the web page then the cast might look like this:
That works okay if there are one or two user controls to deal with but with 20 of them I immediately realized that the code would get rather unwieldy, with a whole lot of switch-case statements necessary to determine which User Control type to cast to. I wanted something much more generic!
At the end of the day the interaction between the web page and each user control is fairly basic, consisting of:
In this way, within each user control I can add newly entered data to the general purpose SortedList container 'NewData' via a key & value pair. Thus, one user control might retrieve the following data from a user:
I don't have a lot of experience with ASP.Net user controls but when I have used them there'd typically be 2 or 3 on every page in the project, representing such things as a toolbar, a pulldown menu, a navigation menu, etc. In those cases it was easy enough to just refer to the user control directly, performing a simple cast like this:
ToolBar toolBar = (ToolBar)...
But the situation with this page is quite different. With it I have a PlaceHolder control called "placeHolderSubForm" into which one user control at a time will be loaded. Where the difficulty arises is when it's time to cast from a Control object to a specific User Control type. For example, if the first user control shown were loaded into the web page then the cast might look like this:
UserControlA ucA = (UserControlA)placeHolderSubForm.Controls[0]
That works okay if there are one or two user controls to deal with but with 20 of them I immediately realized that the code would get rather unwieldy, with a whole lot of switch-case statements necessary to determine which User Control type to cast to. I wanted something much more generic!
At the end of the day the interaction between the web page and each user control is fairly basic, consisting of:
- Passing some basic parameters to each user control.
- Retrieving a varied assortment of user entered data from each user control.
public class UserControlBase : System.Web.UI.UserControl
{
// Input Parameters (more to be added when required)
public virtual int ContractIdx { get; set; }
// Output Data - passed via SortedList
public SortedList NewData = new SortedList();
// Generic event handler to notify parent web page
public virtual event EventHandler NewData_Handler;
}
In this way, within each user control I can add newly entered data to the general purpose SortedList container 'NewData' via a key & value pair. Thus, one user control might retrieve the following data from a user:
- Width: 5.0
- Height: 7.0
- Length: 144
- Grade: 2.7%
- Overtime: Yes
- Crew: Alpha Prime
SortedList newData = ((UserControlBase)placeHolderSubForm.Controls[0]).NewData;
Please note that I have posted this blog entry not as "the definitive way" to do this kind of thing but simply to throw out to the ASP.Net developer community what approach I've used. I very much look forward to feedback from others about alternate approaches they prefer and will definitely modify things accordingly if I learn of a better practice.
Monday, May 3, 2010
Tracking a Telerik RadTreeView via the UniqueID Property of Each Node
The large ASP.Net project I've been working on makes extensive use of Telerik's excellent TreeView control. Here's an example of its implementation:
It's important to note that the Save button you see on the toolbar is used for saving all changes on the page as a batch. There is no need for a user to press "OK" (or equivalent) after changes on each node. To accomplish this was a little tricky because the user has the ability to make one or more changes on a given record (ie. the controls on the right side) and then click on another node or go to another module or close the browser altogether. In all of these cases the aforementioned changes are correctly recorded, associated with the appropriate node.
I solved this by introducing a modular level property called "CurrentNodeID". It keeps track of the currently selected node. There's also an associated property called "CurrentNode". It uses CurrentNodeID as a lookup value to find the currently selected node.
To ensure that each CurrentNodeID was unique, I initially just generated a new GUID and stored it in each node's "id" attribute. This worked very well. It was especially easy to find find the current node with a statement like this:
It's also important to realize that I'm not populating the entire tree at once but only populating on demand. To accomplish this I originally had each node's "ExpandMode" property set to "ServerSide". Configured this way, finding a node via its "id" attribute property was very straightforward.
But more recently I've been experimenting with a newer ExpandMode property value: ServerSideCallBack It's very powerful, using AJAX to perform a much quicker population of child nodes. But testing quickly revealed that once the ExpandMode property was changed this way, the "id" attribute was no longer visible. An inquiry with Telerik revealed that perhaps I should use an attribute name other than "id", as it's already the name of a node property.
Rather than go down that road I instead went back to basics and asked myself why I was using the attribute approach in the first place, for I had long known that each node's "UniqueID" property had a unique value as well. So I set about to drop the use of the "id" attribute and instead use this new approach. When it came to the "PreviousNodeID", rather than storing the randomly generated GUID in it, instead I'd just store the UniqueID value. This change worked very well except for two things:
To resolve issue #1 I had to create a new method. Here is that method, along with how I've defined the CurrentNodeID and CurrentNode properties:
To resolve issue #2, one must just reset the value of PreviousNodeID after a node is moved:
I now have a UI that works faster and there ends up being a bit less code than before. That's a true Win-Win!
It's important to note that the Save button you see on the toolbar is used for saving all changes on the page as a batch. There is no need for a user to press "OK" (or equivalent) after changes on each node. To accomplish this was a little tricky because the user has the ability to make one or more changes on a given record (ie. the controls on the right side) and then click on another node or go to another module or close the browser altogether. In all of these cases the aforementioned changes are correctly recorded, associated with the appropriate node.
I solved this by introducing a modular level property called "CurrentNodeID". It keeps track of the currently selected node. There's also an associated property called "CurrentNode". It uses CurrentNodeID as a lookup value to find the currently selected node.
To ensure that each CurrentNodeID was unique, I initially just generated a new GUID and stored it in each node's "id" attribute. This worked very well. It was especially easy to find find the current node with a statement like this:
treeView.FindNodeByAttribute("id", CurrentNodeID)
It's also important to realize that I'm not populating the entire tree at once but only populating on demand. To accomplish this I originally had each node's "ExpandMode" property set to "ServerSide". Configured this way, finding a node via its "id" attribute property was very straightforward.
But more recently I've been experimenting with a newer ExpandMode property value: ServerSideCallBack It's very powerful, using AJAX to perform a much quicker population of child nodes. But testing quickly revealed that once the ExpandMode property was changed this way, the "id" attribute was no longer visible. An inquiry with Telerik revealed that perhaps I should use an attribute name other than "id", as it's already the name of a node property.
Rather than go down that road I instead went back to basics and asked myself why I was using the attribute approach in the first place, for I had long known that each node's "UniqueID" property had a unique value as well. So I set about to drop the use of the "id" attribute and instead use this new approach. When it came to the "PreviousNodeID", rather than storing the randomly generated GUID in it, instead I'd just store the UniqueID value. This change worked very well except for two things:
- You can search a treeview for a node via its attribute value but there's no direct way to search for a node via its UniqueID value.
- When you drag & drop a node, its UniqueID value changes.
To resolve issue #1 I had to create a new method. Here is that method, along with how I've defined the CurrentNodeID and CurrentNode properties:
/// References the value of the UniqueID property of the most current treeview node. public string CurrentNodeID { get { if (ViewState["CurrentNodeID"] == null) return null; return ViewState["CurrentNodeID"].ToString(); } set { ViewState["CurrentNodeID"] = value; } }/// References the most current treeview node. public RadTreeNode CurrentNode { get { if (CurrentNodeID == null) return null; return FindNodeByUniqueID(treeViewMain, CurrentNodeID); } }/// Every node has a Unique ID value. There's no built-in method to locate /// a node in a treeview via its Unique ID. This method accomplishes that. public static RadTreeNode FindNodeByUniqueID(RadTreeView treeView, string uniqueID) { RadTreeNode foundNode = null; int nodeCount = treeView.GetAllNodes().Count; RadTreeNode[] allNodes = new RadTreeNode[nodeCount]; treeView.GetAllNodes().CopyTo(allNodes, 0); foreach (RadTreeNode node in allNodes) { if (node.UniqueID == uniqueID) { foundNode = node; break; } } return foundNode; }
To resolve issue #2, one must just reset the value of PreviousNodeID after a node is moved:
PreviousNodeID = node.UniqueID
I now have a UI that works faster and there ends up being a bit less code than before. That's a true Win-Win!
Thursday, December 31, 2009
How Best to Keep a Treeview in Sync with SQL Server?
I've run into a bit of a challenge with my work and thought I might be able to get some good advice by drawing attention to it here. I've outlined the problem quite succinctly here.
If you have any Best Practice ideas based on your own work, please do leave a comment if you can. Thanks!
If you have any Best Practice ideas based on your own work, please do leave a comment if you can. Thanks!
Thursday, November 19, 2009
Syntax Highlighter
I'd like to send a shout out & much thanks to Nilesh Thakkar, a software engineer at SIHL in India for recommending I introduce Syntax Highlighter into this blog. It provides a super simple way to display source code to fellow developers. You'll see it used in all future posts.
Incidentally, for anyone else writing a blog on Blogger, Matthew Ball has provided a step-by-step explanation of how to add Syntax Highlighter.
Thank you, Nilesh!!
Incidentally, for anyone else writing a blog on Blogger, Matthew Ball has provided a step-by-step explanation of how to add Syntax Highlighter.
Thank you, Nilesh!!
Wednesday, November 18, 2009
Encoding & Decoding Bit-Sums For a CheckBoxList
Have you ever had occasion where you need to allow users to select one or more items in a list? With ASP.Net a good UI control to do that with is the CheckBoxList. Here's a pair of examples:
Then the question arises, how do you store their selections? If the potential list is quite long then you're probably going to want to use a CDF string of the values you've assigned to each list item - example: "2,3,11,17,34"
But for smaller, more finite lists a good way to go is with bit-encoded sums. You assign each list item a base-2 value, such as:
Encoding the values is very straightforward. Here's a little algorithm that does the job:
Decoding though is somewhat trickier. I imagine there are many possible approaches. Here's a method I created to do the job:
Then the question arises, how do you store their selections? If the potential list is quite long then you're probably going to want to use a CDF string of the values you've assigned to each list item - example: "2,3,11,17,34"
But for smaller, more finite lists a good way to go is with bit-encoded sums. You assign each list item a base-2 value, such as:
- Item A 2^0 = 1
- Item B 2^1 = 2
- Item C 2^2 = 4
- Item D 2^3 = 8
Encoding the values is very straightforward. Here's a little algorithm that does the job:
int newSum = 0;
foreach (ListItem item in checkBoxList.Items)
{
if (item.Selected)
newSum += Convert.ToInt32(item.Value);
}
Decoding though is somewhat trickier. I imagine there are many possible approaches. Here's a method I created to do the job:
private void SelectCblItems(CheckBoxList cbl, int sum)
{
// Determine the largest Base-2 Power that comprises sum
int maxPower = (int)Math.Log(sum, 2);
for (int pow = maxPower; pow >= 0; pow--)
{
int partSum = (int)Math.Pow(2, pow);
if (partSum <= sum)
{
ListItem listItem = cbl.Items.FindByValue(partSum.ToString());
if (listItem != null)
listItem.Selected = true;
sum -= partSum;
}
}
}
Labels:
ASP.net,
bit encoding,
C#,
CheckBoxList,
decode,
encode
Sunday, September 20, 2009
Lessons Learned from Setting Up One's Own Test Server
I've had occasion to set up a simple test server on which to test an AJAX enabled ASP.Net web app. 99% of the time there'll be just me or a client (in a distant location) testing it. Knowing that this load was quite small, I felt confident using an old P4 machine with Windows XP.
But lo and behold, strange things started occurring. At seemingly random times the app would hang. This was most often noticeable when I was opening a treeview and additional nodes were being loaded on demand. It was strange though because this never happened within the Visual Studio 2008 debug environment. It also never occurred when I opened a browser on the test server itself and connected via the localhost. So what was the problem?
At first I thought it had something to do with the fact that from my workstation I was going out to the Internet and then coming back into the test server. This would explain an increased latency (lag time) but not necessarily explain why it was timing out so frequently.
Eventually, through the help of ASP.Net forums contributor Jeffery Tay, I found the answer! When I mentioned to him that I was using Windows XP on the test server he immediately clued me into the fact that the default number of connections for XP was just 10. Because multiple connections are frequently opened for even simple operations, this might explain why the server appeared to be hanging. After a little research I found this useful discussion. It showed me how to bump up the connection limit to 39.
Only time will tell if this is a complete & final solution but so far it appears to have resolved everything!
But lo and behold, strange things started occurring. At seemingly random times the app would hang. This was most often noticeable when I was opening a treeview and additional nodes were being loaded on demand. It was strange though because this never happened within the Visual Studio 2008 debug environment. It also never occurred when I opened a browser on the test server itself and connected via the localhost. So what was the problem?
At first I thought it had something to do with the fact that from my workstation I was going out to the Internet and then coming back into the test server. This would explain an increased latency (lag time) but not necessarily explain why it was timing out so frequently.
Eventually, through the help of ASP.Net forums contributor Jeffery Tay, I found the answer! When I mentioned to him that I was using Windows XP on the test server he immediately clued me into the fact that the default number of connections for XP was just 10. Because multiple connections are frequently opened for even simple operations, this might explain why the server appeared to be hanging. After a little research I found this useful discussion. It showed me how to bump up the connection limit to 39.
Only time will tell if this is a complete & final solution but so far it appears to have resolved everything!
Labels:
ASP.net,
connection limit,
server,
testing,
Windows XP
Friday, September 18, 2009
Frustrating Deployment Experience!
I've deployed a few simple AJAX-enabled web pages to a local server that is accessed through the Internet by just two people: me & a client.
When running locally in the VS2008 environment it works perfectly fine but when accessed through this server there are frequent and mysterious timeouts. There appears to be no pattern to when & why they occur. Sometimes I get strange errors with codes in the 40000 & 50000 range. Other times it just hangs. There is so little load on this server that I do not understand why this is occurring.
I suspect this is an AJAX related problem but am not sure of that. I'm wondering if anyone else has experienced something similar and might have some tips about how to resolve it?
When running locally in the VS2008 environment it works perfectly fine but when accessed through this server there are frequent and mysterious timeouts. There appears to be no pattern to when & why they occur. Sometimes I get strange errors with codes in the 40000 & 50000 range. Other times it just hangs. There is so little load on this server that I do not understand why this is occurring.
I suspect this is an AJAX related problem but am not sure of that. I'm wondering if anyone else has experienced something similar and might have some tips about how to resolve it?
Thursday, August 27, 2009
Find All Occurences of a Specific Field Name in SQL Server
Here's a useful T-SQL script that will identify every table that has a field with a specific name:
select sysobjects.name
from syscolumns
left join sysobjects on sysobjects.id = syscolumns.id
where syscolumns.name like 'myFieldName'
order by 1Source - Steve Gray
Wednesday, August 26, 2009
Developer AutoLogin & Redirect
A large project I'm working on these days includes a Login page which each user must pass through before being able to access the rest of the site. There is code similar to this that prevents unauthorized entry onto every page:

For some time I would just comment out this code until the rest of the module was complete. The problem with that is that as the application became more & more sophisticated, there was assorted initialization code elsewhere that has to be executed each & every time.
For the Run-Debug cycle it became exceptionally time consuming (and annoying) to manually go through the entire login process every time. Here's an example of the steps involved:



Oh sure, it's just a few keystrokes and mouse clicks each time but repeated dozens (or hundreds!) of times per day, one soon hopes for a way to automate the process. And I found it!
In Visual Studio's Solution Explorer, right-click on whatever page you're working on and choose "Set As Start Page" from the popup menu. Note: You've probably already done this!
Now in the Page_Init event handler of your Master Page (FYI all my projects use Master Pages) add this code:
protected void Page_Init(object sender, EventArgs e)
{
if (!IsPostBack)
{
// If we're on a development machine then record the Start Page that Visual Studio is set to
string currPage = Page.ResolveUrl(Request.ServerVariables["URL"].ToString());
if (System.Diagnostics.Debugger.IsAttached && SessionData.GetCurrentUser() == "")
if (SessionData.GetSessionObject("CurrentDebugPage") == null)
SessionData.SaveSessionObject("CurrentDebugPage", currPage);
}
}
Then in the Page_Load event handler of your login page add this code:
// Note: This must be set above: using System.Web.Security;
protected void Page_Load(object sender, EventArgs e)
{
// This tests whether we're running on a development machine. If so, we'll automatically login.
if (System.Diagnostics.Debugger.IsAttached && SessionData.GetCurrentUser() == "")
{
string currUser = "your_username";
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(currUser, false, 5);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
Response.Cookies.Add(authCookie);
// If a specific page has been set as the Start Page then redirect to it; otherwise go to the Default page
string currDebugPage = SessionData.GetSessionObject("CurrentDebugPage").ToString();
HttpContext.Current.Response.Redirect((currDebugPage == null) ? "~/default.aspx" : currDebugPage, true);
}
}
Note: The various SessionData methods are just simple ones I've constructed to ease the process of storing & retrieving variables in the Session State. I will gladly provide them upon request.
The result is that now, every time I run my project in Visual Studio, it jumps right to the desired web page, logging me in automatically. And yet when run in non-Debug mode it works normally. So I don't have to alter any code whenever I release a new production version.
In case you're wondering how this works, the process is quite straightforward:
For some time I would just comment out this code until the rest of the module was complete. The problem with that is that as the application became more & more sophisticated, there was assorted initialization code elsewhere that has to be executed each & every time.
For the Run-Debug cycle it became exceptionally time consuming (and annoying) to manually go through the entire login process every time. Here's an example of the steps involved:

Oh sure, it's just a few keystrokes and mouse clicks each time but repeated dozens (or hundreds!) of times per day, one soon hopes for a way to automate the process. And I found it!
In Visual Studio's Solution Explorer, right-click on whatever page you're working on and choose "Set As Start Page" from the popup menu. Note: You've probably already done this!
Now in the Page_Init event handler of your Master Page (FYI all my projects use Master Pages) add this code:
protected void Page_Init(object sender, EventArgs e)
{
if (!IsPostBack)
{
// If we're on a development machine then record the Start Page that Visual Studio is set to
string currPage = Page.ResolveUrl(Request.ServerVariables["URL"].ToString());
if (System.Diagnostics.Debugger.IsAttached && SessionData.GetCurrentUser() == "")
if (SessionData.GetSessionObject("CurrentDebugPage") == null)
SessionData.SaveSessionObject("CurrentDebugPage", currPage);
}
}
Then in the Page_Load event handler of your login page add this code:
// Note: This must be set above: using System.Web.Security;
protected void Page_Load(object sender, EventArgs e)
{
// This tests whether we're running on a development machine. If so, we'll automatically login.
if (System.Diagnostics.Debugger.IsAttached && SessionData.GetCurrentUser() == "")
{
string currUser = "your_username";
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(currUser, false, 5);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
Response.Cookies.Add(authCookie);
// If a specific page has been set as the Start Page then redirect to it; otherwise go to the Default page
string currDebugPage = SessionData.GetSessionObject("CurrentDebugPage").ToString();
HttpContext.Current.Response.Redirect((currDebugPage == null) ? "~/default.aspx" : currDebugPage, true);
}
}
Note: The various SessionData methods are just simple ones I've constructed to ease the process of storing & retrieving variables in the Session State. I will gladly provide them upon request.
The result is that now, every time I run my project in Visual Studio, it jumps right to the desired web page, logging me in automatically. And yet when run in non-Debug mode it works normally. So I don't have to alter any code whenever I release a new production version.
In case you're wondering how this works, the process is quite straightforward:
- The Master page's Page_Init event handler is run before every Content page's Page_Load event handler. It records the start page that the developer wanted to go to, placing this string into a Session variable.
- When that desired page's Page_Load event handler is initially entered, the security checks therein reject entry because the user is not logged in. They then redirect to the Login page.
- In the Login page the special test in Page_Load passes because the code is being run from Visual Studio and because the user is not yet logged in.
- A FormsAuthenticationTicket is instantiated and then placed in a Cookie. This allows one to artificially login through the ASP.Net Login mechanism.
- The desired start page, originally stored by the Master Page, is retrieved from the Session State and control is redirected to that page.
- This second time the security checks in the desired page's Page_Load event handler pass because the user is now officially logged in.
Monday, August 17, 2009
A "Subtle" MessageBox
For years now, whenever I wanted to display a message box to the user I'd use some variation of the following:
// Displays an alert message box to the user.
public static void ShowMessage(string msg)
{
msg = Tools.FixJavaScriptString(msg);
Page page = HttpContext.Current.CurrentHandler as Page;
ScriptManager.RegisterStartupScript(page, page.GetType(),
Guid.NewGuid().ToString(), "alert('" + msg + "');", true);
}
// A single apostrophe is not allowed in a SQL string
// on its own so we need to prefix it with a backslash.
public static string FixJavaScriptString(string text)
{
string text2 = text;
if (text.IndexOf("'") != -1)
text2 = text.Replace("'", "\\'");
return text2;
}
This works fine but the drawback is that the user must press the OK button on every Alert box.
Inspired by a feature I first noticed in FaceBook, I decided to create a MessageBox that would appear but then fade away after a developer-defined period of time. You can see it in action in this video:
The solution works with all of the following, which I use in most of my web development work:

I always place this code right near the bottom of every Content page, just above "".
Then add the following server-side code to your web app (I have it in a shared code file called "Common.cs") :
// Displays a message box that disappears on its own.
public static void ShowSubtleMessage(string msg, int duration, string[,] cssParams)
{
msg = Tools.FixJavaScriptString(msg);
string script = "$('div#msg').empty().html('" + msg + "'); $('div.subtleMsg')";
if (cssParams != null)
{
string property = "";
foreach (string cssItem in cssParams)
{
if (property == "")
property = cssItem;
else
{
string propVal = cssItem;
script += ".css('" + property + "', '" + propVal + "')";
property = "";
}
}
}
Page page = HttpContext.Current.CurrentHandler as Page;
script += ".fadeIn(1000).animate({ opacity: 1.0 }, " + duration.ToString() + ").fadeOut(2000);";
ScriptManager.RegisterStartupScript(page, page.GetType(), Guid.NewGuid().ToString(), script, true);
}
Here is the CSS code that I use:
div.subtleMsg
{
display:none;
float: left;
position:absolute;
left:360px;
top:220px;
width:300px;
height:100px;
background:#0e90e6 url(../Images/Gradients/blueRectGradient4.jpg);
text-align:center;
padding:10px;
font-size:16px;
font-weight:bold;
color:White;
border:ridge 5px darkgray;
}
And here's a typical call to the method:
string msg = "Blank descriptions are not allowed!";
Common.ShowSubtleMessage(msg, 2000, new string[,] { { "left", "470px" }, { "top", "310px" }, { "width", "330px" }, { "height", "60px" } });
There are several improvements that could be made:
// Displays an alert message box to the user.
public static void ShowMessage(string msg)
{
msg = Tools.FixJavaScriptString(msg);
Page page = HttpContext.Current.CurrentHandler as Page;
ScriptManager.RegisterStartupScript(page, page.GetType(),
Guid.NewGuid().ToString(), "alert('" + msg + "');", true);
}
// A single apostrophe is not allowed in a SQL string
// on its own so we need to prefix it with a backslash.
public static string FixJavaScriptString(string text)
{
string text2 = text;
if (text.IndexOf("'") != -1)
text2 = text.Replace("'", "\\'");
return text2;
}
This works fine but the drawback is that the user must press the OK button on every Alert box.
Inspired by a feature I first noticed in FaceBook, I decided to create a MessageBox that would appear but then fade away after a developer-defined period of time. You can see it in action in this video:
The solution works with all of the following, which I use in most of my web development work:
- The ASP.Net AJAX UpdatePanel
- C#
- jQuery
I always place this code right near the bottom of every Content page, just above "".
Then add the following server-side code to your web app (I have it in a shared code file called "Common.cs") :
// Displays a message box that disappears on its own.
public static void ShowSubtleMessage(string msg, int duration, string[,] cssParams)
{
msg = Tools.FixJavaScriptString(msg);
string script = "$('div#msg').empty().html('" + msg + "'); $('div.subtleMsg')";
if (cssParams != null)
{
string property = "";
foreach (string cssItem in cssParams)
{
if (property == "")
property = cssItem;
else
{
string propVal = cssItem;
script += ".css('" + property + "', '" + propVal + "')";
property = "";
}
}
}
Page page = HttpContext.Current.CurrentHandler as Page;
script += ".fadeIn(1000).animate({ opacity: 1.0 }, " + duration.ToString() + ").fadeOut(2000);";
ScriptManager.RegisterStartupScript(page, page.GetType(), Guid.NewGuid().ToString(), script, true);
}
Here is the CSS code that I use:
div.subtleMsg
{
display:none;
float: left;
position:absolute;
left:360px;
top:220px;
width:300px;
height:100px;
background:#0e90e6 url(../Images/Gradients/blueRectGradient4.jpg);
text-align:center;
padding:10px;
font-size:16px;
font-weight:bold;
color:White;
border:ridge 5px darkgray;
}
And here's a typical call to the method:
string msg = "Blank descriptions are not allowed!";
Common.ShowSubtleMessage(msg, 2000, new string[,] { { "left", "470px" }, { "top", "310px" }, { "width", "330px" }, { "height", "60px" } });
There are several improvements that could be made:
- The HTML markup code could potentially be created with jQuery, via a call from the server-side code. I tried to do this but after several hours gave up. Perhaps someone reading this will come up with a solution!
- Potentially the minimum required dimensions of the message box could be automatically calculated using some method in the .Net library.
Tuesday, June 16, 2009
IPostBackHandler and EnableViewState
I've spent several hours troubleshooting a crazy bug, which is explained in detail here. In a nutshell, the changed state of toolbar buttons (ASP.Net LinkButton controls) was not being kept. Every time a partial postback occurred on the web page, the changes would disappear.
I did much research and learned that the IPostBackHandler mechanism is what was responsible for restoring the control values after each postback. For some reason it didn't appear to be working. I even built a small test project but it did work in that!
Suddenly I noticed that the test page in my main project had EnableViewState=false. I changed it to true and ... everything worked fine!
My findings seem to conflict with several articles, including this one. All I know is that in my case EnableViewState must be set to true.
I'm posting this, both as a future reminder to myself and in the hopes that a State Management guru will read it and explain to me what's really going on.
I did much research and learned that the IPostBackHandler mechanism is what was responsible for restoring the control values after each postback. For some reason it didn't appear to be working. I even built a small test project but it did work in that!
Suddenly I noticed that the test page in my main project had EnableViewState=false. I changed it to true and ... everything worked fine!
My findings seem to conflict with several articles, including this one. All I know is that in my case EnableViewState must be set to true.
I'm posting this, both as a future reminder to myself and in the hopes that a State Management guru will read it and explain to me what's really going on.
Labels:
ASP.net,
IPostBackHandler,
state management,
ViewState
Thursday, June 11, 2009
pageLoad() and $(document).ready()
I just read a pair of articles by Dave Ward over at his excellent Encosia site. This one focuses on the differences between pageLoad() and $(document).ready() and this one focuses on having a pageLoad() function in both a Content page and a Master page.
If you're not 100% familiar about when these functions fire (and don't fire) then I'd strongly recommend reading both articles.
If you're not 100% familiar about when these functions fire (and don't fire) then I'd strongly recommend reading both articles.
Tuesday, June 9, 2009
Cacading Events in a Master-Content Page Project
I came across a situation where I needed to add a toolbar to a Master Page and monitor the toolbar button events from the Content Pages. I did a bunch of research and came across this article. It explains what one must do to accomplish this.
One key necessity is to add a directive similar to this on every content page:
<%@ MasterType VirtualPath="~/main.Master" %>
Then in the Master Page you have to define an event handler similar to this:
public event EventHandler CommandButton_ClickHandler;
When a button in the Master Page is pressed, you explicitly fire the event like this:
protected void CommandButton_Click(object sender, EventArgs e)
{
CommandButton_ClickHandler(sender, e);
}
Finally, in every content page you have to wire up the event handler mechanism:
protected void Page_Init(object sender, EventArgs e)
{
Master.Toolbar_ClickHandler += new EventHandler(CommandButton_Click);
}
Whenever a button in the Master Page is pressed, it causes the event handler you setup to fire, which in turn is monitored by the listener in the content page. It's all quite simple but you have to get the syntax just right.
I created a small proof of concept project, which includes buttons directly located in a Master Page, as well as another example of buttons in a toolbar, which in turn sits in a Master Page. You can download this project here. For the first approach, set "default.aspx" as the start page. For the second approach, use "default2.aspx" instead. Hopefully this will help you implement this powerful concept in your own work!
One key necessity is to add a directive similar to this on every content page:
<%@ MasterType VirtualPath="~/main.Master" %>
Then in the Master Page you have to define an event handler similar to this:
public event EventHandler CommandButton_ClickHandler;
When a button in the Master Page is pressed, you explicitly fire the event like this:
protected void CommandButton_Click(object sender, EventArgs e)
{
CommandButton_ClickHandler(sender, e);
}
Finally, in every content page you have to wire up the event handler mechanism:
protected void Page_Init(object sender, EventArgs e)
{
Master.Toolbar_ClickHandler += new EventHandler(CommandButton_Click);
}
Whenever a button in the Master Page is pressed, it causes the event handler you setup to fire, which in turn is monitored by the listener in the content page. It's all quite simple but you have to get the syntax just right.
I created a small proof of concept project, which includes buttons directly located in a Master Page, as well as another example of buttons in a toolbar, which in turn sits in a Master Page. You can download this project here. For the first approach, set "default.aspx" as the start page. For the second approach, use "default2.aspx" instead. Hopefully this will help you implement this powerful concept in your own work!
Subscribe to:
Posts (Atom)
