![]() |
Greg's App Infrastructure | |
| Home « Computers « DevBlog | ||
Back to: Development Blog Contents
An essay about my need for a framework library that contains common code that can easily be used to create applications of different types.
History
For many years now, each time I created a new Windows forms or ASP.NET web application I found that I was cloning huge amounts of code that perform common tasks such as logging, configuration, emailing, qualifying files and folders, loading resources, messaging, etc. I held a lot of the common code in master copy files, but it was inconvenient to propagate changes over multiple files in different applications. Other parts of the common code had diverged to suit the needs of different applications, so I had similar blocks of code scattered over multiple applications.
For years I had been planning to extract all of the common code into a reusable library, but the different needs of forms applications versus web applications frustrated my plans. For example, folder locations, resource handling and state management differs significantly across different application types. I knew that all of the differences between the application types could be abstracted and handled by different implementations, but I never found the time to work through the whole job.
The pattern I traditionally used for an application infrastructure was based upon having a singleton (or static) class that held references to other classes that performed all important work (logging, resources, email, etc). The singleton class was available from anyhere in the AppDomain so it was simple to get references to the work classes from anywhere. In forms apps the references would be alive between the Load and Closed events of the form's lifetime. In web apps the references would be alive between the Application_Start and Application_End global lifetime.
Thinking about "Services"
At Code CampOz 2008 I was casually discussing the general problem of how you handle common reused tasks in applications and when I described my pattern that used a singleton class, someone replied "so the singleton class acts as a kind of service locator". Now, those words galvanised me, because years earlier I had used Sun's JINI and Jiro technologies which used a real service locator, one that could multicast to find distributed services.
I realised that if I strictly thought of my singleton class as a "service locator" and all of the other work classes as "services", then it became much clearer about how structure them in an application-neutral and reusable way.
In late 2007 I had been working with CAB (Composite Application Block), and although I found it far too heavyweight for developing moderate sized apps single-handed, it contained two great ideas that I really liked:
The design takes shape
After all of the discussions and experience mentioned above, a clear plan formed in my head about how to create a reusable application-neutral inrastructure library. My needs were modest, so I only need to formalise the best ideas for use within a single AppDomain. UI patterns and remote services were not necessary (yet).
All
classes that encapsulated some common funtionality would be called a "service"
and would have their lifetime and basic behaviour defined with the help of an
interface and a convenient abstract base class. I would define some standard
services for logging, configuration, emailing, etc, but it would be easy to
write another service.
A
singleton class I call the "service locator" would still exist for
the purpose of locating, loading and returning references to the services. The
services to be loaded would be defined in the application config file. Parent
applications would not need to know where the services come from or how they
are loaded or stored, the service locator would hide those details and simply
hand out references to objects that implement a specific service interface.
By following these ideas I could create the infrastructure library I had needed for so many years. There was still the problem of how to deal with the different application types, but I could create two services classes that implement the same interface, one for forms apps and one for web apps. The config file would say which one was to be loaded and used. A good example would be the abstraction of session state in a web app. No longer would a web app code something like this:
Session["SomeKey"] = someObject; anotherThing = (MyClass)Session["AnotherKey"];
A "state service" would be created, one for web apps that would use the Session collection as the backing store (the default for web apps), and another for forms apps that used a Dictionary<string,object> as the artificial backing store. Both apps could now code something like this:
IStateService state = ServiceLocator.GetService<IStateService>();
:
state.PutSession("SomeKey", someObject);
anotherThing = state.GetSession<MyClass>("AnotherKey");
The library is created
On a weekend in late October 2008 I worked feverishly to convert my clearer mental picture of the library into code. I took the best pieces of existing apps and refactored the stub of a recently created live web site to use the new library, and at the same time I used a test forms application to use the library. Over the following week I tweaked the code slightly to ensure that web related code and references were only inside service classes that were web related.
Following is a description how a parent application would define and use some infrastructure services.
A custom section defines the behaviour of the service locator and the services it will load. The sample below defines 3 services: logging, configuration and file persistence. Each <service> element defines the class that implements the service element and it can have optional <parameters> as child elements that are passed to the service when it is started. The logging service has 3 parameters and the others have none.
<configSections>
<section name="infrastructureSection" type="Orthogonal.Infrastructure.ConfigSectionDecoder, Orthogonal.Infrastructure"/>
</configSections>
:
<infrastructureSection>
<services>
<service type="Orthogonal.Infrastructure.ImplLogServiceRollFile, Orthogonal.Infrastructure" sequence="10" name="Log" isLogger="true">
<parameters>
<baseName>${LocalApplicationData}\${AssemblyCompany}\${AssemblyProduct}\${Major}.${Minor}\sample.log</baseName>
<rollCount>4</rollCount>
<maxKB>128</maxKB>
</parameters>
</service>
<service type="Orthogonal.Infrastructure.ImplConfigService, Orthogonal.Infrastructure" sequence="20" name="Configure" />
<service type="Orthogonal.Infrastructure.ImplPersistServiceFile, Orthogonal.Infrastructure" sequence="30" name="Persist">
</services>
</infrastructureSection>
Don't be frightened by the ${XXX} tokens in the parameters, as these are simply optional variables that get replaced with real values at runtime. This is very handy to avoid hard-coding special values in the config. The sample <baseName> parameter combines some variables to refer to a folder under the user's local settings.
The parent application will locate and use the services in the following way.
//-- When the application starts, the following call will tell the
//-- service locator to instantiate all service classes defined in
//-- the configuration file section.
ServiceLocator locator = ServiceLocator.Instance;
locator.StartManager();
:
//-- When a form or web page loads you can create convenient references
//-- to the services to use them easily in the code.
ILoggingService logger = ServiceLocator.GetService<ILoggingService>();
IConfigService config = ServiceLocator.GetService<IConfigService>();
IPersistService persist = ServiceLocator.GetService<IPersistService>();
:
//-- Somewhere in the code you can now call these services.
logger.Info(this, "This is a sample logging information message");
bool specialModeFlag = config.GetBool("specialMode");
string filename = persist.MakeDataFile("foobar.txt");
:
//-- When the application closes, you can make the following call, but
//-- the locator will be disposed nicely even if you forget.
ServiceLocator.Instance.Dispose();
I know it's a little bit more combersome to use a general purpose library this way, but the advantage is that huge amounts of potentially duplicated code have been extracted into a common library and can be reused in both forms applications and ASP.NET web applications. The layer of abstraction between the parent application and the services also make your code more transportable.
Controllers
I have adopted the CAB Guidance technique of having a controller class behind all controls, keeping the control code-behind as thin as possible (as I mentioned previously). As a result, I find that the contoller classes are frequently making calls to services to perform work, so frequently in fact that I created an IController interface and a ControllerBase class that can be used as a base for any controllers that use services. The base class caches some references to the services on first use and it contains a set of miscellaneous "convenience" methods to make the controller code less cluttered.
The code in a controller for a web page might look something like the following example. The LogInfo, GetConfigBool and PutSession methods are in the base class which actually calls the underlying services. The convenience methods make the code easier to read.
public class WorkPageController : ControllerBase
{
public void DoSomeWork()
{
// A button click might call this method for example
LogInfo(this, "The button was clicked to do work");
if (GetConfigBool("someOption"))
{
PutSession("WorkTime", DateTime.Now);
}
// Imagine the following service is not one of the standard ones in the
// infrastructure library, but one you created to perform your own work.
ISpecialService spec = ServiceLocator.GetService<ISpecialService>();
spec.DoTheWork();
}
}
The ControllerBase class is app neutral and can be used as a base for code-behind controllers of forms controls or ASP.NET controls. The built-in Command and Message services can be used to allow different controllers to "talk" to each other.
Built-in Services
I have the following services built into the infrastructure library. I find that my "data layer" or security code differs greatly in different apps, so I haven't tried to factor out common code from them yet. I do have plans for a service that works with the ASP.NET 2.0 membership facility.
| Interface | Implementation |
|---|---|
| ILoggingService | Buffers logging records and saves them in a set of rolling files. Parameters specify the location and name of the file set, the maximum number of files in the set and the maximum size of a file. |
| ICommandService | Registers named commands and associates them with Form controls. Control events fire the command to all listeners. Commands can be set to enable/disabled and selected/unselected states and associated controls have their UI automatically adjusted to match the command state. |
| IConfigService | Reads values from the configuration file. The backing is currently the standard application configuration file. |
| IMailService | Sends emails. Parameters specify the SMTP server address and optional authentication user and password. One or more parameter sets can be specified, keyed by the domain name, and the appropriate one is picked at runtime. |
| IMessageService | A central "sink" takes messages and then event broadcasts them any listeners. |
| IPersistService | Provides ways of locating standard folders and manipulating file names. There is one implemenation for forms and one for web apps. |
| IResourceService | Loads resources. Implementations are provided for standard embeded resources and resouces loaded at runtime from resx or txt files in a forms or web app environment. |
| IStateService | Provides a facility like ASP.NET Session state. |
I find that I often derive from the IPesistService to define my own special folders and utility file methods. The Command and Message services are new and experimental, and they are simple versions of the similar facilities built into WPF and CAB.
Download
My infrastructure library is very new, but I've provided a download just in case anyone might find it useful as a learning exercise (and I always welcome technical feedback). There is a slight problem at the moment caused by the fact that the infrastructure library has a reference to my csutylib class of various utility classes. The dependence is only small, but irritating, and I'm working on a way of removing the reference to make the infrastructure library self-contained.
| infrastructure.zip — A zip of both the infrastructure library and the csutylib utility library that it is curently dependent upon. The source does not have comments in all of the vital places yet, but I certainly plan to add them as time permits. There is an NUnit test, a test ASP.NET app and a test forms app that all call infrastructure services to prove that the environments are working. You will have to register http://localhost/testweb with IIS or delete it from the solution to allow a clean compile. |
Back to: Development Blog Contents