Skip to content

Commit fc7cb5c

Browse files
authored
Re-organization of internal OfflineDbContext APIs (#69)
1 parent 7c12d6d commit fc7cb5c

24 files changed

+1947
-1958
lines changed

src/CommunityToolkit.Datasync.Client/Offline/DatasyncOfflineOptions.cs

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/CommunityToolkit.Datasync.Client/Offline/DatasyncOfflineOptionsBuilder.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using CommunityToolkit.Datasync.Client.Http;
6+
using CommunityToolkit.Datasync.Client.Offline.Internal;
67

78
namespace CommunityToolkit.Datasync.Client.Offline;
89

@@ -105,25 +106,26 @@ public DatasyncOfflineOptionsBuilder Entity(Type entityType, Action<EntityOfflin
105106
}
106107

107108
/// <summary>
108-
/// Retrieves the offline options for the entity type.
109+
/// Converts the builder into a read-only set of options.
109110
/// </summary>
110-
/// <param name="entityType">The entity type.</param>
111-
/// <returns>The offline options for the entity type.</returns>
112-
internal DatasyncOfflineOptions GetOfflineOptions(Type entityType)
111+
/// <returns>The offline options built from this builder.</returns>
112+
internal OfflineOptions Build()
113113
{
114-
ArgumentNullException.ThrowIfNull(entityType, nameof(entityType));
115114
if (this._httpClientFactory == null)
116115
{
117116
throw new DatasyncException($"Datasync service connection is not set.");
118117
}
119118

120-
if (!this._entities.TryGetValue(entityType.FullName!, out EntityOfflineOptions? options))
119+
OfflineOptions result = new()
121120
{
122-
throw new DatasyncException($"Entity is not synchronizable.");
121+
HttpClientFactory = this._httpClientFactory
122+
};
123+
foreach (EntityOfflineOptions entity in this._entities.Values)
124+
{
125+
result.AddEntity(entity.EntityType, entity.ClientName, entity.Endpoint);
123126
}
124127

125-
HttpClient client = this._httpClientFactory.CreateClient(options.ClientName);
126-
return new DatasyncOfflineOptions(client, options.Endpoint);
128+
return result;
127129
}
128130

129131
/// <summary>

src/CommunityToolkit.Datasync.Client/Offline/DbSetExtensions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,12 @@ public static Task<PushOperationResult> PushAsync<TEntity>(this DbSet<TEntity> s
4242
throw new DatasyncException("DbContext for this dataset is not an OfflineDbContext.");
4343
}
4444
}
45+
46+
/// <summary>
47+
/// Converts the string to an empty string if null.
48+
/// </summary>
49+
/// <param name="value">The value to convert.</param>
50+
/// <returns>The converted value.</returns>
51+
public static string AsNullableEmptyString(this string? value)
52+
=> value ?? string.Empty;
4553
}

src/CommunityToolkit.Datasync.Client/Offline/ExecutableOperation.cs

