Saturday, January 30, 2010

Evolution of test coverage

One of the projects I've worked on over the last couple of years has been benefitting from ever-increasing test coverage. The data layer was originally written using DataSets, but awhile back was moved to a domain model with NHibernate as the ORM. The domain model adheres quite closely to the database ERM (one class per table). A unit testing layer has been built along with the domain model. The unit testing layer has been growing/evolving as new business requirements have been added to the project.

The tests have adhered closer to a "test-last" or "test-middle" model than a "test-first" model, since initial tests were built immediately after migration of the domain model:
  • First, the domain model was created, with entity classes mapped closely to the underlying database tables
  • Second, the original business logic was migrated across, which in effect redistributed it from Transaction Script pattern to Domain Model pattern.
  • Finally, an initial set of unit tests were built against the classes of the domain model.
Since then, unit tests have been built at two levels of scope, corresponding roughly to two different points of entry on a sequence diagram. In the domain model, the sequence diagram would begin with a higher-level entity, and then drill down into (and come back up from) lower-level objects in the domain. Therefore:
  • If a unit test is addressing an individual entity method farther to the right in the sequence diagram, it will be small and focused exclusively on verification of the behaviour of that method.
  • If a unit test is addressing a method at the far left of the sequence diagram (a point-of-entry), the test will tend to be much larger, begin with a large stub, and then verify multiple points within an object graph at the end of the test. These larger tests tend to be grouped, with each version of the test checking a different scenario from a matrix diagram.
In the current project, the combination of the above tests has evolved to 300+ tests, and this has provided an invaluable safety net to more easily make changes to the domain model in response to ongoing business requirements.

So, does this imply that QA no longer has any work to do? No, but the issues that are found by QA now tend to concentrate elsewhere, either in:
1) Newly discovered business logic that differs from the understanding currently reflected in the domain tests (resulting in 95% domain coverage rather than 100% domain coverage.)
2) Layers ABOVE the domain model layer that are more difficult to test, including:
     a) Repository (database query) layer: missing query information
     b) Service layer: incorrect coordination of calls to repository and domain tiers
     c) Remote facade/web service layer: missing elements or nulls when mapping to/from web service DTOs or DataSets

Integration tests do exist for testing a complete process, including database interactions and the service layer, but these tend to be tied to specific data and require some amount of setup, often involving cooperation from QA.

On a couple of previous projects, I have had some success with greater automated test coverage across all layers, but this required use of a database sandbox: rather that working with a copy of production data (which tends to be large, constantly evolving, and therefore poor for testing multiple integration scenarios) instead build an entire database from a script, which can then be dropped/recreated before each run of the automated tests, populating the sandbox with only the subset of data required for the integration tests.  

Conclusion

The ultimate goal for test coverage is to be as complete as possible, covering every layer, not just the domain model, but all levels above it, up to and including the client.

Wednesday, January 27, 2010

The Vetrinary Admin: Linking TDD Kata to creating user stories

Today is my 12th day of doing the TDD kata experiment. Mostly it has been with the Calculator kata, but I've started experimenting with creating new katas. First was a Model-View-Presenter kata with mocks, but I am now taking this one step further.

What the TDD kata makes obvious is the practise of writing tests based on a user story.

Historically I am more accustomed to working from a detailed requirements spec, which the last few years has tended to look like this:

1) Read the spec
2) Create a domain model and NHibernate mappings, to database tables which typically were created previously (legacy code or a previous phase).
3) Figure out how the spec translates into distribution across the domain model objects.
4) Write unit tests for various domain model methods as you create them

Not fully test first, more like test middle. And the domain model tends to be mapped fairly closely to the database tables, so the domain entities tend to grow.

But that's a discussion for a whole other blog post. My purpose here is let the TDD kata approach (getting direction from a user story) be my opportunity to practice creating tests directly from user stories.

I've started reading User Stories Applied by Mike Cohn, and I've decided to try some experiments with TDD kata by creating a list of user stories (eg. for a vetrinarian administrator), choosing one of the user stories to write out, and then building a TDD kata based on that story.

Here is today's example, for the vetrinarian administrator:

5 User Stories
#1 - Vet admin can enter pet details (register the pet)
#2 - Vet admin can log a single pet visit
#3 - Vet admin can track and add medications for a pet
#4 - Vet admin can create an invoice for the visit
#5 - Vet admin can create a purchase order for specialized pet foods

[Edit - adding more user story details]
#1 Vet admin can enter pet details (register the pet)
a) Can enter pet with name, breed, age, temperment, and brief health history.
b) Can enter owner name and address (if new).
c) Can associate the owner to the pet (add to owner's list of pets)

#2 Vet admin can log a single pet visit
a) Vet admin can create a pet visit record.
b) Vet admin can enter the comments provided by the vet about the visit
c) Vet admin can add a list of new prescriptions to the pet's prescription list, and link them to this visit.
d) Vet admin can issue a receipt for payment.
[end Edit.]

If I take the 3rd user story, I then write it out in detail:

#3 Vet admin can track pet medications
a) Vet admin can search for medications by name
b) Vet can assign found medication to Fluffy the dog's record
c) If Fluffy has allergy to the newly assigned medication, a flag will be raised
d) If new medication has contraindications with Fluffy's existing meds, a flag will be raised.
e) If flag is raised, vet admin must get Vet override before adding
f) Vet admin can enter prescription date, dosage instructions
g) Vet admin can print prescription.

Now, obviously this user story is too big for a 30 minute TDD kata, but it's a place for me to get started. My hope was that I could just address the domain in my kata, but the user story already includes a medication lookup, which is probably a repository query, so I decided to build the tests at the Model-View-Presenter level with mocked repository and view.

In 30 minutes, I managed to complete only the first step:


[TestFixture]
public class MedicationTrackerPresenterTests
{
private MockRepository _mockRepository;
private IMedicationTrackerView _medicationTrackerView;
private IMedicationRepository _medicationRepository;

[SetUp]
public void SetUp()
{
_mockRepository = new MockRepository();
_medicationTrackerView = _mockRepository.StrictMock();
_medicationRepository = _mockRepository.StrictMock();
}

[TearDown]
public void TearDown()
{
_mockRepository.ReplayAll();
_mockRepository.VerifyAll();
}

[Test]
public void VetAdminCanSearchForMedicationsByName()
{
_medicationTrackerView.SearchEvents += null;
var searchMedicationsEventRaiser = LastCall.IgnoreArguments().GetEventRaiser();
const string searchInput = "Tylenol";
var medications = new List();
Expect.Call(_medicationTrackerView.SearchInput).Return(searchInput).IgnoreArguments();
Expect.Call(_medicationRepository.FindMedicationsByName(searchInput)).Return(medications);
_medicationTrackerView.MedicationsSearchResult = medications;

_mockRepository.ReplayAll();

var medicationTrackerPresenter = new MedicationTrackerPresenter(_medicationRepository, _medicationTrackerView);
searchMedicationsEventRaiser.Raise(_medicationTrackerView, EventArgs.Empty);
}
}


Tomorrow, I will go back to Calculator kata (every 2nd day at least). But this process of going from application concept, to a list of user stories, to fleshing out 1 user story, to building the tests for that story as a TDD kata, feels like a strong practise that I want to reinforce.

Sunday, January 24, 2010

Upgraded version of #goos C# sample code ch.14 posted with "WinFormLicker"

I have just posted an update of the #goos (Growing Object-Oriented Software, Guided by Tests) C# sample code for chapter 14. This version now adheres more closely to the Java sample code by providing classes in a "WinFormLicker" namespace that launch a WinForm instance in a separate thread and to observe the actions applied against the controls, similar to the behaviour applied to the Swing JFrame window in the Java sample code.

The code is posted here:

http://github.com/dgadd/GOOS_sample_csharp


Saturday, January 23, 2010

Creating a C# Window Inspector to parallel WindowLicker in the Java #goos sample code (updated)

