I am starting a short series of blog posts that will go over various techniques of working with data on Windows Phone 7. I am going to create a Silverlight application for the phone that gets local and remote data in a number of ways.
I this first post I will build a WCF service and connect to it from phone application. I am going to use Entity Framework Code Frist approach to expose the data through my WCF Service.
Let’s get started.
Step 1
I am going to create new phone application. Start Visual Studio 2010 and create new project. Select a project template for Windows Phone Pivot Application.
Now, I forget about pre-generated code and create a WCF service. To do so, add new project to created solution. To give you a head start, select template for WCF Service Application.
Now, it is time to get Entity Framework code first CTP 5 up and going to create database and access it. I am going to use NuGet (install it first if you do not have it). Right click on references in your WCF project and select add Package References. Choose to search on line and first the CTP 5.
I am installing the package which will download the DLL and add references to my project. I am also adding a reference to System.ComponentModel.DataAnnotations.
Now I am going to add my EF classes, which are POCO classes. I am going to go for something simple, so I am creating new single object called Person along with my data context. Person class is very simple and here is the code for it.
using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace WcfPhoneService
{
[DataContract]
public class Person
{
[Key]
[Required]
[DataMember]
public Guid PersonID { get; set; }
[MaxLength(30)]
[Required]
[DataMember]
public string FirstName { get; set; }
[MaxLength(30)]
[Required]
[DataMember]
public string LastName { get; set; }
}
}
There are a couple of points worth noticing. I am using data annotation attributes to denote the requirement of all my future columns in the table. I am setting the maximum length as well as the key for the table. I am also setting the WCF required attributes as I am going to be eventually transferring these classes to/from the phone application. I also need to create the data context, akin to database that will contain my list of persons. It inherits from base class in EF CTP 5 and contains the list (set) of persons. Again. code is pretty simple:
using System.Configuration;
using System.Data.Entity;
using System.Data.Entity.Database;
namespace WcfPhoneService
{
public class PersonContext : DbContext
{
public PersonContext()
{
//Set initializer to handle model changes
DbDatabase.SetInitializer<PersonContext>(new PersonContextConfig());
this.Database.Connection.ConnectionString =
ConfigurationManager.ConnectionStrings["PersonConnectionString"]
.ConnectionString;
}
/// <summary>
/// List of persons
/// </summary>
public IDbSet<Person> Persons { get; set; }
/// <summary>
/// Add custom configurations if necessary
/// </summary>
/// <param name="modelBuilder">Model builder</param>
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
}
You can see an extra class in the constructor called PersonContextConfig. This class simply controls the default database behavior of the context when the model changes. I am setting it to recreating of the database in my case. I also added a couple of rows of data just for the demo by overriding Seed method.
using System.Data.Entity.Database;
using System;
namespace WcfPhoneService
{
public class PersonContextConfig : DropCreateDatabaseIfModelChanges<PersonContext>
{
protected override void Seed(PersonContext context)
{
base.Seed(context);
context.Persons.Add(new Person() { PersonID = Guid.NewGuid(), FirstName = "John", LastName = "Doe" });
context.Persons.Add(new Person() { PersonID = Guid.NewGuid(), FirstName = "Jane", LastName = "Doe" });
context.SaveChanges();
}
}
}
Now, it is time to create my service. I am going to add just two methods to it – get list of persons and insert a new person. When project was created, it created default IService1 interface and Service1 implementation. I am just going to rename both to IPersonService and PersonService respectively. Then, I am going to implement the two methods above
using System.Collections.Generic;
using System.ServiceModel;
namespace WcfPhoneService
{
[ServiceContract]
public interface IPersonService
{
[OperationContract]
IEnumerable<Person> GetPeople();
[OperationContract]
void AddPerson(Person person);
}
}
using System.Collections.Generic;
using System.Linq;
namespace WcfPhoneService
{
public class PersonService : IPersonService
{
public IEnumerable<Person> GetPeople()
{
using (PersonContext context = new PersonContext())
{
return context.Persons.OrderBy(one => one.LastName).ThenBy(one => one.FirstName).ToList();
}
}
public void AddPerson(Person person)
{
using (PersonContext context = new PersonContext())
{
context.Persons.Add(person);
context.SaveChanges();
}
}
}
}
Pretty simple, right. Now I am going to switch to my Phone project. I am going to remove all pre-generated code from all view models first and build the project to make sure it is all good. Now, it is time to add a service reference to my WCF project. To do so, right click on references node in phone project and choose add service reference menu item. Click on Discover button on the service dialog box. Change namespace to PersonService for consistency.
Click OK and let the studio generate all your code for you. To see generated code, click on PersonService reference under service references, then click on Show All Files on top of Solution explorer window. You can then drill down to see Reference.cs. This contains all your generated code.
I am now going to update view model to let it connect to the service and get the list of people. I have to create an instance of the client (proxy), setup a handler for GetPeople method, then call it. I also add a property to hold Person collection in. All fairly simple. Here is the entire code:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using WindowsPhonePivotAppWithData.PersonService;
namespace WindowsPhonePivotAppWithData
{
public class MainViewModel : INotifyPropertyChanged
{
PersonService.PersonServiceClient client = new PersonService.PersonServiceClient();
public MainViewModel()
{
}
private ObservableCollection<Person> people;
public ObservableCollection<Person> People
{
get { return people; }
private set { people = value; NotifyPropertyChanged("People"); }
}
public void LoadData()
{
client.GetPeopleCompleted += client_GetPeopleCompleted;
client.GetPeopleAsync();
}
void client_GetPeopleCompleted(object sender, PersonService.GetPeopleCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.ToString());
}
else
{
People = e.Result;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
The last part is to modify the MainPage.xaml and update item template to point to the properties that belong to person class, namely last name and first name.
<controls:Pivot Title="MY APPLICATION">
<!–Pivot item one–>
<controls:PivotItem Header="first">
<!–Double line list with text wrapping–>
<ListBox x:Name="FirstListBox" Margin="0,0,-12,0"
ItemsSource="{Binding People}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock
Text="{Binding FirstName}"
TextWrapping="Wrap"
Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock
Text="{Binding LastName}"
TextWrapping="Wrap"
Margin="12,-6,12,0"
Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PivotItem>
Now we will just call LoadData from the code behind of MainPage. This is just a temporary fix, ordinarily I would have the view / view model hook up elsewhere.
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
DataContext = App.ViewModel;
App.ViewModel.LoadData();
}
}
That was the last part. Now we can run the phone application in the emulator. And we should be able to see the list of two people that were inserted in Seed() method above.
To summarize, we just consumed WCF service from the phone. To call the same from a deployed application, we will need to change the address in ServiceReference.ClientConfig file. That is it.
The other part I would like to try is to insert a new person. I am going to add a button to the second pivot item and call the insert method. I am going to just use code behind, but you can look at my past posts on how do use MVVM Light to wire up commands.
<controls:PivotItem Header="second">
<Grid>
<Button
Content="Insert Person"
Click="Button_Click"
Width="200"
Height="100"/>
</Grid>
</controls:PivotItem>
Here is the code in the view model that I need to add:
public void InsertPerson()
{
client.AddPersonAsync(new Person()
{ PersonID = Guid.NewGuid(), FirstName = "John", LastName = "Johnson" });
}
Here is the final code for my view model:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using WindowsPhonePivotAppWithData.PersonService;
namespace WindowsPhonePivotAppWithData
{
public class MainViewModel : INotifyPropertyChanged
{
PersonService.PersonServiceClient client;
public MainViewModel()
{
client = new PersonService.PersonServiceClient();
client.AddPersonCompleted += client_AddPersonCompleted;
client.GetPeopleCompleted += client_GetPeopleCompleted;
}
private ObservableCollection<Person> people;
public ObservableCollection<Person> People
{
get { return people; }
private set { people = value; NotifyPropertyChanged("People"); }
}
public void LoadData()
{
client.GetPeopleAsync();
}
void client_GetPeopleCompleted(object sender, PersonService.GetPeopleCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.ToString());
}
else
{
People = e.Result;
}
}
public void InsertPerson()
{
client.AddPersonAsync(new Person()
{ PersonID = Guid.NewGuid(), FirstName = "John", LastName = "Johnson" });
}
void client_AddPersonCompleted(object sender, AsyncCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.ToString());
}
else
{
MessageBox.Show("Succecss adding person");
client.GetPeopleAsync();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
And that is all there is to it. We created a WCF service and consumed it from Windows phone application. I used Entity Framework code first to create classes and I used the same classed as data contract for my service. In the next post of this short series I will work on consuming a custom OData service.
Hi there, nice article. I have one question. I have a very similar setup, the code is almost the same. However when the WCF service throws a FaultException, it won’t be catched. Any idea how to solve this?
It should be exposed on Error property in your result object, as long as your server is reachable. If not, you would need ot wrap the call insie tr/catch,
Ah found some articles on how to include better exception handling; http://blogs.microsoft.co.il/blogs/bursteg/archive/2007/04/07/Shielding-WCF-Services-with-Exception-Handling-Application-Block-_2D00_-Part-1.aspx
http://blogs.msdn.com/b/brajens/archive/2007/04/23/exception-handling-in-wcf-web-service.aspx
Hello
Thank you, It’s a nice example.
I only have a question. Do you know if is it possible to harness the DataAnnotations of the DataContracts for validation on the client?
Unfortunatley, no. The entire namespace is not available on WP7. You can invent something similar, but you have to write all the code yourself 🙁