Lines changed: 0 additions & 154 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace CommunityToolkit.Datasync.Client.Offline.Internal;
6+
7+
/// <summary>
8+
/// The executable operation for an "ADD" operation.
9+
/// </summary>
10+
/// <param name="operation">The operation to execute.</param>
11+
internal class AddOperation(DatasyncOperation operation) : ExecutableOperation
12+
{
13+
/// <summary>
14+
/// Performs the push operation, returning the result of the push operation.
15+
/// </summary>
16+
/// <param name="client">The <see cref="HttpClient"/> to use for communicating with the datasync service.</param>
17+
/// <param name="endpoint">The fully-qualified URI to the table endpoint.</param>
18+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe.</param>
19+
/// <returns>The result of the push operation (async).</returns>
20+
internal override async Task<ServiceResponse> ExecuteAsync(HttpClient client, Uri endpoint, CancellationToken cancellationToken = default)
21+
{
22+
endpoint = MakeAbsoluteUri(client.BaseAddress, endpoint);
23+
using HttpRequestMessage request = new(HttpMethod.Post, endpoint)
24+
{
25+
Content = new StringContent(operation.Item, JsonMediaType)
26+
};
27+
using HttpResponseMessage response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
28+
return new ServiceResponse(response);
29+
}
30+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Net.Http.Headers;
6+
7+
namespace CommunityToolkit.Datasync.Client.Offline.Internal;
8+
9+
/// <summary>
10+
/// The executable operation for a "DELETE" operation.
11+
/// </summary>
12+
/// <param name="operation">The operation to execute.</param>
13+
internal class DeleteOperation(DatasyncOperation operation) : ExecutableOperation
14+
{
15+
/// <summary>
16+
/// Performs the push operation, returning the result of the push operation.
17+
/// </summary>
18+
/// <param name="client">The <see cref="HttpClient"/> to use for communicating with the datasync service.</param>
19+
/// <param name="endpoint">The fully-qualified URI to the table endpoint.</param>
20+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe.</param>
21+
/// <returns>The result of the push operation (async).</returns>
22+
internal override async Task<ServiceResponse> ExecuteAsync(HttpClient client, Uri endpoint, CancellationToken cancellationToken = default)
23+
{
24+
endpoint = MakeAbsoluteUri(client.BaseAddress, endpoint);
25+
using HttpRequestMessage request = new(HttpMethod.Delete, new Uri(endpoint, operation.ItemId));
26+
if (!string.IsNullOrEmpty(operation.EntityVersion))
27+
{
28+
request.Headers.IfMatch.Add(new EntityTagHeaderValue($"\"{operation.EntityVersion}\""));
29+
}
30+
31+
using HttpResponseMessage response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
32+
return new ServiceResponse(response);
33+
}
34+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using CommunityToolkit.Datasync.Client.Service;
6+
using Microsoft.Extensions.Options;
7+
using System.Net;
8+
using System.Net.Http.Headers;
9+
using System.Text.Json;
10+
11+
namespace CommunityToolkit.Datasync.Client.Offline.Internal;
12+
13+
/// <summary>
14+
/// The abstract class that encapsulates all logic dealing with pushing an operation to the remote server.
15+
/// </summary>
16+
internal abstract class ExecutableOperation
17+
{
18+
/// <summary>
19+
/// The JSON media type.
20+
/// </summary>
21+
internal MediaTypeHeaderValue JsonMediaType { get; } = MediaTypeHeaderValue.Parse("application/json");
22+
23+
/// <summary>
24+
/// Converts a base address + relative/absolute URI into the appropriate URI for the datasync service.
25+
/// </summary>
26+
/// <param name="baseAddress">The base address from the client.</param>
27+
/// <param name="relativeOrAbsoluteUri">A relative or absolute URI</param>
28+
/// <returns></returns>
29+
internal static Uri MakeAbsoluteUri(Uri? baseAddress, Uri relativeOrAbsoluteUri)
30+
{
31+
if (relativeOrAbsoluteUri.IsAbsoluteUri)
32+
{
33+
return new Uri($"{relativeOrAbsoluteUri.ToString().TrimEnd('/')}/");
34+
}
35+
36+
if (baseAddress != null)
37+
{
38+
if (baseAddress.IsAbsoluteUri)
39+
{
40+
return new Uri($"{new Uri(baseAddress, relativeOrAbsoluteUri).ToString().TrimEnd('/')}/");
41+
}
42+
}
43+
44+
throw new UriFormatException("Invalid combination of baseAddress and relativeUri");
45+
}
46+
47+
/// <summary>
48+
/// Performs the push operation, returning the result of the push operation.
49+
/// </summary>
50+
/// <param name="client">The <see cref="HttpClient"/> to use for communicating with the datasync service.</param>
51+
/// <param name="endpoint">The fully-qualified URI to the table endpoint.</param>
52+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe.</param>
53+
/// <returns>The result of the push operation (async).</returns>
54+
internal abstract Task<ServiceResponse> ExecuteAsync(HttpClient client, Uri endpoint, CancellationToken cancellationToken = default);
55+
56+
#pragma warning disable IDE0060 // Remove unused parameter - cancellationToken is kept for API consistency.
57+
/// <summary>
58+
/// Creates a new <see cref="ExecutableOperation"/> based on the operation model.
59+
/// </summary>
60+
/// <param name="operation">The operation to execute.</param>
61+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe.</param>
62+
/// <returns>The <see cref="ExecutableOperation"/> to execute.</returns>
63+
/// <exception cref="DatasyncException">If the operation Kind is not supported for push.</exception>
64+
internal static Task<ExecutableOperation> CreateAsync(DatasyncOperation operation, CancellationToken cancellationToken = default) => operation.Kind switch
65+
{
66+
OperationKind.Add => Task.FromResult<ExecutableOperation>(new AddOperation(operation)),
67+
OperationKind.Delete => Task.FromResult<ExecutableOperation>(new DeleteOperation(operation)),
68+
OperationKind.Replace => Task.FromResult<ExecutableOperation>(new ReplaceOperation(operation)),
69+
_ => throw new DatasyncException($"Invalid operation kind '{operation.Kind}'"),
70+
};
71+
#pragma warning restore IDE0060 // Remove unused parameter
72+
}

0 commit comments

Comments
 (0)