Tuesday, October 18, 2011

Applying SRP to WebForms

Most applications based on ASP.Net WebForms fall foul of good OO design practices because of the page life cycle and the plethora of events exposed by the many web controls. One of the key principles of good OO design is the Single Responsibility Principal (SRP). I often find that this is either completely ignored or not used enough when an application is based on ASP.Net WebForms. SRP states that every object should have a single responsibility and that responsibility should be encapsulated by its class.

With WebForms, business logic is written in the many event handlers which are part of the WebForms model. This is a hang over from the Visual Basic days when true client server applications were being built. Here the UI was a procedural wrapper over a set of Stored Procedures.

SRP forces you to ask if the code you are writing belongs in that class. If it does not, then a new class is needed for the job. Following this practice leads to a well factored code base, full of objects doing one job. In the case of the WebForm it is now only responsible for building the UI and handling the HTTP requests and response. This avoids complex and overly long WebFoms that are hard to understand and difficult to debug.

Violating SRP

In this example, the Page is a form gathering contact details from the visitor. The visitor could have arrived from a marketing campaign. The tracking codes for the campaign are a comma list of key value pairs stored in a Cookie. The four values extracted from the cookie must be included when submitted to the process which writes to the database.

A common implementation is to extract the values from the Cookie in the page load event and store them in a field on the form. When the event fires to save the form, the values are passed to the method which writes to the database.


I feel this method of working is poor. Sure, it will work. You can extract the values and send them to the database. However, the Page should only be responsible for managing the incoming Request, the out going Response and building the UI. Also, what if there are many forms on the site which have to capture this information? The code will be duplicated in many places causing problems if the name of the cookie changes. Instead I prefer to hand the task of capturing data from the cookie to a couple of classes which encapsulate the process and return a single object for the marketing data.

Applying SRP



All that this page is responsible for is passing the CookieCollection to the MarketingTrackerBuilder object. It then stores this in a private field to be passed on to the database when the form is submitted.


This class is essentially a DTO. It has no other job than to store the four pieces of information about the campaign which brought the visitor to the website. It also implements an interface. We will see why that is useful later.


Most of the work is being done in the Builder. The class knows how to extract the fields from the cookie. It is also where the name of the cookie is defined. Keeping this information here means that if anything to do with reading the cookie changes, it will only change here. Often this kind of code is scattered around many WebForm pages. Then a change to the implementation requires a search and replace on the entire code base.

When the Build method is called it first checks that the cookie exists. If the check fails it returns an instance of a NullMarketingTracker.


The NullMarketingTracker object is the reason for using the IMarketingTracker interface. We are now free to substitute the type returned as long as we code to the interface. If you review the code you can see that all references to the MarketingTracker have used the IMarketingTracker interface.

Now when values are written to the database, there is no need to check for a null strings first.

Summary

The Single Responsibility Principal is a great way to think about structuring code. By applying this to the WebForms Page object I decided that its only responsibility is to deal with the incoming request and the outgoing response. By further applying it to the code which captures the cookie data, the final design is well structured and easily maintained. If the implementation changes then the change will not ripple through the code base.

I find that this a good way to work and a great way to keep the code behind files readable and manageable.

No comments: