(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Advanced MVVM concepts using Contoso Cookbook: An analysis of a complete MVVM application: Part 2

(2 votes)
Gill Cleeren
>
Gill Cleeren
Joined Apr 02, 2010
Articles:   63
Comments:   6
More Articles
2 comments   /   posted on Mar 04, 2013
Tags:   mvvm , windows-8 , gill-cleeren
Categories:   Windows 8
Tweet

Welcome to part 2 of this deep-dive analysis of a complete MVVM application. In part 1, we’ve discussed the architecture, explained the DI container and have detailed the NavigationService. In this second part, we will continue to explore more advanced concepts around MVVM.

These articles are a companion to the Advanced MVVM webinar that I hosted at SilverlightShow. You can look at the recorded webcast here.

Accessing data using a repository and the data service layer abstraction

Almost every Windows 8 Store application will need data to perform its job. Calendar apps, note taking apps, Twitter apps… they all need access to some sort of data, whether that data lives in a database behind a service, is saved in the local app data directory…

There’s plenty of information available that explains how to access WCF services, REST services and so on, so I’m not going to spend time on that. Instead, I want to focus on the concepts behind the way I’m doing data access in the Contoso Cookbook application.

For the data access, the application uses the repository pattern. If you look up this pattern, you’ll find something along the lines of “It creates an abstraction between the data persistence and the data consumption code”. That’s exactly what we need: an abstraction in order to achieve loose coupling, which will allow us to change (dare I say replace) the “data access” logic from the code that works with it. I’ve put data access between code, since of course this can be very broad, it can go anywhere from reading a local file to accessing an oData service. The fact remains that you should see the repository as the central location for the data access.

For the consumer of the data, the data access is to be transparent. It shouldn’t be bothered knowing the details of the data access, it should just be able to say: “Give me all RecipeGroups”. That’s what the repository brings us.

A repository is however very often concerned with a single resource collection, so a RecipeRepository would be used to access RecipeGroup and Recipe instances (think parent-child relation). That’s why I won’t let my ViewModels talk with my repositories directly. If I would do so, my ViewModels would actually have to communicate with several repositories and combine the results. This is not the job of the VM (SoC!). Instead, I add another layer that’s more concerned with the business, the functionality of the model. Introducing my data services.

A separate service (or services) for the data access, one per block of functionality, is the point of contact for my VMs. These services in turn communicate with the repositories. Also, these services can be shared easier between VMs, allowing for better sharing of code as well. My data services, just like most other services, are managed by the container.

Another function I use my repositories for is caching. The repository could contain functionality that checks if the data is already available on the device or not. The actual data caching can be done using the local or the roaming data API, which allow us to save files locally or roam to other devices, as well as a dictionary known as the Settings. If it’s cached, it can load from the cache; if not, it can load from the service and cache for a subsequent call. Again, this is not the task of the consumer (the service), since checking if the data is cached and using from cache is data persistence, not data consumption.

Let’s look at some code. Below you can see part of the repository. Notice I haven’t created an interface on this, however, I could have done so.

public class RecipeRepository
{
    private ObservableCollection<IRecipeGroup> _recipeGroups = new ObservableCollection<IRecipeGroup>();
    public ObservableCollection<IRecipeGroup> RecipeGroups
    {
        get { return this._recipeGroups; }
    }
 
    public async Task<ObservableCollection<IRecipeGroup>> LoadRecipes()
    {
        if (RecipeGroups.Count == 0)
        {
            // Retrieve recipe data from Recipes.txt
            var file = await Package.Current.InstalledLocation.GetFileAsync("Data\\Recipes.txt");
            var stream = await file.OpenReadAsync();
            var input = stream.GetInputStreamAt(0);
            var reader = new DataReader(input);
            uint count = await reader.LoadAsync((uint)stream.Size);
            var result = reader.ReadString(count);
 
            // Parse the JSON recipe data
            var recipes = JsonArray.Parse(result.Substring(1, result.Length - 1));
 
            // Convert the JSON objects into RecipeDataItems and RecipeDataGroups
            CreateRecipesAndRecipeGroups(recipes);
                
        }
 
        return RecipeGroups;
    }
}

The RepositoryDataService has a hard reference to the repository. Notice that my data service has an async API. This is very important since the data service will be used from the VMs. If I would not create this API using the await/async, the calls from the VM could not be awaited.

public class RecipeDataService: IRecipeDataService
{
    private ObservableCollection<IRecipeGroup> recipeGroups;
 
    public async  Task<ObservableCollection<IRecipeGroup>> GetAllRecipeGroups()
    {
        if (recipeGroups == null)
        {
            RecipeRepository recipeRepository = new RecipeRepository();
 
            recipeGroups = await recipeRepository.LoadRecipes();
 
               
        }
        return recipeGroups;
    }
 
    public async Task<ObservableCollection<IRecipe>> SearchRecipes(string searchValue)
    {
        ObservableCollection<IRecipe> results = new ObservableCollection<IRecipe>();
        if (recipeGroups == null)
        {
            RecipeRepository recipeRepository = new RecipeRepository();
 
            recipeGroups = await recipeRepository.LoadRecipes();
        }
 
        foreach(IRecipeGroup group in recipeGroups)
        {
            foreach (var recipe in group.Recipes)
            {
                if (recipe.Title.Contains(searchValue))
                {
                    results.Add(recipe);
                }
            }
        }
 
        return results;
    }
}

Once we have the data, we can use it in our UI. Why not bind it to one of the new list controls that come with Windows 8…?

Data binding to list controls

Windows 8 comes with a lot of new list controls. Microsoft has added the GridView, the ListView, the FlipView and the SemanticZoom controls. These allow us to work with lists of data in a touch-driven world by supporting gestures that allow scrolling through (large amounts of) data more easily. List data is all around, think of streams of Flickr images, RSS news items, even files on the local file system. That’s probably why Microsoft decided to include in Windows a bunch of cool list-based controls.

Let’s take a look at how we can work with them in an MVVM scenario. Up first, the GridView. The landing page of the application contains a Grouped GridView (GG). A GG binds easily to a list of lists (each group contains itself a list of items). Just as a reminder, here’s a screenshot of the landing page (RecipeGroupView.xaml).

So here we are binding to an ObservableCollection of RecipeGroups, exposed on the ViewModel.

public ObservableCollection<IRecipeGroup> RecipeGroups { get; set; }

Agreed, this is still a demo, and your actual data might not always be available in this format. It might cost you some more time to transform it into a format that’s usable for binding to a GridView. Let’s see how we can now bind to this collection.

In the XAML, I’ve defined a CollectionViewSource, which is binding on the ObservableCollection.

<CollectionViewSource
    x:Name="groupedItemsViewSource"
    Source="{Binding RecipeGroups}"
    IsSourceGrouped="true"
    ItemsPath="Recipes"
    />

My GridView is wrapped in a SemanticZoom, we’ll look at these details in just a second. Here’s the code for the GridView. Notice that the GridView is bound to the CollectionViewSource. For the items, a default item template was created. We are creating a GG, so we are also including a definition of the group: how is a group to be represented. A group contains a header as well. This header is a button that has its content bound to the Title property of the RecipeGroup.

<GridView 
    x:Name="itemGridView"
    AutomationProperties.AutomationId="ItemGridView"
    AutomationProperties.Name="Grouped Items"
    ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
    ItemTemplate="{StaticResource Standard250x250ItemTemplate}"
    SelectedItem="{Binding SelectedRecipe, Mode=TwoWay}"
    >
    <!-- Old way-->
    <!--
    <Win8nl_Behavior:EventToCommandBehavior Event="SelectionChanged" 
                                            Command="RecipeSelectedCommand" 
                                            CommandParameter="{Binding SelectedRecipe, Mode=TwoWay}"    
                                            />-->
    <WinRtBehaviors:Interaction.Behaviors>
        <Win8nl_Behavior:EventToBoundCommandBehavior Event="SelectionChanged"
                                                        Command="{Binding RecipeSelectedCommand}"
                                                        CommandParameter="{Binding SelectedRecipe, Mode=TwoWay}"/>
    </WinRtBehaviors:Interaction.Behaviors>
 
    <GridView.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel Orientation="Horizontal" Margin="116,0,40,46" />
        </ItemsPanelTemplate>
    </GridView.ItemsPanel>
    <GridView.GroupStyle>
        <GroupStyle>
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <Grid Margin="1,0,0,6">
                        <Button 
                            Command="{Binding DataContext.RecipeGroupSelectedCommand, ElementName=LayoutRoot}"
                            CommandParameter="{Binding UniqueId}"
                            AutomationProperties.Name="Group Title"
                            Content="{Binding Title}"
                            Style="{StaticResource TextButtonStyle}">
                        </Button>
                    </Grid>
                </DataTemplate>
            </GroupStyle.HeaderTemplate>
            <GroupStyle.Panel>
                <ItemsPanelTemplate>
                    <VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0"/>
                </ItemsPanelTemplate>
            </GroupStyle.Panel>
        </GroupStyle>
    </GridView.GroupStyle>
</GridView>

So far, there was still no need to do anything in code behind… That’s about to change, when I want to support a semantic zoom (SZ) control. The landing page actually has its GG contained within a SZ, as shown below.

The code for this is shown next (I’ve left out the GG).

<SemanticZoom Grid.Row="1">
    <SemanticZoom.ZoomedInView>
        ...
    </SemanticZoom.ZoomedInView>
 
    <SemanticZoom.ZoomedOutView>
        <GridView x:Name="groupGridView" Margin="116,0,40,46">
            <GridView.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Image Source="{Binding Group.Image}" Width="320" Height="240"  Stretch="UniformToFill" />
                        <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>
    </SemanticZoom.ZoomedOutView>
</SemanticZoom>

If you take a look at this definition, you see that on the SZ level, there are no data binding statements. Also the zoomed-out level GridView doesn’t contain any. Thing is however, the data binding isn’t going to “fall through” because we’ve defined it for the other GridView. The SZ is not capable of letting us bind to a list of lists and knowing that the zoomed-out level would then bind to the groups. We have to give it a helping hand before it knows that. That’s why I have some code in my code-behind.

What? Code in the code-behind you say? Dear author, didn’t you make a big speech about not adding any code in the code-behind, since this isn’t testable and maintainable and… Yes and no. Yes, I told you to avoid writing code in the code-behind that does things with the model. That should be avoided at all cost. But code that does UI stuff *should* be in the code-behind, not in the VM. And specifying to the zoomed-out level what it should be binding to hardly does anything do the model, apart from reading data. So there you go, a perfect excuse to place some code in the code-behind.

In fact, the code I’m putting in is very limited:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    this.groupGridView.ItemsSource = this.groupedItemsViewSource.View.CollectionGroups;
}

This one line does one thing: setting the ItemsSource for the zoomed-out gridview to the groups of the collection.

Summary

In this second part, we’ve explored the data aspects of an application: accessing the data and using it in combination with the Windows 8 controls. Stay tuned for part 3, the last part of this series.

About the author

Gill Cleeren is Microsoft Regional Director, Silverlight MVP, Pluralsight trainer and Telerik MVP. He lives in Belgium where he works as .NET architect at Ordina. Gill has given many sessions, webcasts and trainings on new as well as existing technologies, such as Silverlight, ASP.NET and WPF at conferences including TechEd, TechDays, DevDays, NDC Oslo, SQL Server Saturday Switserland, Silverlight Roadshow in Sweden, Telerik RoadShow UK… Gill has written 2 books: “Silverlight 4 Data and Services Cookbook” and Silverlight 5 Data and Services Cookbook and is author of many articles for magazines and websites. You can find his blog at www.snowball.be. Twitter: @gillcleeren


Subscribe

Comments

  • lionpham

    Re: Advanced MVVM concepts using Contoso Cookbook: An analysis of a complete MVVM application: Part 2


    posted by lionpham on May 15, 2013 14:21

    Nice article! 

    I have a question related to this article:

    Assume that I have an app which display content of books. The book sources of this app come from a lot of online services, each service has different format, for example: service A uses XML format, service B user JSON format, etc. My solution to deal with that such many service is wrapping them into seperated classes (each service I will wrap it into a class) (I will call these classes is oDataServiceClass). I use the reponsitory pattern in my app, assume that I have a class named BookReponsitory in the Reponsitory layer, in this class I call methods of the oDataServiceClass to get data from the online services. The question is:

    What layer should I put the oDataServiceClass classes in?

    Looking forward to your helo.

    Best

  • Vimlesh

    Re: Advanced MVVM concepts using Contoso Cookbook: An analysis of a complete MVVM application: Part 2


    posted by Vimlesh on Jun 19, 2013 09:48

    Great Article, I'm using this to create my own app. I'm stuck implementing drag and drop functionality. Please provide some guidance for same.

    Thanks.

Add Comment

Login to comment:
  *      *       

From this series