This morning, I started into chapter 15 of Growing Object-Oriented Software, Guided by Tests. At this point, reliance on WindowLicker in the Java code to inspect the changes happening in the GUI layer is increasing. I had been avoiding this by simply using a mock IAuctionSniperView interface, and validating that the interface's Status string property had been set.

However, this creates a few problems:
1) The C# code isn't fully parallel to the Java sample code
2) In the book, the end-to-end/acceptance tests operate at single level of scope, calling either ApplicationRunner or FakeAuctionServer to validate each step of the test. By using a mocked interface, I had to place the mock expected actions (replay and verify) at the top level (rather than inside AppicationRunner).
3) And, of course, it's not truly an end-to-end test, as it stops at the view interface.

I decided to experiment with writing some tests in a new project to see what the minimal amount of code would be necessary to create a simple WinForm inspector that could start simply by observing the activity of the status Label being set.

After a few false starts (and needing to review my knowledge of the ParmeterizedThreadStart class) I managed to get this working and displaying a label.

The tests:


[TestFixture]
public class WinFormInspectorTests
{
private WinFormInspector _winFormInspector;

[SetUp]
public void Setup()
{
_winFormInspector = new WinFormInspector(new Main());
}

[Test]
public void Inspector_Can_Instantiate_WinForm()
{
Assert.IsNotNull(_winFormInspector.Main);
}

[Test]
public void Inspector_Can_Launch_Application()
{
_winFormInspector.LaunchApplication();
_winFormInspector.SleepApplication(1000);
_winFormInspector.QuitApplication();
}

[Test]
public void Inspector_Can_Observe_Status_Label()
{
const string status = "Lost";

_winFormInspector.LaunchApplication();
_winFormInspector.Main.SniperStatus = status;
_winFormInspector.ShowsSniperStatus(status);
_winFormInspector.SleepApplication(1000);
_winFormInspector.QuitApplication();
}
}


The WinFormInspector class:


public class WinFormInspector
{
private readonly Main _main;
private Thread _thread;


public WinFormInspector(Main main)
{
_main = main;
}

public Main Main
{
get { return _main; }
}

public void ShowsSniperStatus(string expectedStatus)
{
if (!_main.SniperStatus.Equals(expectedStatus))
{
throw new Exception("Expected status does not match SniperStatus label.");
}
}

public void LaunchApplication()
{
_thread = new Thread(new ParameterizedThreadStart(Launch));
_thread.Start(this.Main);
}

public void SleepApplication(int sleepMilliseconds)
{
Thread.Sleep(sleepMilliseconds);
}

public void QuitApplication()
{
this.Main.Close();
Application.Exit();
}

private static void Launch(object input)
{
var form = (Form)input;
Application.Run(form);
}
}


...and the WinForm class, "Main":


public class Main : Form
{
private readonly Label _lblStatus;

public Main()
{
_lblStatus = new Label();
this.Controls.Add(_lblStatus);
}

public string SniperStatus
{
get
{
return _lblStatus.Text;
}
set
{
_lblStatus.Text = value;
}
}
}


My next step is to move this over into the AuctionSniper C# sample code project. One of the things I'm debating is whether to keep the mocked view tests as well.

Friday, January 22, 2010

Eclipse / Visual Studio keyboard shortcuts for TDD Calculator kata

Tonight I tried out the TDD Calculator kata in Eclipse.

As part of the process, I searched for equivalent keyboard shortcuts in Eclipse, and came up with the following quick comparison:

EclipseVisual Studio
with Resharper
Task
Ctrl-F6Ctrl-TabJump between Classes
Ctrl-F7Ctrl-Tab-LeftArrowJump between Views
Alt-Shift-Q,PCtrl-Alt-LJump to Package / Solution Explorer
Ctrl-Shift-WAlt-W,LClose All Editor Windows
Ctrl-Shift-F8F5Go to Debug (Switch Perspectives)
Alt-Shift-X, TCtrl-R-A(VS)
Option-R-U-N(R#)
Run All Tests
Alt-Shift-D, TCtrl-R,Ctrl-T(VS)
Option-R-U-D(R#)
Run Contextual Test in Debug Mode
F2Alt-Enter OR Alt-Shift-F10Show refactoring suggestions
Alt-Shift-MCtrl-R-MExtract Method
Alt-Shift-VCtrl-R-OMove Class to another Namespace
Ctrl-7 (toggle)Ctrl-K-CComment a block of code
Ctrl-7 (toggle)Ctrl-K-UUncomment a block of code

Thursday, January 21, 2010

TDD Calculator kata: thoughts on day 6

I've been doing the TDD Calculator kata (as per Roy Osherove: http://osherove.com/tdd-kata-1/) the last 6 days. Today was the first day where I did the complete kata. I got the first section down to 22 minutes, but by the time I tackled the final issue (recognizing and processing multiple custom delimiters) the frustration had kicked in and I had metaphorically rolled up my sleeves: staring at output in debug mode, watching side-effects break 4 of the previous tests, and feeling stress levels go up as the clock ticked. I finally resolved all issues, had all tests passing, and did final refactoring in just under 45 minutes.

Temporary stress levels aside, this has been a very productive experience. I've seen a number of patterns emerging with each successive repetition of the practise:

1) Faster and faster interaction with Resharper
I have had friends recommending Resharper to me for a couple of years now, but it was actually working with Eclipse & Java again to build the sample code in "Growing Object-Oriented Software, Guided by Tests" that reminded me about all the tools that Eclipse provides to assist with code generation as you build test-first. When I returned to Visual Studio to build the equivalent code in C#, it quickly became apparent that Resharper was the Eclipse-ification of Visual Studio. With the beginning of the TDD kata practise, the usage of Resharper has become even more prominent, with reliance on it for class and interface geneation, constant reference to its recommendations for improving the code, and quick in-browser test runs with NUnit.

2) Getting serious about Visual Studio (and Resharper) keyboard shortcuts
I have been quite happy to mouse along in Visual Studio, but watching some of the kata samples out there have brought home the usefulness (first for the kata, but already quickly apparent in my daily work) of using keyboard shortcuts to stay caught up to the train of thought. The 10 most-useful that I have started using regularly are:
* Ctrl-Alt-L to jump to Solution Explorer (and down and left hours to collapse projects)
* Ctrl-Tab to move between tests and code ("Active Files") and to other Visual Studio windows
* Shift-F10 instead of right-click (yes, I had to google that one)
* Option-R-U-N to run all tests in the Resharper window
* Alt-Enter to look at contextual Resharper recommendations (typically to invert if conditionals or switch declarative types to var)
* Alt-Shift-F10 to look at contextual Visual Studio recommendations (typically to either reference a using statement for a class, or to cascade a renaming across the code)
* F9 to set a breakpoint, and Ctrl-Shift-F9 to clear all breakpoints
* Ctrl-K-C and Ctrl-K-U to comment/uncomment code
* Ctrl-R-M to extract a method, and
* Ctrl-R-O to move a class to a difference namespace

3) Learning to use the simplest solution possible
It felt gimmicky at first, but solving two tests passing either "" or "3" to return 0 from the first, and 3 from the second, is most appropriately solved with:

return inputString.Length > 0 ? 3 : 0;

Of course that isn't "real", but it forces me to not overbuild. I notice that when I hit the more challenging issues to resolve (eg. the final requirement in the kata) that it's very tempting to start building the Sistine Chapel, but of course then the question becomes how do I test a coding monstrosity?

I'll come back to this one at the end, with it's implications for the coding of larger projects

4) Correspondence of test names and one-at-a-time issues to resolve
The Calculator kata states a series of issues to resolve. Each test is named with the resolution of that issue, for example:


[Test]
public void Calculator_Allows_Multiple_MultiChar_Custom_Delimiter()


It's very, very clear, and is a excellent parallelism to the original stated issues.

