BBQ Shack – Ocean v2 for Visual Studio 2008

homepage What the Heck is BBQ Shack?

In September of 2009 I went on a cruise to Alaska with a simple goal of writing a WPF application that shared business objects and Ocean framework code with a Silverlight 3 project within the solution.  The WPF and Silverlight code sharing has since been made much easier.  Since Visual Studio 2010 Beta2, Silverlight 3 and 4 can easily share code with WPF;  I blogged how to do this here.

Before this cruise Jaime Rodriguez was encouraging me to quit writing line of business applications that looked and felt like Windows Forms applications.  He wanted me to start taking advantage of the capabilities of the WPF platform and not settle with an older UI paradigm.

Additionally, I felt challenged by the WPF work Billy Hollis did in 2008.  I strongly recommend that every WPF & Silverlight line of business application developer watch this challenging dnrTV video.

To my surprise, once I got aboard ship, I realized that two suite cases of Scope Creep got dropped off in my suite.  BBQ Shack began to grow into a medium size application requiring a lot of new Ocean framework features be coded on this cruise.

BBQ Shack was inspired by a real BBQ take-out, Outlaw BBQ Shack that is located in Matthews, NC.  If you are ever in the Charlotte, NC area you must visit the Outlaw BBQ Shack.  This small family owned business provided the BBQ for the Super Bowl XLI in 2007.

The BBQ Shack application story is; a software provider writes a WPF application that is initially used for manager functions, item maintenance and touch screen cash register software.  After a few months of use, the customer asks for a web presence.  So a Silverlight ordering application is deployed.  To save time and money, code reuse was critical.  (this is our scenario, not actual events in real life)

While the above story is just a scenario, I spent a good bit of time (and had fun) creating a data model that would allow me to have a very flexible way of maintaining inventory and understanding actual costs for products sold.

My goal for this blog post and the BBQ Shack application is to demonstrate the features in Ocean v2, while at the same time provide a real world MVVM line of business example.  Trying to reign in the scope, I did not write the Sales or Order Packing menu options nor the actual checkout screens.

I wrote 90% of BBQ Shack on the week long cruise, including a simple code generator for the business objects, business and data layer code.  After getting back from the cruise, I spent time with superstars Glenn Block and Jeff Handley.  They encouraged me to go back and make BBQ Shack IOC and test friendly.  Glenn and I also knocked around on a Friday evening for a few hours and wrote BLL(Of T) and DAL(Of T).  I refined that work and integrated it into BBQ Shack and Ocean v2.

In case you’re wondering, I used Expression Blend to create the above BBQ grill in XAML.

Table of Contents

Videos

The below links are to my Vimeo videos.  I have a short blog post here, that explains Vimeo and the features it provides you as a viewer.

I recommend that you view at least the first video before reading the blog post.  This will give you context and help your understanding of the application and Ocean API features introduced here.

BBQ Shack WPF version

BBQ Shack Silverlight version

Non-linear navigation deep dive

Ocean v2

I have created a home page for Ocean here.  Ocean v1, born on an Alaskan cruise in September of 2008 is the name for the framework I use for my application development.

Ocean v2 was created on my September 2009 Alaskan cruise along with the BBQ Shack.

See Ocean’s home page for more on Ocean v3.

BBQ Shack Features

I’m going to limit this blog post to the following feature list, otherwise I would never publish this work.  The videos at the bottom of the post will talk about every feature in BBQ Shack.

  • Non-linear application navigation with transitions
  • Task switcher
  • MVVM Modal & logging services
  • Master page concept forms
  • Background loading of all data
  • Business entity validation
  • BLL(Of T)
  • DAL(Of T)
  • Silverlight Code Sharing

While not a feature, Dan Dole a co-worker at Microsoft told me about a color pallet site called kuler.  I’ve long been told my UI’s don’t look so great.  I was trying to turn out a decent UI for BBQ Shack so I searched through the many color pallets at kuler.  BBQ Shack only uses a single pallet with 5 colors, along with black, white and a light gray.  Simple turned out to be best.

Non-Linear Application Navigation – View Manager Services

Billy Hollis used the phrase, “non-linear navigation” in the PDC presentation he did that was similar to the above dnrTV video.  The application he demonstrated was a product he wrote for his customer.  He spoke about the ability to hyperlink to any form or data record from anywhere in the application. 

Visualize Dashboard that presents a list of items that are low on inventory.  The ability to navigate directly to the item or another pertinent form is invaluable.  (yes sounds simple, keep reading)

Visualize some UI that presents the user a list of all active forms with the ability to navigate back to it from anywhere in the application.

WPF and Silverlight do not ship with baked in MDI support.  In my opinion this is good because it forces developers to design much better UI’s for their customers that don’t include countless little windows.

I also see non-linear navigation as a solution for end users that are required to multi-task to perform their job function.

BBQ Shack demonstrates non-linear navigation that allows a form to have a modal dialog displayed, slide that form out of the way to allow the user to perform another tasks and then easily navigate back to that task, just like users need to do in the real world.

BBQ Shack also provides 4 different animated form transitions that can be user selectable at run-time as part of a users profile if desired.

The below image pictures the list that is displayed when the user right-clicks on the circled “4” at the top of the Inventory button.  Clicking on one of the records will navigate the user directly to the form with that record.

listnavigation

When form is slide out of the way, it is not destroyed.  In fact it is running and can continue to run processes when its out of view.  This is one reason I listed background loading of all data in the features list.  If you process data access on the UI thread, you will severely limit your applications ability to provide multi-tasking features to your users and will also have issues with UI animation if the UI thread is also performing data access functions.  Background data access and processing solves this problem.

This non-linear, non-destructive feature really opens the possibilities for Silverlight 4 line of business applications.  Not forcing user to complete a form before being able to work on another form is a great UI feature.  This Silverlight feature will I ship with Ocean v3.1 for Visual Studio 2010 with Silverlight 4 support.  I need to wait until Silverlight 4 RTM’s before shipping any Silverlight 4 code.

The below image pictures the Inventory module with 4 active screens (4 includes the one pictured).  The user can easily see which forms are active with several options for getting to each one.  Notice in both the above and below images that the form state is displayed along with the record key and record title.

acitverecords

Consumers of business application software are experts in their vertical markets and are also very busy people.  Software that makes their job easier and transforms the computer from some sort of black box with a screen to a user friendly tool should be one of the goals of modern software developers.

The above search results have a unique feature.  The text of the result is a single click hyperlink to the record.  If the user clicks to the right of the text, that row will be selected and the Edit and Delete buttons will be enabled.

The below image shows a maintenance form with a modal dialog displayed.  The parent form is slightly grayed out while the application buttons at the bottom are not grayed out.  This allows for another application function to be carried out and then return to this form at a later time.   

modalform

Notice in the modal form the Item Base label is underlined.  This indicates that this is a hyperlink that when clicked will bring the Item Base Maintenance form into view, allowing that table to be modified.  When that Item Base form is closed, the application will come back to the above form in the same state it left it and will refresh the ComboBox data.

Notice the bread crumb trail under the form title of the above grayed out form.  This indicates to the user exactly where they are in the stack of forms.  If they were to click the Item Base hyperlink in the model window, the application would be navigated to the below form.  Notice the new breadcrumb and now the active forms goes from 4 to 5 in the Inventory button.

breadcrumb

If you look the above images, you’ll see several common UI patterns. 

Save and Close buttons and the Add, Edit and Delete buttons.  In the video Billy Hollis spoke to simplifying our UI’s.  I’ve always been a Toolbar developer until now.  I like these buttons and believe end users will too.

Also notice the top of every form is the same.  This is just one of the free features available by using the master page concept form custom control.  See the below section for coverage of this feature.

How Does Non-Linear Navigation Work?

A single ViewManagerService exists for each application and is accessed through the IViewMangagerService Interface.  The ViewManagerService creates and controls one or more ViewManagers.  ViewManagers can only be accessed through the ViewManagerService.  The BBQ Shack has two ViewManagers, one for each Window in the application, ApplicationMainWindow and CashierView.

Accessing the ViewManagerService through the Interface enables IOC scenarios for object construction and makes unit/integration testing of the ViewModels very easy since mock objects can be injected.

Each ViewManager is responsible for keeping track of each form transitioned into and out of view and it also provides the hook for animating forms in and out of view.

The below ApplicaitonMainWindow.xaml has been stripped down to the bare essential to illustrate navigation concepts.

The Grid named applicationLayoutRoot is the Panel where all non-linear navigation will take place.

<Grid>

    <Grid.RowDefinitions...>

    <!-- ViewServiceManger loads/transistions all views here-->
    <Grid Grid.Row="1" x:Name="applicationLayoutRoot" />

</Grid>

The below code fragments are from the above ApplicationMainWindow.xaml code behind file and show how the BBQ Shack navigation is setup and how the application starts up.

The constructor is where the Grid applicationLayoutRoot is registered. The Add method creates a new ViewManager, assigns the Panel to it and provides the ability to hook into the form transitioning process.  This hook also enables platform specific transitions and for developers to create their own.  As you can see below I have provided four transitions for WPF applications with this version of Ocean.

Since the BBQ Shack is a navigation driven application, I have elected to have the Views create most of my ViewModels in the application as you can see in the last line of the constructor.  This is not a rule, it just made sense to me and simplified my application.

Private _objIViewManagerService As IViewManagerService = ViewManagerService.CreateInstance

Public Sub New()
    
    InitializeComponent()    
    
    _objIViewManagerService.Add(Me.applicationLayoutRoot, AddressOf AnimateTransition)
    Me.DataContext = New ApplicationMainViewModel
End Sub

Private Sub AnimateTransition( _
    ByVal objCurrentView As FrameworkElement, ByVal objNewView As FrameworkElement)
    
    'TODO - developers - you can add a user preferences check here to select a different animation 
    '       or no animation
    '
    'Call one of the below built in transitions or write your own.
    '
    'Transistions.CreateInstance.NoTransition(Me, objCurrentView, objNewView)
    'Transistions.CreateInstance.DoublePaneRightToLeftTransition(Me, objCurrentView, objNewView)
    Transistions.CreateInstance.SinglePaneRightToLeftTransition(Me, objCurrentView, objNewView)
    'Transistions.CreateInstance.FadeTransition(Me, objCurrentView, objNewView)
End Sub

Private Sub ApplicationMainWindow_Loaded( _
    ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded    
    
    _objIViewManagerService.Select.Transition(Of HomeView)(My.Settings.HomeView, Nothing, _
                                                            strApplicationSuite:="BBQ Shack")
End Sub

All navigation functions are accessed through the IViewManagerService; this service manages the individual ViewManagers.  The Add method creates new ViewManagers.  The Select method returns a ViewManager.  The ShowWindow method provides a way for an application to launch a new window.  This allows your ViewModel’s to display a new window, either modal or non-modal.  Since the ViewManagerService will be mocked during unit/integration testing, the window will not actually open a new UI.

 iviewmanagerservice

The ViewManager Transition method has the following capabilities:

  • Creates a new instance of a View if required
  • Can pass a primary key to the View; that View’s ViewModel will then look that record up for display
  • Can bring a previously open View into view
  • Can pass a DataContext to a View
  • Automatically handles multiple instances of the same form.  For example, the user could have two separate Person Maintenance forms adding new records and a third form editing a Person record
  • Meets one of my requirements which was to have the navigation work in a single line of code

Transition is the most used method of the ViewManager; it has five possible method signatures that are listed below.

Public Sub Transition(ByVal objNavigateKey As NavigateKey)

Public Sub Transition(Of T As {FrameworkElement, New})(ByVal strViewKey As String)

Public Sub Transition(Of T As {FrameworkElement, New})(ByVal objNavigateKey As NavigateKey, _
                                                       ByVal objDataContext As Object)

Public Sub Transition(Of T As {FrameworkElement, New})( _
            ByVal strViewKey As String, ByVal objDataContext As Object, _
            Optional ByVal objRecordPrimaryKey As Object = Nothing, _
            Optional ByVal strApplicationSuite As String = "")

Public Sub Transition(Of T As {FrameworkElement, New})(ByVal objNavigateKey As NavigateKey)

Before looking at each of the above overloads we need to understand the NavigateKey and its roll.

navigatekey

Each View managed by the ViewManager has a unique NavigateKey associated with it. When a new View is transitioned to a NavigateKey is created if a new one is not passed. 

The ParentNavigateKey is used by the ViewManager to navigate back to the View that opened a new View when the new View is closed.

If the RecordPrimaryKey is assigned, this can be used by the View/ViewModel to retrieve data and display it.

The Version property enables having multiple instance of the same View in the same state.  For example, the user opened the Person Maintenance View and is adding a record.  They then navigate to another form that allows them to open the Person Maintenance View again and add another record.  A customer service person could easily find themselves needing to do this.

When adding a new record the View’s RecordPrimaryKey will be null.  The Version is a simple Integer, that is supplied by the IViewManagerService NextVersion property.  Look back at the above IViewManagerService class diagram to see the NextVersion property.

The ViewKey is an application assigned string value.  I have put all my ViewKeys in the application settings.  You could also put them in a constants file.  Having constants ensures that your application won’t be a victim of a spelling mistake.

Transition Timing Cycle

When the Transition method is called, the following actions occur in the order specified:

  • Check if the requested View has been created, if not it will be created
  • Check if the requested View is the current view, if so stop further process
  • If the requested View is not contained in the registered Panel, add it to the Panel
  • Iterate all Views in the registered Panel and if not collapsed, set Visibility to Collapsed
  • Set the requested View’s Visibility to Visible
  • If the View implements INotifyOnBringIntoView, then call the OnBringIntoView method
  • If the View’s DataContext implements INotifyOnBringIntoView, then call the OnBringIntoView method
  • If the current View implements INotifyOnHidingView, then call the OnHidingView method
  • If the current View’s DataContext implements INotifyOnHidingView, then call the OnHidingView method
  • Set the current View’s ZIndexProperty to 0
  • Set the requested View’s ZIndexProperty to 99
  • Set the requested View’s Opacity to 1
  • Take a snapshot image of the requested View (used by the Task Switcher)
  • Invoke the animation callback that was registered, if no callback was registered then collapse the current View
  • Set the requested View as the current View
  • If the NavigateKey has a primary key value and the DataContext passed in the Transition method implements ILoadable then call the LoadRecord method
  • If the requested View was created and the requested View’s DataContext is implements ILoadable then call the LoadRecord method
  • If the NavigateKey has a primary key value and the View implements ILoadable then call the LoadRecord method
  • If a DataContext was passed in the Transition method, the assign that DataContext to the requested View.

The above is why I like the non-linear navigation API.  It handles so many actions that I would otherwise have to program and simplifies bringing a form into view down to a single line of code.

I also like the ability for Views and ViewModels to sign up for Load, UnLoad and LoadRecord notifications by implementing an Interface and overriding a method instead of responding to events and writing event handlers.

I’m hoping others will be inspired to create even better API’s and black box form management for WPF and Silverlight.  I’ve only got about 4 hours invested in design and coding this API.  It does meet my needs, but I’m sure there are other scenarios I have not taken into consideration.  I welcome all feedback and discussion on this topic.

Let’s have a look at each overload of the Transition method and see how it works and is used in BBQ Shack.

Transition Signature One

Public Sub Transition(ByVal objNavigateKey As NavigateKey)

The above method signature is use to navigate to an existing View, typically from a list of active views.  If the NavigateKey is null or not found an exception is thrown.

_objIViewManagerService.Select.Transition(CType(param, ActiveView).NavigateKey)

The above line of code can be found in 8 locations in the BBQ Shack code where active Views are listed and the user can navigate to them by clicking on their hyperlink.  If you look above to the second image in this post, you’ll see a list of Views, when one is clicked the above code is executed.  The third image shows a list of active item records.  When the Lays chips item is clicked, the above line of code will run.

The ActiveView type is used to encapsulate an active view in lists.  The ActiveView type also provides a DataContext property that UI DataTemplates can consume to display data when the list is rendered.

Transition Signature Two

Public Sub Transition(Of T As {FrameworkElement, New})(ByVal strViewKey As String)

The above method signature is use to navigate to a View that does not perform record maintenance.  Examples of these types of forms are the the HomeView and Cashier MealSelectorView.  There will never be more than one instance of Views loaded using this method signature since there is no Version number or RecordPrimaryKey to differentiate the Views.

_objIViewManagerService.Select.Transition(Of HomeView)(Settings.HomeView)

_objIViewManagerService.Select(STR_CASHIER).Transition(Of MealSelectorView) _
                                (My.Settings.MealSelectorView)

The first line of the above code should now be familiar.

The second line is the same with a twist.  Notice Select(STR_CASHIER).  STR_CASHIER is a constant that is used to tell the ViewManagerService to use the ViewManager identified by the key that has a value returned by the constant STR_CASHIER.

Transition Signature Three

Public Sub Transition(Of T As {FrameworkElement, New})(ByVal objNavigateKey As NavigateKey, _
                                                       ByVal objDataContext As Object)

The above method signature is not used by the BBQ Shack.  However, the other Transition methods call this method internally in the ViewManager, except the first signature that does not need the services provided by this method.  If you study the code in this method signature and the PerformTransition method, you’ll have a full understanding of how the ViewManager provides its services.

Transition Signature Four

Public Sub Transition(Of T As {FrameworkElement, New})( _
            ByVal strViewKey As String, _
            ByVal objDataContext As Object, _
            Optional ByVal objRecordPrimaryKey As Object = Nothing, _
            Optional ByVal strApplicationSuite As String = "")

The above method signature is used twice in the BBQ Shack, both are listed below. 

_objIViewManagerService.Select.Transition(Of HomeView)( _
        Settings.HomeView, Nothing, strApplicationSuite:="BBQ Shack")

_objIViewManagerService.Select.Transition(Of InventoryView)( _
    Settings.InventoryMenuView, Nothing, strApplicationSuite:="Inventory")

It’s primary purpose is to load up a View and assign that View to an ApplicationSuite.

Transition Signature Five

Public Sub Transition(Of T As {FrameworkElement, New})(ByVal objNavigateKey As NavigateKey)

The above method signature is the most commonly found in the BBQ Shack.  This is used when you need open a View and create a new record, or navigate to a View and edit an existing record.

_objIViewManagerService.Select.Transition(Of ItemCategoryView)(_ 
    New NavigateKey(_objIViewManagerService.NextVersion, Settings.ItemCategoryView, _
                    Me.NavigateKey, Settings.Inventory))

The above code demonstrates navigating to a new instance of the ItemCategoryView and setting the Version property to the value returned by the NextVersion property.  This code is run when the Add button is clicked.

Notice the ParentNavigateKey is set to the NavigateKey property of the current ViewModel.

Settings.Inventory sets the application suite this View belongs to.

_objIViewManagerService.Select.Transition(Of UnitOfMeasureView)( _
    New NavigateKey(Settings.UnitOfMeasureView, _
                    CType(param, uom_UnitOfMeasure_FillSelector).uom_UnitOfMeasureIdent, _
                    New NavigateKey(Settings.InventoryMenuView), Settings.Inventory))

The above code demonstrates navigating to the UnitOfMeasureView, instructing the View/ViewModel to load the record with a primary key value equal to value of uom_UnitOfMeasureIdent.  This code is run when a record is selected from the search results listing (see third image above.)

The ViewModel that is calling this code does not have direct access to its Parent’s Parent NavigateKey, so I simply pass a new NavigateKey that will point to the Inventory main View (see third image above).

Settings.Inventory sets the application suite this View belongs to.

Injecting View Manager Services

The below two constructors from the UnitOfMeasureViewModel demonstrate how the BBQ Shack utilizes a default object construction used by the application at run-time, while at the same time exposing the ability to use IOC for object construction or inject mock objects for unit/integration testing.

This ability exists because only Interface types are used.  The first constructor does create concrete objects but those are immediately passed to the second constructor and only the Interface type is used.

Even if you are not using IOC, I would still recommend using this pattern in your MVVM applications to easily enable unit/integration testing.  Additionally, programming to an interface gives you long term flexibility and decoupling that later on you may wish you had.  Cost to do this up front is super cheap as opposed to changing a lot of code later on. 

''' <summary>
''' Default object construction, creates a default BLL and DAL
''' </summary>
Public Sub New()
    Me.New(ViewManagerService.CreateInstance, _
           ViewModelUIService.CreateInstance, _
           BLL(Of uom_UnitOfMeasure).CreateInstance)
End Sub

''' <summary>
''' Used for unit testing or if you want to swap out the default concrete BLL or DAL
''' The DAL is swapped out by passing in a BLL that has a swapped out DAL.
''' </summary>
''' <param name="objIBLL">IBLL(Of uom_UnitOfMeasure)</param>
Public Sub New(ByVal objIViewManagerService As IViewManagerService, _
               ByVal objIViewModelUIService As IViewModelUIService, _
               ByVal objIBLL As IBLL(Of uom_UnitOfMeasure))

    MyBase.New(objIViewModelUIService)
    _objIViewManagerService = objIViewManagerService
    _objIBLLUnitOfMeasure = objIBLL

End Sub

Task Switcher

taskswitcher

I originally released the Task Switch in this blog post and demonstrated it in this application blog post.  One of the readers of my blog asked me to port this code to work with MVVM applications.  When I added the View Manager Services to Ocean this was real easy to accomplish.

The BBQ Shack ApplicationMainWindow listens for the CTRL+TAB key combination and will display the above Task Switcher.  Users can click on the desired task to switch to or they can also keep pressing CTRL+TAB to cycle through active tasks and release the CTRL key to navigate to the selected form.

Try to make application navigation as easy as possible for your users.  They will appreciate your innovation and extra efforts in this space.

The XAML for the Task Switcher is pictured below.  As you can see its stacked on top of the applicationLayoutRoot, centered within Grid.Row 1.

<!-- ViewServiceManger loads/transitions all views here-->
<Grid Grid.Row="1" x:Name="applicationLayoutRoot" />

<local:TaskSwitcherControl 
    Grid.Row="1" x:Name="ucTaskSwitcherControl" HorizontalAlignment="Center" 
    VerticalAlignment="Center" Visibility="Collapsed">
    
    <local:TaskSwitcherControl.BitmapEffect>
        <OuterGlowBitmapEffect GlowSize="8" GlowColor="#FF5A5A5A" />
    </local:TaskSwitcherControl.BitmapEffect>
    
</local:TaskSwitcherControl> 

MVVM Modal and Logging Services – View Model UI Services

Opening a modal dialog window using Window.ShowModal() in a ViewModel will send most seasoned MVVM developers into a rage.  (OK, I might have overstate their reaction, but not by much).

So what’s the MVVM developer to do?  Logically they know they have to put up error dialogs, file open, file save dialogs and folder selection dialogs.  But they also understand that using these dialogs will foul up unit/integration tests or make that task much more complex than it needs to be.

I’ve read several good solutions by fellow developers.  The one I present here works for me, is fully testable and as an added benefit I’ve added logging capabilities to the every dialog.

This service is managed just like the View Manager Services.  The service is passed as an Interface into the constructor of types that need its services.  See the code in the above section, Injecting View Manager Services for an example.

The ViewModelUIService provides a rich set of methods to display message dialogs, file and folder dialogs and to log messages.

The message dialogs are all wrappers around the Task Dialog custom control I wrote back in 2007.  The latest version is in Ocean v2.

 viewmodeluiservice

The Task Dialog custom control mimics the Windows Vista Task Dialog appearance even on Windows XP.  It has the same capabilities and can show the same icons and test regions at the Task Dialog.  Until the .NET Framework provides a wrapper for the Task Dialog, this is a very good option.

 deletedialogbox

Did you notice the Log event in the above class diagram?

If you want to enable the logging of dialogs, in application startup, add a handler for the Log event.  Logging user responses to dialogs in applications is invaluable.  The LogMessage method allows adding a log message without a dialog.

Private Sub Application_Startup(ByVal sender As Object, _
                                ByVal e As System.Windows.StartupEventArgs) Handles Me.Startup
    '
    'TODO - developers - set up logging event handler
    'AddHandler _objIViewModelUIService.Log, AddressOf WriteLogToFile-Database-EventLog-Email-etc
    '
End Sub

For unit/integration testing application startup would not normally run.  Instead, you can add this line of code to your test code to test logging.

The below line of code displays the delete confirmation dialog pictured above.  The (3) is the number of seconds the user will have to wait until they can click the Yes or No buttons.  While waiting, a ProgressBar shows the remaining time.

If _objIViewModelUIService.YesNoConfirmDelete(3) = CustomDialogResult.Yes Then

The below line of code demonstrates showing the end user an exception message.  The idea was to make common programming tasks very easy.  The below ExceptionDialog method creates a dialog with the correct dialog caption, buttons and message.  As a bonus, if the application is a debug build, the Stack Trace is placed in the Additional Text collapsible region of the dialog.

_objIViewModelUIService.ExceptionDialog(e.Error)

Master Page Concept Control

ASP.NET has had master pages since .NET 1.1 (Paul Wilson’s custom control for .NET 1.1. .NET 2.0 added them in the box).

Developers are always looking for ways to simplify the coding of their applications and reduce the need to repeat boiler maker code to a minimum.  Using a master page paradigm also makes it much easier to refactor and change the general appearance of your application without having to actually touch the individual forms.

The below XAML fragment is from BBQShackFormResourceDictionary.xaml and is the BBQShackForm custom control that acts as a master page control.

I wrote about the below AdornerDecorator that wraps the form in this blog post.  Basically, this is required when a form puts up UI Elements in the WPF Adorner layer and that UI is then taken out of view and then brought back into view.  Without this you’ll end up with turds in your adorner layer.

This custom control provides the nice inner and outer borders, form titles, breadcrumb trail and form background. 

<AdornerDecorator>
    <Border x:Name="bdr" Style="{StaticResource formOuterBorderStyle}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" MinHeight="400" />
            </Grid.RowDefinitions>
            <Border Style="{StaticResource formTitleBorderStyle}">
                <StackPanel>
                    <TextBlock Text="{Binding Path=ViewModelFriendlyName}" 
                               Style="{StaticResource formTitleStyle}" />
                    
                    <TextBlock Margin="11,0,0,0" Text="{Binding Path=BreadcrumbTrail}" 
                               Style="{StaticResource formBreadcrumbStyle}" />
                </StackPanel>
            </Border>
            <Rectangle Grid.Row="1" Style="{StaticResource formBackgroundRectangleStyle}" />

            <!--Put the form below here-->
            <ContentPresenter Grid.Row="1" />    
            <!--Put the form above here-->

        </Grid>
    </Border>
</AdornerDecorator>

The below XAML fragment is from UnitOfMeasureView.xaml and is the Unit of Measure maintenance form.

UnitOfMeasureView is a UserControl that derives from EditFormViewBase.  This UserControl’s content is the above BBQShackForm custom control.  The custom control’s content is contained in the below Grid.  See the below image that boxes out the regions on a form.

<Ocean_MVVM:EditFormViewBase ...>
    <local:BBQShackForm>
        <Grid Margin="11">
            <Grid KeyboardNavigation.TabNavigation="Cycle">
                <Grid.RowDefinitions ...>
                <Grid.ColumnDefinitions ...>

                <local:FormStatusUserControl 
                    Grid.ColumnSpan="2" 
                    DateCreated="{Binding Path=uom_UnitOfMeasure.uom_DateCreated}" 
                    DateModified="{Binding Path=uom_UnitOfMeasure.uom_DateModified}" 
                    CreatedBy="{Binding Path=uom_UnitOfMeasure.uom_CreatedBy}" 
                    ModifiedBy="{Binding Path=uom_UnitOfMeasure.uom_ModifiedBy}" 
                    ErrorHeaderText="{Binding Path=ErrorHeaderText}" 
                    WatermarkMessage="{Binding Path=WatermarkMessage}" 
                    IDataErrorInfoError="{Binding Path=uom_UnitOfMeasure.Error}" 
                    ErrorMessage="{Binding Path=ErrorMessage}" 
                    UIValidationErrorMessages="{Binding Path=UIValidationErrorMessages}" />

                <Label Grid.Column="0" Grid.Row="1" Content="Id" />
                <Label Grid.Column="0" Grid.Row="2" Content="Title" />

                <TextBlock Grid.Column="1" Grid.Row="1" 
                           Text="{Binding Path=uom_UnitOfMeasure.uom_UnitOfMeasureIdent}" ... />
                <TextBox x:Name="txtuom_Title" Grid.Column="1" Grid.Row="2" Width="75" 
                         Text="{Binding Path=uom_UnitOfMeasure.uom_Title}” MaxLength="15"/>

                <Grid Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="2" 
                      Grid.IsSharedSizeScope="True" HorizontalAlignment="Right">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="Buttons" />
                        <ColumnDefinition SharedSizeGroup="Buttons" />
                    </Grid.ColumnDefinitions>
                    <Button Command="{Binding Path=SaveCommand}" Content="Save" ... />
                    <Button Command="{Binding Path=CloseCommand}" Content="Close" ... />
                </Grid>
            </Grid>
        </Grid>
    </local:BBQShackForm>
</Ocean_MVVM:EditFormViewBase>

The FormStatusUserControl provides the FormValidationControl and the two icons on the right side of each form.

I could have added the FormStatusUserControl to the BBQShackForm master page control but chose not to.  At the time I wrote it, I wasn’t sure if I would have boxed myself in, so I kept them separated.  In my next application I may move this to the master page control.

The below image shows a box diagram of the form.  The white box shows the BBQShackForm’s area and how the Grid contents as nested within the BBQShackForm.  Notice the lack of form coloring in the above code.  The BBQShackForm control takes care of all form colors.  This is why I use the master page concept, so that most of my application layout structure and coloring is in one location.  It would be super easy for a developer to change the colors or application layout structure of an application that uses this pattern.

customcontrol

The FormStatusUserControl contains a FormNotification control and two image buttons.

The FormNotification control is similar to the ASP.NET ValidationSummary control.  If the form is valid, the WatermarkMessage is displayed as in the above image, Valid record.  Any errors reported by the Model or ViewModel are listed in the expanded view as pictured below.  The FormNotification can also display other messages like, Record Saved after a successful write to the data store.

This form validation and error reporting is free, and is part of the Ocean framework.  Go back and look at the above XAML.  You’ll notice the FormStatusUserControl has a number of properties that are data bound to the Model and ViewModel.  It is that easy.

 requireditems

The below blue “A” image button opens a form that allows a case correction rule to be added.  The end user scenario is, the user is editing a form and the current case correction rules have made an unwanted correction.  Without interrupting the users workflow or train of thought, they can click the “A”, enter the new rule and go right back to their task.  Try it, you’ll like it.

The below properties icon provides a Tooltip that displays the current record metadata.  I’ve always added the below four fields to all of my database records.  But most users don’t need to see this information on every form, so an inhanced Tooltip made the most sense.

 recordmetadata

Background Loading and Processing of All Data

Windows Forms, WPF and Silverlight UI’s are all single threaded apartments (STA).  The bottom line for you is that you can only access the UI on the same thread that created it.  This means that you can’t spawn an additional thread and have that thread interact directly with the UI.

Side note:  I’ve been thinking about a black box that would make UI programming easier and that would take full advantage of the multiple cores of the host machine without the programmer having to write additional code, manage threads or background workers. 

There has to be a way to get rid of the UI bottleneck so my 8 cores can all spring into action.  I need to go on another cruise!

Microsoft added the Systetem.ComponentModel.BackgroundWorker to the .NET 2.0 framework.  This class encapsulates spawning new threads, optionally getting progress reports and the final results of the work.

BBQ Shack uses the BackgroundWorker for all data access.  By accessing all data off the UI thread, the UI remains response and all animations can run smoothly.  Let me give you an example; in BBQ Shack when you click on a record in the search results, the current form is animated out of view, the new form is animated into view and at the same time the new form is making a call to the database to retrieve the required data.  If this was all accomplished on one thread, BBQ Shack’s performance and usability would be severely degraded.

If you have not programmed with Silverlight, you may not know that all data access must be on a background thread.  If you are planning to write “cloud hosted” applications, you’ll want all your cloud access  performed on a background thread.

If you have not checked out .NET 4.0 Parallel Programming do this today.  This fun and ground breaking computing.

The below code snippet is from the UnitOfMeasureViewModel.vb file.  It shows the minimum code required to use the BackgroundWorker.

LoadRecord is a method on my ViewModel base class.  When this method is called and a primary key of the correct type is passed in, the BackgroundWorker.RunWorkerAsync method is called.  This method will then raise the BackgroundWorker.DoWork event on another thread.  When DoWork has completed its task or an unhandled exception occurs, the BackgroundWorker.RunWorkCompleted event is raised back on the UI thread.  It is that simple, just more code needs to be authored and maintained.  (now you know why I want to create a black box to do this for me)

 Private WithEvents _objBackgroundWorker As New BackgroundWorker

 Public Overrides Sub LoadRecord(ByVal objRecordKey As Object)

     If objRecordKey Is Nothing Then
         Me.uom_UnitOfMeasure = _objIBLLUnitOfMeasure.CreateEntity

     ElseIf Not TypeOf objRecordKey Is Integer Then
         Throw New ArgumentNullException("objRecordKey", AppResources.RecordKeyWrongType)

     Else
         _objLoadKey = objRecordKey
         _objBackgroundWorker.RunWorkerAsync(objRecordKey)
     End If

 End Sub 

Private Sub _objBackgroundWorker_DoWork( _
         ByVal sender As Object, _
         ByVal e As System.ComponentModel.DoWorkEventArgs) _
             Handles _objBackgroundWorker.DoWork

     e.Result = _objIBLLUnitOfMeasure.Select(CType(e.Argument, Integer))
 End Sub

 Private Sub _objBackgroundWorker_RunWorkerCompleted( _
         ByVal sender As Object, _
         ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
             Handles _objBackgroundWorker.RunWorkerCompleted

     If e.Error Is Nothing Then

         If e.Result IsNot Nothing Then
             Me.uom_UnitOfMeasure = CType(e.Result, uom_UnitOfMeasure)

         Else
             _objIViewModelUIService.MessageDialog("Data Error", "Record Not Found", ...) 
         End If

     Else
         _objIViewModelUIService.ExceptionDialog(e.Error)
     End If

 End Sub

Notice the very first line of code in the above RunWorkerCompleted method.  You must check the value of e.Error before doing anything else.  Any exceptions from the other thread will be in the Error property.

If no exceptions were thrown on the background worker thread, you can then check the value of e.Result and consume the result.  Remember RunWorkerCompleted is executed back on the UI thread.

Business Entity Validation

I’ve written about business entity validation here and here.  Most of the content in this Code Project article are applicable to Ocean as they have the same API’s.

All of the business entity objects in BBQ Shack are shared by the WPF & Silverlight projects.  All of the validation code is platform agnostic.

One of Ocean’s goals was to enable cross platform business entity objects.  That goal has been achieved.

This works in Silverlight because when I create the Service References, I choose to reused types instead of having the proxy generated code re-implement my types.  I strongly recommend that you never allow the proxy generated code re-implement your types, always reuses your types.

I have trimmed the below code snippet for purposes of illustration.

This c_Customer class is from the c_Customer.gen.vb file.  The .gen. is my naming convention for files that are code generated.  The c_Cusomter type is complete with the c_Customer.vb partial class.  I love that partial classes allow me to add code to types that are code generated.

I’ve used this naming convention in all business applications since my days with SQL Server 7.0.  Essentially it provides a unique name for each object in the database, including columns.  That uniqueness is inherited in my code. 

While it looks strange at first, it makes it SUPER easy to search for all instance of the Customer Email field in my database, including stored procedures, all .NET code, XAML or HTML files.

Behind delivering customer features, code maintenance is my #1 priority.  This naming convention has worked for me accomplish that goal.  You should do that works for you and your team.

<DataContract(Namespace:="Ocean")> _
Partial Public Class c_Customer
    Inherits BusinessEntityBase

    <DataMember(IsRequired:=True)> _
    <CompareValueValidator(ComparisonType.Equal, 0, RequiredEntry.Yes, RuleSet:=STR_INSERT)> _
    <CompareValueValidator(ComparisonType.GreaterThan, 0, _
                           RequiredEntry.Yes, RuleSet:=STR_UPDATE_DELETE)> _
    Public Property c_CustomerIdent() As Integer
        Get
            Return _intc_CustomerIdent
        End Get
        Set(ByVal Value As Integer)
            MyBase.SetPropertyValue("c_CustomerIdent", _intc_CustomerIdent, Value)
        End Set
    End Property

    <DataMember(IsRequired:=True)> _
    <CharacterCasingFormatting(CharacterCasing.ProperName)> _
    <StringLengthValidator(1, 20)> _
    Public Property c_FirstName() As String
        Get
            Return _strc_FirstName
        End Get
        Set(ByVal Value As String)
            MyBase.SetPropertyValue("c_FirstName", _strc_FirstName, Value)
        End Set
    End Property

    <DataMember(IsRequired:=True)> _
    <CharacterCasingFormatting(CharacterCasing.LowerCase)> _
    <RegularExpressionValidator(RegularExpressionPatternType.Email, RequiredEntry.Yes)> _
    Public Property c_Email() As String
        Get
            Return _strc_Email
        End Get
        Set(ByVal Value As String)
            MyBase.SetPropertyValue("c_Email", _strc_Email, Value)
        End Set
    End Property

In BBQ Shack I have chosen to use opt-in data serialization as evidenced by the DataContract and DataMember attributes.

It took me awhile to warm up to the DataContractSerializer as opposed to the default opt-out serialization added to .NET 3.5.  I’m now using the opt-in DataContract serialization for all my WCF applications.

BusinessEntityBase

All my business entity objects derive from BusinessEntityBase.  This class provides the plumbing necessary for deriving objects to have  property and object validation,  case correction of entity values, change notification, validation interfaces implementation and other features.

The MyBase.SetPropertyValue method provides the entry point to have data validated, case correction applied and change notification.

Applying Validation Rules

Validation rules can be applied as attributes to properties, in code as shared rules or in code as an instance rule.  I like using the attributes because they are super easy to code generate, the rule is physically near the property so that when I read a business entity’s code I can “see” everything related to this property.

There are .NET restrictions as to data an attribute can contain.  For example you can put a function in an attribute so creating a rule that states that the date entered must be before Date.Now() is not permitted.  This is where the ability to add a rule in code comes into play.

For a great example of this open the BBQ Shack file, ccc_CharacterCasingCheck.vb located in the BBQShack.BusinessEntityObject project.  Now expand the Methods region.  In the AddSharedValidationRules method, not only did I add a business rule, but pointed that rule to some code I wrote that is specific to this class and will never be used again.

Ocean provides a rich set of lines of business validation rules you can use.  However, if you need your own, writing them is very easy.

  • BankRoutingNumberRule
  • ComparePropertyRule
  • CompareValueRule
  • CreditCardNumberRule
  • DomainRule
  • InRangeRule
  • NotNullRule
  • RegularExpressionRule
  • StateAbbreviationRule
  • StringLengthRule

I have also designed the validation attributes to accept both Decimal and Date values in the attribute.  Normally you can’t do this because values of these types can’t be used in an attribute.  For one example of this look at the Sale_Item.vb file in the BBQShack.BusinessEntityObjects project.

Shared validation rules are rules that all instances of a type will use.  The first time a type is created, all shared rules are added to a dictionary for the type so you only pay the cost to parse the attributes once.

Instance validation rules are added each time an instance of an object is created.  There is an AddInstanceValidationRules method in BusinessEntityBase that deriving types can override and provide required implementation.

I strongly recommend that you look at every business entity class.  You’ll find many real world examples of business rules being applied.

Rule Set

In the above code, the first validation attribute has the RuleSet property set to STR_INSERT.  This rule will only be applied when the business object is in insert mode; the mode of the object is set by assigning the BusinessEntityBase.RuleSet property.

Apply Case Correction Rules

In the above code you’ll also notice the CharacterCasingFormatting attribute in use.  This attribute adds a shared rule that gets applied by the BusinessEntityBase when a property value is set.  The following rules are provided by Ocean.

  • None
  • LowerCase
  • OutlookPhoneNoProperName
  • OutlookPhoneProperName
  • OutlookPhoneUpper
  • ProperName
  • UpperCase

The case correction API also provides hooks for developers to inject their own application specific case correction rules.  BBQ Shack provides a UI for end users to do just that.

IBLL(Of T)  c# – IBLL<T>

Superstar Glenn Block and I were in one of our code jam sessions and the idea to get rid of boiler maker business logic layer and data layer code came up.  I had originally written BBQ Shack with concrete BLL and DAL types for each business object.  That is one standard way to code these layers that many developers use.  I like it because its very easy to code generate and extend later as required.

Glenn and I cranked up a new Unit Test project, using TDD wrote IBLL(Of T) and IDAL(Of T).  I spend a few evenings of refactoring, creating concrete types and changed BBQ Shack over to use these.  Bottom line was I deleted to directories of files.  Part of the refactoring was injecting the concrete implementation of IBLL(Of T) into my ViewModels.  The testing of my ViewModels can be against a database or with mock objects.  Like the View Manager Services the ViewModels can be created by IOC too.

The main purpose of IBLL(Of T) was to relieve the developer from coding the most common business and data layer functions that deal with data store operations (CRUD).  These would be consumed by other developer written business or data layer code.

Real World Example:  When a new Customer is added, the business layer must initiate adding 3 other records and updating 2 more.  The ViewModel would call a single method in a developer written business layer method.  That method would utilize the IBLL(Of T) methods when reading and writing to the data store.  In this example, the developer only had to code the “real” business logic and not boiler maker code.

Just a quick glance at the below Interface will show the basic methods most BLL’s have today.  This is one of the four Interfaces that make up the generic BLL API.

Public Interface IBLL(Of T As {IBusinessEntity, Class, New})

  Function CreateEntity() As T

  Function Delete(ByVal intKey As Integer, ByVal objTimeStamp As Byte(), _
                    Optional ByVal strProcedureName As String = "") As Integer

  Function Delete(ByVal strKey As String, ByVal objTimeStamp As Byte(), _
                    Optional ByVal strProcedureName As String = "") As Integer

  Function Delete(Of K)(ByVal Key As K, ByVal objTimeStamp As Byte(), _
                          Optional ByVal strParameterName As String = "", _
                          Optional ByVal strProcedureName As String = "") As Integer

  Function Insert(ByVal Entity As T, ByRef intNewIdent As Integer, _
                    Optional ByVal strProcedureName As String = "") As Integer

  Function Insert(ByVal Entity As T, Optional ByVal strProcedureName As String = "") As Integer

  Function [Select](ByVal intKey As Integer, Optional ByVal strProcedureName As String = "") As T

  Function [Select](ByVal strKey As String, Optional ByVal strProcedureName As String = "") As T

  Function [Select](Of K)(ByVal Key As K, Optional ByVal strParameterName As String = "", _
                            Optional ByVal strProcedureName As String = "") As T

  Function Update(ByVal Entity As T, Optional ByVal strProcedureName As String = "") As Integer

End Interface

The BLL(Of T) and DAL(Of T) by default use a naming convention to call the correct stored procedure for database operations and to property name the parameters in the stored procedure calls.  In the case where a specific or unconventional name is required, the Optional ProcedureName parameter can be supplied.

Let’s take a look at the IBLL(Of T).Update method being called in a ViewModel.

Notice that the business object IsValid method is being called before Update is called.  If the IsValid method returns false, the ErrorHeaderText property is set to Save Errors. 

I always call the IsValid method in every layer of my code.  Doing this in the UI, ViewModel, BLL and DAL ensures that an invalid object never gets consumed.  Individual layers should be programmed defensively and never trust other layers (developers; LOL). 

Private Function Update() As Integer

    Try
        Me.uom_UnitOfMeasure.uom_DateModified = Now
        Me.uom_UnitOfMeasure.uom_ModifiedBy = Data.UserName

        If Me.uom_UnitOfMeasure.IsValid Then

            If _objIBLLUnitOfMeasure.Update(Me.uom_UnitOfMeasure) = 1 Then
                Return 1
            End If

        End If

        Me.ErrorHeaderText = "Save Errors"

    Catch ex As Exception
        _objIViewModelUIService.ExceptionDialog(ex)
    End Try

    Return 0
End Function

If you are new to WPF the purpose of setting the above ErrorHeaderText property or how it works may not be obvious. 

If you look back to the above XAML for the UnitOfMeasureView UserControl, you’ll notice that the ErrorHeaderText property on the FormNotification control is data bound to the DataContext’s ErrorHeaderText property.  This property has built-in change notification, so that when the value is assigned in a ViewModel, that value is automatically consumed by the UI.

This is one of programming paradigm differences between WPF and other UI technologies.  In WPF or Silverlight we set properties in data that is bound to the UI.  In other UI technologies, we would normally set a property on a control in the UI.  The WPF pattern is much easier to program and when you adopt MVVM is much easier to unit/integration test.

The actual call to the business layer is done through the Interface and is a single line of code, wrapped in a Try Catch block.  Any errors are reported to the end user and are logged by the method call to the View Model UI Service in the Catch block.

Ocean provides concrete implementations for each of the business and data layer Interfaces that make up the IBLL(Of T) and IDAL(Of T) story.  These concrete implementations are coded the way I would program my layers.  You can easily rewrite them to comply with how you do things, or you can just inject your own concrete implementations of the Interfaces.  You are not locked into doing things the way I do, not at all.

Below is the default concrete implementation of the BLL(Of T) provided by Ocean.  DAL is a private property of type IDAL(Of T).

Public Function Update( _
    ByVal Entity As T, _
    Optional ByVal strProcedureName As String = "") As Integer Implements IBLL(Of T).Update

    Return DAL.Update(Entity, strProcedureName)
End Function

There are many very solid and wildly accepted reasons for layering your applications. One is that it enables the swapping out of data layers without having to change your application Views or ViewModels.

If I wanted to move my data store from a local SQL Server database to Azure or to SQL Server Compact or to a WCF Service, I would only have to provide a different concrete implementation of IDAL(Of T) and recompile. 

Heck, you could even just make that a config setting, have your IOC pick it up and with the flick of a user setting, change the data store.  Now you really understand why programming asynchronous data access up front is a must; it provides your applications the flexibility they need to meet the ever changing technologies we work with.

IDAL(Of T)   c#- IDAL<T>

IDAL(Of T) is the lower application layer that IBLL(Of T) consumes to perform data store operations.  The UI or ViewModels would never call methods on IDAL(Of T) directly.

IDAL(Of T) has the same Interface methods as IBLL(Of T) so I won’t repeat that code.  There are a total of four matching Interfaces exposed by IDAL that are consumed by IBLL.

The concrete implementation of IDAL(Of T) provided by Ocean is programmed against SQL Server 2008.  To target other data stores all that is required is a new concrete implementation of IDAL(Of T).

The hardest piece of the puzzle to solve was the creation of the stored procedure calls.  Proper ADO.NET parameters must be created, in the case of BBQ Shack and the provided concrete DAL implementation I had to create SQLParameters.  This requires knowledge of the business objects data store characteristics.

In BBQ Shack, I used an attribute on the business object properties to provide the required metadata.  These were easy to code generate and the action of parsing the attribute only has to be done once.  There you go, that is the trade off.  Add a code generated attribute and I get to delete two directories of files that I’ll never have to maintain going forward. 

The below code snippet is from the uom_UnitOfMeasure.vb file in the BBQShack.BusinessEntityObjects project.

The DataColumn attribute provides all the required metadata for the concrete implementation of DAL(Of T) to create SQLParameters or other types of parameters.  DataColumn has other properties and overloads that are not used below.

With a quick glace at the below code snippet and you can easily determine the data store characteristics of each property. 

Did you notice the lack of Booleans being used in any of my attributes.  Spending a few extra minutes to create an Enum pays off big time with respect to code readability.

<DataColumn(ColumnType.Timestamp)> _
<NotNullValidator(RuleSet:=STR_UPDATE_DELETE)> _
Public Property uom_Timestamp() As Byte()
    Get
        Return _objuom_Timestamp
    End Get
    Set(ByVal Value As Byte())
        MyBase.SetPropertyValue("uom_Timestamp", _objuom_Timestamp, Value)
    End Set
End Property

<DataColumn(ColumnType.VarChar, 15)> _
<CharacterCasingFormatting(CharacterCasing.LowerCase)> _
<StringLengthValidator(1, 15)> _
Public Property uom_Title() As String
    Get
        Return _struom_Title
    End Get
    Set(ByVal Value As String)
        MyBase.SetPropertyValue("uom_Title", _struom_Title, Value)
    End Set
End Property

<DataColumn(ColumnType.Int, PrimaryKey.Yes, Identity.Yes)> _
<CompareValueValidator(ComparisonType.Equal, 0, _
                       RequiredEntry.Yes, RuleSet:=STR_INSERT)> _
<CompareValueValidator(ComparisonType.GreaterThan, 0, _
                       RequiredEntry.Yes, RuleSet:=STR_UPDATE_DELETE)> _
Public Property uom_UnitOfMeasureIdent() As Integer
    Get
        Return _intuom_UnitOfMeasureIdent
    End Get
    Set(ByVal Value As Integer)
        MyBase.SetPropertyValue("uom_UnitOfMeasureIdent", _
                                _intuom_UnitOfMeasureIdent, Value)
    End Set
End Property

Ocean provides its own data stack contained in the DataAccess class.  DataAccess provides many services like connection string management, connection management, functions with overloads for accessing ADO.NET data methods, concurrency violation notification and index violation notification.

The below concrete implementation of the Update method is from the DAL.vb file in the OceanFrameworkGenericLayers project.

It first performs standard sanity checks, then builds the parameters and finally calls the ExecuteNonQuery method in Ocean’s DataAccess class.

I had thought about caching the created parameters and then just update them, but in the end, this was easier and performance was not affected.

Public Function Update( _
    ByVal obj As T, Optional ByVal strProcedureName As String = "") _
        As Integer Implements IDAL(Of T).Update

    If obj Is Nothing Then
        Throw New ArgumentNullException("obj", "Business object was null")
    End If

    If obj.ActiveRuleSet <> STR_UPDATE Then
        Throw New InvalidOperationException( _
            String.Format("{0} using wrong ActiveRuleSet for update.  Current setting: {1}.", _
                      Me.EntityName, obj.ActiveRuleSet))
    End If

    If Not obj.HasBeenValidated Then
        Throw New InvalidOperationException( _
            String.Format("{0} has not been validated for update.", _
                    Me, EntityName))
    End If

    Dim da As DataAccess = GetDataAccess()
    Dim params As New List(Of SqlParameter)

    For Each kvp As KeyValuePair(Of String, DataColumn) In _
            SharedDataColumnsManager.GetManager(Me.EntityType).DataColumnDictionary.ToList

        If kvp.Value.IsDateCreated OrElse kvp.Value.IsCreatedBy Then
            Continue For
        End If

        If kvp.Value.Nullable Then

            Dim objValue As Object = obj.GetType.GetProperty(kvp.Key).GetValue(obj, Nothing)

            If objValue Is Nothing Then
                params.Add(BuildSQLParameter( _
                           kvp.Value.ParameterName, _
                           GetSqlDataType(kvp.Value.ColumnType), _
                           ParameterDirection.Input, DBNull.Value))

            Else
                params.Add(BuildSQLParameter( _
                           kvp.Value.ParameterName, _
                           GetSqlDataType(kvp.Value.ColumnType), _
                           ParameterDirection.Input, objValue))
            End If

        ElseIf kvp.Value.MaximumLength > 0 Then
            params.Add(BuildSQLParameter( _
                       kvp.Value.ParameterName, _
                       GetSqlDataType(kvp.Value.ColumnType), _
                       kvp.Value.MaximumLength, ParameterDirection.Input, _
                       obj.GetType.GetProperty(kvp.Key).GetValue(obj, Nothing)))

        Else
            params.Add(BuildSQLParameter( _
                       kvp.Value.ParameterName, _
                       GetSqlDataType(kvp.Value.ColumnType), _
                       ParameterDirection.Input, _
                       obj.GetType.GetProperty(kvp.Key).GetValue(obj, Nothing)))
        End If

    Next

    Return da.ExecuteNonQuery( _
            CommandType.StoredProcedure, _
            GetStoredProcedureName(Me.UpdatestrProcedureName, strProcedureName), _
            params.ToArray)

End Function

I  have not used IBLL(Of T) or IDAL(Of T) in a production application.  BBQ Shack is the first application. 

What I like about it is that all my CRUD business and data layer implementation is its basically in two files instead of boiler maker code repeated in many files.

I know, jury is still out, but I wanted to share the concept with you.

Silverlight Code Sharing

If you look at the BBQShack.BusinessEntityObjects.Silverlight project you’ll see that all the files are linked to files in the BBQShack.BusinessEntityObjects project.

Now for the real benefit of code sharing with Silverlight.

Look at the Service Reference in the BBQShack.Silverlight project pictured below.  Notice that I provided specific hints for type reuse. 

By doing this, the proxy wizard did not re-implement my business objects and I can now take full advantage of all the code in my business objects.

servicereference

I have written about Silverlight code sharing in this blog post.

System Requirements

  • Visual Studio 2008 with SP1  (I have not tried BBQ Shack on Visual Studio 2010)
  • Silverlight 3 Developer Version (not Silverlight 3 Beta, not Silverlight 4 Beta)
  • Note:  If you cannot create a Silverlight application in Visual Studio, you do not have the Silverlight Developer version installed.
  • SQL Server 2008 or SQL Server 2008 Express (select install from the 3rd or 4th column)

Downloads

Sky Drive BBQ Shack Source Code, Database and Ocean v2  (opens page where three zip files are located.)

You must download all three of the zip files.  The BBQShack and Ocean zip files should be unziped in the same folder, like your \Projects folder.  After unzipping the folders will look like this:

…\Projects\BBQShack

…\Projects\Ocean

Doing this will ensure that the Ocean projects can be found by the BBQ Shack solution.

After unzipping the above download zip files, you will have these folders in your projects folder.

folders

Database Installation

The above BBQ Shack download includes the SQL Server database .mdf and .ldf files.

If you do not have SQL Server Express 2008 download it from this page.  Select the install from the 3rd or 4th column of the download page.

If you have a SQL Server 2008 you must “attach” the database to an instance of SQL Server 2008.

If you have SQL Server Express you either can either “attach” the database to an instance of SQL Server Express or you can use the file attachment method in your connection string.

Next you must change the connection string in the BBQShack project and the BBQShack.Silverlight.Web project. 

To do this double click on the “My Properties” icon below the project name in the Solution Explorer.  Select the Settings tab and edit the connection string “Data Source” property of the connection string.  You MUST do do this for BOTH projects.

Close

BBQ Shack was a fun adventure that took me places (including Alaska) I never thought I would go.  I hope that you can learn from the code and that some of the innovation in this project provides inspiration and possibly adoption.

Have a great day,

Just a grain of sand on the worlds beaches.

34 Responses to BBQ Shack – Ocean v2 for Visual Studio 2008

  1. [...] BBQ Shack ? Ocean v2 for Visual Studio 2008 (Karl Shifflett) [...]

  2. [...] has been working for quite some time on Ocean, a code generation system that blows my mind, and BBQ Shack, a great line-of-business WPF and Silverlight application that is built by Ocean.  His prolonged [...]

  3. Josh Smith says:

    AMAZING WORK! You’ve outdone yourself, my good man!! :)

  4. Shaun says:

    Really nice work. My favorite examples are real life applications so this is great. Let me get this straight though… You went on an Alaskan cruise and spent the time writing WPF applications? :)

    • Shaun,

      2 Alaskan cruises and a Hawaiian cruise, just to write code. I know, I need help.

      Its very relaxing coding aboard ship. Reminds me of my days in the Marines aboard ship.

      Planning another now.

      Cheers,

      Karl

  5. [...] MVVM toolkit got us talking about [...]

  6. billgerold says:

    Hi Karl,
    Great Article and Nicely done Videos
    I really like how you worked the Non Linear Navigation in the sample application. This has interested me the first time I saw the Billy Hollis post you referenced. Your approach is very nicely done.

    I do have an issue with attaching the SQL2008 database. It appears the issue is because I have SQL2005 Server also installed on the machine.
    Is there a Script or SQL2005 database we can download

    Thanks once again for the great work.

    • The easy way to attache the data base is to use the above links and download the SQL Sever 2008 Express Tools. This will give you SSMS which has a right click action for attaching databases.

      Cheers,

      Karl

  7. bobranck says:

    Karl,

    Could you think about going one more step with BBQ Shack? Would you create a deployment assumming the Use of SQL Express? The purpose is to bring to the surface – – – issues that we all face in deploying a Line of business application. For my part, I have really been teraing hair out about deploying my own 32 bit application to a fancy 64 Bit Windows 7 laptop. My bet is that you will demonstrate some nice cool ideas about how to deploy a LOB application.

    Sorry I wont make your 3 day Training in Redmond, but will keep hoping that your executives will spring for a travel budget for you and Jamie. Last saw you in PHX andwould suggest it for another great meeting. Can’t help but learn from you guys.

    Bob Ranck

    • Bob,

      WPF deployment is fairly straightforward with several options.

      1. Setup project
      2. ClickOnce
      3. xcopy with a database installer

      The real challenge is, how to update an application that has a database. Now we are in the soup.

      In your case, if you deploy an LOB app, chances are the person installing the app, will not be the only user. So in your case, I would have one setup project for the database that gets run on the box that will act as a server or is the server.

      Then during the other LOB installs, just install the app and point the app to the database.

      The problem with a blog post is, there are SO MANY variables. If you follow the above pattern, you’ll be successful.

      Cheers,

      Karl

  8. redwingshoe says:

    Thanks for the great series of articles – Someday I hope to turn our company in this direction – alas, someday.

    I have a basic property setting seniro that your methodology seems would have similar trouble addressing. It could very well be that I have made some fundemental error in wanting to do it this way. I figure you can set me straight if so.

    I have a business object that is used to set the configuration of a Windows Mobile device. I have a TextBox data bound to a property called Wand that is: Required and has a valid range – easy as pie I get the adorner when the UI control is blank or out-of-range. The control’s container’s (a UserControl) Enable is data bound to another B.O.’s property called Connected. The screen automatically comes to life when the device is connected and disables when the device is disconnected – again easy as pie.

    According to the business rules, if the textBox is empty while the device is disconnected, the error adorner should not be displayed – That is, the B.O. can only be in error when connected. I have my B.O. error logic such that asking it for errors when the device is disconnected always returns [NO ERRORS] which makes my object correctly state based.

    The problem is this: WPF does not always come to read my B.O.’s errors because the value of Wand does not always change when the device is disconnected, which causes Wand to be set to NULL. If the UI was blank prior to disconnect, Wand goes from NULL to NULL and the initial lines of SetPropertyValue return becuase the value is not changing. Since the B.O. does not say that Wand has changed, WPF does not reread the error state to see that it should drop the adorner.

    I am thinking I need some way to connect multiple properties for the purpose of PropertyChanged and Error notifications but I am not exactly sure what that is. That is, I need WPF to think Wand has changed sometimes even when it didn’t. This is not the first time I have had properties that have error conditions “outside themselves” and I am wondering what you would do for such a thing. A good generic example of this might be a B.O. with a IsCompany property and then CompanyName and CompanyPhone, both required when IsCompany = True. the Name and Phone UI should never be in error if IsCompany = false.

    I hope I have provided enough information to make my problem clear and not so much as to be problematic.

    • I would have the BO recheck its rules each time it is disconnected or when its gets connected. This should solve the problem.

      Cheers,

      Karl

      • redwingshoe says:

        Ok, I had that, and that does accomplish 1 thing, but not the thing I was refering to in my post. This will get me a OnPropertyChanged(“Error”), which causes the FormNotification to show the correct list, but it does not change the state of the individual property bound controls. I end up with no errors in my FormNotification but each individual control still displaying as if it were in error. What I need is a OnPropertyChanged() for the individual properties that are now “not in error” due to the change in state. WPF only requests the individual controls error state when it’s bound property notifies.

        I have added a property in BusinessEntityBase called ProcessingErrors (Active or Enabled might be more correct but maybe not abstract enough for a base class) and added code in the CheckRules routines to only process the rules when ProcessingErrors = True (still clearing appropriate errors even if False). What I need is a way to know when I also need to call OnPropertyChanged(RulePropertyName)

        Alternatively, and maybe better yet, I could set up my base WPF style to only display error adorners when Enabled = False (for me, Enabled = true when ever disconnected). Unfortunately, my WPF skills are not what they should be and so I am not exactly sure how to do that.

        Thanks for you quick reply, I hope this additional information helps clarify my problem.

      • redwingshoe says:

        I believe I see the change. In CheckAllRules, you already had a OnPropertyChanged(obj.RuleEventArgs.PropertyName) in case the rule was in an error state. I just added code inside the If Me.ValidatationErrors.Remove(obj.RuleName) path too.

        But in reality, I added a Dictionary to cache the property names detected and then call OnPropertyChanged() once each. That way if there are a lot of rules of a certain property changing state, there is only 1 call to notify that property name. We have found in the past that too many notification can case performance issues.

    • I’m not sure I understand the issue. Do you have a simple repro project?

      Karl

  9. redwingshoe says:

    I am sorry that I don’t have a version of your Ocean.FeatureDemo that demonstrates it but I can send you my version of BusinessEntityBase and you can look at the differences. A B.O. flips _ProcessErrors when it is “offline” – my app does it at the event handler where DevicePresent is determined (basically _ProcessErrors = DevicePresent) – for your sample app, a UI button connected to a routine in the B.O. that can flip it and call CheckAllRules() afterwards (a pretend way to take the B.O. “offline”) (sorry for the C#) (and I just realized that the CheckAllRules call can possibly be moved into the base as you would likely do it everytime _ProcessErrors changed):

    _ProcessErrors = !_ProcessErrors;
    CheckAllRules();

    That will show that my changes make it all better, but not show the problem. In order to show the problem, you would need just the _ProcessErrors portion of my changes. Send me an email at rick.weyrauch at redwingshoe dot com and I will be happy to forward it to you.

    • Rick,

      I’m trying to understand if you are asking for help, or letting me know something.

      If you have code the works for you, right?

      Karl

      • redwingshoe says:

        I see – sorry about that. I was first saying “I have a problem” and now I am saying “I found a solution.” I have modified BusinessEntityBase to solve my problem and I believe it is something you should want to have and I am happy to send it to you. I also feel the changes are low risk and easy to understand.

        I also made an unrelated change to BusinessEntityBase that I can comment on when I send you the file.

        I need you to send me an email (or something) in order to be able to send you my version of BusinessEntityBase.

        Rick

  10. Karl,
    I really enjoyed your project, in particular the non linear navigation and the ViewManger.
    I did a demo project to test my knowledge on what you have relaized and I moved some steps.
    I started also using PRISM (or CompositeWPF) and I was thinking about how to integrate the ViewManger with it but I am a bit confused.
    I imagine a PRISM application where you have some modules, each module privides a navigationButton to be placed in a navigation region like the one you have in the BBQShack and when a user click on the navigationbutton the module mainview managed by the viemanager will be displayed.
    Am I thinking to something wrong or is it feasible?
    How to integrate ViewManger with PRISM and modules?

    • I have not looked at using the PRISM modules yet.

      I see the ViewManage as a way to manage what is displayed and off-screen rather than actually composing content for the ViewManager.

      As life would have it, I’ll be looking at PRISM modules this wee for another work project, so I’ll look into this and see where it leads.

      Thank you very much for your question.

      Cheers,

      Karl

    • Still have not had time to see if the PRISM scenario is feasible.

      From what I could piece together, you’ll want to choose between VeiwManager and modules/regions.

      While anything is possible, the ViewManager I wrote is designed for simplicity; the ViewManager manages a region and can load content in and out of it. I think if you mix in regions/modules without a very good reason, it may become difficult to manage.

      Have a great day,

      Karl

  11. damonronco says:

    I was wondering if you would mind sharing your code generation templates for creating your business objects in the BBQShack application.

    Thanks

    Damon

    • Damon,

      When I did the code gen in this app, I didn’t use T4. I had an app from pre-T4 days just modified that when I was on the cruise. I didn’t share the app, because I don’t have time to write up docs and its one of those “use as directed” apps, which could cause other problems or wanting changes to fit their scenario. Since it is a throw away app to me, I didn’t want to spend more cycles with it.

      Since then, I’ve written an app called Crank. That is 100% T4 and extend-able. I have one project ahead of Crank, then I’ll put that out.

      Cheers,

      Karl

  12. [...] I was writing the BBQ Shack, I noticed that view model code calling into the business layer was repetitive across view models. [...]

  13. gsxrmarky says:

    Hi Karl and thanks very much for this fantastic piece work which has helped me tremendously in embarking the would of WPF; I have a wee question, I was about to add one of my own tables to extend the application to familiarize myself the data side and noticed that the Business Entity Objects appear to have been created by some kind of code generation tool (c_Customer.gen.vb), is this tool available to use?

    • Glad you like the BBQ Shack. I wrote a small code generator that I used for BBQ Shack. It read the database, then spit out the code I needed. I never released this because it was very simple and a bit of a hack for the project.

      I’m working on a new version of BBQ Shack that uses Prism and Ocean v3. I’ll release the code generator application when I publish this. I’m showing it publically at Boise Code Camp on 26 Feb and at Dallas Code Camp on 4 Mar. It will be on my blog before 26 Feb.

      Cheers,

      Karl

  14. gsxrmarky says:

    Excellent, 14 days and counting…

  15. joekahl says:

    Hi Karl. What is the status of your next version BBQ Shack v2 with Prism and Ocean v3?
    This is a fantastic help to me. I prefer to learn from the newer version, and I am comfortable with Prism.
    Joe Kahl

    • Karl says:

      Hi Joe,

      I’m just about to ship Project Silk and have already started a new Windows Phone 7 guidance project.

      I have to do the BBQ Shack v2 on nights and weekends and have several projects ahead of this one. I don’t want to give you a date and then disappoint. I wish I had a better answer.

      Best,

      Karl

      • joekahl says:

        That’s fine Karl. Thanks for the status.

        I am looking for a recommendation on a best practice for setting up web services for CRUD data operations. I want some guidance for leveraging VS2010 and the Entity Framework. I would like to have service I/O with DTOs and apply something like the POCO T4 template. I wonder how I am going to track changes on the client and then how to get them persisted on the server. Can you direct me to something, say from Julie Lerman? I really like the RIA services for Silverlight. Can I use them for WPF AND Silverlight? The server/client double use of validations is really great. Where can I find a best practices guidance or a reference application in this space?

        Sincerely, Joe Kahl

        • Karl says:

          Hi Joe,

          I don’t use EF, change tracking entities, or RIA services so I can’t advise on this.

          I would check Julie Lerman’s blog and training on Pluralsite.

          Sorry,

          Karl

Follow

Get every new post delivered to your Inbox.

Join 247 other followers