This is going to be more of a 'ad-hoc' guide than a single tip. I want to merely point out how easy it is to Harvest a framework in a relatively short period of time.
I spent 9 hours doing all of this guide, and I now have the IBuySpyStore application separated
Application part and
Framework part
It is just the start, but is now ready to evolve into its separate areas properly.
Why did I choose to do this, because I wanted to see how long it would take to Harvest it, rather than start from scratch
I think I saved at least 3 days work, by refactoring the existing sample. I did not have to create the database, queries, initial logic (cart, checkout etc) or the Stylesheet and so on.
Getting Started
The problem with IBuySpyStore is that it is code written to teach ASP.NET functionality.
For this purpose it has lots of ASPX Pages, some UserControls, and shows off some functionality like Forms Authentication.
The first thing to do in this situation is to discover what is generic, and what is not. Luckily, the IBuySpyStore source code, can be used for any product, so it is already quite general in its code.
Here is a starting point of what to identify
- Infrastructure elements
- Extensible Elements
Here is a quick division of the some elements of the Application. Note that some appear in both.
Infrastructure
- Login.aspx
- Register.aspx
- Account.aspx
- ErrorPage.aspx
- Cart.aspx
Extensible
- Login.aspx
- Register.aspx
- ErrorPage.aspx
- Cart.aspx
- ProductList.aspx
- ProductDetails.aspx
- Cart.aspx
Currently the code is basically all in the root and within ASPX pages.
The first thing to do is to create two temporary folders under the root.
Two folders,
Infrastructure and
Extensible would be good temporary folders.
Refactoring the Application
The trick is to gently migrate the Application into two distinct sections. The code will basically remain the same, except for where you are repairing links, paths and code that keeps the application running.
Giving Control to the Framework
A Framework should have control of flow and context
(see
Tip 5).
Since it is far easier to update a single page with logic, styles and functionality, it is recommended that some form of a PageController pattern be used.
I prefer loading UserControls, so I will show you a quick way to do it.
Simply add a System.Web.UI.WebControls.PlaceHolder control to the Default.aspx page.
In the load event of the Default.aspx page, add the following code:
if (Request["View"] !=null)
     {
       string view = Request["View"].ToString();
       if (view!="")
       {
         _ContentArea.Controls.Add(LoadControl(GetVirtualUserContentDirectory() + view + ".ascx"));
       }
     }
     else
     {
       
       _ContentArea.Controls.Add(LoadControl(GetVirtualUserContentDirectory() + "Home.ascx"));
     }
Where _ContentArea is a PlaceHolder Control (System.Web.UI.WebControls.PlaceHolder)
and GetExtensibleDirectory() is a method where you obtain the Extensible directory you created earlier.
Until you have a place for it (you will still refactor more later), you can create a simple method in the Default.aspx page.
public string GetExtensibleDirectory()
{
 return "Extensible/";
}
This will provide you with functionality to load a UserControl into the placeholder by looking at the "View" in the QueryString.
Therefore, links must change. The main links are those in the menu. Instead of pointing to Cart.Aspx, it will be default.aspx?View=Cart
You should updated all the links on the default.aspx page and the menu usercontrols it loads.
Next you must drag the ASPX pages, such as ProductList.aspx, Login.Aspx to the appropriate directory. One of the two new folders you created.
Once all the pages are moved into one or other new folder, you must convert the ASPX page to a ASCX page.
This is relatively straight forward. As an example, I will convert login.aspx.
I did this in three hours, including fixing all the links to the new paths and changing the Forms Authentication to work with UserControls (see
deny users post.
Web.Config refers to Login.aspx - change to default.aspx?View=Login (the login.ascx)
I also created a SecureUserControl class and put it into the Infrastructure folder.
I derived UserControls like Orders.ascx from this new base class, providing me with the Forms Authentication mechanism.
All other controls, like ProductsList, ProductDetails derived from the standard UserControl.
Creating the Notion
Once you have this all done, you will have two distinct folders containing two distinct 'ideas/notion', infrastructure and extensible.
These two ideas are important to a framework.
Next, change the Namespaces for the files so that they make more sense to what we are trying to do.
As an example, I named the Login, MyAppNamespace.Infrastructure.Authentication.Login and ProductList
MyAppNamespace.Extensible.ProductList
Making it Extensible
The next step is to make the elements that we deemed as Extensible, more appropriate for extensibility.
As an example, the ProductDetails UserControl currently shows the details in a set way.
For this we should separate the UserControl into two Types.
- Add a new Class to the Extensible folder.
- Copy the code from the ProductDetails code behind into the new class (overwriting the template class code)
- Rename the class to ProductDetailsBase
- Change all the protected members at the top of the class (the variables for the UI controls) to public
- In the code-behind for the ProductDetails.ascx UserControl, change the inherited Type from UserControl to ProductPageBase
Comment out the code in the load event
Comment out all the protected members
This is not yet complete, but it will run (if it was done correctly). The public members of ProductDetailsBase are instantiated by ASP.NET as it would have in the standard code-behind.
This is not ideal, as the User would have to have created the controls on the ProductDetails.ascx with the same name. We need to change this.
The next step, is to create Properties on ProductDetailsBase for the relevant data for the UI, such as UnitCost, ModelNumber etc.
Then change the public members to private, and use these variables as the internal fields for the properties.
private System.Web.UI.WebControls.Label _UnitCost;
public System.Web.UI.WebControls.Label UnitCost
{
 get
 {
   return _UnitCost;
 }
 set
 {
   _UnitCost = value;
 }
}
Next, in the ProductDetails code-behind, add code the Load event to hook it all up.
private void Page_Load(object sender, System.EventArgs e)
{
 base.UnitCost = this.UnitCost;
   }
You will now have noticed that the protected members we commented out, is now back again, generated by VS.NET.
Remove the commented version of the control variables.
At this point, you have a distinction between the User Interface, the names of the controls on the ProductDetails.ascx page, and the code that sets the values for these controls.
You can move the controls around, or create a new ProductDetailsEx version that derives from ProductDetailsBase and the 'base' functionality remains.
Easy
To properly demonstrate this, create a different ProductsDetails page, that has a different layout, but derives from the ProductDetailsBase.
Once you have done this for all the UserControls in the Extensible directory, you will have the first glimpse of the Framework to be.
The next step
Whats next?
Here is a small list of next plausible steps:
- Separate the project into two (Framework and Application)
- All base classes, Infrastructure remain in the Framework
- Within the Extensible directory we split the usecontrols into the UI and the base class
, therefore we can move the UI elements into new project. This is the Application that uses the framework
- Create some custom tags in web.config for settings ie. Which implementation of ProductDetails should be loaded and add the logic to the PageController Pattern
- Think about tiers, layers and further refactor the application
- Build two simple applications (preferably very different products) using the framework, and extend the Framework as required.