Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

Removing Repetitive Boiler Maker Code From View Models

, 17 Nov 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
While I was writing the BBQ Shack, I noticed that view model code calling into the business layer was repetitive across view models. The pattern I used was simple and widely accepted; background worker to make the asynchronous calls to the business layer. The below code snippet was typical.

While I was writing the BBQ Shack, I noticed that view model code calling into the business layer was repetitive across view models. The pattern I used was simple and widely accepted; background worker to make the asynchronous calls to the business layer.

The below code snippet was typical. Background worker, exception checking, call to business layer, successful result code path, and exception result code path.

Old Pattern

void BackgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) {
    e.Result = _iBLLItemCategory.Select(Convert.ToInt32(e.Argument));
}

void BackgroundWorker_RunWorkerCompleted(object sender,
    System.ComponentModel.RunWorkerCompletedEventArgs e) {
    if (e.Error == null) {
        if (e.Result != null) {
            this.ic_ItemCategory = (ic_ItemCategory)e.Result;

        } else {
            _iViewModelUIService.MessageDialog("Data Error",
                "Record Not Found",
                string.Format("Item Category key: {0} not found", _objLoadKey.ToString));
        }

    } else {
        _objIViewModelUIService.ExceptionDialog(e.Error);
    }
}

I kept thinking about this, always looking for a better pattern. I also wanted a pattern that only required a single line of code and still gave me all of the same benefits of the above code.

New Pattern

All of the above code condenses nicely down to one line of code that provides all of the above “old pattern” features.

The below repository method signatures describe the pattern. The pattern for the method signatures is zero or more parameters, followed by the same last two; Action<T> resultCallback and Action<Exception> errorCallback.

public interface IItemRepository {

    void GetAll(Action<IEnumerable<Item>> resultCallback, Action<Exception> errorCallback);

    void Get(Int32 itemId, Action<Item> resultCallback, Action<Exception> errorCallback);

    Item Create();
}

The successful result of the method call always calls the resultCallback delegate.

The unsuccessful result of the method call always calls the errorCallback delegate.

This pattern is incredibly simple and leads to much cleaner code in the view model. The errorCallback is a method in the view model base class. This method can use a variety of techniques for displaying the error message to the user.

void LoadItems() {
    _itemRepository.GetAll(result => { this.DataItems.Source = result; }, 
	this.DisplayException);
}

Repository Implementation – Bring on the Task Parallel Library (TPL)

The below implementation leverages the TPL Futures pattern. Use of the TPL is optional, I’m using it here in this WPF application. The TPL is not available in Silverlight 4 applications yet. For Silverlight applications, change the implementation to use the common Silverlight asynchronous pattern.

The “pattern” is described in the above IItemRepository interface. Like all interface contracts, implementation specifics can be platform specific and up to the developer.

I strongly recommend you read this book on MSDN: Parallel Programming with Microsoft .NET. The book can also be purchased from Amazon here. There is a book and Kindle version available.

[Export(typeof(IItemRepository))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ItemRepository : IItemRepository {

    readonly ThePhoneCompanyEntities _dataService;

    [ImportingConstructor]
    public ItemRepository(DataServiceFacade dataServiceFacade) {
        _dataService = dataServiceFacade.DataService;
    }

    void IItemRepository.GetAll(Action<IEnumerable<Item>> resultCallback,
        Action<Exception> errorCallback) {

        // This code can be refactored into a generic method
        // I left it this way to help with the learning process
        Task<RepositoryResult<IEnumerable<Item>>> task =
            Task.Factory.StartNew(() => {
                try {
                    return new RepositoryResult<IEnumerable<Item>>(
                                _dataService.Items.ToList(), null);
                } catch(Exception ex) {
                    return new RepositoryResult<IEnumerable<Item>>(null, ex);
                }
            });

        task.ContinueWith(r => {
            if(r.Result.Error != null) {
                errorCallback(r.Result.Error);
            } else {
                resultCallback(r.Result.Package);
            }
        }, CancellationToken.None, TaskContinuationOptions.None,
            TaskScheduler.FromCurrentSynchronizationContext());
    }

    void IItemRepository.Get(Int32 itemId, Action<Item> resultCallback,
        Action<Exception> errorCallback) {

        if(itemId != 0) {

            // This code can be refactored into a generic method
            // I left it this way to help with the learning process
            Task<RepositoryResult<Item>> task =
                Task.Factory.StartNew(() => {
                    try {
                        return new RepositoryResult<Item>(
                            _dataService.Items.Where(
                                i => i.ItemID == itemId).FirstOrDefault(), null);
                    } catch(Exception ex) {
                        return new RepositoryResult<Item>(null, ex);
                    }
                });

            task.ContinueWith(r => {
                if(r.Result.Error != null) {
                    errorCallback(r.Result.Error);
                } else {
                    resultCallback(r.Result.Package);
                }
            }, CancellationToken.None, TaskContinuationOptions.None,
                TaskScheduler.FromCurrentSynchronizationContext());
        } else {
            resultCallback(((IItemRepository)this).Create());
        }
    }

    // I always create my entity objects in the business layer and 
    // never in the presentation layer
    Item IItemRepository.Create() {
        return new Item();
    }
}

Key Points

  • Call that does the actual work is wrapped in a try catch block.
  • The result of the asynchronous operation is wrapped in a RepositoryResult object.
  • The RepositoryResult is used to drive invoking the resultCallback or the errorCallback on the calling thread. The TaskScheduler.FromCurrentSynchronizatikonContext method call in the task.ContinueWith ensures the two callbacks are invoked on the calling thread.

Download

This code is from one of my presentations at the patterns & practices 2010 Symposium.

The article and video is here: http://blogs.msdn.com/b/kashiffl/archive/2010/10/21/patterns-and-practices-2010-symposium-content.aspx.

This code can be downloaded from the article.

Close

I hope that this short post encourages you to look for possible refactorings in your own code and to take a look at the TPL.

Have a great day,

Just a grain of sand on the worlds beaches.


Filed under: C#, CodeProject, M-V-VM, MVVM, Prism, TPL, Visual Studio 2010, WPF General

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Karl Shifflett
Architect Gayle Manufacturing Company
United States United States
Karl loves .NET, WPF, WCF, ASP.NET, VB.NET and C#.
 
Awards:
 
  • December 2008 VB.NET Code Project Article Award
  • 2009 Code Project MVP
  • 2008 Code Project MVP
  • 2008 Microsoft MVP - Client App Dev
  • December 2007 VB.NET Code Project Article Award
  • Gold Medal Winner at IBM's 1998 PROIV Programming Contest in Las Vegas
Click here to check out my Blog
 
Click here to learn about Mole 2010 debugging tool for Visual Studio 2010
 
Click here to read about XAML Power Toys
 

Just a grain of sand on the worlds beaches.

Follow on   Twitter

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141216.1 | Last Updated 17 Nov 2010
Article Copyright 2010 by Karl Shifflett
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid