Alessandro Del Sole's Blog

/* A programming space about Microsoft® .NET® */
posts - 142, comments - 0, trackbacks - 0

My Links

News

Your host

This is me! This space is about Microsoft® .NET® and Microsoft® Visual Basic development. Enjoy! :-)

These postings are provided 'AS IS' for entertainment purposes only with absolutely no warranty expressed or implied and confer no rights.

Microsoft MVP

My MVP Profile

I'm a VB!

Watch my interview in Seattle

My new book on VB 2012

Order my book about VB 2012 on Amazon My book "Visual Basic 2012 Unleashed" is available. Click the cover!

My new book on LightSwitch!

Visual Studio LightSwitch Unleashed My book "Visual Studio LightSwitch Unleashed" is available. Click the cover!

Your visits

campusMVP.NET - Tutored online training for Microsoft developers

Follow me on Twitter!

Messenger me!


CyberInstaller Beta Tester

Download CIS 2008!!

CodePlex download Download my open-source projects from CodePlex!

Search the blog



Article Categories

Archives

Post Categories

.NET Framework

Blogroll

Help Authoring

Microsoft & MSDN

Setup & Deployment

Visual Basic 2005/2008/2010

WPF: Introducing the Model-View-ViewModel pattern for Visual Basic 2010 developers - part 7

Let's retake our journey through the MVVM pattern in WPF 4, with Visual Basic 2010, against ADO.NET Entity Framework. Last time we saw how to implement a Messenger class, describing what it is for although we'll see such a class in action in my next post. In this article we will write some code for data access which is important to understand how complex can be building applications based on the MVVM but also how many benefits it can bring into your developer life.

Problem of the day: does the ViewModel know what's the data source?

I confessed that I had to delay the blog post series about MVVM and the Entity Framework because while I was studying the pattern I understood that there was something wrong in my approach and that I was making some mistakes. We are at the point in which we have some data, a data access layer, an object model that allows working with the data. In theory it would be enough writing a ViewModel able of performing data access operations by implementing the appropriate commands.

But here is the real problem: if we think of abstraction, if the ViewModel could interact directly against an EDM it would strictly depend on the EDM itself. But one of the most important purposes of the MVVM is bringing the abstraction level between layers to the max and thus such an approach is not good. The ViewModel should be so indipendent that it should be able of dialing with any data source without suffering in case the data source itself is replaced. In other words, if today I work with XML and tomorrow I will decide to replace it with SQL Server, I should have a so versatile ViewModel that it will not change if my data source changes and, most of all, this approach also allows Views to work with no code edits. Can we do this? Yes, of course. We need just some more work :-)

Thinking about services

The goal is answering the following question: how do I create a ViewModel that does not know what is the underlying data source but that is able of reading and editing such a data source? The problem can be solved by adding a service layer that works like this:

  1. We define an interface named by convention as IxxxDataService, where XXX is the name of the data to represent (e.g. ICustomerDataService). Such an interface defines members that will interact with the data source like queries, insert/update/delete, etc.
  2. We declare a class, named by convention as xxxDataService, where XXX is the name of the data to represent (e.g. CustomerDataService), that implements the previous interface and that executes the actual work against data. 
  3. We write a class named ServiceLocator, which is responsible for passing to callers all the tools for working with the data source, providing separation logic.
  4. We write the ViewModel, which will invoke members from the xxxDataService class, without knowing what the underlying data source is.

What is the advantage of all the above mentioned infrastructure? Imagine that your ViewModel exposes a command for saving data. The ViewModel will expose a command named Save, that will invoke the same-named command from the service class. In this way the ViewModel does not strictly depend on the data source, because it does not know what the underlying source is. Moreover, it exposes a "conventional" command and is able of reaching the objective (that is, saving data). Maybe you are a little bit confused after my discussion. But don't worry, in next post I will begin talking about the ViewModel and everything will be clearer. Just a clarification: you should generally implement as many interfaces/classes as many models you have. We are now ready to implement a service layer.

Refactoring? Yes, of course! But in the final post!

Probably at the end of this blog post, the most experienced developers will point out that some refactoring on our code should be performed. This is true but I have in mind a specific blog post on refactoring the full project, so please don't care about this for now.

Implementing the ServiceLocator class

As I mentioned before, it's necessary implementing a class named by convention as ServiceLocator which offers the possibility of registering interfaces from the service layer and that will also allow getting the instances of the service classes. Such a class is also explained inside the Prism documentation, so you can check this out for details. At the moment I just need to describe the class implementation and its methods. With that said let's add a new class named ServiceLocator to the Services project subfolder. This is the code:

Public Class ServiceLocator
    Implements IServiceProvider
 
    
Private services As New Dictionary(Of TypeObject)()
 
    
Public Function GetService(Of T)() As T
        Return CType(GetService(GetType(T)), T)
    
End Function
 
    
Public Function RegisterService(Of T)(ByVal service As TByVal overwriteIfExists As BooleanAs Boolean
        SyncLock services
            
If Not services.ContainsKey(GetType(T)) Then
                services.Add(GetType(T), service)
                
Return True
            ElseIf overwriteIfExists Then
                services(GetType(T)) = service
                
Return True
            End If
        End SyncLock
        Return False
    End Function
 
    
Public Function RegisterService(Of T)(ByVal service As TAs Boolean
        Return RegisterService(Of T)(service, True)
    
End Function
 
    
Public Function GetService(ByVal serviceType As TypeAs Object Implements IServiceProvider.GetService
        
SyncLock services
            
If services.ContainsKey(serviceType) Then
                Return services(serviceType)
            
End If
        End SyncLock
        Return Nothing
    End Function
End Class

Basically the class implements the IServiceProvider interface and exposes two methods: RegisterService, which registers a type as a service provider, and GetService which retrieves the instance of the service class that makes the actual work against data. Once we have this class, we can define interfaces. There is just one minor step that we need to do before: instantiating the ObjectContext.

Declaring  the ObjectContext

We need a place where to create the instance of the ObjectContext which is named NorthwindEntities in the current project. This cannot be done inside the ViewModel in order to preserve the abstraction logic. This could be done at the service layer level, but that is not the perfect choice. We can consider declaring a project level variable instead; so let's add a new module named Helper to the Helpers subfolder and then let's write the following code:

Module Helper
    Public Northwind As New NorthwindEntities
End Module

We are now really ready for providing interfaces.

Implementing IDataService interfaces

If you remember the figure where I showed how the application will look like at the end of the post series, you may remember how on the left side there is the customers list that the code retrieves from the Customers table in the database. With regard to customers, we don't need anything else so we just need to simply implement an interface defining one method that retrieves the list of customers. Let's add a new interface named ICustomerDataService to the Services subfolder. This is the code:

Public Interface ICustomerDataService

    
Function GetAllCustomers() As IQueryable(Of Customer
)
End Interface

As you can see the interface defines a simple method called GetAllCustomers that, once implemented, will return the full list of customers under the form of an IQueryable(Of Customer). Now it's time for a couple of considerations: the method returns simply an IQueryable instead of an ObjectQuery(Of T) which is typical in Entity Framework. This is appropriate because the result does not depend on the underlying data source. Moreover, one day I could replace the Customer type exposed by my EDM with a custom Customer business object; with the current approach, the ViewModel that receives the result will not be affected at all.

As I mentioned before, for each ViewModel you should implement a service interface. We are working with customers and ordes, so now we need an interface for the latter data type. The new interface is quite interesting because we need to implement:

  1. a method that retrieves the orders list. We will provide two overloads, one for getting the list as an IQueryable and one for getting the list of orders for a given Customer under the form of an ObservableCollection because we are also interested in editing data.
  2. a method for saving data
  3. a method for adding new orders
  4. a method for removing existing orders
  5. methods for navigating between orders

With that said, the new interface is called IOrderDataService and we add it to the Services project subfolder. This is the code:

Imports System.Collections.ObjectModel
Public Interface IOrderDataService
    Function GetOrderDetailsByOrderId(ByVal ID As IntegerAs ObservableCollection(Of Order_Detail)

    
Function GetAllOrders() As IQueryable(Of Order
)
    
Function GetAllOrders(ByVal customerID As StringAs ObservableCollection(Of Order
)
    
Sub
 Save()

    
Sub Delete(ByVal dataSource As Object
)
    
Sub Insert(ByVal dataSource As ObjectByVal selectedCustomer As Customer
)
    
Sub MoveToNext(ByVal dataSource As Object
)
    
Sub MoveToPrevious(ByVal dataSource As Object
)
End Interface

I will better explain some details while implementing both interfaces; for now I would like to underline that the first overload of GetAllOrders, which returns IQueryable, is just implemented for future utilization even if actually we will not use it inside the current application. Other methods receive, as an argument, the data source to work with. Such a data source is passed as an Object. This is useful so that methods can accept different data sources and the appropriate conversions are performed inside service classes.

Implementing service classes

Once the interfaces are defined, we need some classes that implement those interfaces. First of all, let's create a new class named CustomerDataService that implements the related interface. Such a class should be added to the Services subfolder and is made of the following code:

Public Class CustomerDataService
    Implements ICustomerDataService
      Public Function GetAllCustomers() As IQueryable(Of CustomerImplements ICustomerDataService.GetAllCustomers
        
Return Northwind.Customers.Include("Orders"
)
    
End Function
End Class

The method here returns a pure .NET type, which is IQueryable and that will be received by the ViewModel. The method body now returns the result of a query executed against an Entity Data Model, but in the future you might want to replace the data source and return the result of a similar query executed against an XML document. With this approach the ViewModel will continue receiving an IQueryable without knowing what changes occurred at the data source level. While this class is pretty simple, the service class that implements IOrderDataService is more complex although the basic idea is the same. With that said let's add a new class named OrderDataService whose code is the following:

Imports System.Data, System.Windows.Data
Imports System.Data.Objects
Imports System.ComponentModel
Imports System.Collections.ObjectModel
 
Public Class OrderDataService
    Implements IOrderDataService
 
    
Public Function GetOrderDetailsByOrderID(ByVal ID As IntegerAs ObservableCollection(Of Order_DetailImplements IOrderDataService.GetOrderDetailsByOrderId
        
Dim OrderDetailsQuery As System.Data.Objects.ObjectQuery(Of Order_Detail) = CType((From det In Northwind.Order_Details
                                                                                   
Where det.OrderID = ID
                                                                                   
Select det),
                                                                                   
Global.System.Data.Objects.ObjectQuery(Of Order_Detail))
 
        
Return New ObservableCollection(Of Order_Detail)(OrderDetailsQuery)
    
End Function
 
    
Public Sub Insert(ByVal dataSource As ObjectByVal customer As CustomerImplements IOrderDataService.Insert
        
Dim newOrder As Order
        Dim tp = dataSource.GetType
 
        
Select Case tp.Name
            
Case Is = "ListCollectionView"
                Dim source = CType(dataSource, ListCollectionView)
 
                newOrder = 
CType(source.AddNew, Order)
                newOrder.Customer = customer
 
                
'Add a new order so that the data binding
                'engine will invoke the ErrorTemplate
                source.CommitNew()
            
Case Is = "BindingListCollectionView"
                Dim source = CType(dataSource, BindingListCollectionView)
 
                newOrder = 
CType(source.AddNew, Order)
                newOrder.Customer = customer
 
                'Add a new order so that the data binding
                'engine will invoke the ErrorTemplate
                source.CommitNew()
            
Case Else
                Throw New InvalidOperationException("Data source is of a type CollectionView which does not support adding items")
        
End Select
 
    
End Sub
 
    
Public Sub Save() Implements IOrderDataService.Save
        
Try
            Northwind.SaveChanges()
        
Catch ex As OptimisticConcurrencyException
            'Handling concurrency
            Northwind.Refresh(Objects.RefreshMode.ClientWins,
                                        Northwind.Orders)
            Northwind.SaveChanges()
        
Catch ex As Exception
            Throw
        End Try
    End Sub
 
    
Public Function GetAllOrders() As IQueryable(Of OrderImplements IOrderDataService.GetAllOrders
        
Return Northwind.Orders
    
End Function
 
    
Public Function GetAllOrders(ByVal customerID As StringAs ObservableCollection(Of OrderImplements IOrderDataService.GetAllOrders
        
Dim query = From ord In Northwind.Orders.Include("Customer")
                  
Where ord.CustomerID = customerID
                  
Select ord
 
        
Return New ObservableCollection(Of Order)(query)
    
End Function
 
    
Public Sub MoveToNext(ByVal dataSource As ObjectImplements IOrderDataService.MoveToNext
        
CType(dataSource, CollectionViewSource).View.MoveCurrentToNext()
    
End Sub
 
    
Public Sub MoveToPrevious(ByVal dataSource As ObjectImplements IOrderDataService.MoveToPrevious
        
CType(dataSource, CollectionViewSource).View.MoveCurrentToPrevious()
    
End Sub
 
    
Public Sub Delete(ByVal dataSource As ObjectImplements IOrderDataService.Delete
        
Dim tp = dataSource.GetType
 
        
Select Case tp.Name
            
Case Is = "ListCollectionView"
                Dim source = CType(dataSource, ListCollectionView)
                source.Remove(source.CurrentItem)
            
Case Is = "BindingListCollectionView"
                Dim source = CType(dataSource, BindingListCollectionView)
                source.Remove(source.CurrentItem)
            
Case Else
                Throw New InvalidOperationException("Data source is of a type that does not support removing items")
        
End Select
 
    
End Sub
End Class

Let's make the following considerations:

  1. The GetOrderDetailsByOrderId method returns the list of Order Details for the specified order. You should actually implement a method like this inside a specific service related to Order Details, but since this is the one and only operation that we make against such data, we can place it here. The result is an ObservableCollection so that we can have full data-binding support.
  2. The Insert method allows adding a new order to the existing data. The first argument is the data source that will store the new element. It is of type Object but the method body analyzes the actual type and performs the appropriate conversion into a ListCollectionView or BindingListCollectionView depending on the caller. This is because we already know that we will specifically work with this kind of objects coming from the ViewModel. Of course you could extend the code by implementing analysis and conversion to ObservableCollection but this is left to you as an exercise. Working with View objects is convenient because they offer members for working with data, such as browsing, grouping and filtering. The second argument is just the instance of the customer that the new order is associated to.
  3. The overloads implementations for GetAllOrders is quite simple. The second one queries the list of orders belonging to the specifdied customer and returns the result under the form of an ObservableCollection. This overload will be used in our ViewModel.
  4. The Save method is easy. It saves data to the database, checks for concurrency and throws exceptions when required.
  5. The Delete method works like Insert but it differs in that it removes an order from the supplied data source.
  6. The MoveToNext and MoveToPrevious methods allow browsing the orders list and they assume that the data source is browsable, thus of type ICollectionView. For this reason there is a direct conversion and invocation to the appropriate methods.

Refactoring the code is required, but this will be done later. Today we have written a lot of code and learned a lot of new concepts.

End of the story

Part 7 has been really hard. We have:

  1. described how the ViewModel must have a high level of abstraction if compared to the data source, acting against data without knowing the actual type of the data source
  2. described how this is possible by implementing a service layer
  3. implemented a ServiceLocator class that will be responsible of registering services and retrieving their instances
  4. implemented service interfaces and classes that will allow working with data, sending the result to the ViewModel

In next post I will definitely begin discussing ViewModels. We are quite near to the end of the work.

Alessandro

Print | posted on martedì 3 agosto 2010 01:42 | Filed Under [ Visual Studio 2010 Visual Basic Windows Presentation Foundation ]

Feedback

No comments posted yet.

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 4 and 3 and type the answer here:

Powered by:
Powered By Subtext Powered By ASP.NET