Compartilhar via


WCSF Application Architecture 4: Environment Abstraction

This article is part of a series;

· WCSF Application Architecture 1: Introduction

· WCSF Application Architecture 2: Application Controller

· WCSF Application Architecture 3: Model View Presenter

Abstraction Contraption

My previous (and future!) posts have focused on some specific sections of the Web Client Software Factory (WCSF), but this one is a little less focused. The point of this post is to encourage you to consider when you should be abstracting away from ASP.NET concepts, and to see how you can achieve this relatively easily.

Why?

As usual, then, the first question is “Why?” There’s no point in doing something if we don’t have a reason. I think there are two very good reasons to abstract away from ASP.NET concepts, and one less good but still valid reason.

1. Testability

2. Good Design

3. Portability

I’m sure you’ve noticed I like presenting lists, and then working through them, so let’s stick to my standard approach!

Testability

Many people adopt patterns such as MVP and Application Controller to help enable them to use automated tests to exercise their code. However, imagine your Application Controller is handling page flow, and needs to redirect the user to the home page. Consider the following code;

void CompleteWizard()

{

    HttpContext.Current.Response.Redirect("Home.aspx");

}

This looks like a perfectly reasonable way to achieve the desired effect... until you come to write a unit test. Most testing frameworks will give you a NullReferenceException when running this code, because there is no “Current” HttpContext. Even if this was not a problem, we might want to test that the correct page has been navigated to – and this is very difficult in the above example.

Good Design

Object Oriented Design is hard, there’s no two ways about it. If you think it’s easy, I’d encourage you to take a step back and think about alternative approaches to the code you wrote yesterday, and you might be surprised! I don’t mean your code is wrong – I mean there are many different ways to design, many different patterns, and many shades of gray. There are almost certainly other ways of writing that code. What should I abstract? What is an acceptable trade-off between pure OO design and pragmatic deadline-focused delivery? No answers here I’m afraid – experience helps, but it will always be a tricky subject.

However, there are two questions I like to ask myself when considering a class design;

1. Does this code do everything that it needs to in order to meet the objective?

2. Does this code have knowledge of anything or perform any tasks that are not core to its primary objective?

This is basically my simplified (and no doubt watered down) interpretation of the Principle of Least Knowledge / Law of Demeter. See an interesting discussion about this on Derek’s Blog.

So if we consider my Application Controller example above, the answer to the second question might be “yes – it has knowledge of the ASP.NET infrastructure”. It is now your call as to whether this is a problem. Sometimes pragmatism must win over, and we’ll ignore the issue – other times, we should take steps to clarify our class responsibilities a little.

Portability

Portability is actually very closely related to testability, so although I’ve flagged this as my “less good” reason it is still completely valid. Basically the question to ask yourself is;

“If I change this application to be a WinForms/WPF/Silverlight/Mobile/WCF/etc (*) app, what will fail due to the lack of an ASP.NET environment?” (* delete as appropriate)

Using my example above, I would have to answer that the Application Controller would fail. This is related to testability as obviously a unit test host is another environment in the same way that a WinForms host is – so I’m sure you see the parallel.

Now this might be very important to some people, and I would encourage you to consider it. However, I would also strongly advise that you exercise caution. Converting an ASP.NET application to run in WinForms may sound great, but in reality it is not straightforward. You need to consider the different way in which users interact with a system (post-backs versus in memory applications), how things are rendered (different data-binding, mark-up, styling, etc), and more. Supporting both WinForms and WebForms with the same code base at the same time is also more difficult than you might imagine – it can be done, but requires careful thought and skilled development.

 

How? A Simple Example

The ideal way to handle environment abstraction in the WCSF is to have one or more “Infrastructure” Foundation Module(s) that publish global services. We’ll discuss modules more later, so I won’t go into the details of this decision, but just take away that we’re making global abstraction services available to my application.

To get the most from this series of posts you need to know a little about the WCSF, but in case you’re not too familiar with it one word of warning! Don’t confuse Services in the WCSF with Web Services, Service Agents, a Service Interface, customer service representatives, or any other kind of service!! Services simply provide a way to expose common functionality across a whole application (including across modules) in a loosely-coupled manner. A service is defined by an interface, and implemented in a C# class – then it is registered by a module for consumption by any other component that needs it.

So looking at the code, an example navigation service might have an interface like this;

interface INavigationService

{

    void Navigate(Screen screen);

}

You could easily just take a string argument to this method that specifies the name of the destination page, but I think this leads to a tendency to include the path and location of each page in the Application Controller. Therefore I create a class named Screen that looks like this;

class Screen

{

    private Screen(string location)

    {

        _location = location;

    }

    private string _location;

    public string Location

    {

        get

        {

            return _location;

        }

    }

    public static readonly Screen Home = new Screen("~/Home.aspx");

    public static readonly Screen Bookings = new Screen("~/Bookings.aspx");

    public static readonly Screen EditProfile = new Screen("~/Edit/MyProfile.aspx");

}

Note that the constructor is private, so only Screen can create instances of itself. I then create a set of static instances that point to specific screen locations. This means the Screen class appears like an enum to the casual observer, yet I have encoded behaviour information into each instance that consumers can use. If I wanted to, I could mark the Location property “internal”, so that only the assembly it is contained in can use it, thus hiding even more implementation.

One important point to note here is that I should really take this a step further and abstract Screen into an IScreen interface and web specific implementation. This is because my implementation above does include information on the web pages locations. I deliberately haven’t done this though to keep this example simple, and to demonstrate one key subject at a time. If this doesn’t seem clear shout up and I’ll elaborate in another blog post.

Anyway, next we need an INavigationService implementation that works in the Web world.

class WebNavigationService : INavigationService

{

    public void Navigate(Screen screen)

    {

        HttpContext.Current.Response.Redirect(screen.Location);

    }

}

This should be pretty self explanatory, and of course we must register this implementation in our module initialising class;

class MyModuleInitializer : ModuleInitializer

{

    protected virtual void AddGlobalServices(

    IServiceCollection globalServices)

    {

        globalServices.AddNew<WebNavigationService, INavigationService>();

    }

}

This creates a new instance of a WebNavigationService, and registers it as an INavigationService for consumption by any WCSF module in the current solution. We’ll talk about structuring modules in another post, remember.

The final important step is to consume our new service. Consider the following Application Controller class;

class ApplicationController

{

    private INavigationService _navigationService;

    [ServiceDependency]

    public INavigationService NavigationService

    {

        set

        {

            _navigationService = value;

        }

    }

    public void CompleteWizard()

    {

        _navigationService.Navigate(Screen.Home);

    }

}

This CompleteWizard method is the very same method we saw earlier, but now we have no direct dependency on an HttpContext. Instead we simply call into an INavigationService implementation, a reference to which is kindly provided to us by the WCSF when the controller is created using Property Injection (note the ServiceDependency attribute).

Prove it!

So what’s the difference now? I can now unit test my Application Controller independently. Of course, there is nothing wrong with testing a number of different classes at once – just remember that if you do this chances are it isn’t a unit test any more, but instead is an integration test. Both of these types of test are invaluable, but I always like to start with a unit test.

A really simple example unit test for our CompleteWizard method might look as follows;

class ApplicationControllerTest

{

    void TestCompleteWizardGoesHome()

    {

        // setup for test

        MockNavigationService navService = new MockNavigationService();

        ApplicationController controller = new ApplicationController();

        controller.NavigationService = navService;

        // attempt navigate

        controller.CompleteWizard();

        // check results

        Assert.AreEqual(navService.LastDestination, Screen.Home.Location);

    }

}

class MockNavigationService : INavigationService

{

    public string LastDestination { get; set; }

    public void Navigate(Screen screen)

    {

        LastDestination = screen.Location;

    }

}

 

Summary

I hope this has helped you see when it is really useful to abstract away from the environment using Services in the Web Client Software Factory. It is also really nice to apply to things like Logging, Tracing, Authorisation, and so on – application infrastructure if you like.

Getting the balance right between when to abstract and when not to is difficult – but do remember that a) once you have chosen not to it is harder to back track than you think, and b) once you’ve chosen to abstract you’ll add to your workload!

I still have nightmares about a fictional scenario with all those HttpContext.Current calls scattered through a whole solution, and trying to rationalise them 1 week before code complete is due...

Comments

  • Anonymous
    June 16, 2008
    The comment has been removed

  • Anonymous
    June 17, 2008
    Having written and posted this, I'd completely forgotten that the Modularity quickstart contains a very very similar example until I revisited it today!! So be sure to check this out if it sounds interesting; http://www.codeplex.com/websf/Wiki/View.aspx?title=ModularityQuickStart The bundle even includes a code download, so you can play with it instead of just read it.

  • Anonymous
    June 18, 2008
    This article is part of a series; · WCSF Application Architecture 1: Introduction · WCSF Application

  • Anonymous
    July 03, 2008
    This article is part of a series; · WCSF Application Architecture 1: Introduction · WCSF Application

  • Anonymous
    July 18, 2008
    This article is part of a series; · WCSF Application Architecture 1: Introduction · WCSF Application

  • Anonymous
    July 31, 2008
    Amazing Job!.  U rock men! U did that the interesting for WCSF returned. Please continue abstracting WCSF stuff.  I have been  waiting to Puleio Finish the WCSF with Unity but It had been a wait long. But you bring new stuff about and answer many questions too!.

  • Anonymous
    August 24, 2008
    Nice writeup. How compatible are the CAB and the WCSF when it comes to developing applications that work across both when abstracted properly as you've shown in this article?

  • Anonymous
    August 25, 2008
    The comment has been removed

  • Anonymous
    November 13, 2008
    This is the fourth post in the series WCSF Application Architecture by Simon Ince, this post explores

  • Anonymous
    November 14, 2008
    This is the fourth post in the series WCSF Application Architecture by Simon Ince, this post explores

  • Anonymous
    November 21, 2008
    This is part 5 of the WCSF Application Architecture series by Simon Ince this post explores the topic