Saturday, August 25, 2007

Smart Client (SCSF) Membership App - Views

This post is part of a series which discusses the journey I took building a smart client membership application using Microsoft's Smart Client Software Factory (SCSF). You can navigate the entire series from here.

Views are probably the easiest artifact for smart client developers to understand and build. The fact that views are implemented as user controls and that there are recipes for generating them only lend to their ease of use. Finally, there is a lot of documentation and good diagrams about views to peruse. Even the model-view-presenter (MVP) paradigm is pretty well known or at least accessible with all the information available. What is perhaps a bit trickier is how to make multiple views work in concert within the SCSF architecture of shells, work items, and controllers.

GS_PersonTabView In this article, I will be dissecting the Person views show in the left screenshot. The Members project treeMemberProjectTree shown on the right currently contains four view folders - two for Person and two for Household. These views were generated using the "Add View (with presenter)" recipe and then customized as needed. For the list views, I used the SmartPart Quickstart that comes with the SCSF installation as a basis for my lists. I chose the Windows.Forms.ListView control as I want to be able to show a richer user interface that's more intuitive such as showing each household in a tree view with the individual persons listed as sub-items underneath the household. I'd also like as much "free-form" navigation as possible. For example, selecting a person from the aforementioned tree-view will display that person's information even though the user has currently navigated to households. The relationship between people and households is so great that it doesn't make sense from an application perspective to force the user to always navigate by going to the menu or toolbar. Seeing a list of households and the people associated, it's natural to want to click on the person and navigate to manage that person's information. This kind of flexibility is more easily met using the ListView as the basis of the work.

The PersonListView shown running in the above screenshot is displayed via a command handler that is wired up to the 'People" menu item and toolbar button. Here's the code that causes the view to be displayed:

  [CommandHandler(CommandNames.ManagePersons)]
public void ManagePersonsHandler(object sender, EventArgs e)
{
ShowViewInWorkspace<PersonListView>(WorkspaceNames.LeftWorkspace);
}

The MVP pattern is implemented for you by the recipe-generated code. When the view is created a presenter instance is also created and made available as a property of the view class:

  [SmartPart]
public partial class PersonListView
{
/// <summary>
/// Sets the presenter. The dependency injection system
/// will automatically create a new presenter for you.
/// </summary>
[CreateNew]
public PersonListViewPresenter Presenter
{
set
{
_presenter = value;
_presenter.View = this;
}
}
}

Following the MVP pattern, the view should only be concerned with displaying the information it is given without knowledge of anything else the application might have available to it or be doing. To help with this the view's OnLoad() method signals that it is "ready" by calling over to the presenter's OnViewReady() method:

  protected override void OnLoad(EventArgs e)
{
_presenter.OnViewReady();
base.OnLoad(e);
}

The presenter's code is where the action takes place - it "contacts" the model, in this case by calling the _memberSvc.GetPersonList() method, to get the data and then gives that data to the view via the View.SetPersons() method:

  [ServiceDependency]
public MemberService MemberService
{
set { _memberSvc = value; }
}

/// <summary>
/// This method is a placeholder that will be called
/// by the view when it has been loaded.
/// </summary>
public override void OnViewReady()
{
base.OnViewReady();
LoadPersons();
}

private void LoadPersons()
{
PersonListItemCollection persons = _memberSvc.GetPersonList();
if (persons != null)
{
View.SetPersons(persons);
}
}

Note that the MemberService is filling the role of the model and will be discussed in another article. The view's SetPersons() method was taken almost verbatim from the SmartPart QuickStart and is responsible for taking data and rendering it:

  #region IPersonListView Members

void IPersonListView.SetPersons(PersonListItemCollection persons)
{
Guard.ArgumentNotNull(persons, "persons");
InnerSetPersons(persons);
}

#endregion

private void InnerSetPersons(PersonListItemCollection persons)
{
List<ListViewItem> toRemove = new List<ListViewItem>();

foreach (ListViewItem item in _personListView.Items)
{
PersonListItem listPerson = PersonMapper.FromListViewItem(item);
int index = persons.IndexOfId(listPerson.ContactId.Value);
if (index > -1)
toRemove.Add(item);
else
persons.Remove(persons[index]);
}

toRemove.ForEach(delegate(ListViewItem lvi) { _personListView.Items.Remove(lvi); });
foreach (PersonListItem person in persons)
_personListView.Items.Add(PersonMapper.ToListViewItem(person));
}

The next part to look at is how users interact with the views. When a person shown in the list view is clicked, the right-hand workspace should show that person's details. To accomplish this we must fall back on the "formula" for MVP - the view is only concerned with rendering the data. The PersonListView therefore does not "know" what to do with the click - the selected person is displayed elsewhere (outside the view) by another part of the application. To accomplish this separation of concerns, the initial Windows Forms event (that is, the on click handler) needs to be translated into a CAB Event that is published to the world (okay, the rest of the application) to be handled elsewhere:

  private void personListView_SelectedIndexChanged(object sender, EventArgs e)
{
if (_personListView.SelectedItems.Count == 0)
return;

//Pass the event directly to the presenter to handle
PersonListItem person = PersonMapper.FromListViewItem(_personListView.SelectedItems[0]);
_presenter.ShowPersonDetails(person.ContactId.Value);
}

Remember the MVP pattern has all three pieces working together so the view simply passes the action on to the presenter to take care of by calling the presenter's ShowPersonDetails() method. It is here in the presenter's method that the user's action is converted to a CAB event and published:

  [EventPublication(EventTopicNames.ShowPersonDetails, PublicationScope.Global)]
public event EventHandler<EventArgs<int>> ShowPersonDetailsHandler;

public void ShowPersonDetails(int contactId)
{
//To maintain a separation of concerns publish an event to be handled elsewhere
if (ShowPersonDetailsHandler != null)
ShowPersonDetailsHandler(this, new EventArgs<int>(contactId));
}

We know we want to display the person's details on the right-hand workspace but the trick is where to place that code and how to make it happen. I jumped the gun in the previous article about work items by showing the logic for creating a person work item and kicking it off. The full code for this is in the ModuleController.cs class. The reason it is there is one of logical grouping and hierarchy of responsibility/ownership. The coarse hierarchy is Shell -> Members_Module -> Child_Work_Items and since the PersonListView is displayed by the module during the menu command handling event, it is the module that "owns" the view (actually, "owns" the model-view-presenter trio). Therefore, when the presenter raises the ShowPersonDetails event, it is best to put that code as close to where it is related while going back up the "chain". Since the Members module contains all things relating to members and since the PersonListView is owned and managed by that module (actually, that module's ModuleController) then the next step back up the chain from the presenter is the ModuleController - remember that the module controller is a specialized work item. So the proper place to scope the event (see previous post about work items being a scoping container) is the next work item up the chain which is also the work item for the entire module. Here is the code in the ModuleController.cs that responds to the CAB Event:

  [EventSubscription(EventTopicNames.ShowPersonDetails, ThreadOption.UserInterface)]
public void OnShowPersonDetails(object sender, EventArgs<int> eventArgs)
{
int personId = eventArgs.Data;

//Create a key for the workitem so we can check
//later if the workitem has already been created.
string key = "Person" + personId;

PersonWorkItem workItem = WorkItem.WorkItems.Get<PersonWorkItem>(key);

if (workItem == null)
{
// add a new work item representing this instance to
// to the collection of work items in this module
workItem = WorkItem.WorkItems.AddNew<PersonWorkItem>(key);

// add a new detail view smart part to the collection of smart parts
workItem.SmartParts.AddNew<PersonDetailView>("PersonDetailView");
workItem.SmartParts.AddNew<PersonChannelsView>("PersonChannelsView");

workItem.Run(personId, WorkItem.Workspaces[WorkspaceNames.RightWorkspace]);
}
else
workItem.Activate();
}

At first there appears to be a lot going on but it boils down to creating a PersonWorkItem instance for the selected person and then "running" the work item that is, telling it the workspace to display its views in. One thing I discovered the hard way through lots of Google "digging" is that if you're using SmartPartPlaceholders on a view you need to ensure the smart parts that are going to be displayed in the placeholders already exist in the SmartParts collection before the view is shown. This is why I'm manually creating the detail and contact channel views and adding them to the collections before the work item's Run method is called. Here is what the PersonWorkItem's Run() method looks like:

  public void Run(int PersonId, IWorkspace ContentWorkspace)
{
Run();

// save the Person data in the work item
Person person = _memberSvc.GetPerson(personId);
Items.Add(person, "Person");

// create a person tab view and show it
tabView = SmartParts.AddNew<PersonTabView>();
TabSmartPartInfo tabInfo = new TabSmartPartInfo();
tabInfo.Title = person.FullName;
tabInfo.ActivateTab = true;
ContentWorkspace.Show(tabView, tabInfo);
}

As you can see above, the person id is passed in and used to retrieve the Person instance. Note that this person instance data is stored in the work item's Items collection - not in it's State. Unfortunately, lots of examples show how easy it is to use the [State] attribute on a property to automagically copy the data from the work item state into the view's property member. This is bad. The state bag is for persistence of data across invocations of the application and it is type-less. That means everything has to be cast and value types are boxed/unboxed which is not the best performing thing to do. The Items collection is a .NET 2.0 generic collection that was specifically designed for holding things, a.k.a. "items". It is easily accessed in views and presenters using the WorkItem property that refers to the work item they are contained in. I confess that I did the bad thing first, using the same examples and documentation but it became harder to follow and manage and I soon found several posts saying using the State was bad, okay it wasn't optimal or necessary if you weren't planning on saving it. Instead of coding your property using the [State] attribute you should do this:

  public Person Person
{
get { return WorkItem.Items.Get<Schema.Person>("Person"); }
}

In summary, I've talked about views, the MVP pattern and how it's implemented, where to put logic, how to convert user events into CAB Events and "catch" them elsewhere, how the work items are brought into play and how to bring it all together. In the next article I'll go into details about the service layer and how it's implemented.

Original post

Friday, August 24, 2007

Smart Client (SCSF) Membership App - Work Items

This post is part of a series which discusses the journey I took building a smart client membership application using Microsoft's Smart Client Software Factory (SCSF). You can navigate the entire series from here.

Work Items are one of the SCSF/CAB artifacts that causes angst amongst smart client developers. Part of the problem lies with the early whitepaper Architecting Composite Smart Clients Using CAB and SCSF and the early CAB examples reflecting the thinking of the p&p team at that time. The section entitled Use-Case-Driven-Strategy would have you create work items based upon each use case. In the banking case study, there would be 17 work items created! After pages and pages of details and diagrams on turning use cases into work items there's about half a page of text following that almost casually mentions another approach- mapping work items to business entities.

In my membership application I took the business entity approach which I felt was reasonable, easier to develop, manage and it seems the p&p team's thinking is also evolving. Peter Provost, Development Lead for the p&p team, sums it up best in this podcast (sorry for their crappy, annoying splash ad!) - "there might not be enough value in creating that many work items". He goes on to say, "a work item is a scoping container" which David Platt echoes in his book, Programming Microsoft Composite UI Application Block and Smart Client Software Factory (Pro-Best Practices)*, equating a work item to a Win32 Process.

GS_PersonTabView Given the major business entities previously mentioned in this article series, it was natural for me to create a PersonWorkItem and HouseholdWorkItem. Each of these scopes the data, views and logic related to a specific business entity during runtime. There aren't any recipes for creating a work item so ya' gotta do it the old fashioned way - writing code by hand. Work Items are just simple classes that derive from Microsoft.Practices.CompositeUI.WorkItem. The example screen shot shown on the right contains a list of people on the left side. Clicking a person's name displays the person's information on the right side.

The Members module contains a ModuleController class which is a specialized work item of type ControlledWorkItem - part of the recipe-generated code. I've added an EventSubscription there to respond to the person being chosen in the list view (see the article about views for more detail regarding the event handling specifics). Here's the logic that's executed within the event subscription:

  int personId = eventArgs.Data;

//Create a key for the workitem so we can check
//later if the workitem has already been created.
string key = "Person" + personId;

PersonWorkItem workItem = WorkItem.WorkItems.Get<PersonWorkItem>(key);

if (workItem == null)
{
// add a new work item representing this instance to the
// collection of work items in this module
workItem = WorkItem.WorkItems.AddNew<PersonWorkItem>(key);

// add a new detail view smart part to the collection of smart parts
workItem.SmartParts.AddNew("PersonDetailView");

workItem.Run(personId, WorkItem.Workspaces[WorkspaceNames.RightWorkspace]);


Remember, this code is in ModuleController.cs which is a work item so the reference WorkItem.WorkItems is the collection of work items within the ModuleController - WorkItem is a reference to the controller's work item and WorkItems is a collection within. Thus, I am attempting to retrieve the existing work item for the selected person and, if it doesn't exist, then a new PersonWorkItem is created and added to the ModuleController's collection of work items that it maintains. Therefore, each person selected for display causes a PersonWorkItem instance to be created and managed. Of course, how many get created and how they are disposed is up to you. In my case, since I'm using a TabWorkspace with one tab per person shown (see the bottom tabs on the above screen shot) I may have several PersonWorkItem instances managed in memory at once.

As the comment shows above, a PersonDetailView smart part is created and added to the new PersonWorkItem instance's collection of smart parts it manages. The new PersonWorkItem instance's Run method is then called and the right hand workspace is passed as an argument telling it where to display itself. Additional smart parts are nested within the detail view using smart part placeholders and it all displays "like magic".

The key concept here is how I scoped work relating to a business entity - a Person - within a work item. That work item instance contains a smart parts collection which holds all the views related to displaying a person as well as the logic and event handling that takes place when the user interacts with the views (a.k.a. smart parts).


* David's book must be vying for the award of longest title for the thinnest technical book :). It is great as an overall introduction and I recommend it if you're just getting started in CAB/SCSF but the "programming" part is light compared to some programming books. Don't get me wrong - David is a gifted technical writer but if you're looking for a lot of meat and in-depth programming examples then you'll be disappointed. If you're looking for a good foundational understanding of SCSF before embarking on a project then by all means buy the book - I wish I had it when I started out but it was just published a couple of weeks before I wrote this article.



Original post

Thursday, August 23, 2007

Error Installing Module(s) on DNN 4.5.5 at WebHost4Life

I was setting up a new DNN installation at WH4L and received this error while running the installation wizard. I first tried manually cleaning things out and doing it again only to receive the same error. Turns out the trick is the file permissions on the newly installed modules under \DesktopModules aren't correct. When you receive the errors, simply open a new browser window, login to WH4L control panel, go to Security | File Permission and reset permissions for NETWORK SERVICE with the box check for subfolders. When that's finished, switch back to the installer wizard with the errors still showing and you can now click Next to continue and finish the installation.

Wednesday, August 22, 2007

Smart Client (SCSF) Membership App - Members Module

This post is part of a series which discusses the journey I took building a smart client membership application using Microsoft's Smart Client Software Factory (SCSF). You can navigate the entire series by going to the introduction.

The Members module is typical of SCSF business modules - it encapsulates the presentation and UI logic of a related set of business functionality. For the membership application I'm building this means:

  • People
  • Households
  • Contact Info (phone, email, etc.)
  • Addresses
  • Relationships between all of the above

GS_MemberEntitySolutionTreeUsing the SCSF recipe to add a business module adds a new project to the Visual Studio solution as shown here:

I've highlighted a few key items to note:

  • EntityLayer is the separate project containing the business entity logic and translators. This project is not directly referenced by the Members module. Refer to my previous blog post for more details.
  • WebServices is a traditional web service layer which is currently referenced by the Members Module but will be replaced with a service layer enabling switching between local and remote databases.
  • Members is the new business module and topic of this post.

Also, notice the views subfolder containing several views which this module manages. Here is an example screen shot which I've decorated to illustrate some of the various views in action:

GS_PersonTabViewThe first thing to note is the menu option added to the shell application entitled "Members". The code for doing this is located in the ModuleController.cs file created by the SCSF recipe - the method ExtendMenu contains a "// TODO:" -like section for you to fill in as shown here:

GS_MembersMenuCode

The toolstrip (a.k.a. toolbar) partially hidden by the dropped down menu also contains icons for accessing People and Households functionality. The key concept with the menu and toolstrip comes from the nature of CAB itself - composite applications. The Members module contains functionality related to members and exposes it thru views and by extending the shell's infrastructure, i.e. menus and toolbars. When the Members module is loaded by the shell it "plugs in" this functionality by extending what's provided in the shell. Thus, the finished application is "composed" of discrete pieces that fit and work together as a whole.

Since the Members module added the menu options and toolstrip buttons, it also registers and contains the command handler to react when UI events occur, that is when either is clicked. When either the People menu option is chosen or the People toolbar icon is clicked, the command handler for that event launches the PersonListView which you see indicated on the left side of the screenshot above. Clicking on a person's name in the list view fires another event which triggers the PersonTabView to be displayed with the selected person's name on the tab as shown at the bottom of the screen shot. This tab view is currently a CAB Tab Workspace which I swapped in place of the default Deck Workspace which the recipe generates.

The PersonTabView currently contains additional views for the summary information as well as the contact information. Household works the same way but with household-specific information such as addresses.

This has only been a high level examination of the Members module. The next few posts will drill down into specific areas for deeper examination.

Original post

Tuesday, August 21, 2007

Smart Client (SCSF) Membership App - Data Layer

This post is part of a series which discusses the journey I took building a smart client membership application using Microsoft's Smart Client Software Factory (SCSF). You can navigate the entire series by going to the introduction.

Several years ago I developed a .NET code generation tool based on XSLT. It evolved over time and even had a front-end GUI to enable selection of various templates as well as database schema browsing. I had to hack my way through the INFORMATION_SCHEMA output of databases and developed an XML schema representation of the data model to drive the XSL templates. Of course, about six months after I had a working version Kathleen Dollard published her book Code Generation in Microsoft .NET and I could've saved myself a lot of effort if I'd just waited.

The generator was "nice" but the real effort was the model and architecture of the code being generated. Eventually I developed an "internal" architecture for use on in-house projects which consisted of a two-tier inheritance model and an "external" architecture for use on client consulting projects that was a simpler one-tier model which I could leave behind. I used my templates at one client site to generate the initial code and later a fellow consultant, Phil Denoncourt, took that generated code and built templates using CodeSmith - yup, he reverse-engineered my generated code back into another code generator. I liked CodeSmith so much that I purchased a license, rebuilt the XSL templates using their ASPX-like syntax and went on adding features to the generated architecture.

Fast-forward to today and once again I'm looking at tweaking the code generation templates. As I mentioned already one of the architectural features of the membership application is the ability to work with an embedded database. Unfortunately, SQL Server 2005 Compact Edition does not support stored procedures - only embedded SQL statements. This led me back to the templates to add an option to the existing support for stored procedures (and generation of them) with a second option of generated embedded, parameterized SQL statements. Of course, this change ripples outward by causing changes to the C# code closest to the SQL calls. After several hours of effort over the course of nights and weekends I was getting back into business - basic CRUD behavior was working again using embedded SQL.

At this point, time is marching on and I'm still not working on the business problem per se, but rather the toolsets and "noise" necessary to build a robust application. Thinking there must be a better way, I went searching for CodeSmith templates that might do what I needed. Instead, I came across EntitySpaces and fell in love. EntitySpaces is a low-cost, high-featured code generator built as templates for the MyGeneration open source generator. Mike Griffin, who co-authored the MyGeneration tool, and team have done a tremendous job providing a commercial offering. It support MS Access, MySQL, Oracle, SQL Server and SQL CE and can generate either stored procedures or embedded SQL - just what the doctor recommended. I took it for a test drive and quickly plopped down $149. At the time I was also considering an upgrade to CodeSmith 4 but that would've cost more and I still would be faced with writing and supporting the templates. Incidentally EntitySpaces 2008 will also ship with CodeSmith templates as well as MyGeneration - this blog entry has some nice diagrams showing the architecture. Anyway, now I can go back to working on the membership application and leave those guys to focus on what they do.

I know it was a long-winded digression but I needed to get that off my chest and wanted to show where I chose to spend time and where I didn't. Remember, this is just the data layer - there's a whole application that sits on top of it. EntitySpaces produces a data layer that consists of hierarchical objects and collections which are first class .NET 2.0 citizens. You can read more about it on their site. For this application, I've now got a Person class which exposes properties such as FirstName, MiddleInitial, LastName and has methods like AddNew(), Load(), LoadByPrimaryKey(), and Save() as well as a PersonCollection which is bindable, enumerable and uses generics. Think about it, if you were charging $50 per hour it would only take three hours (okay, four if you count overhead) to recoup the cost. Now, how many hours would it take to design, build, test, and support all that functionality? Run, don't walk, and go find a tool like EntitySpaces!

GS_PersonDataLayerLike my home-grown tool originally, EntitySpaces generates a two-tier model of base classes (theirs are prefixed with "es" before the table name) and derived classes that are "empty" of properties and logic. You should never change the generated base classes as they will be regenerated when you re-run the tool as your model changes and evolves. This screen shot shows the derived Person class generated for the Person table:

Notice the red arrow indicating the "Generated" sub-folder - this is where the base generated classes are located. One example of customizing the generated code is show here in the derived Person class - I added a property called FormalName built dynamically from the base properties (columns) of Salutation, FirstName, MiddleInitial, LastName, Suffix.

The SCSF introduction has a nice graphic about halfway down showing the factory layers (it's some kind of bluish-purple color). Like many examples, the data layer sits closest to the database and typically models the database very closely. The generated classes I'm using do so as well - at a very granular level, one logical class per table - and I put them in their own assembly called...DataLayer.

I've implemented the notion of business entities a little differently though. The SCSF Hands On Labs show creating and transforming business entities right in the client layer the "raw" entities returned by the web service layer. You could place the business entities and their translators in a business module which you've created or in the pre-built Infrastructure projects - entity in Infrastructure.Interface.BusinessEntities and translator in Infrastructure.Library.Translators.

My first attempt was to follow the SCSF guidance - I put the DataLayer assembly behind a web service, created business entities and translators in the UI layer and it worked. However, for my standalone model with an embedded database, I'd need the data layer on the client as well as the web server. Plus, I didn't like the inheritance model, e.g. Person and Household inheriting from Contact, to be exposed and carried/spread around my application world. Looking at the guidance approach and building on past experience with multi-tiered applications, I decided to refactor things a bit.

SchemaLayerI created two separate assemblies (foundational modules in SCSF parlance) to represent the business layer - one contains the business entity schema and the other contains the business entity logic for transforming the underlying data layer to the business entity. The business layer sits logically between the main application and the back-end database/data layer as shown here:

One important point relates to the web service layer. So many times I see books, articles, how-to's, and samples which make out the web service layer to be so much more than it is. In my opinion, a web service is nothing more than a facade over a wire protocol - it's XML over HTTP, that's it...SOAP. My web service layers have nothing in them but code to call the back-end applications and return results to the caller. No data access code, no ADO.NET connections, nothing, nada, nil. As you can see from the above diagram, the web service layer can be removed and another protocol or none in the case of a local database can be used without disrupting the rest of the application stack.

The business entity schema layer is nothing more than class definitions containing properties which can be serialized as shown here:

PersonSchemaClass

Notice that I "flattened" the underlying hierarchy by including in the Person the inherited properties of the Contact table. In other words, my business entities are things like Person and Household while the underlying data model's connecting or "linkage" tables and relations are kept hidden.

The entity layer contains the logic for retrieving and flattening the model:

PersonEntityClass

This will also be the place where additional business logic lives - raising events, logging actions, etc.

Finally, as I said before, the web service layer is simply a facade that provides a SOAP interface so you'll find no logic or data access code here:

PersonWebMethod

The serialization to/from a string is no doubt causing some Microsoft purists to groan, jump up and down and wave their arms :) I chose to do this so I could simply reference the same business entity schema on the client side and deserialize the results back into a Person. If I had returned a Person class, it would've been a Person in the web service's namespace which creates unnecessary hassles. Plus, I get the added benefit of allowing easy encryption/compression of the data flowing across the wire (a feature for later), the client only needs to reference the schema assembly (and the web service of course), and it'll fit well later on when I introduce the SCSF service layer which will hide the conversion to/from a serialized XML string.

Original post

Smart Client (SCSF) Membership App - Design

This post is part of a series which discusses the journey I took building a smart client membership application using Microsoft's Smart Client Software Factory (SCSF). You can navigate the entire series by going to the introduction.

The membership application is primarily about tracking and managing memberships in an organization such as a church or synagogue, small club or user group, sports team or league, etc. In the case of our church we need to track both individual persons and households. People may give us cell phone as well as home phone numbers and we mail our weekly newsletter to people who couldn't attend worship. Thus phone numbers can be associated with an individual person or a household and, of course, households have physical addresses for mailing purposes - we wouldn't mail two copies of a newsletter to a couple but rather one copy to their household address.

GS_ContactSchemaI chose "Contact" as the primary entity, e.g. something which we have contact with and derived both "Person" and "Household" from that base entity. This allows either a person or a household to have contact information associated with it and allows a household to have one or more locations associated with it, e.g. main address, winter address, summer vacation address. Here's a portion of the schema which represents these relationships:
 
One other interesting relationship is that one or more people are associated with a household and have a "role" in that relationship, e.g. husband, wife, daughter, son, etc. Thus, a single household contact is associated with one or more person contacts. It's these kind of sticky relationships that confounded me when trying to use Access' forms engine and event-driven data model where much of the magic goes on under the covers.

For manageability and future growth, I decided to logically bundle information and functionality into separate modules which I've identified as:

  • Members
  • Groups
  • Events
  • Reporting

Members is the foundational module upon which the others are built. Groups allows for dividing and tracking members in smaller units, e.g. youth group, men's group, committees, teams - assuming you're managing a whole sports league, etc. Events allows managing of date-related information, scheduling, tracking attendance, etc. Reporting is a bit of a catch-all for more complex information generation - each module should have limited print capabilities but reporting is a whole separate beast so to speak. Of course, SCSF uses the concept of modules - see link in first paragraph for overview of SCSF and its capabilities - so this type of decomposition fits naturally with the toolset.

Architecturally speaking, I have a set of goals I'd like to accomplish as well. The first is driven by commercial and market interests - I'd like the ability for the application to be downloaded and installed in a simple, standalone manner with minimum fuss. While I haven't decided yet, I can envision the possibility for shareware, trialware, or free editions that have a basic set of functionality. For me this means using an embedded database such as SQL Server 2005 Compact Edition or SQLite both of which have a very small footprint and can be deployed within the application without a lot of overhead. Can you imagine a pastor, part-time coach, choir director or quilting club chairperson having to install, configure and secure a full-blown database server on their computer not to mention the overhead it takes to run one?

At the same time, I need the ability to enable distributed management of information. Specifically, our church has two sites and two part-time administrative assistants who share duties. They both need access to the same data which means some type of remote capability so a hosted web service layer with a shared database behind it fits the bill nicely. Of course, when considering a remote model the notion of occasionally connected or disconnected clients must also be taken into consideration. Again, features which are provided for by SCSF.

As in many or most applications, there exists a common set of tasks which need to be repeated over and over. While the list of modules is varied, three of them share the common need to maintain information so I will focus on the Members module primarily.

Original post

Smart Client (SCSF) Membership App - Background

This post is part of a series which discusses the journey I took building a smart client membership application using Microsoft's Smart Client Software Factory (SCSF). You can navigate the entire series by going to the introduction.
My first approach was to fire up trusty old MS Access to build a single database even though I knew that sharing information wouldn't be easy yet is possible. I've used Access off and on since it was first introduced and even resurrected some screen shots (here, here, and here) using Virtual PC to run Windows for Workgroups 3.11. I had recently developed a one-off application for a friend to track customers and orders at her day spa. She first asked me to help by fixing some bugs and adding a couple of enhancements to an Access 2000 application originally written by a college student she had hired. It looked to me like this person had hacked up the Northwind sample and made it work reasonably well for her but there were a few problems - like canceling an order that was started did not return the products to inventory. In short, I took it as a challenge and opportunity to brush up my Access skills. When it was done there was a whole new application consisting of 36 tables, 35 queries, about the same number of forms and reports and a dozen modules. It took a couple of months of nights and weekends to do but I enjoyed it and felt I had a good, solid code base to work from for future projects.
Using that code base as a starting point, I figured it would be pretty easy to build on it to develop the membership system. After working out the data requirements and designing the schema I set about building the UI portion. After a while, it became apparent to me that I was asking too much of Access. Simple flat single-table screens and parent-child screens can be done very easily - lots of examples to follow! However, more complex tabbed user interfaces housing controls for many tables and complex relationships such as one-to-one and one-to-many-to-one drove me deeper and deeper into hacks and workarounds trying to get things to behave. Before long, I found myself remembering (daydreaming) how I had built similarly complex user interfaces in the past using Visual Basic 3/4/5/6 and my frustration mounted. Finally, I came to the conclusion that I was spending all my time trying to work around the tool and not solving the business problem at hand.
I then went back to square one and began looking around for a better toolset to work with. Requirements such as distributed access, offline capabilities, easy remote deployment and updating, and scalability led me to look at the SCSF toolset.
Original post

Monday, August 20, 2007

Building a Smart Client application using CAB / SCSF

I am building a smart client application using free tools from Microsoft's patterns & practices team. The primary toolset is the Smart Client Software Factory, referred to as "SCSF", which is an integrated set of architecture guidance, templates and Visual Studio 2005 "recipes" that both makes the job easier (perhaps an oxymoron as you'll see) and gives you a solid framework to build a robust, scalable, professional-quality application. My aim is to document and share key points along the way.
The application I'm building is a membership system used to manage and engage members of a group or organization. Key features it has are:
  • Flexible design enabling use by a variety of audiences.
  • Easy to use, familiar user interface.
  • Enable distributed management of data.
  • Enough features to provide a compelling reason to use it.
I am doing this for two reasons - to build a low cost (free) application that my church can use to manage its membership and to satisfy my geek nature by building a serious application that others can use.
My wife is the administrative assistant for our church and when she took over the position she inherited a variety of "data sources" - a church directory consisting of names, addresses, telephone numbers and email addresses in an MS Word document, other assorted membership lists in their own MS Word documents, Excel spreadsheets used to track attendance and an Outlook Express address book used for email communication. Our situation is more common than you may realize even in today's electronic age. Non-profits operate on very low (or no!) budgets and volunteers who donate some time and effort. What started out for us as small organized lists has grown over time to become unmanageable and even outdated. Information has to be manually synchronized across multiple documents and inevitably things get missed and forgotten.
Here are the posts which document this journey:
  1. Introduction (this post)
  2. Background
  3. Overall Design
  4. Data Layer
  5. Business Module
  6. Work Items
  7. Views
  8. Services
  9. Deployment Packaging
Original post

Monday, August 13, 2007

Catching up

In the last eight months I have...

  • Gone on a 2nd annual mission trip to western Louisiana with my two daughters to build a new home for a family of six who lost everything in Hurricane Rita 18 months before. [Editorial tirade: Funny how the media jackals are off to find fresh meat and nothing is mentioned about the people still struggling. If it is mentioned, it's always a brief aside about New Orleans and Hurricane Katrina and that's it. I guess Paris Hilton, Britney Spears, Lindsay Lohan, Nicole Richie, and God forbid, let's not forget about poor Anna Nicole are far more important headlines. Gimme a break!]
  • Navigated the sale, along with my business partner, of our small consulting business to a software company. I along with 5 employees converted to full time employees joining the new company. For me, it's a change from being your own boss, a.k.a. "the boss" to being in an organizational chart that's 25 times larger.
  • Re-doubled efforts to build a membership system for our local church, on the side (several more posts coming from this effort).

Forget CentOS under Virtual PC - Ubuntu is "IN"

Late last year I blogged about trying to use CentOS under Virtual PC. After several attempts, I basically gave up. There's something to be said for marketing and "mind share" - which Ubuntu has plenty of. Earlier this year I took a stab at using Ubuntu with Virtual PC (and Virtual Server) and got a lot further. In fact, I managed to get a full working version for a developer environment. I also got a version going for an Oracle server, a WebSphere/DB2 server and even a streaming media server.

Things went so well that I repurposed an old dual-processor Dell workstation to run as an Ubuntu development machine on "bare metal". I normally work in Windows but now can "dabble" in Linux without difficulty...

WorkDesk

Because I'm working on enterprise-class applications using J2EE, I opted for using Ubuntu 6.06 (a.k.a. Dapper Drake) since it is the official server version with L.T.S. (long term support).

I can install and use the following on either environment:

  • Java 1.4 and 1.5
  • Eclipse IDE
  • JBoss application server
  • DB-Visualizer
  • Ant/Tomcat
  • Perforce
  • Firefox

The cool thing to me as a geek is that I can be on two totally different architectures and platforms and use the same tool sets to work on the same code.