Wednesday, May 28, 2008

MVC Using Workflow (WF) as Model/Controller and ASP.NET as View

Download source code for this article
Agenda

Since workflow aims to provide an application business logic engine, it seems natural that we should be able to plug to it a presentation technology of our choice.

As usual, let's start from the requirements to our solution:
  1. Total separation between View and Workflow (Model/Controller) logic. We assume that Workflow is provided by an external team.
  2. Workflow should be developed with no assumptions on the presentation technology used.
  3. The components should be plugged by configuration.
Workflow definition

We assume it is an event driven state machine. This is how it looks for our example:

For each State we will create an interface with:

    1. Initialize() method, which is responsible for state initialization (in our case opening a form to collect data from the user).

    2. Several events that can happen in this state (user submits data).

    3. SubmitXXX methods, each of them raising a corresponding event.

    For example, for a State where we need to collect a customer name we will create a following interface:

    [ExternalDataExchange] //required for Workflow binding 
    public interface IGetNameState
    {
    event EventHandler NameReceived;

    void Initialize(Guid instanceId);
    void SubmitName(Guid instanceId, string first, string last); //raises NameReceived event.
    }

    At the end of the StateInitializationActivity we invoke the corresponding Initialize() method:


    to show the appropriate view.

    Once we are done for each State, we can provide our workflow to the Presentation team.

    Presentation

    First we must admit that the controller provided by the Workflow does not show any form. Further, it cannot do it by definition, since it does not know how! Instead it calls a relevant Initialize() method to be implemented by the concrete Controller.


    That means that we must provide a concrete Controller implementation to perform the actual navigation, i.e:

    void IGetNameState.Initialize(Guid instanceId)
    {
    HttpContext.Current.Server.Execute
    ("~/GetName.aspx?InstanceId=" + instanceId, false);
    }

    Later on we will see how we plug this Controller in by configuration.

    Now for each state the correct form is shown to the user, but his actions are not handled. Let's do it:

    protected void Button1_Click(object sender, EventArgs e)
    {
    WorkflowRuntime workflowRuntime = (WorkflowRuntime)Context.Application["WorkflowRuntime"];

    workflowRuntime.SubmitName(
    new Guid(Request.QueryString["InstanceId"]),
    txtFirstName.Text, txtLastName.Text);

    // Once we call Workflow, it's responsible to route the request
    Response.End();
    }
    Initialization and Configuration
    • Initialization:

      In the Application_Start handler we initialize the WorkflowRuntime:

      WorkflowRuntime workflowRuntime = new WorkflowRuntime("WorkflowRuntime"); //passing the configuration section name 

      workflowRuntime.StartRuntime();

      Application["WorkflowRuntime"] = workflowRuntime;

      and in Application_End we stop it:

      WorkflowRuntime workflowRuntime = (WorkflowRuntime)Application["WorkflowRuntime"];
      workflowRuntime.StopRuntime();
    • Configuration:

      Let's configure ExternalDataExchangeWorkflowService with our concrete Controller type:
      <ExternalDataExchangeWorkflowServices>
      <Services>
      <!-- The concrete Controller implementation -->
      <add type="Workflow.RuntimeServices.Controller, Controller, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3a221384c77ffbe5"/>
      </Services>
      </ExternalDataExchangeWorkflowServices>
      <WorkflowRuntime>
      <Services>
      <add type="System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
      <add type="System.Workflow.Activities.ExternalDataExchangeService, System.Workflow.Activities, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" ConfigurationSection="ExternalDataExchangeWorkflowServices"/>
      </Services>
      </WorkflowRuntime>
      Summary

      This tutorial helps to build a skeleton implementation of ASP.NET front-end for Workflow Controller. In our design we put a special attention on separation and correct interaction between the two. A very important property is that the Workflow does not dictate the implementation to the Presentation layer, its work can be performed in one or several steps. Only when all the information required by the Workflow is collected, it is invoked. For example, to collect the user full name, consisting of first and last names, the presentation may open one or two forms; provide any validation logic it wants and only at the end Submit the information. Such approach separates the Big application flow from the Presentation, opening a possibility to customize/update/change technology/upgrade it in any possible way.



    Saturday, May 24, 2008

    ASP.Net Unit Testing

    Some tell there are many solid solutions by HP (Mercury) or Microsoft's TeamSystem, some claim that all those are not Unit, but Integrative tests. And there are many that mostly perform manual testing... Who is right and what is the best approach?

    As usual, let's start from the requirements from the testing framework.

    1. Granularity. We are talking about unit testing, the name suggests that we want to test application units, separately.
    2. Ability to automate. Definitely we want an ability to run the tests automatically, for example after a build. This requirement also yields a requirement for results stability through the application life cycle.
    3. Coverage. Every part that is not covered by a unit test joins the risk group of deferred bugs. Even if there is integrative testing, it probably does not run frequently and therefore our control over correct system status reduces when the un-unit-tested group grows.

    Let's partition our application and see how we can test every part.

    1. Libraries, that do not require any "web context". Here we can simply write plain unit tests. No problems.
    2. Custom and User controls. In fact, those are also libraries, providing a well defined API, that can be systematically tested... But how to do it if they require "web context"?
    3. Pages, pages, pages, which actually produce our application on the server side and...
    4. Client side logic - let's not forget all the Java Script we run on the client browsers.

    Unfortunately most of the "well known" solutions, including those mentioned at the beginning address mainly point 3 above. The common pattern is to record the requests/responses sent/returned by the server; later sent those requests back to the servers, receive the response and validate it again previously recorded one.

    This introduces several problems:

    1. Granularity (since 'Custom and User controls' part is handled on the 'Page' level). When the test fails, we know that the Page failed, not some specific control and therefore had to invest more time in regression diagnostics.
    2. Ability to automate. Since the page evolves over time, our old recording may become invalid, even if everything is correct. This produces many false alarms, preventing automation.
    3. Coverage. Client side logic is left untested.

    In other words we have a problem in every aspect of our requirements, what can we do?

    1. Re-introducing test-driven development. Since not every code can be easily tested, we must include its testability into the very beginning of its design phase. The rule of dumb says that testability reduces when we move forward over the following path: 'Simple Library' -> 'Custom/User control' -> 'Page' -> 'Client Side logic'. This means that we must strive to move our application code as 'left' as possible.
    2. Create unit tests for our Custom/User controls. We use to create a separate test for every class in 'Simple Library', the same we can do with controls. Once we have a dedicated test page per control, it does not change when the application changes, improving our 'Ability to Automate' over time.
    3. Client Side logic testing. Since client logic runs in the browser, in order to test it, it must run in the browser, period. Any other option simply does not do the job. One of the best tools I know to run tests on the browser is Selenium - it will be covered in the next article.

    Summary

    1. Partition your application as described above.
    2. Try to move as much code as possible 'up' within the partition.
    3. For each User/Custom controls write a dedicated test page to test its functionality.
    4. Use some tool for testing client side logic, my recommendation is Selenium.
    5. If you go with Selenium for client side logic, for consistency use it also for Pages testing.