diff --git a/samples/datasync-server/Sample.Datasync.Server.sln b/samples/datasync-server/Sample.Datasync.Server.sln
index 11ceb1d..bf831c0 100644
--- a/samples/datasync-server/Sample.Datasync.Server.sln
+++ b/samples/datasync-server/Sample.Datasync.Server.sln
@@ -9,11 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Datasync.Server", "s
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "library", "library", "{95358590-6440-469A-8A6A-6ACC47F52966}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.Server", "..\..\..\..\CommunityToolkit\Datasync\src\CommunityToolkit.Datasync.Server\CommunityToolkit.Datasync.Server.csproj", "{D42DBA41-28AA-4B6D-83A4-A5B839EC8B25}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.Server.Abstractions", "..\..\src\CommunityToolkit.Datasync.Server.Abstractions\CommunityToolkit.Datasync.Server.Abstractions.csproj", "{54E9A0B2-A0B0-4CB1-8FAD-11DB9E4535A6}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.Server.Abstractions", "..\..\..\..\CommunityToolkit\Datasync\src\CommunityToolkit.Datasync.Server.Abstractions\CommunityToolkit.Datasync.Server.Abstractions.csproj", "{CECB1B21-46C7-4197-B8D2-EC54D0AF8145}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.Server", "..\..\src\CommunityToolkit.Datasync.Server\CommunityToolkit.Datasync.Server.csproj", "{DEC37ED1-B52A-4287-8F63-8210328AFF70}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.Server.EntityFrameworkCore", "..\..\..\..\CommunityToolkit\Datasync\src\CommunityToolkit.Datasync.Server.EntityFrameworkCore\CommunityToolkit.Datasync.Server.EntityFrameworkCore.csproj", "{8CDD3736-4B8D-43F5-8F9A-222210D73DAC}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.Server.EntityFrameworkCore", "..\..\src\CommunityToolkit.Datasync.Server.EntityFrameworkCore\CommunityToolkit.Datasync.Server.EntityFrameworkCore.csproj", "{2086DD5C-C7C1-4957-B667-847C5FEE832C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -25,26 +25,26 @@ Global
{67A76156-0033-4085-86BE-558DC28688A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67A76156-0033-4085-86BE-558DC28688A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67A76156-0033-4085-86BE-558DC28688A6}.Release|Any CPU.Build.0 = Release|Any CPU
- {D42DBA41-28AA-4B6D-83A4-A5B839EC8B25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D42DBA41-28AA-4B6D-83A4-A5B839EC8B25}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D42DBA41-28AA-4B6D-83A4-A5B839EC8B25}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D42DBA41-28AA-4B6D-83A4-A5B839EC8B25}.Release|Any CPU.Build.0 = Release|Any CPU
- {CECB1B21-46C7-4197-B8D2-EC54D0AF8145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CECB1B21-46C7-4197-B8D2-EC54D0AF8145}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CECB1B21-46C7-4197-B8D2-EC54D0AF8145}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CECB1B21-46C7-4197-B8D2-EC54D0AF8145}.Release|Any CPU.Build.0 = Release|Any CPU
- {8CDD3736-4B8D-43F5-8F9A-222210D73DAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8CDD3736-4B8D-43F5-8F9A-222210D73DAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8CDD3736-4B8D-43F5-8F9A-222210D73DAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8CDD3736-4B8D-43F5-8F9A-222210D73DAC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {54E9A0B2-A0B0-4CB1-8FAD-11DB9E4535A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {54E9A0B2-A0B0-4CB1-8FAD-11DB9E4535A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {54E9A0B2-A0B0-4CB1-8FAD-11DB9E4535A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {54E9A0B2-A0B0-4CB1-8FAD-11DB9E4535A6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DEC37ED1-B52A-4287-8F63-8210328AFF70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DEC37ED1-B52A-4287-8F63-8210328AFF70}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DEC37ED1-B52A-4287-8F63-8210328AFF70}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DEC37ED1-B52A-4287-8F63-8210328AFF70}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2086DD5C-C7C1-4957-B667-847C5FEE832C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2086DD5C-C7C1-4957-B667-847C5FEE832C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2086DD5C-C7C1-4957-B667-847C5FEE832C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2086DD5C-C7C1-4957-B667-847C5FEE832C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{67A76156-0033-4085-86BE-558DC28688A6} = {4B8E4DD5-C2CA-4729-A10B-8A3C993018EC}
- {D42DBA41-28AA-4B6D-83A4-A5B839EC8B25} = {95358590-6440-469A-8A6A-6ACC47F52966}
- {CECB1B21-46C7-4197-B8D2-EC54D0AF8145} = {95358590-6440-469A-8A6A-6ACC47F52966}
- {8CDD3736-4B8D-43F5-8F9A-222210D73DAC} = {95358590-6440-469A-8A6A-6ACC47F52966}
+ {54E9A0B2-A0B0-4CB1-8FAD-11DB9E4535A6} = {95358590-6440-469A-8A6A-6ACC47F52966}
+ {DEC37ED1-B52A-4287-8F63-8210328AFF70} = {95358590-6440-469A-8A6A-6ACC47F52966}
+ {2086DD5C-C7C1-4957-B667-847C5FEE832C} = {95358590-6440-469A-8A6A-6ACC47F52966}
EndGlobalSection
EndGlobal
diff --git a/samples/datasync-server/src/Sample.Datasync.Server/Db/TodoItem.cs b/samples/datasync-server/src/Sample.Datasync.Server/Db/TodoItem.cs
index afd02d6..c320645 100644
--- a/samples/datasync-server/src/Sample.Datasync.Server/Db/TodoItem.cs
+++ b/samples/datasync-server/src/Sample.Datasync.Server/Db/TodoItem.cs
@@ -10,7 +10,7 @@ namespace Sample.Datasync.Server.Db;
public class TodoItem : EntityTableData
{
[Required, MinLength(1)]
- public string Text { get; set; } = string.Empty;
+ public string Title { get; set; } = string.Empty;
public bool IsComplete { get; set; }
}
diff --git a/samples/datasync-server/src/Sample.Datasync.Server/Sample.Datasync.Server.csproj b/samples/datasync-server/src/Sample.Datasync.Server/Sample.Datasync.Server.csproj
index 5d9fbd5..8287f2b 100644
--- a/samples/datasync-server/src/Sample.Datasync.Server/Sample.Datasync.Server.csproj
+++ b/samples/datasync-server/src/Sample.Datasync.Server/Sample.Datasync.Server.csproj
@@ -22,9 +22,9 @@
-
-
-
+
+
+
diff --git a/samples/todoapp/Samples.TodoApp.sln b/samples/todoapp/Samples.TodoApp.sln
index 3762275..5692d55 100644
--- a/samples/todoapp/Samples.TodoApp.sln
+++ b/samples/todoapp/Samples.TodoApp.sln
@@ -9,6 +9,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoApp.WinUI3", "TodoApp.W
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.Client", "..\..\src\CommunityToolkit.Datasync.Client\CommunityToolkit.Datasync.Client.csproj", "{2AC73FBE-9E76-4702-B551-B5884383CC68}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoApp.WPF", "TodoApp.WPF\TodoApp.WPF.csproj", "{410D4BBD-5ED7-4BC0-A2CF-547A4784732F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Datasync.Server", "..\datasync-server\src\Sample.Datasync.Server\Sample.Datasync.Server.csproj", "{E67734DD-B397-4A65-AA50-D62F37EF05DD}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -61,6 +65,38 @@ Global
{2AC73FBE-9E76-4702-B551-B5884383CC68}.Release|x64.Build.0 = Release|Any CPU
{2AC73FBE-9E76-4702-B551-B5884383CC68}.Release|x86.ActiveCfg = Release|Any CPU
{2AC73FBE-9E76-4702-B551-B5884383CC68}.Release|x86.Build.0 = Release|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Debug|x64.Build.0 = Debug|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Debug|x86.Build.0 = Debug|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Release|ARM64.Build.0 = Release|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Release|x64.ActiveCfg = Release|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Release|x64.Build.0 = Release|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Release|x86.ActiveCfg = Release|Any CPU
+ {410D4BBD-5ED7-4BC0-A2CF-547A4784732F}.Release|x86.Build.0 = Release|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Debug|x64.Build.0 = Debug|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Debug|x86.Build.0 = Debug|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Release|ARM64.Build.0 = Release|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Release|x64.ActiveCfg = Release|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Release|x64.Build.0 = Release|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Release|x86.ActiveCfg = Release|Any CPU
+ {E67734DD-B397-4A65-AA50-D62F37EF05DD}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/samples/todoapp/TodoApp.WPF/App.xaml b/samples/todoapp/TodoApp.WPF/App.xaml
new file mode 100644
index 0000000..b4f4840
--- /dev/null
+++ b/samples/todoapp/TodoApp.WPF/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/samples/todoapp/TodoApp.WPF/App.xaml.cs b/samples/todoapp/TodoApp.WPF/App.xaml.cs
new file mode 100644
index 0000000..3bec578
--- /dev/null
+++ b/samples/todoapp/TodoApp.WPF/App.xaml.cs
@@ -0,0 +1,85 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Data.Sqlite;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using System.Windows;
+using TodoApp.WPF.Database;
+using TodoApp.WPF.ViewModels;
+
+namespace TodoApp.WPF;
+
+///
+/// Interaction logic for App.xaml
+///
+public partial class App : Application, IDisposable
+{
+ private readonly SqliteConnection dbConnection;
+
+ ///
+ /// The IoC service provider
+ ///
+ public IServiceProvider Services { get; }
+
+ public App()
+ {
+ // Create the connection to the SQLite database
+ this.dbConnection = new SqliteConnection("Data Source=:memory:");
+ this.dbConnection.Open();
+
+ // Create the IoC Services provider.
+ Services = new ServiceCollection()
+ .AddTransient()
+ .AddScoped()
+ .AddDbContext(options => options.UseSqlite(this.dbConnection))
+ .BuildServiceProvider();
+
+ // Initialize the database
+ InitializeDatabase();
+ }
+
+ private void InitializeDatabase()
+ {
+ // using IServiceScope scope = Ioc.Default.CreateScope();
+ using IServiceScope scope = Services.CreateScope();
+ IDbInitializer initializer = scope.ServiceProvider.GetRequiredService();
+ initializer.Initialize();
+ }
+
+ ///
+ /// A helper method for getting a service from the services collection.
+ ///
+ ///
+ /// You can see this in action in the class.
+ ///
+ /// The type of the service.
+ /// An instance of the service
+ public static TService GetRequiredService() where TService : notnull
+ => ((App)App.Current).Services.GetRequiredService();
+
+ #region IDisposable
+ private bool hasDisposed;
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!this.hasDisposed)
+ {
+ if (disposing)
+ {
+ this.dbConnection.Close();
+ }
+
+ this.hasDisposed = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ #endregion
+}
+
diff --git a/samples/todoapp/TodoApp.WPF/AssemblyInfo.cs b/samples/todoapp/TodoApp.WPF/AssemblyInfo.cs
new file mode 100644
index 0000000..76e9b08
--- /dev/null
+++ b/samples/todoapp/TodoApp.WPF/AssemblyInfo.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/samples/todoapp/TodoApp.WPF/Database/AppDbContext.cs b/samples/todoapp/TodoApp.WPF/Database/AppDbContext.cs
new file mode 100644
index 0000000..dfd5c46
--- /dev/null
+++ b/samples/todoapp/TodoApp.WPF/Database/AppDbContext.cs
@@ -0,0 +1,57 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Datasync.Client.Http;
+using CommunityToolkit.Datasync.Client.Offline;
+using Microsoft.EntityFrameworkCore;
+using TodoApp.WPF.Services;
+
+namespace TodoApp.WPF.Database;
+
+public class AppDbContext(DbContextOptions options) : DbContext(options)
+{
+ public DbSet TodoItems => Set();
+
+ // protected override void OnDatasyncInitialization(DatasyncOfflineOptionsBuilder optionsBuilder)
+ // {
+ // HttpClientOptions clientOptions = new()
+ // {
+ // Endpoint = new Uri("https://Y.azurewebsites.net/"),
+ // HttpPipeline = [new LoggingHandler()]
+ // };
+ // _ = optionsBuilder.UseHttpClientOptions(clientOptions);
+ // }
+
+ public async Task SynchronizeAsync(CancellationToken cancellationToken = default)
+ {
+ // PushResult pushResult = await this.PushAsync(cancellationToken);
+ // if (!pushResult.IsSuccessful)
+ // {
+ // throw new ApplicationException($"Push failed: {pushResult.FailedRequests.FirstOrDefault().Value.ReasonPhrase}");
+ // }
+
+ // PullResult pullResult = await this.PullAsync(cancellationToken);
+ // if (!pullResult.IsSuccessful)
+ // {
+ // throw new ApplicationException($"Pull failed: {pullResult.FailedRequests.FirstOrDefault().Value.ReasonPhrase}");
+ // }
+ }
+}
+
+///
+/// Use this class to initialize the database. In this sample, we just create
+/// the database using . However, you
+/// may want to use migrations.
+///
+/// The context for the database.
+public class DbContextInitializer(AppDbContext context) : IDbInitializer
+{
+ ///
+ public void Initialize()
+ => context.Database.EnsureCreated();
+
+ ///
+ public Task InitializeAsync(CancellationToken cancellationToken = default)
+ => context.Database.EnsureCreatedAsync(cancellationToken);
+}
diff --git a/samples/todoapp/TodoApp.WPF/Database/IDbInitializer.cs b/samples/todoapp/TodoApp.WPF/Database/IDbInitializer.cs
new file mode 100644
index 0000000..5be08ff
--- /dev/null
+++ b/samples/todoapp/TodoApp.WPF/Database/IDbInitializer.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace TodoApp.WPF.Database;
+
+///
+/// An interface to initialize a database.
+///
+public interface IDbInitializer
+{
+ ///
+ /// Synchronously initialize the database.
+ ///
+ void Initialize();
+
+ ///
+ /// Asynchronously initialize the database.
+ ///
+ /// A to observe.
+ /// A task that resolves when complete.
+ Task InitializeAsync(CancellationToken cancellationToken = default);
+}
diff --git a/samples/todoapp/TodoApp.WPF/Database/OfflineClientEntity.cs b/samples/todoapp/TodoApp.WPF/Database/OfflineClientEntity.cs
new file mode 100644
index 0000000..bcf2223
--- /dev/null
+++ b/samples/todoapp/TodoApp.WPF/Database/OfflineClientEntity.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ComponentModel.DataAnnotations;
+
+namespace TodoApp.WPF.Database;
+
+///
+/// An abstract class for working with offline entities.
+///
+public abstract class OfflineClientEntity
+{
+ [Key]
+ public string Id { get; set; } = Guid.NewGuid().ToString("N");
+ public DateTimeOffset? UpdatedAt { get; set; }
+ public string? Version { get; set; }
+ public bool Deleted { get; set; }
+}
diff --git a/samples/todoapp/TodoApp.WPF/Database/TodoItem.cs b/samples/todoapp/TodoApp.WPF/Database/TodoItem.cs
new file mode 100644
index 0000000..1205316
--- /dev/null
+++ b/samples/todoapp/TodoApp.WPF/Database/TodoItem.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text.Json;
+
+namespace TodoApp.WPF.Database;
+
+public class TodoItem : OfflineClientEntity
+{
+ public string Title { get; set; } = string.Empty;
+ public bool IsComplete { get; set; } = false;
+
+ public override string ToString()
+ => JsonSerializer.Serialize(this);
+}
\ No newline at end of file
diff --git a/samples/todoapp/TodoApp.WPF/Images/AddItem.png b/samples/todoapp/TodoApp.WPF/Images/AddItem.png
new file mode 100644
index 0000000..3366479
Binary files /dev/null and b/samples/todoapp/TodoApp.WPF/Images/AddItem.png differ
diff --git a/samples/todoapp/TodoApp.WPF/Images/RefreshItems.png b/samples/todoapp/TodoApp.WPF/Images/RefreshItems.png
new file mode 100644
index 0000000..51748ed
Binary files /dev/null and b/samples/todoapp/TodoApp.WPF/Images/RefreshItems.png differ
diff --git a/samples/todoapp/TodoApp.WPF/MainWindow.xaml b/samples/todoapp/TodoApp.WPF/MainWindow.xaml
new file mode 100644
index 0000000..cfcdeca
--- /dev/null
+++ b/samples/todoapp/TodoApp.WPF/MainWindow.xaml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/todoapp/TodoApp.WPF/MainWindow.xaml.cs b/samples/todoapp/TodoApp.WPF/MainWindow.xaml.cs
new file mode 100644
index 0000000..b60bdbf
--- /dev/null
+++ b/samples/todoapp/TodoApp.WPF/MainWindow.xaml.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows;
+using System.Windows.Input;
+using TodoApp.WPF.ViewModels;
+
+namespace TodoApp.WPF;
+///
+/// Interaction logic for MainWindow.xaml
+///
+public partial class MainWindow : Window
+{
+ public MainWindow()
+ {
+ InitializeComponent();
+ DataContext = App.GetRequiredService();
+ }
+
+ protected async void TextboxKeyDownHandler(object sender, KeyEventArgs e)
+ {
+ if (e.Key is Key.Return or Key.Enter)
+ {
+ await ((TodoListViewModel)DataContext).AddItemAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/todoapp/TodoApp.WPF/Services/LoggingHandler.cs b/samples/todoapp/TodoApp.WPF/Services/LoggingHandler.cs
new file mode 100644
index 0000000..c9c40bb
--- /dev/null
+++ b/samples/todoapp/TodoApp.WPF/Services/LoggingHandler.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Net.Http;
+
+namespace TodoApp.WPF.Services;
+
+///
+/// A delegating handler that logs the request/response to stdout.
+///
+public class LoggingHandler : DelegatingHandler
+{
+ public LoggingHandler() : base()
+ {
+ }
+
+ public LoggingHandler(HttpMessageHandler innerHandler) : base(innerHandler)
+ {
+ }
+
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ Debug.WriteLine($"[HTTP] >>> {request.Method} {request.RequestUri}");
+ await WriteContentAsync(request.Content, cancellationToken);
+
+ HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
+
+ Debug.WriteLine($"[HTTP] <<< {response.StatusCode} {response.ReasonPhrase}");
+ await WriteContentAsync(response.Content, cancellationToken);
+
+ return response;
+ }
+
+ private static async Task WriteContentAsync(HttpContent? content, CancellationToken cancellationToken = default)
+ {
+ if (content != null)
+ {
+ Debug.WriteLine($"[HTTP] >>> {await content.ReadAsStringAsync(cancellationToken)}");
+ }
+ }
+}
diff --git a/samples/todoapp/TodoApp.WPF/TodoApp.WPF.csproj b/samples/todoapp/TodoApp.WPF/TodoApp.WPF.csproj
new file mode 100644
index 0000000..18a5930
--- /dev/null
+++ b/samples/todoapp/TodoApp.WPF/TodoApp.WPF.csproj
@@ -0,0 +1,31 @@
+
+
+
+ WinExe
+ net8.0-windows
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/todoapp/TodoApp.WPF/ViewModels/NotificationEventArgs.cs b/samples/todoapp/TodoApp.WPF/ViewModels/NotificationEventArgs.cs
new file mode 100644
index 0000000..6d1f93d
--- /dev/null
+++ b/samples/todoapp/TodoApp.WPF/ViewModels/NotificationEventArgs.cs
@@ -0,0 +1,9 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace TodoApp.WPF.ViewModels;
+
+internal record NotificationEventArgs(string Title, string Message, bool IsError)
+{
+}
diff --git a/samples/todoapp/TodoApp.WPF/ViewModels/TodoListViewModel.cs b/samples/todoapp/TodoApp.WPF/ViewModels/TodoListViewModel.cs
new file mode 100644
index 0000000..1acb756
--- /dev/null
+++ b/samples/todoapp/TodoApp.WPF/ViewModels/TodoListViewModel.cs
@@ -0,0 +1,120 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Datasync.Client;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Microsoft.EntityFrameworkCore;
+using TodoApp.WPF.Database;
+
+namespace TodoApp.WPF.ViewModels;
+
+///
+/// The view model for the TodoListWindow.
+///
+public partial class TodoListViewModel(AppDbContext service) : ObservableRecipient
+{
+ internal event EventHandler? NotificationHandler;
+
+ [ObservableProperty]
+ private bool isRefreshing = false;
+
+ [ObservableProperty]
+ private ConcurrentObservableCollection items = new([.. service.TodoItems]);
+
+ [ObservableProperty]
+ private string title = string.Empty;
+
+ [RelayCommand]
+ public async Task AddItemAsync(CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ // Create a new item
+ TodoItem addition = new()
+ {
+ Id = Guid.NewGuid().ToString("N"),
+ Title = Title
+ };
+
+ // Add te item to the database
+ _ = service.TodoItems.Add(addition);
+ _ = await service.SaveChangesAsync(cancellationToken);
+
+ // Add the item to the end of the list.
+ Items.Add(addition);
+
+ // Update the title field ready for ext insertion.
+ Title = string.Empty;
+ }
+ catch (Exception ex)
+ {
+ NotificationHandler?.Invoke(this, new NotificationEventArgs(ex.GetType().Name, ex.Message, true));
+ }
+ finally
+ {
+ NotificationHandler?.Invoke(this, new NotificationEventArgs("Item Added", "", false));
+ }
+ }
+
+ [RelayCommand]
+ public async Task EditItemAsync(string itemId, CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ // Retrieve the item (by ID) from the service.
+ TodoItem item = await service.TodoItems.FindAsync([itemId], cancellationToken)
+ ?? throw new ApplicationException($"Item with ID '{itemId}' not found.");
+
+ // Update the item in the database
+ item.IsComplete = !item.IsComplete;
+ _ = service.TodoItems.Update(item);
+ _ = await service.SaveChangesAsync(cancellationToken);
+
+ // Update the item in the list
+ _ = Items.ReplaceIf(x => x.Id == itemId, item);
+ }
+ catch (Exception ex)
+ {
+ NotificationHandler?.Invoke(this, new NotificationEventArgs(ex.GetType().Name, ex.Message, true));
+ }
+ finally
+ {
+ NotificationHandler?.Invoke(this, new NotificationEventArgs("Item Updated", "", false));
+ }
+ }
+
+ [RelayCommand]
+ public async Task LoadPageAsync(CancellationToken cancellationToken = default)
+ {
+ await RefreshItemsAsync(cancellationToken);
+ }
+
+ [RelayCommand]
+ public async Task RefreshItemsAsync(CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ IsRefreshing = true;
+
+ // Synchronize data with the remote service (if any).
+ await service.SynchronizeAsync(cancellationToken);
+
+ // Pull all items from the database.
+ IEnumerable itemsFromDatabase = await service.TodoItems.ToListAsync(cancellationToken);
+
+ // Replace all the items in the collection.
+ Items.ReplaceAll(itemsFromDatabase);
+ }
+ catch (Exception ex)
+ {
+ NotificationHandler?.Invoke(this, new NotificationEventArgs(ex.GetType().Name, ex.Message, true));
+ }
+ finally
+ {
+ IsRefreshing = false;
+ NotificationHandler?.Invoke(this, new NotificationEventArgs("Items Refreshed", "", false));
+ }
+ }
+}
diff --git a/samples/todoapp/TodoApp.WinUI3/App.xaml.cs b/samples/todoapp/TodoApp.WinUI3/App.xaml.cs
index 819c2a0..ee40d03 100644
--- a/samples/todoapp/TodoApp.WinUI3/App.xaml.cs
+++ b/samples/todoapp/TodoApp.WinUI3/App.xaml.cs
@@ -21,6 +21,8 @@ public partial class App : Application, IDisposable
private Frame m_frame;
private readonly SqliteConnection dbConnection;
+ public IServiceProvider Services { get; }
+
public App()
{
InitializeComponent();
@@ -28,19 +30,23 @@ public App()
this.dbConnection = new SqliteConnection("Data Source=:memory:");
this.dbConnection.Open();
- IServiceCollection services = new ServiceCollection()
+ // Create the IoC Services provider.
+ Services = new ServiceCollection()
.AddTransient()
.AddScoped()
- .AddDbContext(options => options.UseSqlite(this.dbConnection));
+ .AddDbContext(options => options.UseSqlite(this.dbConnection))
+ .BuildServiceProvider();
- Ioc.Default.ConfigureServices(services.BuildServiceProvider());
+ // Initialize the database using the registered database initializer.
+ InitializeDatabase();
+ }
- // Initialize the database
- using (IServiceScope scope = Ioc.Default.CreateScope())
- {
- IDbInitializer initializer = scope.ServiceProvider.GetRequiredService();
- initializer.Initialize();
- }
+ private void InitializeDatabase()
+ {
+ // using IServiceScope scope = Ioc.Default.CreateScope();
+ using IServiceScope scope = Services.CreateScope();
+ IDbInitializer initializer = scope.ServiceProvider.GetRequiredService();
+ initializer.Initialize();
}
protected override void OnLaunched(LaunchActivatedEventArgs args)
@@ -52,6 +58,17 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
_ = this.m_frame.Navigate(typeof(TodoListPage));
}
+ ///
+ /// A helper method for getting a service from the services collection.
+ ///
+ ///
+ /// You can see this in action in the class.
+ ///
+ /// The type of the service.
+ /// An instance of the service
+ public static TService GetRequiredService()
+ => ((App)App.Current).Services.GetRequiredService();
+
#region IDisposable
private bool hasDisposed;
diff --git a/samples/todoapp/TodoApp.WinUI3/Database/AppDbContext.cs b/samples/todoapp/TodoApp.WinUI3/Database/AppDbContext.cs
index a1faf2d..98f9faa 100644
--- a/samples/todoapp/TodoApp.WinUI3/Database/AppDbContext.cs
+++ b/samples/todoapp/TodoApp.WinUI3/Database/AppDbContext.cs
@@ -2,11 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using CommunityToolkit.Datasync.Client.Http;
using CommunityToolkit.Datasync.Client.Offline;
using Microsoft.EntityFrameworkCore;
using System;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using TodoApp.WinUI3.Services;
namespace TodoApp.WinUI3.Database;
@@ -16,8 +19,28 @@ public class AppDbContext(DbContextOptions options) : DbContext(op
//protected override void OnDatasyncInitialization(DatasyncOfflineOptionsBuilder optionsBuilder)
//{
- // _ = optionsBuilder.UseEndpoint(new Uri("https://myservice.azurewebsites.net"));
+ // HttpClientOptions clientOptions = new()
+ // {
+ // Endpoint = new Uri("https://YOURSITEHERE.azurewebsites.net/"),
+ // HttpPipeline = [new LoggingHandler()]
+ // };
+ // _ = optionsBuilder.UseHttpClientOptions(clientOptions);
//}
+
+ public async Task SynchronizeAsync(CancellationToken cancellationToken = default)
+ {
+ //PushResult pushResult = await this.PushAsync(cancellationToken);
+ //if (!pushResult.IsSuccessful)
+ //{
+ // throw new ApplicationException($"Push failed: {pushResult.FailedRequests.FirstOrDefault().Value.ReasonPhrase}");
+ //}
+
+ //PullResult pullResult = await this.PullAsync(cancellationToken);
+ //if (!pullResult.IsSuccessful)
+ //{
+ // throw new ApplicationException($"Pull failed: {pullResult.FailedRequests.FirstOrDefault().Value.ReasonPhrase}");
+ //}
+ }
}
///
diff --git a/samples/todoapp/TodoApp.WinUI3/Services/LoggingHandler.cs b/samples/todoapp/TodoApp.WinUI3/Services/LoggingHandler.cs
new file mode 100644
index 0000000..3cfab74
--- /dev/null
+++ b/samples/todoapp/TodoApp.WinUI3/Services/LoggingHandler.cs
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace TodoApp.WinUI3.Services;
+
+///
+/// A delegating handler that logs the request/response to stdout.
+///
+public class LoggingHandler : DelegatingHandler
+{
+ public LoggingHandler() : base()
+ {
+ }
+
+ public LoggingHandler(HttpMessageHandler innerHandler) : base(innerHandler)
+ {
+ }
+
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ Debug.WriteLine($"[HTTP] >>> {request.Method} {request.RequestUri}");
+ await WriteContentAsync(request.Content, cancellationToken);
+
+ HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
+
+ Debug.WriteLine($"[HTTP] <<< {response.StatusCode} {response.ReasonPhrase}");
+ await WriteContentAsync(response.Content, cancellationToken);
+
+ return response;
+ }
+
+ private static async Task WriteContentAsync(HttpContent content, CancellationToken cancellationToken = default)
+ {
+ if (content != null)
+ {
+ Debug.WriteLine($"[HTTP] >>> {await content.ReadAsStringAsync(cancellationToken)}");
+ }
+ }
+}
diff --git a/samples/todoapp/TodoApp.WinUI3/ViewModels/TodoListViewModel.cs b/samples/todoapp/TodoApp.WinUI3/ViewModels/TodoListViewModel.cs
index f3cded3..af12185 100644
--- a/samples/todoapp/TodoApp.WinUI3/ViewModels/TodoListViewModel.cs
+++ b/samples/todoapp/TodoApp.WinUI3/ViewModels/TodoListViewModel.cs
@@ -3,13 +3,11 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Datasync.Client;
-using CommunityToolkit.Datasync.Client.Offline;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TodoApp.WinUI3.Database;
@@ -104,21 +102,13 @@ public async Task RefreshItemsAsync(CancellationToken cancellationToken = defaul
{
IsRefreshing = true;
- //PushResult pushResult = await service.PushAsync(cancellationToken);
- //if (pushResult.IsSuccessful)
- //{
- // PullResult pullResult = await service.PullAsync(cancellationToken);
- // if (!pullResult.IsSuccessful)
- // {
- // throw new ApplicationException($"Pull failed: {pullResult.FailedRequests.FirstOrDefault().Value.ReasonPhrase}");
- // }
- //}
- //else
- //{
- // throw new ApplicationException($"Push failed: {pushResult.FailedOperations.FirstOrDefault().Value.ReasonPhrase}");
- //}
+ // Synchronize data with the remote service (if any).
+ await service.SynchronizeAsync(cancellationToken);
+ // Pull all items from the database.
IEnumerable itemsFromDatabase = await service.TodoItems.ToListAsync(cancellationToken);
+
+ // Replace all the items in the collection.
Items.ReplaceAll(itemsFromDatabase);
}
catch (Exception ex)
diff --git a/samples/todoapp/TodoApp.WinUI3/Views/TodoListPage.xaml.cs b/samples/todoapp/TodoApp.WinUI3/Views/TodoListPage.xaml.cs
index 23eecf2..9a64d1e 100644
--- a/samples/todoapp/TodoApp.WinUI3/Views/TodoListPage.xaml.cs
+++ b/samples/todoapp/TodoApp.WinUI3/Views/TodoListPage.xaml.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml.Controls;
using System;
@@ -15,7 +14,7 @@ public sealed partial class TodoListPage : Page
public TodoListPage()
{
InitializeComponent();
- DataContext = Ioc.Default.GetRequiredService();
+ DataContext = App.GetRequiredService();
ViewModel.NotificationHandler += PublishNotification;
}
diff --git a/src/CommunityToolkit.Datasync.Client/Offline/OperationsQueue/PushResult.cs b/src/CommunityToolkit.Datasync.Client/Offline/OperationsQueue/PushResult.cs
index bc4f628..f8b9844 100644
--- a/src/CommunityToolkit.Datasync.Client/Offline/OperationsQueue/PushResult.cs
+++ b/src/CommunityToolkit.Datasync.Client/Offline/OperationsQueue/PushResult.cs
@@ -13,7 +13,7 @@ namespace CommunityToolkit.Datasync.Client.Offline;
public class PushResult
{
internal int _completedOperations = 0;
- internal readonly ConcurrentDictionary _failedOperations = new();
+ internal readonly ConcurrentDictionary _failedRequests = new();
///
/// The number of completed operations.
@@ -23,12 +23,12 @@ public class PushResult
///
/// The number of failed operations.
///
- public IReadOnlyDictionary FailedOperations { get => this._failedOperations.ToImmutableDictionary(); }
+ public IReadOnlyDictionary FailedRequests { get => this._failedRequests.ToImmutableDictionary(); }
///
/// Determines if the operation was successful.
///
- public bool IsSuccessful { get => this._failedOperations.IsEmpty; }
+ public bool IsSuccessful { get => this._failedRequests.IsEmpty; }
///
/// Adds an operation result in a thread safe manner.
@@ -43,7 +43,7 @@ internal void AddOperationResult(DatasyncOperation operation, ServiceResponse? r
}
else
{
- _ = this._failedOperations.TryAdd(operation.Id, response);
+ _ = this._failedRequests.TryAdd(operation.Id, response);
}
}
}
diff --git a/tests/CommunityToolkit.Datasync.Client.Test/Offline/OfflineDbContext_Tests.cs b/tests/CommunityToolkit.Datasync.Client.Test/Offline/OfflineDbContext_Tests.cs
index 0a4cc58..9876b51 100644
--- a/tests/CommunityToolkit.Datasync.Client.Test/Offline/OfflineDbContext_Tests.cs
+++ b/tests/CommunityToolkit.Datasync.Client.Test/Offline/OfflineDbContext_Tests.cs
@@ -496,7 +496,7 @@ public async Task PushAsync_NoSynchronizableEntities(int nItems)
PushResult result = await this.context.PushAsync(entityTypes, options);
result.CompletedOperations.Should().Be(0);
- result.FailedOperations.Count.Should().Be(0);
+ result.FailedRequests.Count.Should().Be(0);
}
[Fact]
@@ -507,7 +507,7 @@ public async Task PushAsync_NoOperations()
PushResult result = await this.context.PushAsync(entityTypes, options);
result.CompletedOperations.Should().Be(0);
- result.FailedOperations.Count.Should().Be(0);
+ result.FailedRequests.Count.Should().Be(0);
}
[Fact]
@@ -517,7 +517,7 @@ public async Task DbSet_PushAsync_NoOperations()
PushResult result = await this.context.Movies.PushAsync(options);
result.CompletedOperations.Should().Be(0);
- result.FailedOperations.Count.Should().Be(0);
+ result.FailedRequests.Count.Should().Be(0);
}
[Fact]
@@ -535,7 +535,7 @@ public async void PushAsync_Addition_Works()
PushResult results = await this.context.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeTrue();
results.CompletedOperations.Should().Be(1);
- results.FailedOperations.Should().BeEmpty();
+ results.FailedRequests.Should().BeEmpty();
this.context.DatasyncOperationsQueue.Should().BeEmpty();
@@ -559,7 +559,7 @@ public async void DbSet_PushAsync_Addition_Works()
PushResult results = await this.context.Movies.PushAsync();
results.IsSuccessful.Should().BeTrue();
results.CompletedOperations.Should().Be(1);
- results.FailedOperations.Should().BeEmpty();
+ results.FailedRequests.Should().BeEmpty();
this.context.DatasyncOperationsQueue.Should().BeEmpty();
@@ -580,8 +580,8 @@ public async Task PushAsync_Addition_HttpError()
PushResult results = await this.context.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeFalse();
results.CompletedOperations.Should().Be(0);
- results.FailedOperations.Should().HaveCount(1);
- ServiceResponse result = results.FailedOperations.First().Value;
+ results.FailedRequests.Should().HaveCount(1);
+ ServiceResponse result = results.FailedRequests.First().Value;
result.StatusCode.Should().Be(500);
result.HasContent.Should().BeFalse();
@@ -605,8 +605,8 @@ public async Task PushAsync_Addition_Conflict()
PushResult results = await this.context.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeFalse();
results.CompletedOperations.Should().Be(0);
- results.FailedOperations.Should().HaveCount(1);
- ServiceResponse result = results.FailedOperations.First().Value;
+ results.FailedRequests.Should().HaveCount(1);
+ ServiceResponse result = results.FailedRequests.First().Value;
result.StatusCode.Should().Be(409);
result.HasContent.Should().BeTrue();
string content = new StreamReader(result.ContentStream).ReadToEnd();
@@ -632,7 +632,7 @@ public async Task PushAsync_Removal_Works()
PushResult results = await this.context.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeTrue();
results.CompletedOperations.Should().Be(1);
- results.FailedOperations.Should().BeEmpty();
+ results.FailedRequests.Should().BeEmpty();
this.context.DatasyncOperationsQueue.Should().BeEmpty();
this.context.Movies.Find(clientMovie.Id).Should().BeNull();
@@ -652,7 +652,7 @@ public async Task DbSet_PushAsync_Removal_Works()
PushResult results = await this.context.Movies.PushAsync();
results.IsSuccessful.Should().BeTrue();
results.CompletedOperations.Should().Be(1);
- results.FailedOperations.Should().BeEmpty();
+ results.FailedRequests.Should().BeEmpty();
this.context.DatasyncOperationsQueue.Should().BeEmpty();
this.context.Movies.Find(clientMovie.Id).Should().BeNull();
@@ -672,8 +672,8 @@ public async Task PushAsync_Removal_HttpError()
PushResult results = await this.context.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeFalse();
results.CompletedOperations.Should().Be(0);
- results.FailedOperations.Should().HaveCount(1);
- ServiceResponse result = results.FailedOperations.First().Value;
+ results.FailedRequests.Should().HaveCount(1);
+ ServiceResponse result = results.FailedRequests.First().Value;
result.StatusCode.Should().Be(500);
result.HasContent.Should().BeFalse();
@@ -700,8 +700,8 @@ public async Task PushAsync_Removal_Conflict()
PushResult results = await this.context.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeFalse();
results.CompletedOperations.Should().Be(0);
- results.FailedOperations.Should().HaveCount(1);
- ServiceResponse result = results.FailedOperations.First().Value;
+ results.FailedRequests.Should().HaveCount(1);
+ ServiceResponse result = results.FailedRequests.First().Value;
result.StatusCode.Should().Be(409);
result.HasContent.Should().BeTrue();
string content = new StreamReader(result.ContentStream).ReadToEnd();
@@ -731,7 +731,7 @@ public async Task PushAsync_Replacement_Works()
PushResult results = await this.context.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeTrue();
results.CompletedOperations.Should().Be(1);
- results.FailedOperations.Should().BeEmpty();
+ results.FailedRequests.Should().BeEmpty();
this.context.DatasyncOperationsQueue.Should().BeEmpty();
}
@@ -754,7 +754,7 @@ public async Task DbSet_PushAsync_Replacement_Works()
PushResult results = await this.context.Movies.PushAsync();
results.IsSuccessful.Should().BeTrue();
results.CompletedOperations.Should().Be(1);
- results.FailedOperations.Should().BeEmpty();
+ results.FailedRequests.Should().BeEmpty();
this.context.DatasyncOperationsQueue.Should().BeEmpty();
}
@@ -774,8 +774,8 @@ public async Task PushAsync_Replacement_HttpError()
PushResult results = await this.context.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeFalse();
results.CompletedOperations.Should().Be(0);
- results.FailedOperations.Should().HaveCount(1);
- ServiceResponse result = results.FailedOperations.First().Value;
+ results.FailedRequests.Should().HaveCount(1);
+ ServiceResponse result = results.FailedRequests.First().Value;
result.StatusCode.Should().Be(500);
result.HasContent.Should().BeFalse();
@@ -803,8 +803,8 @@ public async Task PushAsync_Replacement_Conflict()
PushResult results = await this.context.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeFalse();
results.CompletedOperations.Should().Be(0);
- results.FailedOperations.Should().HaveCount(1);
- ServiceResponse result = results.FailedOperations.First().Value;
+ results.FailedRequests.Should().HaveCount(1);
+ ServiceResponse result = results.FailedRequests.First().Value;
result.StatusCode.Should().Be(409);
result.HasContent.Should().BeTrue();
string content = new StreamReader(result.ContentStream).ReadToEnd();
diff --git a/tests/CommunityToolkit.Datasync.Client.Test/Offline/OperationsQueueManager_Tests.cs b/tests/CommunityToolkit.Datasync.Client.Test/Offline/OperationsQueueManager_Tests.cs
index 524b6b8..095f142 100644
--- a/tests/CommunityToolkit.Datasync.Client.Test/Offline/OperationsQueueManager_Tests.cs
+++ b/tests/CommunityToolkit.Datasync.Client.Test/Offline/OperationsQueueManager_Tests.cs
@@ -108,7 +108,7 @@ public async Task PushAsync_NoSynchronizableEntities(int nItems)
PushResult result = await queueManager.PushAsync(entityTypes, options);
result.CompletedOperations.Should().Be(0);
- result.FailedOperations.Count.Should().Be(0);
+ result.FailedRequests.Count.Should().Be(0);
}
[Fact]
@@ -119,7 +119,7 @@ public async Task PushAsync_NoOperations()
PushResult result = await queueManager.PushAsync(entityTypes, options);
result.CompletedOperations.Should().Be(0);
- result.FailedOperations.Count.Should().Be(0);
+ result.FailedRequests.Count.Should().Be(0);
}
[Fact]
@@ -137,7 +137,7 @@ public async void PushAsync_Addition_Works()
PushResult results = await queueManager.PushAsync([ typeof(ClientMovie) ], new PushOptions());
results.IsSuccessful.Should().BeTrue();
results.CompletedOperations.Should().Be(1);
- results.FailedOperations.Should().BeEmpty();
+ results.FailedRequests.Should().BeEmpty();
this.context.DatasyncOperationsQueue.Should().BeEmpty();
@@ -158,8 +158,8 @@ public async Task PushAsync_Addition_HttpError()
PushResult results = await queueManager.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeFalse();
results.CompletedOperations.Should().Be(0);
- results.FailedOperations.Should().HaveCount(1);
- ServiceResponse result = results.FailedOperations.First().Value;
+ results.FailedRequests.Should().HaveCount(1);
+ ServiceResponse result = results.FailedRequests.First().Value;
result.StatusCode.Should().Be(500);
result.HasContent.Should().BeFalse();
@@ -183,8 +183,8 @@ public async Task PushAsync_Addition_Conflict()
PushResult results = await queueManager.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeFalse();
results.CompletedOperations.Should().Be(0);
- results.FailedOperations.Should().HaveCount(1);
- ServiceResponse result = results.FailedOperations.First().Value;
+ results.FailedRequests.Should().HaveCount(1);
+ ServiceResponse result = results.FailedRequests.First().Value;
result.StatusCode.Should().Be(409);
result.HasContent.Should().BeTrue();
string content = new StreamReader(result.ContentStream).ReadToEnd();
@@ -210,7 +210,7 @@ public async Task PushAsync_Removal_Works()
PushResult results = await queueManager.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeTrue();
results.CompletedOperations.Should().Be(1);
- results.FailedOperations.Should().BeEmpty();
+ results.FailedRequests.Should().BeEmpty();
this.context.DatasyncOperationsQueue.Should().BeEmpty();
this.context.Movies.Find(clientMovie.Id).Should().BeNull();
@@ -230,8 +230,8 @@ public async Task PushAsync_Removal_HttpError()
PushResult results = await queueManager.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeFalse();
results.CompletedOperations.Should().Be(0);
- results.FailedOperations.Should().HaveCount(1);
- ServiceResponse result = results.FailedOperations.First().Value;
+ results.FailedRequests.Should().HaveCount(1);
+ ServiceResponse result = results.FailedRequests.First().Value;
result.StatusCode.Should().Be(500);
result.HasContent.Should().BeFalse();
@@ -258,8 +258,8 @@ public async Task PushAsync_Removal_Conflict()
PushResult results = await queueManager.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeFalse();
results.CompletedOperations.Should().Be(0);
- results.FailedOperations.Should().HaveCount(1);
- ServiceResponse result = results.FailedOperations.First().Value;
+ results.FailedRequests.Should().HaveCount(1);
+ ServiceResponse result = results.FailedRequests.First().Value;
result.StatusCode.Should().Be(409);
result.HasContent.Should().BeTrue();
string content = new StreamReader(result.ContentStream).ReadToEnd();
@@ -289,7 +289,7 @@ public async Task PushAsync_Replacement_Works()
PushResult results = await queueManager.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeTrue();
results.CompletedOperations.Should().Be(1);
- results.FailedOperations.Should().BeEmpty();
+ results.FailedRequests.Should().BeEmpty();
this.context.DatasyncOperationsQueue.Should().BeEmpty();
}
@@ -309,8 +309,8 @@ public async Task PushAsync_Replacement_HttpError()
PushResult results = await queueManager.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeFalse();
results.CompletedOperations.Should().Be(0);
- results.FailedOperations.Should().HaveCount(1);
- ServiceResponse result = results.FailedOperations.First().Value;
+ results.FailedRequests.Should().HaveCount(1);
+ ServiceResponse result = results.FailedRequests.First().Value;
result.StatusCode.Should().Be(500);
result.HasContent.Should().BeFalse();
@@ -338,8 +338,8 @@ public async Task PushAsync_Replacement_Conflict()
PushResult results = await queueManager.PushAsync([typeof(ClientMovie)], new PushOptions());
results.IsSuccessful.Should().BeFalse();
results.CompletedOperations.Should().Be(0);
- results.FailedOperations.Should().HaveCount(1);
- ServiceResponse result = results.FailedOperations.First().Value;
+ results.FailedRequests.Should().HaveCount(1);
+ ServiceResponse result = results.FailedRequests.First().Value;
result.StatusCode.Should().Be(409);
result.HasContent.Should().BeTrue();
string content = new StreamReader(result.ContentStream).ReadToEnd();