<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7812083001688597725</id><updated>2011-11-28T10:46:52.193+11:00</updated><category term='c#'/><category term='crud'/><category term='logscrobbler'/><category term='winforms'/><category term='java'/><category term='stored procedure'/><category term='cookies'/><category term='identity'/><category term='development'/><category term='asp.net'/><category term='design'/><category term='.net'/><category term='tomcat'/><category term='open source'/><category term='sql server'/><category term='database'/><title type='text'>Tokartronica: The Life and Learnings of Tokes</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://tokartronica.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7812083001688597725/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://tokartronica.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Tokes</name><uri>http://www.blogger.com/profile/10013493208982762899</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>5</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7812083001688597725.post-7851051864875758039</id><published>2008-05-30T18:19:00.003+10:00</published><updated>2008-05-30T18:25:24.317+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='cookies'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='tomcat'/><title type='text'>Beware of Hash Cookies</title><content type='html'>As posted on the &lt;a href="http://blogs.atlassian.com/developer/2008/04/beware_of_hash_cookies.html"&gt;Atlassian Developer Blog&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;During my latest stint as the JIRA Developer on Support, I came across an issue where a customer reported that the Remember Me functionality of JIRA wasn't working - but only for &lt;em&gt;some&lt;/em&gt; users. In his instance, he had two users: &lt;strong&gt;wmlalai&lt;/strong&gt; and &lt;strong&gt;lagonil&lt;/strong&gt; - for now, let's call them B1 and B2. For B1, the Remember Me functionality worked as expected; for B2, not so.&lt;br /&gt;&lt;br /&gt;Curious to the cause of this problem, I loaded up the customer's data in a local instance, and sure enough I was able to reproduce it. Monitoring the cookies within the browser to make sure they were being saved correctly, I noticed that upon the first login, the cookie was present in the browser for both users. When I closed the browser and restarted it, the cookie again was still there. But, when I navigated to JIRA, B2's cookie was being deleted. This was accompanied by an exception in JIRA's logs:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;font size="2"&gt;2008-03-07 15:46:32,470 http-8090-Processor2 DEBUG [atlassian.seraph.cookie.EncryptedCookieEncoder] Invalid password cookie submitted, trying insecure&lt;br /&gt;java.lang.RuntimeException: javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher&lt;br /&gt; at com.atlassian.seraph.util.EncryptionUtils.decrypt(EncryptionUtils.java:77)&lt;br /&gt; at com.atlassian.seraph.cookie.EncryptedCookieEncoder.decodePasswordCookie(EncryptedCookieEncoder.java:43)&lt;br /&gt; at com.atlassian.seraph.auth.DefaultAuthenticator.decodeCookie(DefaultAuthenticator.java:393)&lt;br /&gt; at com.atlassian.seraph.auth.DefaultAuthenticator.getUserFromCookie(DefaultAuthenticator.java:245)&lt;br /&gt; at com.atlassian.seraph.auth.DefaultAuthenticator.getUser(DefaultAuthenticator.java:221)&lt;/font&gt;&lt;/pre&gt;&lt;br /&gt;&lt;strong&gt;Why would the cookie be the wrong length?&lt;/strong&gt; I set a breakpoint in the &lt;tt&gt;DefaultAuthenticator.getUserFromCookie()&lt;/tt&gt; method in Seraph, where the cookie is retrieved and decoded. When attempting to log in with B2, I observed the following value for the &lt;tt&gt;seraph.os.cookie&lt;/tt&gt;:  &lt;p&gt;&lt;tt&gt;lLaNPRBsPajwpMtCaoXPpImn&lt;/tt&gt;&lt;/p&gt;  &lt;p&gt;&lt;br /&gt;as compared to the actual value stored in the cookie in the browser:&lt;/p&gt;  &lt;p&gt;&lt;tt&gt;lLaNPRBsPajwpMtCaoXPpImn&gt;Ca7ic&lt;ls8ggjfoitvg45tfk&gt;&lt;/ls8ggjfoitvg45tfk&gt;&lt;/tt&gt;&lt;/p&gt;  &lt;p&gt;i.e., when retrieving the cookie value from the request, it was being truncated at the '&gt;' character.&lt;/p&gt;  &lt;p&gt;&lt;br /&gt;&lt;strong&gt;Who stole (part of) the cookie from the cookie jar?&lt;/strong&gt; I sniffed the network traffic to ensure the browser was sending the full value, and it was, so this led me to believe that the application server was the fiend. After a bit of searching, I found that &lt;a href="https://issues.apache.org/bugzilla/show_bug.cgi?id=44442"&gt;others had been the victim&lt;/a&gt; of Tomcat's cruel appetite. What confused me more was Apache's response:&lt;/p&gt;  &lt;blockquote&gt;That is because those characters are illegal in version 0 cookies (as per the spec). You need to use version 1 cookies.&lt;/blockquote&gt;  &lt;p&gt;I had read the specs they were referring to: &lt;a href="http://wp.netscape.com/newsref/std/cookie_spec.html"&gt;"Version 0"&lt;/a&gt; and &lt;a href="http://www.ietf.org/rfc/rfc2109.txt"&gt;"Version 1"&lt;/a&gt;, and neither made mention of illegal characters in cookie values other than semi-colon, comma and whitespace. Regardless, I tried their suggested solution of switching to "Version 1" cookies (by using the &lt;tt&gt;setVersion()&lt;/tt&gt; method on the Cookie object when constructing it), and it did work. Note however that, when I tested the issue on Orion and Resin, they did not experience this problem, even though the cookies did have the "illegal" characters in them, AND the Cookie objects were "Version 0". Go figure.&lt;/p&gt;  &lt;p&gt;&lt;br /&gt;&lt;strong&gt;Why do we use these characters in our cookies anyway?&lt;/strong&gt; Interestingly, this problem only appeared after the release of JIRA 3.12, when updates were made to Seraph code to increase the security of the &lt;tt&gt;seraph.os.cookie&lt;/tt&gt;, by taking the hash of the username and password and encrypting it a little. And sometimes, this hash happens to contain those characters. What's cool is that it seems to be reproducible, so if you fire up your own instance of JIRA running on Tomcat, and create a user named &lt;strong&gt;lagonil&lt;/strong&gt;, you should get the same problem. We're hoping to resolve this issue very soon; you can track it &lt;a href="http://jira.atlassian.com/browse/SER-117"&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;&lt;br /&gt;&lt;strong&gt;So let that be a lesson to you, kids - if you're ever going to use hash cookies, make sure you know what's in them!&lt;/strong&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7812083001688597725-7851051864875758039?l=tokartronica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tokartronica.blogspot.com/feeds/7851051864875758039/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7812083001688597725&amp;postID=7851051864875758039' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7812083001688597725/posts/default/7851051864875758039'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7812083001688597725/posts/default/7851051864875758039'/><link rel='alternate' type='text/html' href='http://tokartronica.blogspot.com/2008/05/beware-of-hash-cookies.html' title='Beware of Hash Cookies'/><author><name>Tokes</name><uri>http://www.blogger.com/profile/10013493208982762899</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7812083001688597725.post-2670874204228249352</id><published>2007-08-12T23:44:00.000+10:00</published><updated>2007-08-12T23:54:21.874+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crud'/><category scheme='http://www.blogger.com/atom/ns#' term='sql server'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='identity'/><category scheme='http://www.blogger.com/atom/ns#' term='stored procedure'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Know your true @@IDENTITY</title><content type='html'>When developing a data access layer for persisting information to a database, one of the commonly used techniques is to implement &lt;a href="http://en.wikipedia.org/wiki/CRUD_(acronym)" target="_blank"&gt;CRUD&lt;/a&gt; operations (Create, Read, Update, Delete) by using stored procedures. When writing the "create" stored procedure, I tend to use the following very simple pattern:&lt;br /&gt;&lt;p class="codeblock"&gt;CREATE PROCEDURE Employee_Insert (&lt;br /&gt; @EmployeeId int OUTPUT&lt;br /&gt;, @FirstName varchar(50)&lt;br /&gt;, @LastName varchar(50)&lt;br /&gt;, @DateOfBirth datetime&lt;br /&gt;) AS&lt;br /&gt;BEGIN&lt;br /&gt; INSERT INTO Employee (&lt;br /&gt;  FirstName&lt;br /&gt; , LastName&lt;br /&gt; , DateOfBirth&lt;br /&gt; )&lt;br /&gt; VALUES (&lt;br /&gt;  @FirstName&lt;br /&gt; , @LastName&lt;br /&gt; , @DateOfBirth&lt;br /&gt; )&lt;br /&gt; &lt;br /&gt; SELECT @EmployeeId = @@IDENTITY&lt;br /&gt;END&lt;/p&gt;&lt;br /&gt;In this example, the Employee table's primary key is the EmployeeId, and it is also an identity column (meaning key values are generated by the database). After inserting the new Employee record into the table using the first statement, the second statement of the stored procedure saves the identity value that was used for the new record into the @EmployeeId output parameter, so that the data access layer can use it in some meaningful way. The way to find out the identity value that was used in the latest INSERT statement is simple - it's stored in the special @@IDENTITY variable, which is maintained by the database engine. This is very convenient, as it saves you from having to query the table again. But - is it really this simple? Does the @@IDENTITY variable always return the identity value from your last INSERT statement?&lt;br /&gt;&lt;br /&gt;The answer is "not necessarily", in SQL Server 2005 at least. From the @@IDENTITY page in &lt;a href="http://msdn2.microsoft.com/en-us/library/ms187342.aspx" target="_blank"&gt;SQL Server 2005 Books Online&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;p class="codeblock"&gt;"If the statement fires one or more triggers that perform inserts that generate identity values, calling @@IDENTITY immediately after the statement returns the last identity value generated by the triggers."&lt;/p&gt;&lt;br /&gt;This means that if are using triggers on your Employee table which fire on insertion, and if those triggers in turn insert records into another table which also has an identity column, the result of the @@IDENTITY variable is the identity value used in the second insertion, and not the first, which is in most cases undesirable.&lt;br /&gt;&lt;br /&gt;I came across this rather fun issue in an application I've been working on. From my data access layer, I was inserting several different records in a transaction, where one record had a foreign key relationship with another record's identity value. The way to save these new records correctly is to save the "parent" record first, then read the identity value returned from the stored procedure, update the corresponding field in the "child" record, and then insert the child record. I was getting unexpected results however, where my child records were not being created because of a violation of the foreign key constraint. After debugging, I found that following the insertion of the parent record, I was getting back identity values that didn't make sense - they were not even close to the previous identity values stored in the table.&lt;br /&gt;&lt;br /&gt;On closer inspection of the database schema, I realised that the table I was inserting the parent record into had a trigger on it, who's purpose was to insert new records into a logging table which, sure enough, had an identity column in it. So when running the SELECT statement, the stored procedure was actually returning the identity value used in the logging table, which explains the violation of the foreign key constraint!&lt;br /&gt;&lt;br /&gt;So now that we know what the issue is, how do we fix it? Once again, trusty SQL Server Books Online has the answer - use SCOPE_IDENTITY() instead of @@IDENTITY.&lt;br /&gt;&lt;br /&gt;&lt;p class="codeblock"&gt;@@IDENTITY and SCOPE_IDENTITY return the last identity value generated in any table in the current session. However, SCOPE_IDENTITY returns the value only within the current scope; @@IDENTITY is not limited to a specific scope.&lt;/p&gt;&lt;br /&gt;What this means essentially is that SCOPE_IDENTITY will always return the last identity value used in an INSERT statement that you called &lt;span style="font-weight:bold;"&gt;directly&lt;/span&gt;; INSERT statements nested inside triggers, or even other stored procedures which are called from the currently executing stored procedure, are considered to be in a different scope. Because of this, my advice would be to always use SCOPE_IDENTITY() when writing the "create" stored procedures for your CRUD operations, because 99% of the time, when you want the last used identity value, you're usually referring to the INSERT statement that you &lt;span style="font-weight:bold;"&gt;can actually see&lt;/span&gt; right in front of you.&lt;br /&gt;&lt;br /&gt;So remember - Know your true @@IDENTITY! Hope this post saves someone out there a little pain. :)&lt;br /&gt;&lt;br /&gt; - Tokes&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7812083001688597725-2670874204228249352?l=tokartronica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tokartronica.blogspot.com/feeds/2670874204228249352/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7812083001688597725&amp;postID=2670874204228249352' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7812083001688597725/posts/default/2670874204228249352'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7812083001688597725/posts/default/2670874204228249352'/><link rel='alternate' type='text/html' href='http://tokartronica.blogspot.com/2007/08/know-your-true-identity.html' title='Know your true @@IDENTITY'/><author><name>Tokes</name><uri>http://www.blogger.com/profile/10013493208982762899</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7812083001688597725.post-6166215762981186883</id><published>2007-07-23T23:19:00.001+10:00</published><updated>2007-07-23T23:38:45.665+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='logscrobbler'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='.net'/><category scheme='http://www.blogger.com/atom/ns#' term='winforms'/><category scheme='http://www.blogger.com/atom/ns#' term='c#'/><title type='text'>LogScrobbler 0.15 released!</title><content type='html'>I have recently spent some time on my first true open source project - &lt;a href="http://code.google.com/p/logscrobbler/" target="_blank"&gt;LogScrobbler&lt;/a&gt;. LogScrobbler is a little tool written by Tim Geiges that allows you to sync music tracks scrobbled from &lt;a href="http://www.rockbox.org" target="_blank"&gt;Rockbox&lt;/a&gt; to your &lt;a href="http://www.last.fm" target="_blank"&gt;Last.fm&lt;/a&gt; account. The app is written in C# and standard Winforms.&lt;br /&gt;&lt;br /&gt;Last week, Tim and I released version 0.15 - the first version since I began working on the project. In this version, I focused mainly on cleaning up the code which was still very prototype-y, and removing some redundant features. I also gave the front end a bit of a face lift by simplifying the interface and standardising the look and feel of the controls. I don't get to do a lot of programming using Winforms in .NET, so this was a cool project to work on. I think in an upcoming release I will refactor the code a bit further and write a command-line interface to the app, just to add a bit more value.&lt;br /&gt;&lt;br /&gt;Working on this project has also got me wanting to work on more open source projects in the future. I might check out the Rockbox code next and see if I can make some slight improvements to the scrobbling/Last.fm module. Rockbox also seems to make my iRiver consume more power than I remember when compared to the original firmware - so that could be another thing to look into.&lt;br /&gt;&lt;br /&gt;If you're interested in checking out the LogScrobbler code or giving the app a try, just head over to the &lt;a href="http://code.google.com/p/logscrobbler/" target="_blank"&gt;project homepage hosted on Google Code&lt;/a&gt;, or you can &lt;a href="http://logscrobbler.googlecode.com/files/LogScrobbler-0.15.zip" target="_blank"&gt;click here&lt;/a&gt; to download the latest version directly. Of course, if you come across any bugs, you should let me know by creating an issue in the project issue tracker (quite a handy feature of Google Code!)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7812083001688597725-6166215762981186883?l=tokartronica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tokartronica.blogspot.com/feeds/6166215762981186883/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7812083001688597725&amp;postID=6166215762981186883' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7812083001688597725/posts/default/6166215762981186883'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7812083001688597725/posts/default/6166215762981186883'/><link rel='alternate' type='text/html' href='http://tokartronica.blogspot.com/2007/07/logscrobbler-015-released.html' title='LogScrobbler 0.15 released!'/><author><name>Tokes</name><uri>http://www.blogger.com/profile/10013493208982762899</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7812083001688597725.post-8514821987258442182</id><published>2007-06-24T21:55:00.000+10:00</published><updated>2007-06-24T23:33:23.841+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='asp.net'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><title type='text'>Small victories - how I saved myself a few hours of work</title><content type='html'>Over the past few weeks at work, I've been working on an internal tool for doing a little financial data entering and reporting. For reasons I won't go into, this tool needed to be developed rapidly, but given that this company's main development platform is ASP.NET, rapid development would not be a problem. The thing about rapid development though, is that requirements often "pop up" at odd stages in your development cycle (someone forgets to mention that this page is meant to do that, or whatever), so it's important that your design decisions are sensible without going overboard (due to time constraints, complexity of the problem, etc).&lt;br /&gt;&lt;br /&gt;One such design decision, which I think is very important in creating ASP.NET applications, is creating a base class (or several base classes) for all the pages in your application to inherit from which captures at the very least a high-level purpose of the page. I think for people who are new to ASP.NET, this might get overlooked from time-to-time, because when you add a new page to your project, it automatically creates the class files for you (which inherit directly from System.Web.UI.Page), and also because application development in ASP.NET tends to be "designer-driven" - a lot of tutorials on the web and a lot of the improvements made to the ASP.NET framework seem to focus on "Look Ma, I didn't need to write any lines of code!". The fact that people tend to miss this design decision is also evidenced by the fact that in the last couple of ASP.NET projects I worked on, none had implemented this design. But the benefits can be really crucial, especially when your project is on a fixed schedule, and above all - it's just good OO practise to do it this way.&lt;br /&gt;&lt;br /&gt;I'll give you an example of this design in practise, and how it helped me save a few hours of work. You might find this really trivial, but I was just happy at the simplicity of it all, and if I can save a few people a few hours of work, all the better. So in this application, there are several pages (somewhere between 10 and 20) of data entry, which all follow a similar kind of design, but the specifics of the data entry are distinct between each page. All pages use a GridView control to display currently entered records and to allow the entry of new records, but the columns of data vary between each page. The "action" columns (containing Add, Edit, Update, Delete buttons) are consistent across all pages. So far so good. Now for the conflict - a change request comes along stating that when a certain condition is met, the user is to be denied access to data entry. This can be implemented in several ways, but the one that makes the most sense is to hide the action columns of the GridView control in each data entry page. To do this for one page, you would have to do something like this in your Load or PreRender event:&lt;br /&gt;&lt;br /&gt;&lt;p class="codeblock"&gt;bool dataEntryCondition = ...;&lt;br /&gt;// assuming that action columns are at the 7th and 8th indexes&lt;br /&gt;myGridView.Columns(7).Visible = dataEntryCondition;&lt;br /&gt;myGridView.Columns(8).Visible = dataEntryCondition;&lt;br /&gt;...&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;I hate having to write code like this, because as soon as you re-order the columns in your GridView, it breaks. What's scary is, I've seen this kind of thing done &lt;span style="font-weight: bold;"&gt;a lot&lt;/span&gt;. A better solution would be something like this:&lt;br /&gt;&lt;br /&gt;&lt;p class="codeblock"&gt;bool dataEntryCondition = ...;&lt;br /&gt;for each (CommandField actionColumn in myGridView.Columns)&lt;br /&gt;{&lt;br /&gt;    actionColumn.Visible = dataEntryCondition;&lt;br /&gt;}&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;This is definitely better, but you can't just assume all CommandFields in your GridView are going to have this logic applied to them (well, you could assume that if you were implementing this for a single page, but the whole point is that we're thinking generally here... it will all come together soon). Also, in my case, the columns in question weren't actually instances of CommandField; because they required special formatting, I had to use TemplateFields instead. So, as you probably figured out by now, what I did was create my own class of column which inherits from TemplateField, then create a very lightweight interface for "toggle-able" fields. The reason for creating a custom field class and an interface was because if I ever needed another kind of custom field that extended from CommandField, I would still be able to get my toggle-able functionality. The declarations looked something like this:&lt;br /&gt;&lt;br /&gt;&lt;p class="codeblock"&gt;interface IToggleField&lt;br /&gt;{&lt;br /&gt;    public void toggleVisibility(bool visibleCondition);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;class MyTemplateField extends TemplateField implements IToggleField&lt;br /&gt;{&lt;br /&gt;    public void toggleVisibility(bool visibleCondition) { this.Visible = visibleCondition; }&lt;br /&gt;}&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;You would then need to modify your GridView in the design view to use this new type of column[1]. And to toggle, just slightly modify the previous attempt to use this new class:&lt;br /&gt;&lt;br /&gt;&lt;p class="codeblock"&gt;bool dataEntryCondition = ...;&lt;br /&gt;// all columns in a GridView inherit from DataControlField&lt;br /&gt;for each (DataControlField actionColumn in myGridView.Columns)&lt;br /&gt;{&lt;br /&gt;    if (actionColumn.GetType() is IToggleField)&lt;br /&gt;        ((IToggleField) actionColumn).toggleVisibility(dataEntryCondition);&lt;br /&gt;}&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;Much better, yes? You're probably thinking "What's the big deal?". Well the big deal is, I have to make this change across 10-20 pages. Changing the GridView columns definitions to use this new class has to be done "manually" (because I shamefully did not use a custom GridView class for my data entry grids, but let's just ignore that little fact for now), but this is easy enough because the columns in question have the exact same definition across all pages (they were copy-pasted :P) which makes it easy to do a search-replace. But, (this is where it all comes together), because I made the easy design decision of all my data entry pages inheriting a DataEntryPage class, adding in the logic to toggle the columns visibility all of those pages, which could have been painful, is really simple! All I had to do was create a single function to do the above procedure, and call it in the page event of my choice from within the DataEntryPage class, which effectively registers the call in all implementers' events. And the result is clean, simple code, the way it should be in OO programming.&lt;br /&gt;&lt;br /&gt;It's because of situations like this, that if I continue doing ASP.NET projects, I will make sure that whoever my development team is knows about these fundamental design choices. If my current experiences are anything to go by, I would suggest that a fair portion of the ASP.NET developers out there probably would have missed this solution. Obviously, it would had been harder to implement had the project been larger and further down the track in development (requiring more refactoring time), but as they always say - you'll never know whether someone's going to have to look at your code in the future, so in my opinion if you can afford to spend the time to refactor the code, it's definitely worth it.&lt;br /&gt;&lt;br /&gt;I hope that this hasn't been too boring! It feels good to write about this though, even if it was a trivial problem. Hopefully, the problems I encounter in the future will be more difficult ;)&lt;br /&gt;&lt;br /&gt; - Tokes&lt;br /&gt;&lt;br /&gt;[1] - for details on how to use custom fields in your pages, and for a more in-depth look on how to make custom fields that actually do complex things, check out this article at MSDN Magazine: &lt;a href="http://msdn.microsoft.com/msdnmag/issues/06/01/CuttingEdge/default.aspx"&gt;Custom Data Control Fields&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7812083001688597725-8514821987258442182?l=tokartronica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tokartronica.blogspot.com/feeds/8514821987258442182/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7812083001688597725&amp;postID=8514821987258442182' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7812083001688597725/posts/default/8514821987258442182'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7812083001688597725/posts/default/8514821987258442182'/><link rel='alternate' type='text/html' href='http://tokartronica.blogspot.com/2007/06/small-victories-how-i-saved-myself-few.html' title='Small victories - how I saved myself a few hours of work'/><author><name>Tokes</name><uri>http://www.blogger.com/profile/10013493208982762899</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7812083001688597725.post-6967472571590795983</id><published>2007-06-21T15:26:00.000+10:00</published><updated>2007-06-20T22:26:15.380+10:00</updated><title type='text'>First Post</title><content type='html'>So, I have finally created a blog. I'm not quite sure what is going to go on here yet. My initial motivation was to have a space where I could talk about the technical challenges I come across at work or through my own exploration, but I guess every now and then I might post some personal stuff too.&lt;br /&gt;&lt;br /&gt;The name of the blog (Tokartronica) came to me randomly... I quite like it, it kinda sounds like a technical, robotic, musical instrument.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7812083001688597725-6967472571590795983?l=tokartronica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7812083001688597725/posts/default/6967472571590795983'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7812083001688597725/posts/default/6967472571590795983'/><link rel='alternate' type='text/html' href='http://tokartronica.blogspot.com/2007/06/first-post.html' title='First Post'/><author><name>Tokes</name><uri>http://www.blogger.com/profile/10013493208982762899</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author></entry></feed>
