Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ from the database table. The Datasync Community Toolkit is a member of the Comm

Currently, the library supports:

* Server: [ASP.NET 8 or later](https://learn.microsoft.com/aspnet/core/)
* Client: .NET 8 or later
* Server: [ASP.NET Core 10.x or later](https://learn.microsoft.com/aspnet/core/)
* Client: .NET clients using .NET 10.x or later

The client platforms that have been tested include:

* [Avalonia UI](https://www.avaloniaui.net/)
* [Blazor WASM](https://learn.microsoft.com/en-us/aspnet/core/blazor/webassembly-build-tools-and-aot)
* [.NET MAUI](https://dotnet.microsoft.com/apps/maui)
* [Uno Platform](https://platform.uno/)
* [Windows Presentation Framework (WPF)](https://learn.microsoft.com/dotnet/desktop/wpf/overview/?view=netdesktop-8.0)
Expand Down
2 changes: 1 addition & 1 deletion docs/in-depth/server/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ You can publish the API defined by data sync controllers using [NSwag](https://l
Review the instructions for each library:

* [NSwag](./openapi/nswag.md)
* [OpenApi](./openapi/net9.md)
* [OpenApi](./openapi/net10.md)
* [Swashbuckle](./openapi/swashbuckle.md)

Currently, NSwag provides the best option for OpenApi document generation.
85 changes: 85 additions & 0 deletions docs/samples/todoapp/blazor-wasm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# TodoApp client for Blazor WebAssembly

!!! info "Blazor usage"
While the Datasync Community Toolkit doesn't require any specific client-side technologies, offline usage requires access to a SQLite database. Blazor WebAssembly runs in the browser sandbox and does not have this access, so this sample demonstrates **online-only** usage of the datasync client instead of the offline synchronization pattern used by the other TodoApp samples. See [online operations](../../in-depth/client/online.md) for more information on the online-only client, and [Blazor WASM support](../../in-depth/client/advanced/blazor-wasm.md) for the current limitations of using Blazor WASM with the Datasync Community Toolkit.

## Run the application first

The Blazor WASM sample hosts an ASP.NET Core server (which also serves the compiled Blazor WebAssembly client) backed by an Entity Framework Core in-memory database, so there is nothing extra to configure to run it locally.

* Open `samples/todoapp-blazor-wasm/TodoApp.BlazorWasm.sln` in Visual Studio (or your editor of choice).
* Set `TodoApp.BlazorWasm.Server` as the startup project.
* Press F5 (or run `dotnet run` from the `TodoApp.BlazorWasm.Server` folder) to start the application.

The database is seeded with a few sample todo items the first time it starts, since it uses an in-memory database that is recreated on every restart.

## The code

### The server

The server is a standard ASP.NET Core host that serves the Blazor WebAssembly client's static files in addition to the datasync API. The interesting parts of `Program.cs` are the same as any other datasync server:

builder.Services.AddDbContext<TodoContext>(options => options.UseInMemoryDatabase("TodoAppDb"));
builder.Services.AddDatasyncServices();
builder.Services.AddControllersWithViews();

The `TodoContext` is a normal `DbContext` with a single `DbSet<TodoItem>`, and `TodoItem` inherits from `EntityTableData` in the usual way.

The `TodoItemsController` exposes the datasync API for `TodoItem`:

[Route("tables/todoitems")]
public class TodoItemsController : TableController<TodoItem>
{
public TodoItemsController(TodoContext context) : base()
{
Repository = new EntityTableRepository<TodoItem>(context);

// UnsafeEntityLogging is intentionally left at its secure default (false).
// This sample stores user-supplied TodoItem content (Title), so only the
// entity ID is logged; the full serialized entity is never written to the logs.
Options = new TableControllerOptions { UnsafeEntityLogging = false };
}
}

As with [the other sample server](./server.md), `UnsafeEntityLogging` is set explicitly to its secure default to show how you can control how much entity data is written to your logs.

### The client

Unlike the other TodoApp samples, the Blazor WebAssembly client doesn't use `OfflineDbContext` — it talks to the datasync service directly using `DatasyncServiceClient<T>` for each request, which is the recommended approach for [online-only client scenarios](../../in-depth/client/online.md).

The `DatasyncServiceClient<TodoItemDto>` is registered in `Program.cs`:

builder.Services.AddHttpClient("DatasyncClient", client =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
});

builder.Services.AddScoped<DatasyncServiceClient<TodoItemDto>>(sp =>
{
IHttpClientFactory httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
HttpClient httpClient = httpClientFactory.CreateClient("DatasyncClient");
Uri tableEndpoint = new("/tables/todoitems", UriKind.Relative);
return new DatasyncServiceClient<TodoItemDto>(tableEndpoint, httpClient);
});

The `TodoService` class wraps the `DatasyncServiceClient<TodoItemDto>` with the CRUD operations the UI needs, using the LINQ query support and the `AddAsync()`/`ReplaceAsync()`/`RemoveAsync()` methods on the client directly (there is no local queue or push/pull step, since every operation talks to the server immediately):

public async Task<IEnumerable<TodoItemDto>> GetTodoItemsAsync()
{
List<TodoItemDto> items = await todoClient.Where(item => !item.Deleted).ToListAsync();
return items;
}

public async Task<TodoItemDto> CreateTodoItemAsync(string title)
{
TodoItemDto newItem = new() { Title = title };
ServiceResponse<TodoItemDto> response = await todoClient.AddAsync(newItem);
if (response.IsSuccessful && response.HasValue)
{
return response.Value!;
}

throw new InvalidOperationException($"Failed to create todo item: {response.ReasonPhrase}");
}

`UpdateTodoItemAsync()` and `DeleteTodoItemAsync()` follow the same pattern using `ReplaceAsync()` and `RemoveAsync()` respectively. Because there is no offline queue, any failure (including a conflict from `ReplaceAsync()`) is surfaced to the caller immediately via the `ServiceResponse<T>` rather than being resolved later during a push operation.
6 changes: 5 additions & 1 deletion docs/samples/todoapp/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,14 @@ Here is a typical table controller for the `TodoItem` entity:
public TodoItemController(AppDbContext context)
: base(new EntityTableRepository<TodoItem>(context))
{
// UnsafeEntityLogging is intentionally left at its secure default (false).
// This sample stores user-supplied TodoItem content (Title), so only the
// entity ID is logged; the full serialized entity is never written to the logs.
Options = new TableControllerOptions { UnsafeEntityLogging = false };
}
}

In many respects, this datasync server is a standard ASP.NET Core Web API controller with minimal changes to support datasync services.
In many respects, this datasync server is a standard ASP.NET Core Web API controller with minimal changes to support datasync services. Note the explicit `UnsafeEntityLogging = false` above — this is the default value, but it is set explicitly here to demonstrate how you can control how much entity data is written to your logs. See [the table controller options](../../in-depth/server/index.md#table-controller-options) for more information.

The sample has support for [NSwag](../../in-depth/server/openapi/nswag.md) and [Swashbuckle](../../in-depth/server/openapi/swashbuckle.md). Set the following in the `appsettings.json` (or `appsettings.Development.json`) file:

Expand Down
4 changes: 2 additions & 2 deletions docs/tutorial/client/part-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ The `TodoItem` model is set up so that it contains the same metadata that the se

These properties are implemented in the `OfflineClientEntity` abstract class. You can use this in your own projects to ensure your datasync models have the right metadata. The UI and service are wired using dependency injection provided by the [CommunityToolkit.MVVM] project.

It's worthwhile taking some time to study the application prior to adding any new code to it. You can also consider using Avalonia, MAUI, WinUI3, or the Uno Platform for your application. The Datasync Community Toolkit is based on .NET 8+ and does not have any client framework-specific code in it.
It's worthwhile taking some time to study the application prior to adding any new code to it. You can also consider using Avalonia, MAUI, WinUI3, or the Uno Platform for your application. The Datasync Community Toolkit is based on .NET 10.x or later and does not have any client framework-specific code in it.

!!! info "Blazor usage"
While the Datasync Community Toolkit doesn't require any specific client-side technologies, offline usage requires access to a SQLite database. Blazor is limited to online usage only.
While the Datasync Community Toolkit doesn't require any specific client-side technologies, offline usage requires access to a SQLite database. Blazor is limited to online usage only. See [Blazor WASM support](../../in-depth/client/advanced/blazor-wasm.md) for more information, and [our Blazor WASM sample](../../samples/todoapp/blazor-wasm.md) for a working example.

## Online operations

Expand Down
6 changes: 4 additions & 2 deletions docs/tutorial/server/part-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ namespace Sample.WebAPI.Controllers;
public class TodoItemController : TableController<TodoItem>
{
public TodoItemController(AppDbContext context, ILogger<TodoItemController> logger) : base()
: base(new EntityTableRepository<TodoItem>(context))
{
Repository = new EntityTableRepository<TodoItem>(context);
Logger = logger;
Expand All @@ -221,7 +220,8 @@ public class TodoItemController : TableController<TodoItem>
EnableSoftDelete = false,
MaxTop = 128000,
PageSize = 100,
UnauthorizedStatusCode = 401
UnauthorizedStatusCode = 401,
UnsafeEntityLogging = false
};
}
}
Expand All @@ -237,6 +237,8 @@ All the values in the options are the defaults. Let's take a look at what each

**UnauthorizedStatusCode** affects what response is generated if the user is not authorized to read or write a record. See [a later tutorial](./part-4.md).

**UnsafeEntityLogging** controls how much entity data is written to the logs. When `false` (the default), only the entity ID is logged at `Information` level. When `true`, the entity ID is logged at `Information` level and the full (serialized) entity contents are also logged at `Debug` level. Entity contents may include personally identifiable information (PII), secrets, or other sensitive business data, so only enable this option when the additional diagnostic detail is required and the log sink is appropriately secured.

To add further table controllers, you should:

* Create the model (based on the same `EntityTableData`).
Expand Down
4 changes: 3 additions & 1 deletion mkdocs.shared.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,19 @@ nav:
- OpenApi:
- NSwag: in-depth/server/openapi/nswag.md
- Swashbuckle: in-depth/server/openapi/swashbuckle.md
- ".NET 9.x": in-depth/server/openapi/net9.md
- ".NET 10.x": in-depth/server/openapi/net10.md
- Client:
- The basics: in-depth/client/index.md
- Authentication: in-depth/client/auth.md
- Online operations: in-depth/client/online.md
- Advanced topics:
- MAUI AOT: in-depth/client/advanced/maui-aot.md
- "Blazor WASM support": in-depth/client/advanced/blazor-wasm.md
- Samples:
- Todo App:
- The server: samples/todoapp/server.md
- Avalonia: samples/todoapp/avalonia.md
- "Blazor WASM": samples/todoapp/blazor-wasm.md
- MAUI: samples/todoapp/maui.md
- "Uno Plaform": samples/todoapp/unoplatform.md
- WinUI3: samples/todoapp/winui3.md
Expand Down