Overall Conclusions
I've written lots of unit tests over the last 3 years, and they have been extremely useful and provided signifigant code coverage (on my current project, I just passed the 300th unit test), but they, and the classes they address, tend to be large. I am using a domain model with NHibernate, I always practise moving business logic down (when it slips into the client or a service layer) into the domain object where it belongs, but then it tends to stop there. The domain model is correct, the logic is inside the entity, it prevents redundancy, all good things, but it makes for large entities and large tests, as opposed to more incremental tests shaping the design. In re-reading Refactoring [Fowler] this past November, I saw that smaller classes, with increasing delegation (eg. a domain entity calling out to a strategy class) makes for greater granularity, and with it, smaller tests. Starting from the tests-first, with the goal to keep the tests small and simple, helps to keep the classes small and delegating naturally.

TDD/mocks kata for MVP (Model-View-Presenter)

Here is a simple TDD Kata for Model-View-Presenter and Rhino Mocks.

Model-View-Presenter is an implementation approach rather than a framework. Therefore it can be used to create testable sub-presentation layers for any GUI platform (WinForm .NET, ASP.NET, Java Swing, SharePoint web controls, you name it) because the view is abstracted to an interface. All of the (testable) interaction logic now occurs in a newly created layer called the presentation layer. The presentation layer is completely agnostic about the view implementation; in fact, it can have MULTIPLE view implementations.

Finally, any time that an SUT (a "system-under-test") is created which interacts with other code through interfaces, those interfaces can (and usually should) be tested with unit tests that isolate the SUT. This is done by either faking the interface implementations (with minimal "fake" implementation classes) or mocking the implementations with mocking tools such as jMock (for Java), or NMock, Moq, or RhinoMocks for .NET.

In this TDD kata example, RhinoMocks is used as the mocking framework.

Model-View-Presenter TDD Kata
PreRequisites:
Create solution.
Reference TDD framework and mocking framework.
Create namespaces for Model, View, Presenter, and UnitTests.

NOTE  For each step which follows, code samples are shown with possible implementations (below).

1) Create a presenter class which instantiates two interfaces: mock repository and view. Use naming prefix "Customer" on the presenter, the repository, and the view.
2) In the View, create an event: Initialize and a  string property: PageTitle
3) Verify that when the Initialize event is raised:
   * the view's PageTitle property is set to "Welcome".
4) Create a Customer in the Model with properties FirstName and LastName.
5) In the View, create an event: GetCustomers and a List property: Customers.
6) Verify that when the GetCustomers event is raised:
   * the repository method GetCustomers()  is called and returns a list of Customers
   * the view's Customers property is set to the Customers list
7) Create a SortCustomerEventHandler delegate with SortCustomerEventArgs that passes SortExpression and IsAscending.
8) In the View, create an event: SortCustomer (using the SortCustomerEventHandler delegate)
9) Verify that when the SortCustomer event is raised:
   * SortExpression and SortDirection properties are passed to SortCustomerEventArgs
   * [possibly that the sort has occured]
   * the view's Customers property is set to the Customers list

Here is what one possible test output look like using RhinoMocks:

[TestFixture]
public class CustomerPresenterTests
{
    private readonly MockRepository _mockRepository = new MockRepository();
    private CustomerPresenter _customerPresenter;
    private ICustomerRepository _mockCustomerRepository;
    private ICustomerView _mockCustomerView;

    [SetUp]
    public void Setup()
    {
        _mockCustomerView = _mockRepository.StrictMock();
        _mockCustomerRepository = _mockRepository.StrictMock();
    }

    [TearDown]
    public void TearDown()
    {
        _mockRepository.ReplayAll();

        _mockRepository.VerifyAll();
    }

    [Test]
    public void CustomerPresenter_Can_Be_Instantiated()
    {
        _mockCustomerView.Initialize += null;
        LastCall.IgnoreArguments();
        _mockCustomerView.GetCustomers += null;
        LastCall.IgnoreArguments();
        _mockCustomerView.SortCustomers += null;
        LastCall.IgnoreArguments();

        _mockRepository.ReplayAll();

        _customerPresenter = new CustomerPresenter(_mockCustomerRepository, _mockCustomerView);
    }

    [Test]
    public void CustomerPresenter_Sets_ViewTitle_When_Initialize_Event_Raised()
    {
        _mockCustomerView.Initialize += null;
        var initializeEventRaised = LastCall.IgnoreArguments().GetEventRaiser();
        _mockCustomerView.GetCustomers += null;
        LastCall.IgnoreArguments();
        _mockCustomerView.SortCustomers += null;
        LastCall.IgnoreArguments();
        _mockCustomerView.PageTitle = "Welcome";

        _mockRepository.ReplayAll();

        _customerPresenter = new CustomerPresenter(_mockCustomerRepository, _mockCustomerView);
        initializeEventRaised.Raise(_mockCustomerView, EventArgs.Empty);
    }

    [Test]
    public void CustomerPresenter_GetsCustomers_When_GetCustomers_Event_Raised()
    {
        var customers = new List();

        _mockCustomerView.Initialize += null;
        LastCall.IgnoreArguments();
        _mockCustomerView.GetCustomers += null;
        var getCustomersEventRaised = LastCall.IgnoreArguments().GetEventRaiser();
        _mockCustomerView.SortCustomers += null;
        LastCall.IgnoreArguments();
        Expect.Call(_mockCustomerRepository.GetCustomers()).Return(customers);
        _mockCustomerView.Customers = customers;

        _mockRepository.ReplayAll();

        _customerPresenter = new CustomerPresenter(_mockCustomerRepository, _mockCustomerView);
        getCustomersEventRaised.Raise(_mockCustomerView, EventArgs.Empty);
    }

    [Test]
    public void CustomerPresenter_SortsCustomers_When_SortCustomers_Event_Raised()
    {
        var customers = new List();

        _mockCustomerView.Initialize += null;
        LastCall.IgnoreArguments();
        _mockCustomerView.GetCustomers += null;
        LastCall.IgnoreArguments();
        _mockCustomerView.SortCustomers += null;
        var getSortCustomerEventRaiser = LastCall.IgnoreArguments().GetEventRaiser();
        Expect.Call(_mockCustomerRepository.GetCustomers()).Return(customers);
        _mockCustomerView.Customers = customers;

        _mockRepository.ReplayAll();

        _customerPresenter = new CustomerPresenter(_mockCustomerRepository, _mockCustomerView);
        var sce = new SortCustomersEventArgs("", true);
        getSortCustomerEventRaiser.Raise(_mockCustomerView, sce);
    }
}



And here is what one possible CustomerPresenter implementation looks like:

public class CustomerPresenter
{
    private readonly ICustomerRepository _customerRepository;
    private readonly ICustomerView _customerView;

    public CustomerPresenter(ICustomerRepository customerRepository, ICustomerView customerView)
    {
        _customerRepository = customerRepository;
        _customerView = customerView;

        _customerView.Initialize += CustomerViewInitialize;
        _customerView.GetCustomers += CustomerViewGetCustomers;
        _customerView.SortCustomers += CustomerViewSortCustomers;
    }

    void CustomerViewSortCustomers(object sender, SortCustomersEventArgs sce)
    {
        List customers = _customerRepository.GetCustomers();

        customers.Sort(delegate(Customer first, Customer second)
        {
            int result;

            switch (sce.SortExpression)
            {
                case "FirstName":
                    result = first.FirstName.CompareTo(second.FirstName);
                    break;
                case "LastName":
                    result = first.LastName.CompareTo(second.LastName);
                    break;
                default:
                    result = 0;
                    break;
            }

            return (sce.IsAscending) ? result : -result;
        });

        _customerView.Customers = customers;
    }

    private void CustomerViewGetCustomers(object sender, EventArgs e)
    {
        List customers = _customerRepository.GetCustomers();
        _customerView.Customers = customers;
    }

    private void CustomerViewInitialize(object sender, EventArgs e)
    {
        _customerView.PageTitle = "Welcome";
    }
}



I've tried a couple of implementations of ICustomerView: WinForm, and as a Sharepoint web part. Interesting to see it work.

