Tutorial: Creating and Deploying a WCF Sync Service to Azure
The walkthroughs in this tutorial provide steps to create a sync service by using Azure Tools for Microsoft Visual Studio (see the Windows Azure MSDN site) and deploy the service to Windows Azure. This section contains the following sub-sections.
Section |
Description |
Walkthrough: Creating a Sample SQL Database
|
This walkthrough provides steps to create a sample database and to use the SyncSvcUtil utility to create sync related artifacts in the database. |
This walkthrough provides steps to create a simple sync service that will be hosted in Windows Azure. |
|
Walkthrough: Creating a Silverlight Offline Client Application |
This walkthrough provides steps to develop an offline-capable Silverlight application that consumes a sync service. |
Walkthrough: Creating a Sample SQL Database
In this walkthrough you will create a SQL Database that is used by a sample sync service and use the SyncSvcUtil utility to create sync related artifacts in the database.
Task 1: Creating a sample database
In this task you will create a new database that will be used by the sync service.
Create SQL Database named listdb.
Execute the following SQL script against the listdb database. You may want to use the Project Houston tool on http://www.sqlazurelabs.com/ tool to do this.
Caution
Don’t use instlistdb.sql file C:\Program Files\Microsoft SDKs\Microsoft Sync Framework\4.0\Samples\config as that SQL file is for the first tutorial Tutorial: Creating and Consuming a Sync Service, which covers a non-Azure scenario.
/****** Object: Table [dbo].[User] Script Date: 05/25/2010 13:23:55 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[User](
[ID] [uniqueidentifier] NOT NULL,
[Name] [nvarchar](50) NOT NULL,
``CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
[ID] ASC
)
)
GO
/****** Object: Table [dbo].[Tag] Script Date: 05/25/2010 13:23:55 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Tag](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](100) NOT NULL,
``CONSTRAINT [PK_Tag] PRIMARY KEY CLUSTERED
(
[ID] ASC
)
)
GO
/****** Object: Table [dbo].[Status] Script Date: 05/25/2010 13:23:55 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Status](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
``CONSTRAINT [PK_Status] PRIMARY KEY CLUSTERED
(
[ID] ASC
)
)
GO
/****** Object: Table [dbo].[Priority] Script Date: 05/25/2010 13:23:55 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Priority](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
``CONSTRAINT [PK_Priority] PRIMARY KEY CLUSTERED
(
[ID] ASC
)
)
GO
/****** Object: Table [dbo].[List] Script Date: 05/25/2010 13:23:55 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[List](
[ID] [uniqueidentifier] NOT NULL,
[Name] [nvarchar](100) NOT NULL,
[Description] [nvarchar](250) NULL,
[UserID] [uniqueidentifier] NOT NULL,
[CreatedDate] [datetime] NOT NULL,
``CONSTRAINT [PK_List] PRIMARY KEY CLUSTERED
(
[ID] ASC
)
)
GO
/****** Object: Table [dbo].[Item] Script Date: 05/25/2010 13:23:55 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Item](
[ID] [uniqueidentifier] NOT NULL,
[ListID] [uniqueidentifier] NOT NULL,
[UserID] [uniqueidentifier] NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Description] [nvarchar](250) NULL,
[Priority] [int] NULL,
[Status] [int] NULL,
[StartDate] [datetime] NULL,
[EndDate] [datetime] NULL,
``CONSTRAINT [PK_Item] PRIMARY KEY CLUSTERED
(
[ID] ASC
)
)
GO
/****** Object: Table [dbo].[TagItemMapping] Script Date: 05/25/2010 13:23:55 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TagItemMapping](
[TagID] [int] NOT NULL,
[ItemID] [uniqueidentifier] NOT NULL,
[UserID] [uniqueidentifier] NOT NULL,
``CONSTRAINT [PK_TagItemMapping] PRIMARY KEY CLUSTERED
(
[TagID] ASC,
[ItemID] ASC,
[UserID] ASC
)
)
GO
/****** Object: Default [DF_List_ID] Script Date: 05/25/2010 13:23:55 ******/
ALTER TABLE [dbo].[List] ADD CONSTRAINT [DF_List_ID] DEFAULT (newid()) FOR [ID]
GO
/****** Object: Default [DF_List_CreatedDate] Script Date: 05/25/2010 13:23:55 ******/
ALTER TABLE [dbo].[List] ADD CONSTRAINT [DF_List_CreatedDate] DEFAULT (getdate()) FOR [CreatedDate]
GO
/****** Object: Default [DF_User_ID] Script Date: 05/25/2010 13:23:55 ******/
ALTER TABLE [dbo].[User] ADD CONSTRAINT [DF_User_ID] DEFAULT (newid()) FOR [ID]
GO
/****** Object: ForeignKey [FK_Item_List] Script Date: 05/25/2010 13:23:55 ******/
ALTER TABLE [dbo].[Item] WITH CHECK ADD CONSTRAINT [FK_Item_List] FOREIGN KEY([ListID])
REFERENCES [dbo].[List] ([ID])
GO
ALTER TABLE [dbo].[Item] CHECK CONSTRAINT [FK_Item_List]
GO
/****** Object: ForeignKey [FK_Item_Priority] Script Date: 05/25/2010 13:23:55 ******/
ALTER TABLE [dbo].[Item] WITH CHECK ADD CONSTRAINT [FK_Item_Priority] FOREIGN KEY([Priority])
REFERENCES [dbo].[Priority] ([ID])
GO
ALTER TABLE [dbo].[Item] CHECK CONSTRAINT [FK_Item_Priority]
GO
/****** Object: ForeignKey [FK_Item_Status] Script Date: 05/25/2010 13:23:55 ******/
ALTER TABLE [dbo].[Item] WITH CHECK ADD CONSTRAINT [FK_Item_Status] FOREIGN KEY([Status])
REFERENCES [dbo].[Status] ([ID])
GO
ALTER TABLE [dbo].[Item] CHECK CONSTRAINT [FK_Item_Status]
GO
/****** Object: ForeignKey [FK_Item_User] Script Date: 05/25/2010 13:23:55 ******/
ALTER TABLE [dbo].[Item] WITH CHECK ADD CONSTRAINT [FK_Item_User] FOREIGN KEY([UserID])
REFERENCES [dbo].[User] ([ID])
GO
ALTER TABLE [dbo].[Item] CHECK CONSTRAINT [FK_Item_User]
GO
/****** Object: ForeignKey [FK_List_User] Script Date: 05/25/2010 13:23:55 ******/
ALTER TABLE [dbo].[List] WITH CHECK ADD CONSTRAINT [FK_List_User] FOREIGN KEY([UserID])
REFERENCES [dbo].[User] ([ID])
GO
ALTER TABLE [dbo].[List] CHECK CONSTRAINT [FK_List_User]
GO
/****** Object: ForeignKey [FK_TagItemMapping_Item] Script Date: 05/25/2010 13:23:55 ******/
ALTER TABLE [dbo].[TagItemMapping] WITH CHECK ADD CONSTRAINT [FK_TagItemMapping_Item] FOREIGN KEY([ItemID])
REFERENCES [dbo].[Item] ([ID])
GO
ALTER TABLE [dbo].[TagItemMapping] CHECK CONSTRAINT [FK_TagItemMapping_Item]
GO
/****** Object: ForeignKey [FK_TagItemMapping_Tag] Script Date: 05/25/2010 13:23:55 ******/
ALTER TABLE [dbo].[TagItemMapping] WITH CHECK ADD CONSTRAINT [FK_TagItemMapping_Tag] FOREIGN KEY([TagID])
REFERENCES [dbo].[Tag] ([ID])
GO
ALTER TABLE [dbo].[TagItemMapping] CHECK CONSTRAINT [FK_TagItemMapping_Tag]
GO
/****** Object: ForeignKey [FK_TagItemMapping_User] Script Date: 05/25/2010 13:23:55 ******/
ALTER TABLE [dbo].[TagItemMapping] WITH CHECK ADD CONSTRAINT [FK_TagItemMapping_User] FOREIGN KEY([UserID])
REFERENCES [dbo].[User] ([ID])
GO
ALTER TABLE [dbo].[TagItemMapping] CHECK CONSTRAINT [FK_TagItemMapping_User]
GO
-- Populate data
INSERT INTO [Priority] ([Name]) VALUES ('Low')
INSERT INTO [Priority] ([Name]) VALUES ('Medium')
INSERT INTO [Priority] ([Name]) VALUES ('High')
GO
INSERT INTO [Status] ([Name]) VALUES ('Not Started')
INSERT INTO [Status] ([Name]) VALUES ('Planning')
INSERT INTO [Status] ([Name]) VALUES ('In Progress')
INSERT INTO [Status] ([Name]) VALUES ('Completed')
INSERT INTO [Status] ([Name]) VALUES ('Abandoned')
GO
INSERT INTO dbo.Tag (Name) VALUES ('ToDo')
INSERT INTO dbo.Tag (Name) VALUES ('Shopping')
INSERT INTO dbo.Tag (Name) VALUES ('Family')
INSERT INTO dbo.Tag (Name) VALUES ('Work')
INSERT INTO dbo.Tag (Name) VALUES ('Produce')
INSERT INTO dbo.Tag (Name) VALUES ('Groceries')
INSERT INTO dbo.Tag (Name) VALUES ('Clothing')
INSERT INTO dbo.Tag (Name) VALUES ('Entertainment')
INSERT INTO dbo.Tag (Name) VALUES ('Travel')
INSERT INTO dbo.Tag (Name) VALUES ('Vacation')
INSERT INTO dbo.Tag (Name) VALUES ('Tickets')
INSERT INTO dbo.Tag (Name) VALUES ('Restaurant')
INSERT INTO dbo.Tag (Name) VALUES ('Friends')
INSERT INTO dbo.Tag (Name) VALUES ('Homework')
INSERT INTO dbo.Tag (Name) VALUES ('Bills')
INSERT INTO dbo.Tag (Name) VALUES ('Mortgage')
Here is the database diagram for the listdb database:
Task 2: Provisioning the database
In this task you will provision the listdb database using the SyncSvcUtil.exe tool that is included in the zip package.
Make a copy of listdbconfig.xml file in the C:\Program Files\Microsoft SDKs\Microsoft Sync Framework\4.0\samples folder and name it as listdbazureconfig.xml.
Open listdbazureconfig.xml file in an editor, update the targetDatabse element to point to the SQL Database, and save the XML file. Here is an example of the update.
< Databases > < TargetDatabase Name="listdb" DbServer="azure server name" DbName="listdb" UserName="user with access to Azure DB" Password="password for the user" UseIntegratedAuth="false" /> </ Databases >
Note
After you logon to SQL Database, you should see the list of databases available on the server. Select listdb from list and then click Connection Strings button to see the correct format for connection string. Confirm that the server name and user name in configuration file are same as the ones displayed by the dialog box.
Open a Command Prompt window and switch to C:\Program Files\Microsoft SDKs\Microsoft Sync Framework\4.0\Samples\config folder. In the Command Prompt window, type the following command and then press ENTER.
..\..\bin\SyncSvcUtil /mode:provision /scopeconfig:listdbazureconfig.xml
The SyncSvcUtil tool will provision the listdb SQL Database with all sync related artifacts (tracking tables, etc…).
You will see the output similar to the following:
Reading specified config file... Generating DbSyncScopeDescription for scope DefaultScope... Provisioning Database ListSample for template scope DefaultScope... SyncSvcUtil completed with no errors.
Note
To provision the database using SyncSvcUtilHelper.exe, a UI tool built on top of SyncSvcUtil.exe, see Server Scope Provisioning or Deprovisioning. Using this UI tool, you can also generate a new or edit an existing sync configuration XML file. See Creating or Editing a Sync Configuration File for more details.
Confirm that the tracking tables are created in the listdb SQL Database.
Important
See http://msdn.microsoft.com/en-us/library/ee336282.aspx if you experience any connectivity issues.
Walkthrough: Creating a Sync Service in Windows Azure
In this walkthrough you will create a sync service that can be hosted in Windows Azure.
Prerequisites
You must have the following products/files installed on your computer to practice the walkthrough.
Visual Studio 2008 or Visual Studio 2010 with C# language components
Download and install the Windows Azure Tools for Microsoft Visual Studio from http://www.microsoft.com/windowsazure/windowsazuresdk/.
If you are using x86 Windows computer, install only x86 version (SyncSDK-v2.1-x86-ENU.msi) of Sync Framework 2.1 SDK.
The following two x86 redistribution packages of Sync Framework 2.1 from Sync Framework 2.1 Redistributable Packages.
Synchronization-v2.1-x86-ENU.msi
DatabaseProviders-v3.1-x86-ENU.msi
Note
In this tutorial, you will be hosting a Silverlight client application in the Visual Studio Developer Server, which requires 32-bit components of Sync Framework 2.1 to run the client successfully. If you are actually deploying and hosting the client to IIS or Windows Azure, you just need 64-bit components.
It is not possible have full 32-bit Sync Framework 2.1 SDK installed side-by-side with the 64-bit SDK; therefore you will have to install one version of SDK (64-bit) completely and only selected components of other version (32-bit) of SDK
Task 1: Generating code for sync service
In this task you will create code files for the service by using the SyncSvcUtil.exe tool
Open listdbazureconfig.xml file and confirm that the TargetDatabase attributes point to SQL Database.
<TargetDatabase Name="listsample" DbServer="<SQL Database Server>" DbName="listdb" UserName="<User Name>" Password="" UseIntegratedAuth="false" />
Note
After you logon to SQL Database, you should see the list of databases available on the server. Select listdb from list and then click Connection Strings button to see the correct format for connection string. Confirm that the server name and user name in configuration file are same as the ones displayed by the dialog box.
Open a Command Prompt window with administrative rights and switch to C:\Program Files\Microsoft SDKs\Microsoft Sync Framework\4.0\Samples\config folder. In the Command Prompt window, type the following command and then press ENTER. This command creates code files for the service by using the SyncSvcUtil.exe tool.
..\..\bin\SyncSvcUtil /mode:codegen /target:server /scopeconfig:listdbazureconfig.xml
Note
If your computer has the x64 version of Windows, use c:\Program Files(x86) folder wherever c:\Program Files is mentioned in this tutorial to access Sync Framework 4.0 October 2010 CTP components.
The tool creates three files: DefaultScopeEntities.cs, DefaultScopeSyncService.svc, DefaultScopeSyncService.svc.cs in the current folder (C:\Program Files\Microsoft SDKs\Microsoft Sync Framework\4.0\samples\Config). You will use these files later in Task 4 when you create a Visual Studio project. Here is the sample output from the previous command:
Reading specified config file... Generating DbSyncScopeDescription for scope DefaultScope... Generating files... SyncSvcUtil completed with no errors.
Note
You can also use the SyncSvcUtilHelper, a UI tool built on top of SyncSvcUtil command-line tool, to generate server side code. See Generating Code for a Server, Generic Client, or Isolated Store Client for more details.
Task 2: Creating a Visual Studio project
In this task, you will create a Visual Studio project for the sync service.
Open Microsoft Visual Studio 2008 with Administrator permissions: Click Start, point to All Programs, point to Microsoft Visual Studio 2008, right-click Microsoft Visual Studio 2008, and then click Run as Administrator.
Note
If you are using Microsoft Visual Studio 2010, use steps similar to preceding steps to launch Visual Studio 2010.
If the User Account Control dialog appears, click Continue.
From the File menu, click New and then click Project.
In the New Project dialog, expand Visual C# in the project types list and select Windows Azure Cloud Service.
Enter the Name ListService. Click OK to create the project.
In the New Cloud Service Project dialog that opens, select and double –click ASP.NET Web Role and then click OK.
In the Solution Explorer, right-click the WebRole1 project, point to Add, and then click Add Existing Item.
Navigate to C:\Program Files\Microsoft SDKs\Microsoft Sync Framework\4.0 \samples and select the 3 files (DefaultScopeEntities.cs, DefaultScopeSyncService.svc, DefaultScopeSyncService.svc.cs), and then click Add to add these files to the project.
Examine the Solution Explorer and make sure that the files appear in the project tree.
In the Solution Explorer, right-click the WebRole1 project, point to Add, and then click Add Reference.
In the Add Reference dialog box, click Browse tab, navigate to C C:\Program Files\Microsoft SDKs\Microsoft Sync Framework\4.0\server\Microsoft.Synchronization.Services.dll, and then click OK.
Repeat previous step to add references to Microsoft.Synchronization.dll, Microsoft.Synchronization.Data.dll, and Microsoft.Synchronization.Data.SqlServer.dll from Sync Framework 2.1 installation folder (ex: C:\Program Files (x86)\Microsoft SDKs\Microsoft Sync Framework\2.1\Runtime\x86 or C:\Program Files (x86)\Microsoft SDKs\Microsoft Sync Framework\2.1\Runtime\ADO.NET\V3.1\x86.).
Multiple-select the four files you added to References, right-click, and then click Properties.
In the Properties window, set the value of Aliases property to global and Copy Local property to True.
Create a source file named activationcontext.cs file with the following content and add the file to WebRole1 project.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; using System.Runtime.InteropServices; using System.IO; namespace Microsoft.Samples.Synchronization { public class ActivationContext { // Activation Context API Functions [DllImport("Kernel32.dll", SetLastError = true)] private extern static IntPtr CreateActCtx(ref ACTCTX actctx); // Activation context structure private struct ACTCTX { public int cbSize; public uint dwFlags; public string lpSource; public ushort wProcessorArchitecture; public ushort wLangId; public string lpAssemblyDirectory; public string lpResourceName; public string lpApplicationName; } private const int ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID = 0x004; private const int ACTCTX_FLAG_SET_PROCESS_DEFAULT = 0x00000010; private IntPtr m_hActCtx = (IntPtr)0; public const UInt32 ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET = 14011; /// <summary> /// Explicitly load a manifest and create the process-default /// activation context. It takes effect immediately and stays /// there until the process exits. /// </summary> static public void CreateActivationContext() { string rootFolder = AppDomain.CurrentDomain.BaseDirectory; string manifestPath = Path.Combine(rootFolder, "webapp.manifest"); UInt32 dwError = 0; // Build the activation context information structure ACTCTX info = new ACTCTX(); info.cbSize = Marshal.SizeOf(typeof(ACTCTX)); info.dwFlags = ACTCTX_FLAG_SET_PROCESS_DEFAULT; info.lpSource = manifestPath; if (null != rootFolder && "" != rootFolder) { info.lpAssemblyDirectory = rootFolder; info.dwFlags |= ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID; } dwError = 0; // Create the activation context IntPtr result = CreateActCtx(ref info); if (-1 == result.ToInt32()) { dwError = (UInt32)Marshal.GetLastWin32Error(); } if (-1 == result.ToInt32() && ActivationContext.ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET != dwError) { string err = string.Format("Cannot create process-default win32 sxs context, error={0} manifest={1}", dwError, manifestPath); ApplicationException ex = new ApplicationException(err); throw ex; } } } }
Right-click WebRole1 project, point to Add, and then click New Folder. Type synchronization.assembly for the name of the folder. Add the following five files to the folder.
Microsoft.Synchronization.dll
Microsoft.Synchronization.Data.dll
Microsoft.Synchronization.Data.SqlServer.dll
Synchronization21.dll
Create a file named synchronization.assembly.manifest, add the following content, and add the file to this folder.
<?xml version='1.0' encoding='UTF-8' standalone='yes'?> <assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'> <assemblyIdentity type="x64" name="synchronization.assembly" version="1.0.0.0" /> <file name = "synchronization21.dll"> <comClass clsid="{EC413D66-6221-4ebb-AC55-4900FB321011}" threadingModel="Both"/> </file> </assembly>
Multiple-select all files under synchronization.assembly folder, right-click, and then click Properties. Set the value of Build Action property to Content and Copy To Output Directory to Copy Always.
Create a file named webapp.manifest, add the following content, and add the file to WebRole1 project.
<?xml version='1.0' encoding='UTF-8' standalone='yes'?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assemblyIdentity name="webapp" version="8.0.0.0" type="x64" /> <dependency> <dependentAssembly> <assemblyIdentity name="synchronization.assembly" version="1.0.0.0" type="x64" /> </dependentAssembly> </dependency> </assembly>
Set the value of Build Action property to Content and Copy To Output Directory to Copy Always for the webapp.manifest file by using Properties window.
Add the following statement to the OnStart method before base.OnStart method call in the WebRole.cs file.
Microsoft.Samples.Synchronization.ActivationContext.CreateActivationContext();
Note
You will need to add clientaccesspolicy.xml file to the WebRole1 project if you want the WebRole1 to host the Silverlight application and set the Build Action property to Content and Copy to Output Directory to Copy Always using Properties window. Here is the content of a sample clientaccesspolicy.xml file.
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
<cross-domain-access>
<policy >
<allow-from http-request-headers="*">
<domain uri="*"/>
<domain uri="http://*"/>
<domain uri="file://*"/>
</allow-from>
<grant-to>
<resource path="/" include-subpaths="true"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
Task 3: Configuring and deploying the sync service
In this task, you will configure the service and build it.
In the Solution Explorer, expand the files under DefaultScopeSyncService.svc and then double-click DefaultScopeSyncService.svc.cs to open the file.
Uncomment the following lines in the InitializeService method.
config.ServerConnectionString = "connection string here"; config.SetEnableScope("scope name goes here");
Replace connection string here with the following:
Data Source=<SQLAzure_Server_Name>;Initial Catalog=listDb;User ID=username;Password=;Trusted_Connection=False;
Replace “scope name goes here” with DefaultScope.
Add the following lines of code to the InitializeService method.
// configure filter parameters used by the service config.AddFilterParameterConfiguration("userid", "User", "@ID", typeof(System.Guid)); config.AddFilterParameterConfiguration("userid", "Item", "@UserID", typeof(System.Guid)); config.AddFilterParameterConfiguration("userid", "List", "@UserID", typeof(System.Guid)); config.AddFilterParameterConfiguration("userid", "TagItemMapping", "@UserID", typeof(System.Guid)); // enable Diagnostic Dashboard feature for the service config.EnableDiagnosticPage = true; // enable verbose errors config.UseVerboseErrors = true;
From the Build menu, click Build Solution. This will build the solution.
If you have the Sync Framework installed on your computer, you can run this service on the Development Fabric at this point
Note
The table name (ex: User) and the parameter name (ex. @ID) correspond to the definitions in the listdbconfig.xml file that was used for provisioning the database.
To deploy the sync service to Windows Azure:
Comment the DiagnosticMonitor.Start method call in the OnStart method of WebRole class in the WebRole.cs file (or) change the DiagnosticConnectionString setting to use Azure storage: In Solution Explorer, expand ListService, expand Roles, right-click WebRole1, and then click Properties. Click … button to enter Azure storage credentials.
Right-click ListService, and then click Publish. You should see a folder opened in a Windows explorer window that contains two files: ListService.cspkg and ServiceConfiguration.cscfg.
Create a service on Windows Azure, upload these two files, and start the service.
Verify that the deployment was indeed successful.
Verify that the deployment was indeed successful.
Open a Web browser and navigate to http://[servicename].cloudapp.net/defaultscopeSyncService.svc/$syncscopes, and confirm that you see sync scopes available from the service.
Use the following syntax to see diagnostics information for the service: http://[servicename].cloudapp.net/defaultscopeSyncService.svc/$diag.
Walkthrough: Creating a Silverlight Offline Client Application
In this walkthrough, you will develop an offline-capable Silverlight application that consumes a sync Service.
Pre-Requisites
You must have the following products/components installed on your computer.
Visual Studio 2008 SP1 or Visual Studio 2010 with C# language components
Silverlight 3 Tools for Visual Studio 2008 or Silverlight 4 Tools for Visual Studio 2010
Task 1: Generating Client Code
In this task, you will generate code files for a Silverlight client using the SyncSvcUtil.exe tool.
Open a Command Prompt window with administrative rights, and switch to C:\Program Files\Microsoft SDKs\Microsoft Sync Framework\4.0\Samples\Config folder.
Note
If your computer has the x64 version of Windows, use c:\Program Files(x86) folder wherever c:\Program Files is mentioned in this tutorial to access Sync Framework 4.0 October 2010 CTP components.
At the command prompt, type the following command and then press ENTER.
..\..\bin\SyncSvcUtil /mode:codegen /target:isclient /scopeconfig:listdbazureconfig.xml
The tool will create two files: DefaultScopeEntities.cs and DefaultScopeOfflineContext.cs in C:\Program Files\Microsoft SDKs\Microsoft Sync Framework\4.0\Samples\Config folder.
Note
You can also use the SyncSvcUtilHelper, a UI tool built on top of SyncSvcUtil command-line tool, to generate the code that can be used to build an isolated store client. See Generating Code for a Server, Generic Client, or Isolated Store Client for more details.
These files will be used in the next task to create the Silverlight project.
Task 2: Creating a Visual Studio Silverlight Application
In this task, you will create a Visual Studio Silverlight project which will consume the sync service.
In the Solution Explorer window, right-click Solution ‘ListService’, point to Add, and then click New Project.
In the New Project dialog, expand Visual C# in the “Project types:” list and select the Silverlight project type.
Select Silverlight Application from the templates menu.
Type the name ListClient and then click OK to create the project.
Click OK on the New Silverlight Application dialog box
In the Solution Explorer, right-click ListClient, point to Add, and then click Add Existing Item.
Navigate to C:\Program Files\Microsoft SDKs\Microsoft Sync Framework\4.0\samples\Config and select the 2 files (DefaultScopeEntities.cs, DefaultScopeOfflineContext.cs) and then click Add.
Examine the Solution Explorer and make sure that that the files appear in the project tree.
In the Solution Explorer, right-click ListClient project, point to Add, and then click Add Reference.
In the Add Reference dialog box, go to the Browse tab and navigate to C:\Program Files\Microsoft SDKs\Microsoft Sync Framework\4.0\client\Silverlight.
Note
To develop Silverlight client application for Windows Phone 7, use the WP7 subfolder of C:\Program Files\Microsoft SDKs\Microsoft Sync Framework\4.0\client\WP7 folder.
Select Microsoft.Synchronization.ClientServices.dll and then click OK.
Follow Step 11 to bring up the Add Reference dialog box again.
Switch to the .NET tab.
Select System.ComponentModel.DataAnnotations and System.Windows.Controls.Data, and then click OK.
At this point, the project should compile successfully. On the Build menu, click Build Solution.
Task 3: Building the application
In this task, you will write code in the project to be able to consume the Tag data from the server (other data requires that a user id to be specified as a filter).
In the Solution Explorer, expand MainPage.xaml and double-click MainPage.xaml.cs to open the file.
At the beginning of the file, add the following statements.
using DefaultScope; using Microsoft.Synchronization.ClientServices.IsolatedStorage;
Inside the MainPage class, create a new variable of type DefaultScopeOfflineContext and name it context.
DefaultScopeOfflineContext context;
In the constructor of the MainPage class, add a line of code to create the context above the InitializeComponent method call.
context = new DefaultScopeOfflineContext("list_client", new Uri(new Uri(new WebClient().BaseAddress), "../DefaultScopeSyncService.svc/"));
The first parameter is the relative directory within isolated storage where the offline data should be cached.
The second parameter specifies the address of the service. Example: http://localhost:65338/DefaultScopeSyncService.svc.
As the scope on the server is filtered by user id, we must specify the parameter here as described in the following list.
Add the following line of code to the constructor of MainPage class above the InitializeComponent statement.
context.CacheController.ControllerBehavior.AddScopeParameters("userid", new Guid("{EE9F0661-AD72-4b0b-8627-73722520A41B}").ToString());
On the Tools menu, click Create Guid.
Select the Registry Format radio button and then click Copy and then click Exit.
Paste the GUID in the clipboard to replace the GUID in the above AddScopeParameters method call.
Important
The tag data is not filtered by user id. Therefore, the GUID we pass here does not matter. For other tables in the database, the user id must match one in the User table.
Now we need to display the data. In the Solution Explorer, double-click on MainPage.xaml in the ListClient project.
Add two rows to the LayoutRoot grid: Add the following lines of code in between <Grid x:Name="LayoutRoot"> and </Grid>.
<Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions>
In the first row, you will place a DataGrid that displays the tags. In the second row, you will place a button to start synchronization
Expand the Toolbox tab and drag a DataGrid into the XML markup. Make sure that it is after Grid.RowDefinitions closing tag, but before the Grid closing tag. Then follow these steps for the data grid:
Add an xml property of x:Name to the DataGrid element and make the value TagsGrid.
Set its Grid.Row property to 0.
Set IsReadOnly to False because we want to be able to modify the tags.
After this, the data grid should be specified as follows (also removing the closing tag and changing the initial tag to end in “/>”:
<data:DataGrid x:Name="TagsGrid" Grid.Row="0" IsReadOnly="False" />
Important
If you are typing in the DataGrid specification by hand, you also have to add the namespace for the DataGrid to the top-level UserControl declaration (dragging from the Toolbox does this automatically). The namespace is as follows:
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
Add a Button to the MainPage.xaml file after the DataGrid, specified as follows:
<Button Content="Sync" Grid.Row="1" HorizontalAlignment="Center"/>
Add an event handler for the button. In order to do so, place the cursor before the end of the tag and type Click=. At this point Visual Studio should display a dropdown. Hit the TAB button to auto-generate a new event handler. Your button specification should look as follows:
<Button Content="Sync" Click="Button_Click" Grid.Row="1" HorizontalAlignment="Center"/>
Now the editing of the [[XAML]] is complete and we are ready to write the code. The XAML file should look as follows:
<UserControl xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="ListClient.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"> <Grid x:Name="LayoutRoot"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <data:DataGrid x:Name="TagsGrid" Grid.Row="0" IsReadOnly="False" /> <Button Content="Sync" Click="Button_Click" Grid.Row="1" HorizontalAlignment="Center"/> </Grid> </UserControl>
Return to the MainPage.xaml.cs page.
In the constructor, add an event handler for the DefaultScopeOfflineContext.LoadCompleted event in the line after you assign the context variable:
context.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(context_LoadCompleted);
Following that line, add an event handler for the MainPage.Loaded event:
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
Your MainPage constructor should now look as follows:
public MainPage() { context = new DefaultScopeOfflineContext("list_client", new Uri(new Uri(new WebClient().BaseAddress), "../DefaultScopeSyncService.svc/")); context.CacheController.ControllerBehavior.AddScopeParameters("userid", new Guid("{EE9F0661-AD72-4b0b-8627-73722520A41B}").ToString()); context.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(context_LoadCompleted); this.Loaded += new RoutedEventHandler(MainPage_Loaded); InitializeComponent(); }
Following the MainPage constructor, create the MainPage_Loaded method (if it wasn’t generated by Visual Studio) to handle the MainPage.Loaded event.
In this method, load the context. This is so that we can put the data into the DataGrid. Since we are loading on startup, we don’t want it to block the UI. Therefore, we call the DefaultScopeOfflineContext.LoadAsync method. The MainPage_Loaded method should look as follows:
void MainPage_Loaded(object sender, RoutedEventArgs e) { context.LoadAsync(); }
We then implement the context_LoadCompleted event, which will be responsible for getting the data from the context and assigning it to the DataGrid. This is accomplished rather simply, by getting the desired collection from the context, in our case Tags, and assigning it to the DataGrid ItemsSource.
One important note about doing this is that the context_LoadCompleted event may be called on a thread that is not the UI thread, which will cause an exception if we try to modify a UI element. As a result, you must use the Dispatcher.BeginInvoke to make sure that the work is done on the UI thread. Because of the simplicity of the code for this, we will use an anonymous delegate to update the data. The code for the method is as follows:
void context_LoadCompleted(object sender, LoadCompletedEventArgs e) { Dispatcher.BeginInvoke(delegate { TagsGrid.ItemsSource = context.TagCollection; }); }
Add the following two statements to the Button_Click method. The first statement saves any changes you made in the data grid and the second statement initiates the synchronization process.
// saves any outstanding changes made by the application context.SaveChanges(); // refreshes the cache by uploading all modified changes // and then by downloading the server changes context.CacheController.RefreshAsync();
At this point, the code for the application is complete. The MainPage.xaml.cs file should look as follows:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using DefaultScope; using Microsoft.Synchronization.ClientServices.IsolatedStorage; namespace ListClient { public partial class MainPage : UserControl { DefaultScopeOfflineContext context; public MainPage() { // create an instance of offline context type // the code for this class is generated by // the SyncSvcUtil utility. context = new DefaultScopeOfflineContext("list_client", new Uri(new Uri(new WebClient().BaseAddress), "../DefaultScopeSyncService.svc/")); // specify a value for the userid parameter for scope context.CacheController.ControllerBehavior.AddScopeParameters("userid", new Guid("{832106DC-2532-4f7a-9E6D-050383957D38}").ToString()); // subscribe for the context.LoadCompleted event context.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(context_LoadCompleted); // subscribe for the Page.Loaded event this.Loaded += new RoutedEventHandler(MainPage_Loaded); // subscribe for the RefreshCompleted event context.CacheController.RefreshCompleted += (sender, args) => { if (args.Error != null) { Dispatcher.BeginInvoke( delegate { // display any error occurred during refresh // operation in a message box MessageBox.Show(args.Error.ToString()); }); } }; InitializeComponent(); } void MainPage_Loaded(object sender, RoutedEventArgs e) { // when the page is loaded, invoke LoadAsync on the context // object to load data from the cache into memory // asynchronously. This method raises the LoadCompleted event // at the end of load operation. context.LoadAsync(); } void context_LoadCompleted(object sender, LoadCompletedEventArgs e) { // bind the TagCollection object of context to ItemsSource // to TagsGrid Dispatcher.BeginInvoke(delegate { TagsGrid.ItemsSource = context.TagCollection; }); } private void Button_Click(object sender, RoutedEventArgs e) { // saves any outstanding changes made by the application context.SaveChanges(); // refreshes the cache by uploading all modified changes // and then by downloading the server changes context.CacheController.RefreshAsync(); } } }
From the Build menu, click Build Solution to the build the solution.
Make sure that the ListService project is set as the Startup project for the solution.
In the Solution Explorer, right-click Solution ‘ListService’ (2 projects), and then click Set Startup Projects.
Confirm that Single startup project option is selected and the ListService is specified as the startup project. If this is not the case, select the Single startup project option and select ListService project from the list.
Click OK to close the Solution ‘ListService’ Property Pages dialog box.
Caution
If you do not set the ListService project as the startup project, you will see the following error when you perform the next step:
“An unhandled exception (‘Unhandled Error in Silverlight Application Uri must be http or https schema Parameter name: serviceUri at ….”
In the ListService project, right-click ListClientTestPage.html, and then click Set As Start Page.
Press F5 or Ctrl+F5 to run the code. You should see an Internet Explorer window that opens html page.
Now, click Sync to see something similar to the following:
If you click Sync button and no data appears, you can register an event handler to discover the error. One example is as follows (if you add these lines to the constructor):
context.CacheController.RefreshCompleted += (sender, args) => { if (args.Error != null) { Dispatcher.BeginInvoke( delegate { MessageBox.Show(args.Error.ToString()); }); } };
Optional steps
Add couple of rows to the Tag table in the listdb database and then click Sync to see the new data that you added to the table in the list.
Delete a row you added in the earlier step and then click Sync to see that deleted row disappears from the list.
Update a row you added in the first step and then click Sync to see that data is also updated in the list.
Click on Mortgage in the list and change it to something else, and then click Sync. Confirm that the value is uploaded to the listdb SQL Server database by using SQL Server Management Studio.