May

Written by: Michael Washington



Visual Studio LightSwitch (In Visual Studio 11 Beta) allows you to access your LightSwitch application via OData. This provides access to the security and business rules of your LightSwitch application.

In this article we will demonstrate using Windows Phone 7 to create an application that will cover C reating, R eading, U pdating, and D eleting data (otherwise known as CRUD) in LightSwitch.

The ODATA Library

Developing OData clients that communicate with LightSwitch is easy when there is an OData library available to assist you. You can find OData libraries at: http://www.odata.org/libraries.

We will use the OData library that is in the Windows Phone SDK. You can download the SDK at this link:

http://create.msdn.com/en-us/home/getting_started.

The LightSwitch OData Service

We will start with the Flower Shop LightSwitch project used in the article: A Full CRUD DataJs and KnockoutJs LightSwitch Example Using Only An .Html Page. In that article we added security that only allows an administrator the ability to edit the Products.

Note: Any security or business rules that you add to the LightSwitch application, are enforced when connecting to the LightSwitch application over OData.

We will publish the LightSwitch application to our local IIS web server so that we can easy connect to it from our Windows Phone 7 application. In our case this address will be: http://localhost/FlowerShop.

We can navigate to the OData service by adding ApplicationData.svc to the URL. Because we have enabled security it will ask us to log in.

The OData service is displayed. For more about connecting to a LightSwitch application over OData see:

Setup The Windows Phone 7 Project

After installing the latest Windows Phone SDK, we open Visual Studio.

We create a Windows Phone application.

We choose OS 7.1 (or higher).

The project will show.

Now, we add a new folder and call it Images.

We copy images to the Images folder from:

C:\Program Files\Microsoft SDKs\Windows Phone\v7.1\Icons\dark

appbar.add.rest.png

appbar.cancel.rest.png

appbar.delete.rest.png

appbar.save.rest.png

Next, we click on each image and set the Properties to match the image above.

We right-click on References in the Solution Explorer and select Add Service Reference.

We enter the address to the OData service and click the Go button. A box will pop up and ask us to authenticate. We use a LightSwitch account that is an Administrator.

This username and password will only be used to create the proxy class used to communicate with LightSwitch. It will not be used when the application is running.

We set the Namespace to FlowerShopService and click OK.

Display List of Entities

We change the context of the MainPage.xaml to the following:

< phone : PhoneApplicationPage x : Class = "LsFlowerShopWp7.MainPage" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns : x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns : phone = "clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns : shell = "clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns : d = "http://schemas.microsoft.com/expression/blend/2008" xmlns : mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" mc : Ignorable = "d" d : DesignWidth = "480" d : DesignHeight = "768" FontFamily = "{StaticResource PhoneFontFamilyNormal}" FontSize = "{StaticResource PhoneFontSizeNormal}" Foreground = "{StaticResource PhoneForegroundBrush}" SupportedOrientations = "Portrait" Orientation = "Portrait" shell : SystemTray . IsVisible = "True" > <!--LayoutRoot is the root grid where all page content is placed--> < Grid x : Name = "LayoutRoot" Background = "Transparent" > < Grid.RowDefinitions > < RowDefinition Height = "Auto" /> < RowDefinition Height = "*" /> </ Grid.RowDefinitions > <!--TitlePanel contains the name of the application and page title--> < StackPanel x : Name = "TitlePanel" Grid . Row = "0" Margin = "12,17,0,28" > < TextBlock x : Name = "ApplicationTitle" Text = "LightSwitchHelpWebsite.com" Style = "{StaticResource PhoneTextNormalStyle}" /> < TextBlock x : Name = "PageTitle" Text = "Flower Shop" Margin = "9,-7,0,0" Style = "{StaticResource PhoneTextTitle1Style}" /> </ StackPanel > <!--ContentPanel - place additional content here--> < Grid x : Name = "ContentPanel" Grid . Row = "1" Margin = "12,0,12,0" > < ListBox x : Name = "ProductListBox" ItemsSource = "{Binding}" SelectionChanged = "ProductListBox_SelectionChanged" > < ListBox.ItemTemplate > < DataTemplate > < StackPanel Orientation = "Vertical" > < TextBlock Text = "{Binding ProductName}" FontSize = "{StaticResource PhoneFontSizeExtraLarge}" /> < TextBlock Text = "{Binding Description}" FontSize = "{StaticResource PhoneFontSizeSmall}" /> < TextBlock Text = "{Binding Price, StringFormat=\{0:C2\}}" FontSize = "{StaticResource PhoneFontSizeSmall}" /> </ StackPanel > </ DataTemplate > </ ListBox.ItemTemplate > </ ListBox > </ Grid > </ Grid > </ phone : PhoneApplicationPage >

We replace the contents of MainPage.xaml.cs with the following code:

using System; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using Microsoft.Phone.Controls; using System.Data.Services.Client; using LsFlowerShopWp7.FlowerShopService; namespace LsFlowerShopWp7 { public partial class MainPage : PhoneApplicationPage { // Url to the OData Service in Visual Studio LighTSwitch private static Uri LightSwitchApplicationUri = new Uri(" http://localhost/flowershop/ApplicationData.svc/ "); // The Data Service Context that encapsulates operations executed against the oData source private ApplicationData ApplicationDataContext; // The DataService Collection that will contain the Products private DataServiceCollection<FlowerShopService.FlowerShopProduct> dsFlowerShopProducts; public MainPage() { InitializeComponent(); } #region OnNavigatedTo protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { base .OnNavigatedTo(e); // Initialize the Data Service Context ApplicationDataContext = new ApplicationData(LightSwitchApplicationUri); // Pass the user name and password for the LightSwitch account to be used // All security and business logic in LightSwitch will be executed using this account ApplicationDataContext.Credentials = new NetworkCredential(" Admin ", " password#1 "); // The query specifying the data var FlowerShopProductsQuery = from FlowerShopProducts in ApplicationDataContext.FlowerShopProducts select FlowerShopProducts; // Initialize the DataService Collection dsFlowerShopProducts = new DataServiceCollection<FlowerShopProduct>(ApplicationDataContext); // Wire up the dsFlowerShopProducts_LoadCompleted method dsFlowerShopProducts.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(dsFlowerShopProducts_LoadCompleted); // Start the request to retrieve the data dsFlowerShopProducts.LoadAsync(FlowerShopProductsQuery); } #endregion #region dsFlowerShopProducts_LoadCompleted void dsFlowerShopProducts_LoadCompleted( object sender, LoadCompletedEventArgs e) { if (e.Error == null ) { // Check to see if there are multiple pages of data if (dsFlowerShopProducts.Continuation != null ) { // Get additional pages of data dsFlowerShopProducts.LoadNextPartialSetAsync(); } else { // Bind the data to the UI this .ProductListBox.DataContext = dsFlowerShopProducts; } } else { // Show any errors MessageBox.Show( string .Format(" An error has occurred: {0} ", e.Error.Message)); } } #endregion #region ProductListBox_SelectionChanged private void ProductListBox_SelectionChanged( object sender, SelectionChangedEventArgs e) { // Is there an item selected? if ( null != ProductListBox.SelectedItem) { // Get the Id of the selected Product int Id = (ProductListBox.SelectedItem as FlowerShopService.FlowerShopProduct).Id; // Open the EditProduct page passing the Id of the selected Product NavigationService.Navigate( new Uri(String.Format(" /EditProduct.xaml?Id={0} ", Id), UriKind.Relative)); } } #endregion } }

We hit F5 to build and run the application.

The Windows Phone emulator will start.

The Products will display.

Display Single Entity

We add a new control and call it EditProduct.xaml.

We change the context of EditProduct.xaml to the following:

< phone : PhoneApplicationPage x : Class = "LsFlowerShopWp7.EditProduct" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns : x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns : phone = "clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns : shell = "clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns : d = "http://schemas.microsoft.com/expression/blend/2008" xmlns : mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" FontFamily = "{StaticResource PhoneFontFamilyNormal}" FontSize = "{StaticResource PhoneFontSizeNormal}" Foreground = "{StaticResource PhoneForegroundBrush}" SupportedOrientations = "Portrait" Orientation = "Portrait" mc : Ignorable = "d" d : DesignHeight = "696" d : DesignWidth = "480" shell : SystemTray . IsVisible = "True" > <!--LayoutRoot is the root grid where all page content is placed--> < Grid x : Name = "LayoutRoot" Background = "Transparent" > < Grid.RowDefinitions > < RowDefinition Height = "Auto" /> < RowDefinition Height = "83*" /> </ Grid.RowDefinitions > <!--TitlePanel contains the name of the application and page title--> < StackPanel x : Name = "TitlePanel" Grid . Row = "0" Margin = "12,17,0,28" > < TextBlock x : Name = "ApplicationTitle" Text = "LightSwitchHelpWebsite.com" Style = "{StaticResource PhoneTextNormalStyle}" /> < TextBlock x : Name = "PageTitle" Text = "Flower Shop" Margin = "9,-7,0,0" Style = "{StaticResource PhoneTextTitle1Style}" /> </ StackPanel > <!--ContentPanel - place additional content here--> < Grid x : Name = "ContentPanel" Grid . Row = "1" Grid . RowSpan = "3" > < Grid.RowDefinitions > < RowDefinition Height = "90" /> < RowDefinition Height = "90*" /> < RowDefinition Height = "90*" /> < RowDefinition Height = "90*" /> < RowDefinition Height = "90*" /> < RowDefinition Height = "90*" /> < RowDefinition Height = "63*" /> </ Grid.RowDefinitions > < TextBlock Text = "Product Name:" Grid . Row = "0" HorizontalAlignment = "Left" VerticalAlignment = "Center" FontSize = "40" /> < TextBox Grid . Row = "1" Name = "txtProductName" Text = "{Binding ProductName, Mode=TwoWay}" Margin = "0,0,20,0" /> < TextBlock Text = "Product Description:" Grid . Row = "2" HorizontalAlignment = "Left" VerticalAlignment = "Center" FontSize = "40" /> < TextBox Grid . Row = "3" Name = "txtProductDescription" Text = "{Binding Description, Mode=TwoWay}" Margin = "0,0,20,0" /> < TextBlock Text = "Product Price:" Grid . Row = "4" HorizontalAlignment = "Left" VerticalAlignment = "Center" FontSize = "40" /> < TextBox Grid . Row = "5" Name = "txtProductPrice" Text = "{Binding Price, Mode=TwoWay}" Margin = "0,0,20,0" Width = "150" HorizontalAlignment = "Left" /> </ Grid > </ Grid > < phone : PhoneApplicationPage.ApplicationBar > < shell : ApplicationBar Mode = "Default" Opacity = "1.0" IsMenuEnabled = "True" IsVisible = "True" > < shell : ApplicationBarIconButton IconUri = "/Images/appbar.cancel.rest.png" Text = "cancel" x : Name = "btnCancel" Click = "btnCancel_Click" /> </ shell : ApplicationBar > </ phone : PhoneApplicationPage.ApplicationBar > </ phone : PhoneApplicationPage >

We replace the contents of EditProduct.xaml.cs with the following code:

using System; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using Microsoft.Phone.Controls; using System.Data.Services.Client; using System.Windows.Data; using LsFlowerShopWp7.FlowerShopService; namespace LsFlowerShopWp7 { public partial class EditProduct : PhoneApplicationPage { // Url to the OData Service in Visual Studio LighTSwitch private static Uri LightSwitchApplicationUri = new Uri(" http://localhost/flowershop/ApplicationData.svc/ "); // The Data Service Context that encapsulates operations executed against the oData source private ApplicationData ApplicationDataContext; // The DataService Collection that will contain the Products private DataServiceCollection<FlowerShopService.FlowerShopProduct> dsFlowerShopProducts; public EditProduct() { InitializeComponent(); // Initialize the Data Service Context ApplicationDataContext = new ApplicationData(LightSwitchApplicationUri); // Pass the user name and password for the LightSwitch account to be used // All security and business logic in LightSwitch will be executed using this account ApplicationDataContext.Credentials = new NetworkCredential(" Admin ", " password#1 "); } #region OnNavigatedTo protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { // Thsi method is called when a user navigates to this page base .OnNavigatedTo(e); string strId; // Is an Id passed? if (NavigationContext.QueryString.TryGetValue(" Id ", out strId)) { if (strId != " -1 ") // An existing Product { // Convert Id to a integer int intId = Convert.ToInt32(strId); // Query only the Product matching the Id var FlowerShopProductsQuery = from FlowerShopProducts in ApplicationDataContext.FlowerShopProducts where FlowerShopProducts.Id == intId select FlowerShopProducts; // Start the process to load the selected Product // Initialize the DataService Collection dsFlowerShopProducts = new DataServiceCollection<FlowerShopProduct>(ApplicationDataContext); // Wire up the dsFlowerShopProducts_LoadCompleted method dsFlowerShopProducts.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(dsFlowerShopProducts_LoadCompleted); // Start the request to retrieve the data dsFlowerShopProducts.LoadAsync(FlowerShopProductsQuery); } } } #endregion #region dsFlowerShopProducts_LoadCompleted void dsFlowerShopProducts_LoadCompleted( object sender, LoadCompletedEventArgs e) { if (e.Error == null ) { if (dsFlowerShopProducts.FirstOrDefault() != null ) { // Display the selected Product this .ContentPanel.DataContext = dsFlowerShopProducts.FirstOrDefault(); } } else { // Display any errors MessageBox.Show( string .Format(" An error has occurred: {0} ", e.Error.Message)); } } #endregion // ApplicationBar Events #region btnCancel_Click private void btnCancel_Click( object sender, EventArgs e) { // Go back to main page NavigationService.GoBack(); } #endregion } #region Extensions public static class Extensions { #region UpdateBinding // From: http://stackoverflow.com/questions/8168861/ // two-way-databinding-from-textbox-doesnt-update-when-button-in-applicationbar-is public static void UpdateBinding( this TextBox textBox) { // This is an extension method that solves the problem // of a user clicking the save button before clicking // outside of a TextBox after making a change BindingExpression bindingExpression = textBox.GetBindingExpression(TextBox.TextProperty); if (bindingExpression != null ) { bindingExpression.UpdateSource(); } } #endregion } #endregion }

We hit F5 to build and run the application.

We select a Product in the list…

The selected product will display on the EditProduct.xaml page.

Clicking the Cancel button will return to the MainPage.xaml page.

Update Entity

First, we add a button to EditProduct.xaml:

< shell : ApplicationBarIconButton IconUri = "/Images/appbar.save.rest.png" Text = "save" x : Name = "btnSave" Click = "btnSave_Click" />

Then, we add the following two methods:

#region btnSave_Click private void btnSave_Click( object sender, EventArgs e) { // Update the bindings on the TextBoxes txtProductName.UpdateBinding(); txtProductDescription.UpdateBinding(); txtProductPrice.UpdateBinding(); // Start the saving changes ApplicationDataContext.BeginSaveChanges( SaveChangesOptions.Batch, OnChangesSaved, ApplicationDataContext); } #endregion #region OnChangesSaved private void OnChangesSaved(IAsyncResult result) { // Use the Dispatcher to ensure that the // asynchronous call returns in the correct thread. Dispatcher.BeginInvoke(() => { // Cast result to the ApplicationDataContext ApplicationDataContext = result.AsyncState as ApplicationData; try { // Complete the save changes operation ApplicationDataContext.EndSaveChanges(result); } catch (Exception ex) { // Display the error from the response. MessageBox.Show( string .Format(" An error has occurred: {0} ", ex.Message)); } finally { // Go back to main page NavigationService.GoBack(); } }); } #endregion

We hit F5 to build and run the application.

Now, when we edit a Product, we can click the save button to save the changes, and return to MainPage.xaml.

Delete Entity

First, we add a button to EditProduct.xaml:

< shell : ApplicationBarIconButton IconUri = "/Images/appbar.delete.rest.png" Text = "delete" x : Name = "btnDelete" Click = "btnDelete_Click" />

Next we add the following two methods:

#region btnDelete_Click private void btnDelete_Click( object sender, EventArgs e) { // Get the product that is bound to the UI FlowerShopProduct ProductToDelete = (FlowerShopProduct) this .ContentPanel.DataContext; if (ProductToDelete != null ) { // Mark the Product to be deleted ApplicationDataContext.DeleteObject(ProductToDelete); // Start the request to delete the Product ApplicationDataContext.BeginSaveChanges(DeleteProductHandler, ProductToDelete); } } #endregion #region DeleteProductHandler private void DeleteProductHandler(IAsyncResult result) { // Use the Dispatcher to ensure that the // asynchronous call returns in the correct thread. Dispatcher.BeginInvoke(() => { // Cast result to the Product FlowerShopProduct ProductToDelete = result.AsyncState as FlowerShopProduct; try { // Delete the Product ApplicationDataContext.EndSaveChanges(result); } catch (Exception ex) { // Display the error from the response. MessageBox.Show( string .Format(" An error has occurred: {0} ", ex.Message)); } finally { // Go back to main page NavigationService.GoBack(); } }); } #endregion

We hit F5 to build and run the application.

Clicking the delete button while viewing a Product in edit mode will delete it, and return to MainPage.xaml.

Note that we will not be allowed to delete a Product that has been used in a Flower Shop order (also note that there is a inner exception message that clearly indicates this, but the code to parse that inner exception message was verbose so it was not included in this sample to keep the code as simple as possible).

Insert Entity

First, we add a button to MainPage.xaml:

< phone : PhoneApplicationPage.ApplicationBar > < shell : ApplicationBar Mode = "Default" Opacity = "1.0" IsMenuEnabled = "True" IsVisible = "True" > < shell : ApplicationBarIconButton IconUri = "/Images/appbar.add.rest.png" Text = "add" x : Name = "btnAdd" Click = "btnAdd_Click" /> </ shell : ApplicationBar > </ phone : PhoneApplicationPage.ApplicationBar >

We add the following method to MainPage.xaml.cs that will navigate us to the EditProduct.xaml page passing a –1 to indicate that it is a new record:

#region btnAdd_Click private void btnAdd_Click( object sender, EventArgs e) { // Open the EditProduct page passing the Id of -1 to indicate it is a new record NavigationService.Navigate( new Uri(" /EditProduct.xaml?Id=-1 ", UriKind.Relative)); } #endregion

On the EditProduct.xaml.cs page, we add the following code to the OnNavigatedTo method to create a new Product:

else // A new Product { // Create a new Product FlowerShopProduct objFlowerShopProduct = new FlowerShopProduct(); // Add the new Product to the Data Service Context ApplicationDataContext.AddToFlowerShopProducts(objFlowerShopProduct); // Set the context of the UI to the new Product this .ContentPanel.DataContext = objFlowerShopProduct; }

When we run the application, we can click the add button to add a new Product.

We enter the Product information and click the save button.

The Product is added.

OData Is Easier Because LightSwitch Will Do Most Of The Work

LightSwitch allows you to connect to it using many different clients. This allows you to centralize all your security and business rules. This way, your external clients such as mobile apps, and web pages do not need to contain any business rules or security.

This should simplify the development of these clients, and make the process of updating and enhancing the application easier.

An Important Note About SSL

A production application must run on a webserver that has SSL enabled. Otherwise, a hacker with a packet sniffer can easily get the data and the usernames and passwords of your users who are connecting to your site using public Wi-Fi access points or other unsecure networks.

Also See

Consume a LightSwitch OData Service from a Windows Phone application

Fun with OData and Windows Phone 7

Open Data Protocol (OData) Client for Windows Phone

Producing and Consuming OData in a Silverlight and Windows Phone 7 Application

OData articles on LightSwitchHelpWebsite.com:

Using Visual Studio LightSwitch To Orchestrate A Unity 3D Game

Shape Your LightSwitch OData Using WCF RIA Services

A Full CRUD DataJs and KnockoutJs LightSwitch Example Using Only An .Html Page

A Full CRUD LightSwitch JQuery Mobile Application

Calling LightSwitch 2011 OData Using Server Side Code

Communicating With LightSwitch Using Android App Inventor

Using The OData Explorer with LightSwitch OData

Learn How To Make OData Calls In LightSwitch 2011

Accessing Your Visual Studio 2011 LightSwitch Application Using OData

A LightSwitch Netflix OData Mash-up

Download Code

The LightSwitch project is available at:

http://lightswitchhelpwebsite.com/Downloads.aspx