But, from the kata point of view, the only part that matters is repeating the creation of the tests, and experiementing/refining the approach.

Tuesday, January 19, 2010

GOOS ch. 14 C# sample code github URL

http://github.com/dgadd/GOOS_sample_csharp

GOOS C# ch. 14 sample code posted to github (with README.txt)

I've been working through sample code for the book Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce. The example provided is in Java; I've been porting it to C# with Rhino Mocks. Details here.

Below is the README.txt file I posted with the GOOS C# ch. 14 sample code to github:

README.txt for GOOS sample code in Visual Studio 2010
================================================

David Gadd
http://www.twitter.com/gaddzeit
Email: gaddzeit@yahoo.ca

This version of the sample code from #goos was written using:
* Visual Studio 2010 Beta 2.0
* Visual Studio's unit-testing framework
(I experienced NUnit compatability issues in VS 2010 Beta 2.0;
to use this code in Visual Studio 2008 with NUnit just retag the test class/method attributes.)
* Reshaper Beta5 (helpful but not required to run code)
* Rhino Mocks version 3.6.0.0 (assume this reference will be broken; you will need to re-reference to a local copy)

This version is complete as of the end of Chapter 14, with all acceptance and unit tests passing.

While I used OpenServer for the Java version, for this version I created a fake XMPP server (all method calls are similar/identical).

Instead of using WindowLicker for a full end-to-end acceptance test, I am simply mocking the IPickerMainView interface
and verifying in RhinoMocks that the SniperStatus string property is being set. To achieve this, the level of scope in
the end-to-end acceptance test is not identical to the Java code; above the method calls to ApplicationRunner.cs
and FakeAuctionServer.cs I am setting RhinoMocks expectations on _mockPickerMainView.

Other than that, I am using the more commonly-used conventions in C# than in Java of:
* prefixing interface names with capital I
* underscore prefixing instance variables

If you have any questions feel free to tweet or email me.

David Gadd

Sunday, January 17, 2010

Re-awakening the blog

This blog has been asleep for almost 3 years. It's time to wake it back up again (add some new posts.) There's only so much I can fit in 140 char on twitter.

Saturday, February 24, 2007

A comparison of Java's Hibernate and .NET 2.0 Strongly-typed DataSets

After a thorough study of Hibernate in Action (and completing my SCBCD [EJB 2.0] certification) in fall 2006, followed by the creation of a Hibernate demo project on my website, I now find myself working on a .NET 2.0 project, using strongly-typed DataSets for the object-relational mapping. I thought it would be useful to compare these two technologies. Hence this entry:

Hibernate and .NET 2.0 strongly-typed DataSets do share some features in common:
  1. An xml document (the DataSet) is used to determine the mapping rules. (It is also convenient--although much more data-driven than object-model driven that the mapping xml can be created in a designer mode by dragging tables onto the screen and linking relations between them.)
  2. This xml document also automatically generates all of the objects, as well as a separate sub-namespace (think Java package) containing the repository classes for populating the objects.
  3. The objects, object collections, and associations are all mapped to the database.
  4. The objects are persisted to and from the database using repository classes (known as table adapters in .NET DataSets.) These classes are generated in a separate sub-namespace (package).
But there are signifigant differences / limitations:
  1. The DataSet model is a very strongly data-driven model. This makes it difficult to develop a true object model as you can in Hibernate. As a result, there is no real control to build the object model according to best practices or design patterns, as the model is entirely generated from the database-driven DataSet. The model also uses database naming conventions: the collection is named with a "DataTable" suffix (eg. the Person collection would be "PersonDataTable") and the contained objects are named with a "Row" suffix (the object would be "PersonRow").
  2. There is no HQL-style query language. You are directly addressing a specific database, and, of course, that database is almost exclusively Microsoft SQL Server. And while you can theoretically use queries, stored procedures are considered the norm.
  3. You cannot populate an object graph from a single query/stored procedure. You have two less-than-ideal choices:

    • You can create a query/sproc that contains joins, and bring back tabular data, which is obviously no longer a true object graph, it's just a result set. (I don't use this option.)
    • The second choice is to create SEPARATE queries in each table that forms a part of the object graph. For example, if you wanted to know about the Person, the person's Invoices, the person's Accounts, and the person's Appointments, all of which form the object graph, you would create separate repository queries (by personID) for EACH of the 4 objects, and you would then make 4 calls to populate each object/collection. This also means that you must populate them in the right order (parent first, then child) to prevent constraint errors.

  4. The most difficult is that there is not a single mapping of the object model in a single namespace (package). Instead, a typical .NET project may contain multiple DataSets, often with redundant data tables across DataSets. The generated classes and related repository(table adapter) classes are DataSet specific, so a Person object may end up redundantly as the PersonRow class in MANY different DataSets. And, because they are in distinct DataSets, they cannot be assigned to each other. Finally, a change in a table of the database must be implmented not in a single location, but redundantly across all DataSets that reference the particular data table.
  5. I have had to extend the generated objects with additional functionality in order to link each of the objects in the object graph to the same transaction. And since each of the auto-geneated repository classes do not share a common interface, I have had to force interface implementation. I have achieved this with a .NET convention called "partial classes" (files where you can "add to" (not extend!) an auto-generated class, so that when the auto-generated class gets regenerated, your modifications are not overwritten. It's hackish, but it works.)
In terms of performance, I've found this solution to be quite fast. And, the DataSets can be used as web service parameters and return types, allowing for a minimal amount of web service calls (typically only one per process.)

Ubuntu - recovering lost password for first account

I have an Ubuntu server, an old machine with only 128MB of RAM. I installed it last summer and stuck it behind the chair in my living room. I log into it from ssh, I use it for my website and my Java/Hibernate applications, and it works beautifully. Ubuntu typically hides the root account, so instead, the first user account created becomes the sudoers account, the one that can be used for all admin work.

All was going well until one day, I changed my user password in a rush, and forgot to write it down. The next time I tried to ssh in, I couldn't remember the password.

A google search for "Ubuntu recover password" provided a lot of responses, but a number of them were quite confusing. So here, for both my future reference and your edification, are some (hopefully clearer) instructions for resetting your user password. Of course, these instructions only apply to physically working from the hardware, this is not something that can be done remotely.

RESETTING YOUR USER PASSWORD FOR UBUNTU
1. Restart your Ubuntu machine.
2. During startup, you will see a reference to Grub boot loader, and a comment that to edit these settings, you should press 'esc'.
3. Therefore, at that point, press 'esc'.
4. The Grub boot loader screen will show you 2-3 options. The first option is the default. The SECOND option, at the end, should say "recover mode". This is the mode you want. Select it, and press enter.
5. Wait for the boot to finish. This will bring you to command line.
6. For the example below, let's pretend that your username is "gcluney".
7. At command line, type:
passwd gcluney
8. You will be asked to provide a new password, and then to retype it.
9. Do this, then restart your machine.

Wednesday, November 08, 2006

How to install CVS as a server repository and connect from Eclipse

I tried to find this on the net the other day and could only find it piece-meal. So, in case you're trying to setup a CVS repository. here it all is in one shot.

I make ONE assumption, which is that you already have a remote server that you are accustomed to connecting to that server using ssh and scp.

SECTION A - LINUX COMMAND LINE COMMANDS
SECTION B - REMOTE SERVER CVS REPOSITORY INSTALL
SECTION C: COMMAND-LINE CVS IMPORT / CHECKOUT / COMMIT / UPDATE
SECTION D: ECLIPSE PROJECT CVS IMPORT
SECTION E: ECLIPSE PROJECT CVS EXPLORATION / GET PROJECTS
SECTION F: ECLIPSE PROJECT FILES CHECKOUT / COMMIT / UPDATE / SYNCHRONIZE

*****

SECTION A - LINUX COMMAND LINE COMMANDS

Let's start with some command-line Linux commands for
creating users and groups, since you'll need these shortly
to set up the CVS repository on the server:

To add a user:
useradd -m -c JohnSmith username
or perl script utility (recommended):
adduser username
To delete a user:
userdel -r username

To change a password:
passwd username
You will be asked for a new password.

To add a group:
groupadd groupname
To delete a group:
groupdel groupname

To add a user to a group:
Go to the /etc directory, and use vi
to edit the text file 'group': locate the group name (likely near the bottom.)
It will have the name plus an identifier, ending in a colon, or an existing user.
If it's a colon, just add the username. If a username is there,
type a comma and add the additional username:
groupname:x:1001:dgadd,cvs

*****

SECTION B - REMOTE SERVER CVS REPOSITORY INSTALL
With those commands in hand, you can proceed with a remote server cvs repository install.

Firstm, use ssh to connect.
Let's suppose the server is named 555.555.555.555
In that case:
ssh myusername@555.555.555.555

Now do the following to set up cvs on the remote server:

1) On Ubuntu use apt-get install cvs to install cvs
2) Create a repository folder:
mkdir /var/lib/cvsroot
3) Use Linux commands to create a user, 'cvs' a group 'cvsusers',
and edit the /etc/group file to add 'cvs' to the end of the 'cvsusers' group.
4) Change the group of the repository folder to cvsusers, make directory read/write/execute, and make cvs the owner.
chgrp cvsusers /var/lib/cvsroot
chmod g+srwx /var/lib/cvsroot
chown -R cvs /var/lib/cvsroot
5) Initialize the repository using the cvs command:
cvs -d /var/lib/cvsroot init

SECTION C: COMMAND-LINE CVS IMPORT / CHECKOUT / COMMIT / UPDATE

1) To import a project on your local server to remote server cvs:
Copy the project to a temp directory, because the copy
you use to import needs to be discarded.
Cd into that temp project directory.
Then, to perform the import, run the following command:
cvs -d cvs@555.555.555.555:/var/lib/cvsroot import example MyProject release_0_1

2) To checkout file1 within the project:
cvs -d cvs@555.555.555.555:/var/lib/cvsroot checkout MyProject/file1

3) To commit (checkin) file1 within the project:
cvs -d cvs@555.555.555.555:/var/lib/cvsroot checkout MyProject/file1

4) To update (synchronize) your local files with the (potentially updated) copies on the server:
Go inside the project directory, and type:
cvs update -d // the -d switch will download new directories

SECTION D: ECLIPSE PROJECT CVS IMPORT

The most important thing to know is that when you right-click on an Eclipse project, there is a Team sub-menu that you may not have noticed before. This Team submenu is the key to all of your CVS interaction!

1) Right-click on your Project folder in Eclipse.
2) Select Team, and then click Share Project...
3) If you have not previously set up your CVS repository, then you will be taken to the Share Project dialog box, which asks you to provide repository information.
4) Enter the following information (assuming your remote server repository ip address is 555.555.555.555, and you have set up the repository as described above):
Host: 555.555.555.555
Repository path: /var/lib/cvsroot
User: cvs
Password: your_password
Connection type: extssh
Use default port: selected
Save password checkbox: checked

5) Click Next->
6) Select the default 'Use project name as module name'.
7) Click Next->
8) Click Finish. (This should launch the Commit Wizard.)
9) A list of default types is shown, all to be saved as binary. Click next.
10) You are now asked to provide a comment before saving. A recommended format would be Date, User, Platform/Machine, Action:
Date: 2006Nov08
User: John Smith
Platform: Linux
Action: Importing project for first time

11) Click Finish. Your project will be imported.

SECTION E: ECLIPSE PROJECT CVS EXPLORATION / GET PROJECTS
Assuming you have completed the previous section and imported your project, you now need to be able to go to Eclipse on other machines, access the CVS repository and get the project(s) that you have imported. Here's how.

1) In Eclipse, from the Window menu, select the Open Perspective submenu and click Other...
2) In the Select Perspective dialog box, click CVS Repository Explorer, and then click OK.
3) Right-click inside the CVS Repositories pallete and select New > Repository Location...
4) In the Add CVS Repository dialog box, enter the following information (assuming your remote server repository ip address is 555.555.555.555, and you have set up the repository as described above):
Host: 555.555.555.555
Repository path: /var/lib/cvsroot
User: cvs
Password: your_password
Connection type: extssh
Use default port: selected
Save password checkbox: checked

5) Expand the new tree that appears in the CVS Repositories pallete.
6) Expand HEAD, and locate the project you imported earlier.
7) Right-click on the project, and click Checkout...
After a few seconds, the project checkout will have completed.
8) Switch back to the default (Java) perspective.
Your newly checked out project will be visible in the Package Explorer.
9) Since library references are imported, but not the libraries themselves, the project will have a red x on it to indicate problems. Click on the Problems palette (typically at the bottom of the Eclipse window) to determine your list of missing libraries.
10) Rclick on the project, select the Build Path submenu, and click Configure Build Path...
11) In the Properties for [Project Name] dialog box, the project referneces will be listed.
12) One-by-one, remove these outdated references and then use the Add External Jar... button to add a reference to a local copy of each jar.

You're done. You can now proceed to checkout, commit, update and synchronize files (below).


SECTION F: ECLIPSE PROJECT FILES CHECKOUT / COMMIT / UPDATE / SYNCHRONIZE

1) To checkout a file:
You don't. Just start editing a file. As soon as you save a change, the file (and it's containing folder / package) will be marked with a greater-than symbol:
>login.jsp

2) To commit a file:
* Right-click on the file, and from the Team submenu, click Commit...
* The Commit Files dialog will appear. Please provide a comment before saving. A recommended format would be Date, User, Platform/Machine, Action:
Date: 2006Nov08
User: John Smith
Platform: Linux
Action: Added blah blah blah to the file.
* Click Finish. The change will be committed.

3) To update a file / project (i.e. get latest).
* If you aren't worried about changes, you can just "get latest" for the entire project.
* Right click on the Project and from the Team submenu, click Update.
* This will update the entire Project to match the latest contents of the repository.

4) To synchronize with the repository
* If you are worried about changes and want the opportunity to diff your files, use this command
* Right click on the project or a specific file, and from the Team submenu, click Synchronize with Repository
* You will be asked to switch to the Team Repository perspective. Allow this.
* In the Synchronize palette, select files that require synchronization. Proceed to diff them, and then commit changes.

Saturday, April 01, 2006

Debugging AJAX/Javascript for IE and Firefox with Visual Studio .NET

The title of this article is slightly inaccurate, because it implies that both IE and Firefox AJAX/Javascript debugging can be performed directly within Visual Studio .NET. That is not exactly the case. While it is easy to set up AJAX/Javascript debugging with IE and Visual Studio .NET; and while it is easy to use Firefox as your .NET debugging browser with Visual Studio .NET; if you specifically want to debug Javascript for Firefox while using Visual Studio .NET, you have to add a Firefox Javascript debugger.

Let's describe the scenario in more detail, and then discuss the three major activities to get IE and Firefox debugging working with Visual Studio .NET.

A. Scenario
B. Setting Up Javascript debugging for Internet Explorer (and Visual Studio .NET)
C. Redirecting the default browser in Visual Studio .NET between IE and Firefox.
D. Setting Up separate Javascript debugging for Firefox (using venkman).


*****

A. Scenario
1. You want to do an AJAX call from your Javascript in an html page, to an ASPX page that is sending back simple XML (REST protocol, not SOAP).
2. Therefore, you begin by creating a Visual Studio .NET project and creating an ASPX page that sends out XML. (How? That's outside the scope of this article. For examples, download the sample projects for the book "Ajax in Action" (Manning)).
3. You then create an html page in the same project, and write AJAX-style Javascript to call the ASPX page. (Again, outside the scope of this article. Again, consult the "Ajax in Action" sample code).
4. When you run the project, you can set breakpoints in your .NET code and debug the .NET code as usual.
5. However, what if you have Javascript bugs? How do you set breakpoints and debug these Javascript errors?
6. Also, since different browsers use different versions of Javascript and the DOM, you need to be able to debug Javascript not only in Internet Explorer, but also in Firefox. How do you do this?

B. Setting Up Javascript debugging for Internet Explorer (and Visual Studio .NET)

Debugging your Javascript code from Internet Explorer is extremely simple--the functionality is already there, it is simply disabled by default.

To enable IE Javascript debugging:
1. Launch Internet Explorer.
2. From the Tools menu, click Internet Options...
3. In the Internet Options window, select the Advanced tab, and then uncheck Disable Script Debugging.
4. Now relaunch Visual Studio, and put two breakpoints: one in your Javascript code, one in your .NET code.

When you run the project, both breakpoints can now be reached. However, what if you now want to perform the same debugging with Firefox and Visual Studio .NET?

C. Redirecting the default browser in Visual Studio .NET between IE and Firefox.

To get Visual Studio .NET to launch Firefox as your default browser, instead of IE, you need to do the following:
1. In the Solution Explorer, right-click on your HTML or ASPX page and click Browse With. (You can also access Browse With from the File menu when an HTML or ASPX page is currently loaded in the main window of Visual Studio .NET).
2. In the Browse With dialog box, select Firefox, click Set as Default, and then click Close.
3. Now, set a breakpoint in both your Javascript code and your .NET code.

What happens? Firefox launches as the browser, and the .NET code breakpoint is reached, but the Javascript breakpoint is ignored.

D. Setting up separate Javascript debugging for Firefox (using venkman).

Visual Studio .NET does not recognize the Javascript breakpoints when Firefox is the default browser. However, you can get around this by installing a Firefox extension for a Firefox Javascript debugger. In our case, we'll use venkman.

To install the venkman extension
1. Google "venkman" to locate the installer.
One current location as of the time of this writing is: https://addons.mozilla.org/extensions/moreinfo.php?id=216
2. Make sure you select the version that corresponds to your version of Firefox, then proceed to install the extension.
3. Restart Firefox.

Now you're ready to put it all together: running Firefox as the default browser in Visual Studio .NET, accessing your .NET code breakpoints with Visual Studio .NET, and accessing your Javascript breakpoints with Venkman.

Tying it all together
1. From Visual Studio, set a .NET code breakpoint (but not a Javscript breakpoint).
2. Verify that you have selected Firefox as your default browser.
3. Run the project.
Firefox will launch.
4. In Firefox, from the Tools menu, click Javascript Debugger.
Venkman Javascript debugger launches. The debugger displays a number of subwindows, including Loaded Scripts, Local Variables,Breakpoints, and the Source Code window.
5. In the Loaded Scripts window, double-click the HTML page or ASPX page to load it. (Or, if you wish to set a breakpoint in a related Javascript, double-click the related script instead.)
6. In the Source Code window, right-click on the line of code that you wish to set a breakpoint and click Set Breakpoint.
7. Now, return to Firefox and perform an action (such as a button or link click) to trigger the Javascripts in the page.

The end result? You should now be able to hit both the Javascript breakpoints (in venkman) and the .NET breakpoints (in Visual Studio .NET).

Thursday, March 30, 2006

Using Javascript to create draggable divs (no frames!)

Today's entry provides sample code for creating draggable div columns to store information in multiple columns without needing to use frames.

Feel free to copy the HTML below into notepad and save it as an HTML file. I've tested this in both Firefox and IE. You should be able to successfully drag the columns wider and narrower and watch the text in the middle column follow the widening and narrowing of the column.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<html>
<head>
<title> Dynamic Table </title>
<style type="text/css">
#DragColumn_0
{
     position:relative;
     z-index:5;
}

#ContentColumn_1
{
     border: 1px solid #006699;
     position:absolute;
     overflow:hidden;
     z-index:10
}

#DragColumn_1
{
     position:absolute;
     cursor:e-resize;
     z-index:11;
}

#ContentColumn_2
{
     border: 1px solid #006699;
     position:absolute;
     overflow:hidden;
     z-index:10
}

#DragColumn_2
{
     position:absolute;
     cursor:e-resize;
     z-index:11;
}

#ContentColumn_3
{
     border: 1px solid #006699;
     position:absolute;
     overflow:hidden;
     z-index:10
}

#DragColumn_3
{
     position:absolute;     
     cursor:e-resize;
     z-index:11;
}
</style>

<script type="text/javascript">

window.onload = function()
{
     PageSetup();
}


// global constants
var ZERO_AMOUNT = 0;
var ONE_MILLISECOND = 1;
var SIX_MILLISECONDS = 6;

// global variables
var _xMousePosition = 0;
var _yMousePosition = 0;
var _mouseButtonDown = false;


// event handlers
function MouseMoveHandler(event) {
     var e = event || window.event;
     _xMousePosition = e.clientX + document.body.scrollLeft;
     _yMousePosition = e.clientY + document.body.scrollTop;
}

function MouseDownHandler(event) {
     var e = event || window.event;     
     var elementID = e.srcElement ? e.srcElement.id : e.target.id;
     var columnIndexValue = elementID.slice(elementID.length-1, elementID.length);
     _mouseButtonDown=true;
     ChangeColumnWidth(columnIndexValue);
}     

function MouseUpHandler(event){
     var e = event || window.event;
     _mouseButtonDown = false;
     var cursorResetFunctionCall = 'document.body.style.cursor = ""';
     window.setTimeout(cursorResetFunctionCall, SIX_MILLISECONDS);
}

function PageSetup()
{
     document.onmousemove = MouseMoveHandler;
     document.onmouseup = MouseUpHandler;
     
     var drag0 = document.getElementById("DragColumn_0");
     var drag1 = document.getElementById("DragColumn_1");
     var drag2 = document.getElementById("DragColumn_2");
     var drag3 = document.getElementById("DragColumn_3");
     var column1 = document.getElementById("ContentColumn_1");
     var column2 = document.getElementById("ContentColumn_2");
     var column3 = document.getElementById("ContentColumn_3");
     var colWidth1 = _columnWidthSettings[1];
     var colWidth2 = _columnWidthSettings[2];
     var colWidth3 = _columnWidthSettings[3];
     
     drag0.style.left = TABLE_LEFT + "px";
     drag0.style.top = TABLE_TOP + "px";
     drag0.style.height = TABLE_HEIGHT + "px";

     column1.style.left = TABLE_LEFT + "px";
     column1.style.width = colWidth1 + "px";
     column1.style.top = TABLE_TOP + "px";
     column1.style.height = TABLE_HEIGHT + "px";

     drag1.style.left = (TABLE_LEFT + colWidth1) + "px";
     drag1.style.width = DRAG_WIDTH + "px";
     drag1.style.top = TABLE_TOP + "px";
     drag1.style.height = TABLE_HEIGHT + "px";
     drag1.onmousedown=MouseDownHandler;
     
     column2.style.left = (TABLE_LEFT + colWidth1 + DRAG_WIDTH) + "px";
     column2.style.width = colWidth2 + "px";
     column2.style.top = TABLE_TOP + "px";
     column2.style.height = TABLE_HEIGHT + "px";

     drag2.style.left = (TABLE_LEFT + colWidth1 + DRAG_WIDTH + colWidth2) + "px";
     drag2.style.width = DRAG_WIDTH + "px";
     drag2.style.top = TABLE_TOP + "px";
     drag2.style.height = TABLE_HEIGHT + "px";
     drag2.onmousedown=MouseDownHandler;

     column3.style.left = (TABLE_LEFT + colWidth1 + DRAG_WIDTH + colWidth2 + DRAG_WIDTH) + "px";
     column3.style.width = colWidth3 + "px";
     column3.style.top = TABLE_TOP + "px";
     column3.style.height = TABLE_HEIGHT + "px";

     drag3.style.left = (TABLE_LEFT + colWidth1 + DRAG_WIDTH + colWidth2 + DRAG_WIDTH + colWidth3) + "px";
     drag3.style.width = DRAG_WIDTH + "px";
     drag3.style.top = TABLE_TOP + "px";
     drag3.style.height = TABLE_HEIGHT + "px";
     drag3.onmousedown=MouseDownHandler;
}



function ChangeColumnWidth(columnIndexValue){
//alert("You reached to the top of the ChangeColumnWidth method!");
var columnIndex = parseInt(columnIndexValue);

if(columnIndex >= 0){
     document.body.style.cursor = "e-resize";
     
     var previousDragColumn = document.getElementById("DragColumn_" + (columnIndex-1));
     var currentDragColumn = document.getElementById("DragColumn_" + columnIndex);
     var currentContentColumn = document.getElementById("ContentColumn_" + columnIndex);
     
     var leftString = previousDragColumn.style["left"] ? previousDragColumn.style["left"]:previousDragColumn.currentStyle["left"];

     var previousDragLeftColumnPosition = parseInt(leftString.slice(0,leftString.length-2));
     var previousDragRightColumnPosition;
     if(columnIndex > 1)
     {
          previousDragRightColumnPosition = previousDragLeftColumnPosition + DRAG_WIDTH;
     }
     else
     {
          previousDragRightColumnPosition = previousDragLeftColumnPosition;
     }
     
     var distanceFromMouseToPreviousColumn = _xMousePosition - (previousDragRightColumnPosition);
     var dragColumnLeftPosition = 0;
     
     if(distanceFromMouseToPreviousColumn < MINIMUM_ALLOWED_WIDTH)
     {
      _columnWidthSettings[columnIndex] = MINIMUM_ALLOWED_WIDTH;
      dragColumnLeftPosition = previousDragLeftColumnPosition + MINIMUM_ALLOWED_WIDTH;
     }
     else
     {
      _columnWidthSettings[columnIndex] = distanceFromMouseToPreviousColumn;
      dragColumnLeftPosition = _xMousePosition;
     }

     currentContentColumn.style.width = (_columnWidthSettings[columnIndex]) + "px";
     currentDragColumn.style.left = dragColumnLeftPosition + "px";
     currentDragColumn.style.width = DRAG_WIDTH + "px";

     
     for(iteration=(columnIndex+1); iteration<_columnWidthSettings.length; iteration++)
     {
          var nextDragColumn = document.getElementById("DragColumn_" + iteration);
          var nextContentColumn = document.getElementById("ContentColumn_" + iteration);
          
          nextContentColumn.style.left = (dragColumnLeftPosition + DRAG_WIDTH) + "px";
          nextContentColumn.style.width = _columnWidthSettings[iteration] + "px";
          nextDragColumn.style.left = (dragColumnLeftPosition + DRAG_WIDTH + _columnWidthSettings[iteration]) + "px";
          nextDragColumn.style.width = DRAG_WIDTH + "px";
          
          dragColumnLeftPosition += DRAG_WIDTH + _columnWidthSettings[iteration];
     }
     
}

var recursiveFunctionCall = "ChangeColumnWidth('"+columnIndex+"')";

if(_mouseButtonDown)
{
     window.setTimeout(recursiveFunctionCall, ONE_MILLISECOND);
}
}


// CUSTOMIZABLE LAYOUT SETTINGS - FEEL FREE TO MODIFY THESE.
var MINIMUM_ALLOWED_WIDTH = 30;
var MINIMUM_ALLOWED_HEIGHT = 30;
var TABLE_LEFT = 25;
var TABLE_TOP = 25;
var TABLE_HEIGHT = 500;
var DRAG_WIDTH = 10;
var _columnWidthSettings = new Array(0,200,300,200);
</script>
</head>


<body>

<div id="DragColumn_0"></div>
<div id="ContentColumn_1"></div>
<div id="DragColumn_1"></div>
<div id="ContentColumn_2"></div>
<div id="DragColumn_2"></div>
<div id="ContentColumn_3"></div>
<div id="DragColumn_3"></div>

</body>
</html>

Wednesday, October 12, 2005

XSLT transformation in .NET

Recently I've been doing a lot of XSLT transformations (XML to HTML) in .NET, and through trial and error I've come to a number of conclusions about how best to go about this. If you are looking for a starting point to get something that "just works" (which I wish I could have found when I started), this blog entry is meant to give you just that.

The XslTransform.Transform() method comes with a lot of overloads in NET 1.0, and as of .NET 1.1 almost the entire set was deprecated and replaced with a new set that added an additional parameter, XmlResolver. You might find the number of choices overwhelming.

Before you start pouring over every possible parameter permutation, try out the following:

XslTransform.Transform(XPathNavigator, XsltArgumentList, Stream, XmlResolver)

Out of these 4 parameters, in this demo you will only need two:
a) XPathNavigator--This parameter contains the source xml in a form navigable by XPath.
b) FileStream--This parameter contains the XSL transformation file.

The other parameters will be passed in as null:
c) XsltArgumentList--this is provided to allow you to pass in values not found in the xml, in case you need to "supplement" the xml values. If you want to do that, you may, but for myself, I place all necessary values into the xml, so I'm not needing to use this parameter and am just passing in null.
d) XmlResolver--the xml that I am generating for transformation is not using namespaces. Therefore, again, I pass in null for this parameter.

Now, let's go ahead and build a working XML to HTML xsl transformation:

1. If you don't have xml yet, generate it. For this example we'll type it in a text editor, but the XmlTextWriter class is very effective at easily generating xml for you using methods such as WriteElementString().

(For example, you might want to retrieve data from a database into a SqlTextReader instance and then use a while(sqlTextReader.Read()){} loop to create your xml document.)

Go ahead and create c:\xsltdemo\xmlfiles\MyXmlFile.xml in Notepad:


<?xml version="1.0" encoding="utf-8" ?>
<customers>
<customer id="35">
<firstname>Sally</firstname>
<lastname>Chiu</lastname>
</customer>
<customer id="36">
<firstname><bold>Garth</bold></firstname>
<lastname>Wachowski</lastname>
</customer>
<customer id="37">
<firstname>Bertha</firstname>
<lastname>Boxleitner</lastname>
</customer>
</customers>


2. Next, you'll need an XSL template to determine how to display this in HTML.

The main tags to understand if you've never used XSLT before are as follows. Read them through, have a look at the template below, then come back and re-read them again.
a) <xsl:output> tag
- set the parameters as shown below. (Note: The indent attribute is only respected when using a FileStream as your 3rd parameter in the XslTransform.Transform() method, as we are doing in this demo. If you use XmlTextWriter as your 3rd parameter, indentation is managed by the XmlTextWriter settings.)
b) <xsl:template> tag
- this is used first to contain the entire HTML for the page. However, below the initial xsl:template tag you may also create many sub-templates whose purpose is to add formatting to the specific node they are assigned to handle. Notice at the bottom of our sample that <bold≷ tags are managed by a separate template.
c) select attribute
- this attribute is contained in most xsl tags including all tags following this one (<xsl:value-of>, <xsl:apply-templates>, <xsl-if>, <xsl-for-each>). Select specifies the path to the node you are referencing within the tag, and can be expressed as an absolute path "/mynode/mysubnode" or a relative path (".", "@someattribute", "./childnode").
d) <xsl:value-of> tag
- This tag simply displays the value of the selected node. NOTE: If you try to place this tag within an HTML tag attribute, it will fail. Instead, you must place the node in curly brackets, as shown here:
regular HTML:
<xsl:value-of select="/mynode"/>
within an HTML tag:
<input type="textbox" name="{/mynode}">
e) <xsl:apply-templates> tag
- This tag is the same as value-of, except that you are throwing the value to any formatting templates within the xslt file to handle nested tags within the returned node string (such as <b> or <i>, or any customized tags).
f) <xsl:if> tag
- This allows you to do if logic. If you need else or further choices, you must switch immediately to the case statement, xsl:choose. (google it for the syntax). Note that you can test simply on a node name (such as /customers/customer) which is equivalent to asking whether the length of the value returned from the node > 0.
g) <xsl:for-each> tag
- perfect for looping. To make it work, you want your xml to contain collections, such as:
<customers>
<customer name="Clark"/>
<customer name="Lois"/>
</customers>
because this corresponds to the for-each tag as follows:
<xsl:for-each select="/customers/customer">
<td><xsl:value-of select="@name"/></td>
</xsl:for-each>

