Skip to content

Commit 9714c65

Browse files
committed
task - added AddJsonOptions and JsonSerializerDefaults.Web for defaults
1 parent a68531f commit 9714c65

File tree

13 files changed

+97
-49
lines changed

13 files changed

+97
-49
lines changed

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ using System.Text.Json.Serialization;
3131
// add as an ASP Service
3232
// allows injection of IDatastarService, to respond to a request with a Datastar friendly ServerSentEvent
3333
// and to read the signals sent by the client
34-
builder.Services.AddDatastar();
34+
builder.Services
35+
.AddDatastar()
36+
.AddJsonOptions(options =>
37+
{
38+
options.Converters.Add(new JsonStringEnumConverter());
39+
});
3540

3641
// displayDate - patching an element
3742
app.MapGet("/displayDate", async (IDatastarService datastarService) =>
@@ -44,11 +49,9 @@ app.MapGet("/displayDate", async (IDatastarService datastarService) =>
4449
app.MapGet("/removeDate", async (IDatastarService datastarService) => { await datastarService.RemoveElementAsync("#date"); });
4550

4651
public record MySignals {
47-
[JsonPropertyName("input")]
4852
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
4953
public string? Input { get; init; } = null;
5054

51-
[JsonPropertyName("output")]
5255
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
5356
public string? Output { get; init; } = null;
5457

@@ -118,13 +121,13 @@ module Program =
118121

119122
```csharp
120123
public class MySignals {
121-
public string myString { get; set; } = "";
122-
public int myInt { get; set; } = 0;
123-
public InnerSignals myInner { get; set; } = new();
124+
public string MyString { get; set; } = "";
125+
public int MyInt { get; set; } = 0;
126+
public InnerSignals MyInner { get; set; } = new();
124127

125128
public class InnerSignals {
126-
public string myInnerString { get; set; } = "";
127-
public int myInnerInt { get; set; } = 0;
129+
public string MyInnerString { get; set; } = "";
130+
public int MyInnerInt { get; set; } = 0;
128131
}
129132
}
130133

examples/csharp/HelloWorld/Program.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44

55
namespace HelloWorld;
66

7-
public class Program
7+
public static class Program
88
{
9-
109
public static void Main(string[] args)
1110
{
1211
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
@@ -21,7 +20,7 @@ public static void Main(string[] args)
2120
Signals? mySignals = await datastarService.ReadSignalsAsync<Signals>();
2221
Debug.Assert(mySignals != null, nameof(mySignals) + " != null");
2322

24-
await datastarService.PatchSignalsAsync(new { show_patch_element_message = true });
23+
await datastarService.PatchSignalsAsync(new { ShowPatchElementMessage = true });
2524

2625
for (var index = 1; index < message.Length; ++index)
2726
{
@@ -38,15 +37,15 @@ public static void Main(string[] args)
3837
Signals? mySignals = await datastarService.ReadSignalsAsync<Signals>();
3938
Debug.Assert(mySignals != null, nameof(mySignals) + " != null");
4039

41-
await datastarService.PatchSignalsAsync(new { show_patch_element_message = false });
40+
await datastarService.PatchSignalsAsync(new { ShowPatchElementMessage = false });
4241

4342
for (var index = 1; index < message.Length; ++index)
4443
{
45-
await datastarService.PatchSignalsAsync(new { signals_message = message[..index] });
44+
await datastarService.PatchSignalsAsync(new { SignalsMessage = message[..index] });
4645
await Task.Delay(TimeSpan.FromMilliseconds(mySignals.Delay.GetValueOrDefault(0)));
4746
}
4847

49-
await datastarService.PatchSignalsAsync(new { signals_message = message });
48+
await datastarService.PatchSignalsAsync(new { SignalsMessage = message });
5049
});
5150

5251
app.MapGet("/execute-script", (IDatastarService datastarService) =>
@@ -57,7 +56,6 @@ public static void Main(string[] args)
5756

5857
public record Signals
5958
{
60-
[JsonPropertyName("delay")]
6159
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
6260
public float? Delay { get; set; } = null;
6361
}

examples/csharp/HelloWorld/wwwroot/hello-world.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
<head>
44
<title>Datastar SDK Demo</title>
55
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
6-
<script src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.6/bundles/datastar.js" type="module"></script>
6+
<script src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.8/bundles/datastar.js" type="module"></script>
77
</head>
88
<body class="bg-white dark:bg-gray-900 text-lg my-16">
99

10-
<div class="mx-auto max-w-3xl text-center" data-signals:signals_message="'Hello, world!'" data-signals:show_patch_element_message="true">
10+
<div class="mx-auto max-w-3xl text-center" data-signals:signals-message="'Hello, world!'" data-signals:show-patch-element-message="true">
1111
<div class="my-16 text-8xl font-bold text-transparent" style="background: linear-gradient(to right in oklch, red, orange, yellow, green, blue, blue, violet); background-clip: text">
12-
<div data-show="$show_patch_element_message"><span id="message">Hello, World!</span></div>
13-
<div data-show="!$show_patch_element_message"><span data-text="$signals_message"></span></div>
12+
<div data-show="$showPatchElementMessage"><span id="message">Hello, World!</span></div>
13+
<div data-show="!$showPatchElementMessage"><span data-text="$signalsMessage"></span></div>
1414
</div>
1515
</div>
1616

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
namespace StarFederation.Datastar.DependencyInjection;
4+
5+
public interface IDatastarBuilder
6+
{
7+
IServiceCollection Services { get; }
8+
}
9+
10+
internal sealed class DatastarBuilder(IServiceCollection services) : IDatastarBuilder
11+
{
12+
public IServiceCollection Services { get; } = services;
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Text.Json;
2+
using Core = StarFederation.Datastar.FSharp;
3+
4+
namespace StarFederation.Datastar.DependencyInjection;
5+
6+
public class DatastarJsonOptions
7+
{
8+
public JsonSerializerOptions SignalsJsonSerializerOptions { get; set; } = new(Core.JsonSerializerOptions.SignalsDefault);
9+
}

src/csharp/DependencyInjection/Services.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Text.Json;
2+
using Microsoft.Extensions.Options;
23
using Microsoft.Extensions.Primitives;
34
using Core = StarFederation.Datastar.FSharp;
45

@@ -60,16 +61,16 @@ public interface IDatastarService
6061
/// <summary>Update signals on the browser. Uses JsonSerializer.Serialize() to convert TType to JSON.</summary>
6162
Task PatchSignalsAsync<TType>(TType signals, JsonSerializerOptions jsonSerializerOptions, PatchSignalsOptions patchSignalsOptions, CancellationToken cancellationToken);
6263

63-
/// <summary>Execute a Javascript snippet on the browser. Implicit CancellationToken = HttpContext.RequestAborted.</summary>
64+
/// <summary>Execute a JavaScript snippet on the browser. Implicit CancellationToken = HttpContext.RequestAborted.</summary>
6465
/// <param name="script">JS snippet; do not include &lt;script&gt; in string</param>
6566
Task ExecuteScriptAsync(string script);
66-
/// <summary>Execute a Javascript snippet on the browser.</summary>
67+
/// <summary>Execute a JavaScript snippet on the browser.</summary>
6768
/// <param name="script">JS snippet; do not include &lt;script&gt; in string</param>
6869
Task ExecuteScriptAsync(string script, CancellationToken cancellationToken);
69-
/// <summary>Execute a Javascript snippet on the browser. Implicit CancellationToken = HttpContext.RequestAborted.</summary>
70+
/// <summary>Execute a JavaScript snippet on the browser. Implicit CancellationToken = HttpContext.RequestAborted.</summary>
7071
/// <param name="script">JS snippet; do not include &lt;script&gt; in string</param>
7172
Task ExecuteScriptAsync(string script, ExecuteScriptOptions options);
72-
/// <summary>Execute a Javascript snippet on the browser.</summary>
73+
/// <summary>Execute a JavaScript snippet on the browser.</summary>
7374
/// <param name="script">JS snippet; do not include &lt;script&gt; in string</param>
7475
Task ExecuteScriptAsync(string script, ExecuteScriptOptions options, CancellationToken cancellationToken);
7576

@@ -97,7 +98,7 @@ public interface IDatastarService
9798
Task<TType?> ReadSignalsAsync<TType>(JsonSerializerOptions options, CancellationToken cancellationToken);
9899
}
99100

100-
internal class DatastarService(Core.ServerSentEventGenerator serverSentEventGenerator) : IDatastarService
101+
internal class DatastarService(Core.ServerSentEventGenerator serverSentEventGenerator, IOptions<DatastarJsonOptions> datastarJsonOptions) : IDatastarService
101102
{
102103
public Task StartServerEventStreamAsync()
103104
=> serverSentEventGenerator.StartServerEventStreamAsync();
@@ -136,17 +137,17 @@ public Task RemoveElementAsync(string selector, RemoveElementOptions options, Ca
136137
=> serverSentEventGenerator.RemoveElementAsync(selector, options, cancellationToken);
137138

138139
public Task PatchSignalsAsync<TType>(TType signals)
139-
=> serverSentEventGenerator.PatchSignalsAsync(JsonSerializer.Serialize(signals, Core.JsonSerializerOptions.SignalsDefault));
140+
=> serverSentEventGenerator.PatchSignalsAsync(JsonSerializer.Serialize(signals, _signalsJsonSerializerOptions));
140141
public Task PatchSignalsAsync<TType>(TType signals, CancellationToken cancellationToken)
141-
=> serverSentEventGenerator.PatchSignalsAsync(JsonSerializer.Serialize(signals, Core.JsonSerializerOptions.SignalsDefault), cancellationToken);
142+
=> serverSentEventGenerator.PatchSignalsAsync(JsonSerializer.Serialize(signals, _signalsJsonSerializerOptions), cancellationToken);
142143
public Task PatchSignalsAsync<TType>(TType signals, JsonSerializerOptions jsonSerializerOptions)
143144
=> serverSentEventGenerator.PatchSignalsAsync(JsonSerializer.Serialize(signals, jsonSerializerOptions));
144145
public Task PatchSignalsAsync<TType>(TType signals, JsonSerializerOptions jsonSerializerOptions, CancellationToken cancellationToken)
145146
=> serverSentEventGenerator.PatchSignalsAsync(JsonSerializer.Serialize(signals, jsonSerializerOptions), cancellationToken);
146147
public Task PatchSignalsAsync<TType>(TType signals, PatchSignalsOptions patchSignalsOptions)
147-
=> serverSentEventGenerator.PatchSignalsAsync(JsonSerializer.Serialize(signals, Core.JsonSerializerOptions.SignalsDefault), patchSignalsOptions);
148+
=> serverSentEventGenerator.PatchSignalsAsync(JsonSerializer.Serialize(signals, _signalsJsonSerializerOptions), patchSignalsOptions);
148149
public Task PatchSignalsAsync<TType>(TType signals, PatchSignalsOptions patchSignalsOptions, CancellationToken cancellationToken)
149-
=> serverSentEventGenerator.PatchSignalsAsync(JsonSerializer.Serialize(signals, Core.JsonSerializerOptions.SignalsDefault), patchSignalsOptions, cancellationToken);
150+
=> serverSentEventGenerator.PatchSignalsAsync(JsonSerializer.Serialize(signals, _signalsJsonSerializerOptions), patchSignalsOptions, cancellationToken);
150151
public Task PatchSignalsAsync<TType>(TType signals, JsonSerializerOptions jsonSerializerOptions, PatchSignalsOptions patchSignalsOptions)
151152
=> serverSentEventGenerator.PatchSignalsAsync(JsonSerializer.Serialize(signals, jsonSerializerOptions), patchSignalsOptions);
152153
public Task PatchSignalsAsync<TType>(TType signals, JsonSerializerOptions jsonSerializerOptions, PatchSignalsOptions patchSignalsOptions, CancellationToken cancellationToken)
@@ -170,11 +171,13 @@ public Stream GetSignalsStream()
170171
=> await serverSentEventGenerator.ReadSignalsAsync(cancellationToken) is { Length: > 0 } signals ? signals : null;
171172

172173
public async Task<TType?> ReadSignalsAsync<TType>()
173-
=> await serverSentEventGenerator.ReadSignalsAsync() is { Length: > 0 } signals ? JsonSerializer.Deserialize<TType>(signals, Core.JsonSerializerOptions.SignalsDefault) : default;
174+
=> await serverSentEventGenerator.ReadSignalsAsync() is { Length: > 0 } signals ? JsonSerializer.Deserialize<TType>(signals, _signalsJsonSerializerOptions) : default;
174175
public async Task<TType?> ReadSignalsAsync<TType>(JsonSerializerOptions options)
175176
=> await serverSentEventGenerator.ReadSignalsAsync() is { Length: > 0 } signals ? JsonSerializer.Deserialize<TType>(signals, options) : default;
176177
public async Task<TType?> ReadSignalsAsync<TType>(CancellationToken cancellationToken)
177-
=> await serverSentEventGenerator.ReadSignalsAsync(cancellationToken) is { Length: > 0 } signals ? JsonSerializer.Deserialize<TType>(signals, Core.JsonSerializerOptions.SignalsDefault) : default;
178+
=> await serverSentEventGenerator.ReadSignalsAsync(cancellationToken) is { Length: > 0 } signals ? JsonSerializer.Deserialize<TType>(signals, _signalsJsonSerializerOptions) : default;
178179
public async Task<TType?> ReadSignalsAsync<TType>(JsonSerializerOptions options, CancellationToken cancellationToken)
179180
=> await serverSentEventGenerator.ReadSignalsAsync(cancellationToken) is { Length: > 0 } signals ? JsonSerializer.Deserialize<TType>(signals, options) : default;
181+
182+
private readonly JsonSerializerOptions _signalsJsonSerializerOptions = datastarJsonOptions.Value.SignalsJsonSerializerOptions;
180183
}
Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
11
using Microsoft.AspNetCore.Http;
22
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Options;
4+
using System.Text.Json;
35
using Core = StarFederation.Datastar.FSharp;
46

57
namespace StarFederation.Datastar.DependencyInjection;
68

79
public static class ServiceCollectionExtensionMethods
810
{
9-
public static IServiceCollection AddDatastar(this IServiceCollection serviceCollection)
11+
public static IDatastarBuilder AddDatastar(this IServiceCollection serviceCollection)
1012
{
13+
serviceCollection.AddOptions<DatastarJsonOptions>();
14+
1115
serviceCollection
1216
.AddHttpContextAccessor()
1317
.AddScoped<IDatastarService>(svcPvd =>
14-
{
15-
IHttpContextAccessor? httpContextAccessor = svcPvd.GetService<IHttpContextAccessor>();
16-
Core.ServerSentEventGenerator serverSentEventGenerator = new(httpContextAccessor);
17-
return new DatastarService(serverSentEventGenerator);
18-
});
19-
return serviceCollection;
18+
new DatastarService(
19+
new Core.ServerSentEventGenerator(svcPvd.GetService<IHttpContextAccessor>()),
20+
svcPvd.GetRequiredService<IOptions<DatastarJsonOptions>>()));
21+
22+
return new DatastarBuilder(serviceCollection);
23+
}
24+
25+
public static IDatastarBuilder AddJsonOptions(this IDatastarBuilder datastarBuilder, Action<JsonSerializerOptions> configureOptions)
26+
{
27+
ArgumentNullException.ThrowIfNull(datastarBuilder);
28+
ArgumentNullException.ThrowIfNull(configureOptions);
29+
30+
datastarBuilder.Services.Configure<DatastarJsonOptions>(options => configureOptions(options.SignalsJsonSerializerOptions));
31+
return datastarBuilder;
2032
}
2133
}

src/csharp/ModelBinding/FromSignalAttribute.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
using System.Text.Json;
22
using Microsoft.AspNetCore.Mvc.ModelBinding;
3-
using Core = StarFederation.Datastar.FSharp;
43

54
namespace StarFederation.Datastar.ModelBinding;
65

76
public class DatastarSignalsBindingSource(string path, JsonSerializerOptions? jsonSerializerOptions) : BindingSource(BindingSourceName, BindingSourceName, true, true)
87
{
98
public const string BindingSourceName = "DatastarSignalsSource";
109
public string BindingPath { get; } = path;
11-
public JsonSerializerOptions JsonSerializerOptions { get; } = jsonSerializerOptions ?? Core.JsonSerializerOptions.SignalsDefault;
10+
public JsonSerializerOptions? JsonSerializerOptions { get; } = jsonSerializerOptions;
1211
}
1312

1413
/// <summary>
@@ -24,6 +23,6 @@ public class DatastarSignalsBindingSource(string path, JsonSerializerOptions? js
2423
public class FromSignalsAttribute : Attribute, IBindingSourceMetadata
2524
{
2625
public string Path { get; set; } = string.Empty;
27-
public JsonSerializerOptions JsonSerializerOptions { get; set; } = Core.JsonSerializerOptions.SignalsDefault;
26+
public JsonSerializerOptions? JsonSerializerOptions { get; set; }
2827
public BindingSource BindingSource => new DatastarSignalsBindingSource(Path, JsonSerializerOptions);
2928
}

src/csharp/ModelBinding/MvcServiceProvider.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,20 @@ namespace StarFederation.Datastar.ModelBinding;
55

66
public static class ServiceCollectionExtensionMethods
77
{
8+
public static IDatastarBuilder AddDatastarMvc(this IDatastarBuilder datastarBuilder)
9+
{
10+
ArgumentNullException.ThrowIfNull(datastarBuilder);
11+
datastarBuilder.Services.AddDatastarMvc();
12+
return datastarBuilder;
13+
}
14+
815
public static IServiceCollection AddDatastarMvc(this IServiceCollection serviceCollection)
916
{
1017
// ReSharper disable once SuspiciousTypeConversion.Global
11-
if (!serviceCollection.Any(_ => _.ServiceType == typeof(IDatastarService))) throw new Exception($"{nameof(AddDatastarMvc)} requires that {nameof(DependencyInjection.ServiceCollectionExtensionMethods.AddDatastar)} is added first");
18+
if (serviceCollection.All(svc => svc.ServiceType != typeof(IDatastarService)))
19+
{
20+
throw new Exception($"{nameof(AddDatastarMvc)} requires that {nameof(DependencyInjection.ServiceCollectionExtensionMethods.AddDatastar)} is added first");
21+
}
1222

1323
serviceCollection.AddControllers(options => options.ModelBinderProviders.Insert(0, new SignalsModelBinderProvider()));
1424
return serviceCollection;

src/csharp/ModelBinding/SignalsModelBinder.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
using Microsoft.AspNetCore.Mvc.ModelBinding;
44
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
55
using Microsoft.Extensions.Logging;
6+
using Microsoft.Extensions.Options;
67
using StarFederation.Datastar.DependencyInjection;
78

89
namespace StarFederation.Datastar.ModelBinding;
910

10-
public class SignalsModelBinder(ILogger<SignalsModelBinder> logger, IDatastarService signalsReader) : IModelBinder
11+
public class SignalsModelBinder(ILogger<SignalsModelBinder> logger, IDatastarService signalsReader, IOptions<DatastarJsonOptions> datastarJsonOptions) : IModelBinder
1112
{
1213
public async Task BindModelAsync(ModelBindingContext bindingContext)
1314
{
1415
DatastarSignalsBindingSource signalBindingSource = (bindingContext.BindingSource as DatastarSignalsBindingSource)!;
16+
JsonSerializerOptions jsonSerializerOptions = signalBindingSource.JsonSerializerOptions ?? datastarJsonOptions.Value.SignalsJsonSerializerOptions ?? FSharp.JsonSerializerOptions.SignalsDefault;
1517

1618
// Get signals into a JsonDocument
1719
JsonDocument doc;
@@ -38,7 +40,7 @@ public async Task BindModelAsync(ModelBindingContext bindingContext)
3840
// SignalsPath: use the name of the field in the method or the one passed in the attribute
3941
string signalsPath = String.IsNullOrEmpty(signalBindingSource.BindingPath) ? bindingContext.FieldName : signalBindingSource.BindingPath;
4042

41-
object? value = doc.RootElement.GetValueFromPath(signalsPath, bindingContext.ModelType, signalBindingSource.JsonSerializerOptions)
43+
object? value = doc.RootElement.GetValueFromPath(signalsPath, bindingContext.ModelType, jsonSerializerOptions)
4244
?? (bindingContext.ModelType.IsValueType ? Activator.CreateInstance(bindingContext.ModelType) : null);
4345
bindingContext.Result = ModelBindingResult.Success(value);
4446
}
@@ -47,11 +49,11 @@ public async Task BindModelAsync(ModelBindingContext bindingContext)
4749
object? value;
4850
if (String.IsNullOrEmpty(signalBindingSource.BindingPath))
4951
{
50-
value = doc.Deserialize(bindingContext.ModelType, signalBindingSource.JsonSerializerOptions);
52+
value = doc.Deserialize(bindingContext.ModelType, jsonSerializerOptions);
5153
}
5254
else
5355
{
54-
value = doc.RootElement.GetValueFromPath(signalBindingSource.BindingPath, bindingContext.ModelType, signalBindingSource.JsonSerializerOptions);
56+
value = doc.RootElement.GetValueFromPath(signalBindingSource.BindingPath, bindingContext.ModelType, jsonSerializerOptions);
5557
}
5658

5759
bindingContext.Result = ModelBindingResult.Success(value);

0 commit comments

Comments
 (0)