pk software dev blog

custom microsoft .net application development

23. September 2008 17:10
by Paul

Separating Concerns within Windows Applications

23. September 2008 17:10 by Paul

When coding, the Separation of Concerns (SoC) is a principal that basically will help organise your applications and make them more manageable. Dependency Injection (DI) typically goes hand in hand with SoC. I thought I would take a look at how to apply “SoC” and “DI” to a windows application...

The example used in this document references Windows Forms, but the principals are equally applicable to a WPF application.

The Approach

The default template when you create an application has a static main and fires up a form. Problem is that from here we start to push functionality into the form, add resource files, create settings files etc. Pretty quickly, we end up with a form (which keep in mind is a class) that reaches out to many different “services” or “content providers” made most conveniently for you by the development environment. Problems arise quickly when you start to move that form to another assembly for example. Other issues could come into play if the default user settings classes just don’t cut it and you need to make extensions or change the storage method. Also, there are issues testing a form through a test harness of some sort, let alone automating it. We run into issues where by testing one form is hampered by the rest of the application.

Now I don’t want to lose the convenience of these resource and settings editors but I want to make the whole experience more fluid.

Some Simple Abstractions

Using interfaces is a great way of tackling the whole SoC game. At the end of the day it doesn’t matter what the actual object is as long as the interface is meet we have a complete contract.

To organise the solution there will be a few projects to help us code with SoC in mind. When using interfaces it is useful to store them in a separate assembly, “Common”. From here we create a separate windows library that contains most of the “GUI doing” code (the forms and controls for example). Finally, add a windows forms application project – this is the entry point for the windows application.

·         PKSoftware.SocDemo.Common – A plain old Class Library project

·         PKSoftware.SocDemo.Gui – Windows Forms project

o   Add a reference to the “PKSoftware.SocDemo.Common” assembly

·         PKSoftware.SocDemo.EntryPointWindows Forms Application project

o   Remove “Form1”

o   Modify the “Assembly Name” from “PKSoftware.SocDemo.EntryPoint” to just “SocDemo” (it just makes locating the EXE easier for the user)

o   Add a reference to the “Common” and “GUI” assemblies

Notice how the references of the project are set up, this is important. They tend to reference the common library and we will push instances into the runtime of the application.

Next create a form (MainForm) in the GUI project and wire it up via the “entry point”. This is the skeleton of our application.

Setting Up the Settings

Using an interface for settings is a good solution to the traps we can fall into with such a common requirement. Once we have a settings interface we simply pass any class meeting that contract into the windows application to use.

Let’s define an interface with a window positioning property (a Point). This interface will exist in an assembly external to the main form, the “Common” assembly:

//~~ PKSoftware.SocDemo.Common\IApplicationSettings.cs

using System;

using System.Drawing;

namespace PKSoftware.SocDemo.Common

{

       public interface IApplicationSettings

       {

              Point MainWindowPosition { get; set; }

              Void Save();

       }

}

//~~//

Now, back in the “EntryPoint” assembly, create the default settings file (or a specific one, it doesn’t really matter) and set the access modifier of the settings file to “Public”.

Next add a settings called “MainWindowPosition” with a type of “System.Drawing.Point”.

Now, because the settings designer is a partial class (see any “Settings.Designer.cs” file or press “View Code” to see) we can apply an interface via a separate file, for example, create a “Settings.Interface.cs” with contents such as:

//~~ PKSoftware.SocDemo.EntryPoint\Settings.Interface.cs

using System;

using PKSoftware.SocDemo.Common;

namespace PKSoftware.SocDemo.EntryPoint.Properties // NOTE the namespace

{                                         

       public partial class Settings : IApplicationSettings

       {

       }

}

 

NOTE: the namespace, “PKSoftware.SocDemo.EntryPoint.Properties”. Without this the interface is not applied to the generated settings class.

If everything is set up correctly you can compile the application successfully. If you get such as this below:

... error CS0535: 'PKSoftware.SocDemo.EntryPoint.Settings' does not implement interface member 'PKSoftware.SocDemo.Common.IApplicationSettings.MainWindowPosition'

Then the problem lies with the namespace of the partial file as mentioned above.