OK, you now have enough knowledge to create your xsl template:

Create c:\xsltdemo\xslt\CustomersList.xslt


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes" encoding="Windows-1252"
omit-xml-declaration="yes" doctype-public="-//W3C//DTD HTML 3.2 Final//EN" />
<xsl:template match="/">
<BODY>
<xsl:if test="/customers/customer">
<table cellspacing="0" class="dtTABLE">
<TR VALIGN="top">
<TH>ID</TH>
<TH>First Name</TH>
<TH>Last Name</TH>
</TR>
<xsl:for-each select="/customers/customer">
<TR VALIGN="top">
<TD><xsl:value-of select="@id"/></TD>
<TD><xsl:apply-templates select="./firstname"/></TD>
<TD><xsl:apply-templates select="./lastname"/></TD>
</TR>
</xsl:for-each>
</table>
</xsl:if>
</BODY>

</xsl:template>

<xsl:template match="bold">
<b><xsl:value-of select="." /></b>
</xsl:template>

</xsl:stylesheet>


3. Finally, you write your code to process the above files. Within a method, do the following:

a) declare the files and paths used within this method as local constants (within the method.)

const string XML_PATH = @"c:\xsltdemo\xmlfiles\MyXmlFile.xml";
const string XSL_TEMPLATE = "c:\xsltdemo\xslt\CustomersList.xslt";
const string HTML_DIRECTORY = @"c:\xsltdemo\htmloutput\";
const string HTML_PAGE = "Customers.html";


b) convert your xml into an XPathNavigator instance.
NOTE Your xml could be located in either a file, an XmlTextReader instance, or a MemoryStream. You are starting with a file or an XmlTextReader, but later on, for much greater performance, refactor your xml configuration/creation into a MemoryStream!


XPathDocument xPathDoc = new XPathDocument(XML_PATH);
XPathNavigator xPathNavigator = xPathDoc.CreateNavigator();


c) create a directory using the DirectoryInfo class, then build a FileStream instance on that directory path:


DirectoryInfo di = new DirectoryInfo(HTML_DIRECTORY;
if (di.Exists != true)
di.Create();

string absoluteFilePath = di.FullName + HTML_PAGE;

FileStream fileStream = File.Create(absoluteFilePath);


d) perform the transformation:


XslTransform xslt = new XslTransform();
xslt.Load(XSL_TEMPLATE);
xslt.Transform(xPathNavigator, null, fileStream, null);


You're done!

Thursday, October 06, 2005

Logic for determining method inheritance type

Topics:
.NET Framework 1.x, 1.1
Reflection
MethodInfo
IsHideBySig
GetBaseDefinition()
Type.GetMethod Method (String, Type[])
overrides keyword
new keyword

*****

Using reflection I can easily determine all kinds of member information, such as whether a method is abstract, static, virtual, final. (MethodInfo.IsVirtual and so on)

However, how to easily determine a method's inheritance type? (corollary: and the inheritance type of a property's accessor methods?)

In other words, how does one determine where a method has originated:

in the current type (i.e. MethodInfo.ReflectedType),
inherited from a base class,
as overridden in the current type (from a virtual method in a base class)
as 'new' keyword in the current type (to hide this method in the current type from a method in the base class of the same name--whether or not the base class method has been declared as virtual.)

To do this, I created a method to parse these four possibilities. To store these values, I created an enumeration, InheritanceTypeEnum:


public enum InheritanceTypeEnum
{
DeclaredLocally,
Inherited,
OverridesKeyword,
NewKeyword
}


I then created two helper methods to parse each inheritnace type.


Note: I encountered two unexpected behaviours with Reflection API that required workarounds:

1. MethodInfo.IsHideBySig returned true in many scenarios other than a method with a 'new' keyword.
Workaround: I created my own method, IsMethodOfSameSignatureFoundInBaseClass(), to determine this.)

2. Type.GetMethod Method (String, Type[]) returned non-null in scenarios with the parameters in the method matched with false positives (when a parameter type was compared against a parameter of type System.Object).
Workaround: I wrote verification code on the returned method.

Here's the code:

/// 2005Oct06 David Gadd
/// new logic to determine the member's inheritance type
/// (local, inherited, overridden or new keyword)
private InheritanceTypeEnum GetInheritanceType(MethodInfo currentMethod)
{
// 1. If the method's current type != method's declaring type then the method is inherited.
// (It does not exist locally at all, it is entirely owned by the base/declaring class.
if(currentMethod.ReflectedType.FullName != currentMethod.DeclaringType.FullName)
{
return InheritanceTypeEnum.Inherited;
}
else
{
// 2. If the class that owns the base(first) definition of this method != the class
// that owns the current definition of this method, then the method is overridden
if(currentMethod.GetBaseDefinition().ReflectedType.FullName != currentMethod.ReflectedType.FullName)
{
return InheritanceTypeEnum.OverridesKeyword;
}
// else the method is defined locally within the current type
else
{
// 3. If the local method has a method of the same name in the base class
// of the current type, then this method must be qualified with the "new" keyword.
// (Originally I tried to do this with MethodInfo.IsHideBySig, but I found that this
// property returned true in many cases other than the 'new' keyword...)
bool hasSameNamedBaseMethod = IsMethodOfSameSignatureFoundInBaseClass(currentMethod);

if(hasSameNamedBaseMethod)
{
return InheritanceTypeEnum.NewKeyword;
}
else
{
// 4. If the local method's signature does not use the 'new' keyword,
// then the method is completely local in origin.
return InheritanceTypeEnum.DeclaredLocally;
}
}
}
}

/// This is meant to be a more reliable alternative to the MethodInfo.IsHideBySig property
/// which seems to render true for many situations other than the application of the new keyword
/// to a method
private bool IsMethodOfSameSignatureFoundInBaseClass(MethodInfo currentMethod)
{
bool matchFound = false;

try
{
MethodInfo baseMethod;

ParameterInfo[] currentMethodParameters = currentMethod.GetParameters();
Type[] currentMethodParameterTypes = new Type[currentMethodParameters.Length];
for(int i = 0; i < currentMethodParameters.Length; i++)
{
currentMethodParameterTypes[i] = currentMethodParameters[i].ParameterType;
}

baseMethod = currentMethod.ReflectedType.BaseType.GetMethod(currentMethod.Name, currentMethodParameterTypes);

if(baseMethod != null)
{
// At this point, if the baseMethod is not null, a succesful match SHOULD have been found.
// However, it appears that Type.GetMethod(name, parameterTypes) may have a bug:
// in testing I have found that in a 1-parameter method where the base class has a
// type of System.Object, it will be perceived as a match to an overload method in the derived
// class of 1-parameter whose type is NOT System.Object. Therefore, the following workaround:
// retrieve the parameter from baseMethod and do an explicit Type comparison.
ParameterInfo[] baseMethodParameters = baseMethod.GetParameters();

for(int i = 0; i < baseMethodParameters.Length; i++)
{
if(currentMethodParameterTypes[i] == baseMethodParameters[i].ParameterType)
{
matchFound = true;
}
else
{
matchFound = false;
// as soon as any false matches are found, exit the loop.
break;
}
}

}
}
catch(AmbiguousMatchException aex)
{
// the code above thoroughly handles method signatures
// so an ambiguous match should never be found.
throw new AmbiguousMatchException(aex.Message);
}
catch(ArgumentNullException)
{
matchFound = false;
}
catch(Exception)
{
matchFound = false;
}

return matchFound;
}