Skip to content

Commit 6c6b1e3

Browse files
author
Adrian Hall
committed
(#358) Updated documentation
1 parent 08d2049 commit 6c6b1e3

File tree

1 file changed

+75
-1
lines changed

1 file changed

+75
-1
lines changed

docs/in-depth/client/index.md

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,29 @@ This example shows all of the options that can be configured for an entity:
117117
* The `Endpoint` can be relative or absolute. If relative, it is relative to the `BaseAddress` of the `HttpClient` that is used.
118118
* The `Query` limits which entities are requested from the remote service.
119119

120+
### Configuring automatic conflict resolution
121+
122+
By default, the library does not do conflict resolution automatically. You can set an automated conflict resolver by writing an `IConflictResolver` or `IConflictResolver<T>` implementation. The library provides two by default:
123+
124+
* `ClientWinsConflictResolver` will force-write the client version to the server.
125+
* `ServerWinsConflictResolver` will replace the client version with the server version.
126+
127+
You can set the conflict resolver in two ways - per-entity or as a fallback default:
128+
129+
```csharp
130+
protected override void OnDatasyncInitialization(DatasyncOfflineOptionsBuilder builder)
131+
{
132+
// A fallback default for cases when you did not set one per entity
133+
builder.UseDefaultConflictResolver(new ClientWinsConflictResolver());
134+
135+
// Set a specific conflict resolver for an entity.
136+
builder.Entity<Movie>(cfg => {
137+
cfg.ConflictResolver = new ServerWinsConflictResolver();
138+
// Along with any other settings you want to use
139+
})
140+
}
141+
```
142+
120143
## Local only entities
121144

122145
You can specify that a dataset is not to be synchronized by using the `[DoNotSynchronize]` attribute:
@@ -165,10 +188,61 @@ When the push result is complete, the `PushResult` is returned. This has the fo
165188

166189
* `CompletedOperations` - the number of operations that were completed successfully.
167190
* `IsSuccessful` - a boolean to indicate that the push was completed with no errors.
168-
* `FailedRequests` - a `Dictionary<Uri, ServiceResponse>` that indicates which requests failed.
191+
* `FailedRequests` - a `Dictionary<string, ServiceResponse>` that indicates which requests failed.
169192

170193
In addition, the operations queue is updated. Completed operations are removed and failed operations are marked as failed. You can use the `FailedRequests` property to see the exact error that was returned by the service.
171194

195+
### Conflict resolution
196+
197+
When a conflict resolver is configured, that will be used before a queued change is marked as failed. In the case of a failed request, you can process the failed requests as follows:
198+
199+
```csharp
200+
foreach (var failedRequest in result.FailedRequests)
201+
{
202+
var operationId = failedRequest.Key;
203+
var serviceResponse = failedRequest.Value;
204+
205+
DatasyncOperation operation = context.DatasyncOperationsQueue.Single(x => x.Id == operationId);
206+
// operation.EntityType is the type of entity being transferred
207+
// operation.Item is the JSON-serialized client-side entity
208+
// operation.EntityVersion is the version of the entity that should be overwritten
209+
// serviceResponse.ContentStream is the JSON-serialized server-side entity
210+
}
211+
```
212+
213+
Handling conflicts is complex and involves modifying the queue entity and/or client-side entity to match requirements. Use conflict resolvers in preference of these manual techniques. A conflict resolver is an implementation of `IConflictResolver` or `IConflictResolver<T>` that is attached to the push operation. The main method is `ResolveConflictAsync()`. For example, let's look at the "client-wins" conflict resolver:
214+
215+
```csharp
216+
public class ClientWinsConflictResolver : IConflictResolver
217+
{
218+
/// <inheritdoc />
219+
public async Task<ConflictResolution> ResolveConflictAsync(object? clientObject, object? serverObject, CancellationToken cancellationToken = default)
220+
{
221+
return new ConflictResolution { Result = ConflictResolutionResult.Client, Entity = clientObject };
222+
}
223+
}
224+
```
225+
226+
The `IConflictResolver<T>` is the same as `IConflictResolver` with the notable exception that the `clientObject` and `serverObject` are typed instead of objects. The `ConflictResolution` result model consists of two parts:
227+
228+
* `Result` is either `ConflictResolutionResult.Client` (indicating that the client wins and the server entity should be overwritten) or `ConflictResolutionResult.Server` (indicating that the server wins and the client entity should be overwritten).
229+
* `Entity` is the entity that should be written.
230+
231+
To provide another example, let's say you want to allow updates from the client for all columns except for a `Title` column. You can do this as follows:
232+
233+
```csharp
234+
public class CustomConflictResolver : IConflictResolver<Movie>
235+
{
236+
public async Task<ConflictResolution> ResolverConflictAsync(Movie? clientObject, Movie? serverObject, CancellationToken cancellationToken = default)
237+
{
238+
clientObject.Movie = serverObject.Movie;
239+
return new ConflictResolution { Result = ConflictResolutionResult.Client, Entity = clientObject };
240+
}
241+
}
242+
```
243+
244+
Here, we copy the server value of the movie title to the client before returning so that the title is preserved.
245+
172246
## Pulling data from the service
173247

174248
As with push operations, there are many ways of pulling data from the service. For most situations, you can specify a single filter when configuring the datasync service in `OnDatasyncInitialization` and then use one of the following methods:

0 commit comments

Comments
 (0)