From this point on we refer to the settings file not via the “Properties.Settings.Default” reference, but by a reference to an instance of “IApplicationSettings”. This is where the “Separation of Concerns” comes in – the reference to “IApplicationSettings” could be implemented by anything. Currently it’s the standard IDE Settings generated class, we can easily change the implementation to use our own file based settings, or even query them from a common location or the registry.

Using the Settings Interface

Now let’s make use of the “IApplicationSettings” interface within the GUI. Constructor injection is a great way of making sure that a dependency is present. Extend the “AppForm” to accept the settings via the constructor storing a reference and then making use of the values. On closing the form we set the location only if the window state is normal (if you are not sure why experiment a bit). The minimised/maximised and normal settings would need to be a separate setting.

//~~ PKSoftware.SocDemo.EntryPoint\Program.cs

using System;

using System.Windows.Forms;

using PKSoftware.SocDemo.Common;

namespace PKSoftware.SocDemo.Gui

{

       public partial class AppForm : Form

       {

              private readonly IApplicationSettings _applicationSettings;

              public AppForm(IApplicationSettings applicationSettings)

              {

                     _applicationSettings = applicationSettings;

                     InitializeComponent();

              }

              private void AppForm_Load(object sender, EventArgs e)

              {

                     Location = _applicationSettings.MainWindowPosition;

              }

              private void AppForm_FormClosing(object sender, FormClosingEventArgs e)

              {

                     if (WindowState == FormWindowState.Normal)

                     {

                           _applicationSettings.MainWindowPosition = Location;

                     }

              }

       }

}

NOTE: In previous versions of the VS.NET IDE I found the designer to misbehave when a “parameterless” constructor was absent but things seem better now. If you do run into issues simply overload the constructor.

Next the entry point needs to be modified to supply the settings to the form and also to save the settings (I leave this at the application level in this example but different requirements may see this type of setting saved more frequently).

//~~ PKSoftware.SocDemo.Gui\AppForm.cs

using System;

using System.Windows.Forms;

using PKSoftware.SocDemo.Common;

using PKSoftware.SocDemo.Gui;

namespace PKSoftware.SocDemo.EntryPoint

{

       internal static class Program

       {

              [STAThread]

              private static void Main()

              {

                     Application.EnableVisualStyles();

                     Application.SetCompatibleTextRenderingDefault(false);

                     IApplicationSettings settings = Properties.Settings.Default;

                     Application.Run(new AppForm(settings));

                     settings.Save();

              }

       }

}

Making use of a Test Harness

Now I am going to extend the solution a bit to include a test harness, in this case for the “GUI” assembly. Create the following project:

·         PKSoftware.SocDemo.Gui.TestHarnessWindows Forms Application project

o   Rename “Form1” to “MenuForm”

o   Add a reference to the “Common” and “GUI” assemblies

Now I want to test the “AppForm” in the “GUI” assembly with controlled settings. Add a class called “StubAppSettings” and implement the “IApplicationSettings” interface, you can use auto properties etc and the “Save” method does not need to do anything.

//~~ PKSoftware.SocDemo.Gui.TestHarness\StubAppSettings.cs

using System;

using System.Drawing;

using PKSoftware.SocDemo.Common;

namespace PKSoftware.SocDemo.Gui.TestHarness

{

       public class StubAppSettings : IApplicationSettings

       {

              public StubAppSettings()

              {

                     MainWindowPosition = new Point(100, 100);

              }

              #region IApplicationSettings Members

              public Point MainWindowPosition { get; set; }

              public void Save()

              {

              }

              #endregion

       }

}

Next, just wire up a simple invocation of the form from the menu:

//~~ PKSoftware.SocDemo.Gui.TestHarness\MenuForm.cs

using System;

using System.Windows.Forms;

namespace PKSoftware.SocDemo.Gui.TestHarness

{

       public partial class MenuForm : Form

       {

              public MenuForm()

              {

                     InitializeComponent();

              }

              private void brnShowAppForm_Click(object sender, EventArgs e)

              {

                     AppForm frm = new AppForm(new StubAppSettings());

                     frm.Show();

              }

       }

}

Set the project as active or run the test harness via the file system after a build, whatever you find easier.

Now when we run the test harness the “AppForm” can be tested separately from other requirements. From here you can set up canned settings etc which is easily done through the “StubAppSettings” class.

 

Comments (1) -

Comments are closed