Skip to content

Instantly share code, notes, and snippets.

@markdstafford
Created August 21, 2012 20:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save markdstafford/3419288 to your computer and use it in GitHub Desktop.
Save markdstafford/3419288 to your computer and use it in GitHub Desktop.
Sample Windows Store app with OData consumption
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Data.Services.Client;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using OData.WindowsStore.NetflixDemo.Common;
using OData.WindowsStore.NetflixDemo.Netflix;
using Windows.Foundation.Metadata;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
namespace OData.WindowsStore.NetflixDemo.Data
{
public class SampleDataSource
{
private static readonly SampleDataSource Instance = new SampleDataSource();
private static readonly NetflixCatalog Context = new NetflixCatalog(new Uri("http://odata.netflix.com/Catalog"));
private readonly ObservableCollection<SampleDataGroup> allGroups = new ObservableCollection<SampleDataGroup>();
static SampleDataSource()
{
LoadMovies();
}
public ObservableCollection<SampleDataGroup> AllGroups
{
get { return allGroups; }
}
public static IEnumerable<SampleDataItem> Search(string searchString)
{
var regex = new Regex(searchString,
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase |
RegexOptions.IgnorePatternWhitespace);
return
Instance.AllGroups.SelectMany(g => g.Items).Where(
m => regex.IsMatch(m.Title) || regex.IsMatch(m.Subtitle)).Distinct(new SampleDataItemComparer());
}
public static IEnumerable<SampleDataGroup> GetGroups(string uniqueId)
{
if (!uniqueId.Equals("AllGroups"))
throw new ArgumentException("Only 'AllGroups' is supported as a collection of groups");
return Instance.AllGroups;
}
public static SampleDataGroup GetGroup(string id)
{
IEnumerable<SampleDataGroup> matches =
Instance.AllGroups.Where(group => group.UniqueId.Equals(id));
return matches.FirstOrDefault();
}
public static SampleDataItem GetItem(string id)
{
IEnumerable<SampleDataItem> matches =
Instance.AllGroups.SelectMany(group => group.Items).Where(item => item.UniqueId.Equals(id));
return matches.FirstOrDefault();
}
/// <summary>
/// Loads movies asynchronously onto the singleton.
/// </summary>
public static async void LoadMovies()
{
IEnumerable<Title> titles =
await
((DataServiceQuery<Title>)
Context.Titles.Expand("Genres,AudioFormats,AudioFormats/Language,Awards,Cast").Where(
t => t.Rating == "PG").OrderByDescending(t => t.ReleaseYear).Take(300)).ExecuteAsync();
foreach (Title title in titles)
{
foreach (Genre netflixGenre in title.Genres)
{
SampleDataGroup genre = GetGroup(netflixGenre.Name);
if (genre == null)
{
genre = new SampleDataGroup(netflixGenre.Name, netflixGenre.Name, String.Empty,
title.BoxArt.LargeUrl,
String.Empty);
Instance.AllGroups.Add(genre);
}
var content = new StringBuilder();
// Write additional things to content here if you want them to display in the item detail.
genre.Items.Add(new SampleDataItem(title.Id, title.Name,
String.Format("{0}\r\n\r\n{1} ({2})", title.Synopsis,
title.Rating,
title.ReleaseYear),
title.BoxArt.HighDefinitionUrl ?? title.BoxArt.LargeUrl,
"Description",
content.ToString()));
}
}
}
}
[WebHostHidden]
public abstract class ODataBindable : BindableBase
{
private static readonly Uri BaseUri = new Uri("ms-appx:///");
private string description = string.Empty;
private ImageSource image;
private String imagePath;
private string subtitle = string.Empty;
private string title = string.Empty;
private string uniqueId = string.Empty;
protected ODataBindable(String uniqueId, String title, String subtitle, String imagePath, String description)
{
this.uniqueId = uniqueId;
this.title = title;
this.subtitle = subtitle;
this.description = description;
this.imagePath = imagePath;
}
public string UniqueId
{
get { return uniqueId; }
set { SetProperty(ref uniqueId, value); }
}
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
public string Subtitle
{
get { return subtitle; }
set { SetProperty(ref subtitle, value); }
}
public string Description
{
get { return description; }
set { SetProperty(ref description, value); }
}
public ImageSource Image
{
get
{
if (image == null && imagePath != null)
{
image = new BitmapImage(new Uri(BaseUri, imagePath));
}
return image;
}
set
{
imagePath = null;
SetProperty(ref image, value);
}
}
public void SetImage(String path)
{
image = null;
imagePath = path;
OnPropertyChanged("Image");
}
public override string ToString()
{
return Title;
}
}
/// <summary>
/// Functionally represents a genre in the Netflix catalog. Name and functionality
/// left as-is to minimize changes to XAML bindings.
/// </summary>
public class SampleDataGroup : ODataBindable
{
private readonly ObservableCollection<SampleDataItem> items = new ObservableCollection<SampleDataItem>();
private readonly ObservableCollection<SampleDataItem> topItem = new ObservableCollection<SampleDataItem>();
public SampleDataGroup(String uniqueId, String title, String subtitle, String imagePath, String description)
: base(uniqueId, title, subtitle, imagePath, description)
{
Items.CollectionChanged += ItemsCollectionChanged;
}
public ObservableCollection<SampleDataItem> Items
{
get { return items; }
}
public ObservableCollection<SampleDataItem> TopItems
{
get { return topItem; }
}
private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Provides a subset of the full items collection to bind to from a GroupedItemsPage
// for two reasons: GridView will not virtualize large items collections, and it
// improves the user experience when browsing through groups with large numbers of
// items.
//
// A maximum of 12 items are displayed because it results in filled grid columns
// whether there are 1, 2, 3, 4, or 6 rows displayed
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (SampleDataItem movie in e.NewItems)
{
movie.Group = this;
}
if (e.NewStartingIndex < 12)
{
TopItems.Insert(e.NewStartingIndex, Items[e.NewStartingIndex]);
if (TopItems.Count > 12)
{
TopItems.RemoveAt(12);
}
}
break;
case NotifyCollectionChangedAction.Move:
if (e.OldStartingIndex < 12 && e.NewStartingIndex < 12)
{
TopItems.Move(e.OldStartingIndex, e.NewStartingIndex);
}
else if (e.OldStartingIndex < 12)
{
TopItems.RemoveAt(e.OldStartingIndex);
TopItems.Add(Items[11]);
}
else if (e.NewStartingIndex < 12)
{
TopItems.Insert(e.NewStartingIndex, Items[e.NewStartingIndex]);
TopItems.RemoveAt(12);
}
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldStartingIndex < 12)
{
TopItems.RemoveAt(e.OldStartingIndex);
if (Items.Count >= 12)
{
TopItems.Add(Items[11]);
}
}
break;
case NotifyCollectionChangedAction.Replace:
if (e.OldStartingIndex < 12)
{
TopItems[e.OldStartingIndex] = Items[e.OldStartingIndex];
}
break;
case NotifyCollectionChangedAction.Reset:
TopItems.Clear();
while (TopItems.Count < Items.Count && TopItems.Count < 12)
{
TopItems.Add(Items[TopItems.Count]);
}
break;
}
}
}
/// <summary>
/// Functionally represents a movie in the Netflix catalog. Name and functionality
/// left as-is to minimize changes to XAML bindings.
/// </summary>
public class SampleDataItem : ODataBindable
{
private string content = string.Empty;
private SampleDataGroup group;
public SampleDataItem(String uniqueId, String title, String subtitle, String imagePath, String description,
String content)
: base(uniqueId, title, subtitle, imagePath, description)
{
this.content = content;
}
public string Content
{
get { return content; }
set { SetProperty(ref content, value); }
}
public SampleDataGroup Group
{
get { return group; }
set { SetProperty(ref group, value); }
}
}
/// <summary>
/// Allows us to get a distinct set of search results.
/// </summary>
public class SampleDataItemComparer : IEqualityComparer<SampleDataItem>
{
#region IEqualityComparer<SampleDataItem> Members
public bool Equals(SampleDataItem x, SampleDataItem y)
{
return String.Equals(x.UniqueId, y.UniqueId, StringComparison.Ordinal);
}
public int GetHashCode(SampleDataItem obj)
{
return obj.UniqueId.GetHashCode();
}
#endregion
}
public static class ExtensionMethods
{
public static async Task<IEnumerable<T>> ExecuteAsync<T>(this DataServiceQuery<T> query)
{
return await Task.Factory.FromAsync<IEnumerable<T>>(query.BeginExecute(null, null), query.EndExecute);
}
public static async Task<IEnumerable<TResult>> ExecuteAsync<TResult>(this DataServiceContext context,
Uri requestUri)
{
return await Task.Factory.FromAsync<IEnumerable<TResult>>(context.BeginExecute<TResult>(requestUri, null, null),
executeAsyncResult =>
{
List<TResult> executeResult =
context.EndExecute<TResult>(executeAsyncResult)
.ToList();
return executeResult;
});
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OData.WindowsStore.NetflixDemo.Data;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Search Contract item template is documented at http://go.microsoft.com/fwlink/?LinkId=234240
namespace OData.WindowsStore.NetflixDemo
{
/// <summary>
/// This page displays search results when a global search is directed to this application.
/// </summary>
public sealed partial class SearchResultsPage : OData.WindowsStore.NetflixDemo.Common.LayoutAwarePage
{
public SearchResultsPage()
{
this.InitializeComponent();
}
/// <summary>
/// Populates the page with content passed during navigation. Any saved state is also
/// provided when recreating a page from a prior session.
/// </summary>
/// <param name="navigationParameter">The parameter value passed to
/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
/// </param>
/// <param name="pageState">A dictionary of state preserved by this page during an earlier
/// session. This will be null the first time a page is visited.</param>
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
var queryText = navigationParameter as String;
// TODO: Application-specific searching logic. The search process is responsible for
// creating a list of user-selectable result categories:
//
// filterList.Add(new Filter("<filter name>", <result count>));
//
// Only the first filter, typically "All", should pass true as a third argument in
// order to start in an active state. Results for the active filter are provided
// in Filter_SelectionChanged below.
var filterList = new List<Filter>();
filterList.Add(new Filter("All", 0, true));
// Communicate results through the view model
this.DefaultViewModel["QueryText"] = queryText;
this.DefaultViewModel["Filters"] = filterList;
this.DefaultViewModel["ShowFilters"] = filterList.Count > 1;
}
/// <summary>
/// Invoked when a filter is selected using the ComboBox in snapped view state.
/// </summary>
/// <param name="sender">The ComboBox instance.</param>
/// <param name="e">Event data describing how the selected filter was changed.</param>
void Filter_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Determine what filter was selected
var selectedFilter = e.AddedItems.FirstOrDefault() as Filter;
if (selectedFilter != null)
{
// Mirror the results into the corresponding Filter object to allow the
// RadioButton representation used when not snapped to reflect the change
selectedFilter.Active = true;
// TODO: Respond to the change in active filter by setting this.DefaultViewModel["Results"]
// to a collection of items with bindable Image, Title, Subtitle, and Description properties
var searchValue = (string)this.DefaultViewModel["QueryText"];
this.DefaultViewModel["Results"] = new List<SampleDataItem>(SampleDataSource.Search(searchValue));
// Ensure results are found
object results;
ICollection resultsCollection;
if (this.DefaultViewModel.TryGetValue("Results", out results) &&
(resultsCollection = results as ICollection) != null &&
resultsCollection.Count != 0)
{
VisualStateManager.GoToState(this, "ResultsFound", true);
return;
}
}
// Display informational text when there are no search results.
VisualStateManager.GoToState(this, "NoResultsFound", true);
}
/// <summary>
/// Invoked when a filter is selected using a RadioButton when not snapped.
/// </summary>
/// <param name="sender">The selected RadioButton instance.</param>
/// <param name="e">Event data describing how the RadioButton was selected.</param>
void Filter_Checked(object sender, RoutedEventArgs e)
{
// Mirror the change into the CollectionViewSource used by the corresponding ComboBox
// to ensure that the change is reflected when snapped
if (filtersViewSource.View != null)
{
var filter = (sender as FrameworkElement).DataContext;
filtersViewSource.View.MoveCurrentTo(filter);
}
}
/// <summary>
/// View model describing one of the filters available for viewing search results.
/// </summary>
private sealed class Filter : OData.WindowsStore.NetflixDemo.Common.BindableBase
{
private String _name;
private int _count;
private bool _active;
public Filter(String name, int count, bool active = false)
{
this.Name = name;
this.Count = count;
this.Active = active;
}
public override String ToString()
{
return Description;
}
public String Name
{
get { return _name; }
set { if (this.SetProperty(ref _name, value)) this.OnPropertyChanged("Description"); }
}
public int Count
{
get { return _count; }
set { if (this.SetProperty(ref _count, value)) this.OnPropertyChanged("Description"); }
}
public bool Active
{
get { return _active; }
set { this.SetProperty(ref _active, value); }
}
public String Description
{
get { return String.Format("{0} ({1})", _name, _count); }
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment