Post by my friend and colleague Gil Fink, has reveled one of the difficulties of our project.
A legacy system, poorly designed and implemented and our need of rewriting some of
it's code in order to make it easier for us to extent it with more features as demanded
by our users.
Another aspect of our legacy system was pages with lots of controls, almost each and every
one of them has server-side event (click, textChanged, selectedIndexChanded, etc.) while
best practice is to divide the page into units of User Controls while the page acts as a router
between them and between them and the business layer.
So, How can we change the existing situation to be closer to the desired one?
Here we have a long and risky process, I'll try and give it's details:
"Before you begin" stage
1. Make the time to work on it. It is long process and you can fall into many
holes along the way. If you have the time to work on it continuously it's you
can proceed, if not - ask for the time. If it is very much needed, you'll be given
the time to do so.
"Begin" stage
2. Cut all markup belongs to the user control which you need to divide into a new
User control. Do not transfer any code-behind at this stage! Put the new
User control inside the page where the transferred markup used to be.
Compilation Errors correction stages
3. look at all compilation errors. Courtesy of VS2008 SP 1 you'll see it all without
compiling, given that your changed page is open. The compilation errors will
mostly be the absence of the controls transferred to the new user control.
For each control being transferred, make a property represents it's data . By all
means, do not expose any inner control as public property . Here you can also
change the data type (which is string most of the time, to the type needed ,
and put all parsing code inside the property).
After doing so, whenever the page's code behind takes data you can replace
the controls by the user control's data properties you've created.
Note - this should be done with caution because data type changing are risky
4. There are still compilation errors, these errors refers to the control's state, style
etc. Here you have no choice but to move this code to the user control.
Make small methods in the user control, corresponding to the methods on
the page's code behind, transfer the lines referring to the internal controls
and call these methods from the original methods.
For example : if you have a method call SetUIState containing lines referring
to the inner control,make a public or an internal method inside the user
control, call it SetUIState, transfer the relevant lines into it and call it
from the original method.
Runtime exceptions correcting stages
5. By now, all compilation errors should be corrected, however our control
won't work mainly because it doesn't contains the event handlers to
handle it's inner controls evens. Therefore we do the following:
For each event of the inner control we make public event of the user
control, a protected event handler that will only invoke the event
recently created.
6. On the page's markup we should add all events created on step 5 and
for each event we associate the event handler to the old event handler was
for the inner control while being on the page. That's how we won't
change server side behavior of our page.
Client code handling
7. Now, that all runtime exceptions should be behind us, it's time to
handle client errors that might occur. Client errors may occur if there
is JavaScript code referring to the control's ID hard coded, and where
there is client scripting referring to the internal controls with server
code islands, which will cause runtime errors.
Here you have to do the following:
A. Take all client code referring to the internal controls to the
user control's markup.
B. Whenever there is reference to the internal control ID's in client
script, you should change it to <%=YourInternalControl.ClientID %>
That's because whenever a control is put inside composite control
(or user control for that meter) it changes it's ClientID.
Summery: This was a long post mainly because the process is long. The goal
is to gain more encapsulation and code clarity for our system without changing
it's functionality. However, this is just the first step. You soon will see
that you have a little bit clearer code and but this is only the first step
of a long journey of making it better.