diff --git a/README.md b/README.md
index 0e05a2be..cd5107b9 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,7 @@ A number fo the documents below are still a work in progress and would be added
#### How to ...
+* [Use configuration](docs/work-configuration.md)
* [Work with Azure IoT Hub](docs/work-with-azure-iot-hub.md)
* [Work with Azure Managed Identities](docs/work-with-azure-managed-identities.md)
* [Advanced Service Bus options](docs/advanced-service-bus-options.md)
@@ -50,6 +51,7 @@ A number fo the documents below are still a work in progress and would be added
## Samples
+* [Using IConfiguration to configure the EventBus](./samples/ConfigSample)
* [Simple Consumer](./samples/SimpleConsumer)
* [Simple Publisher](./samples/SimplePublisher)
* [Build a custom event serializer](./samples/CustomEventSerializer)
diff --git a/Tingle.EventBus.sln b/Tingle.EventBus.sln
index 99a04174..b4f51155 100644
--- a/Tingle.EventBus.sln
+++ b/Tingle.EventBus.sln
@@ -56,6 +56,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureIotHub", "samples\Azur
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureManagedIdentity", "samples\AzureManagedIdentity\AzureManagedIdentity.csproj", "{A9AA8DC8-F463-4BB2-AD7B-59060C758862}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigSample", "samples\ConfigSample\ConfigSample.csproj", "{8E115759-87CC-4F45-9679-A9EBBD59992B}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomEventConfigurator", "samples\CustomEventConfigurator\CustomEventConfigurator.csproj", "{8C0EE13F-701F-45EF-BADF-6B7A22AA6785}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomEventSerializer", "samples\CustomEventSerializer\CustomEventSerializer.csproj", "{2C55FABC-8C94-4104-BE05-42A477D2AD9E}"
@@ -156,6 +158,10 @@ Global
{A9AA8DC8-F463-4BB2-AD7B-59060C758862}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9AA8DC8-F463-4BB2-AD7B-59060C758862}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9AA8DC8-F463-4BB2-AD7B-59060C758862}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8E115759-87CC-4F45-9679-A9EBBD59992B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8E115759-87CC-4F45-9679-A9EBBD59992B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8E115759-87CC-4F45-9679-A9EBBD59992B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8E115759-87CC-4F45-9679-A9EBBD59992B}.Release|Any CPU.Build.0 = Release|Any CPU
{8C0EE13F-701F-45EF-BADF-6B7A22AA6785}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C0EE13F-701F-45EF-BADF-6B7A22AA6785}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C0EE13F-701F-45EF-BADF-6B7A22AA6785}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -216,6 +222,7 @@ Global
{C369A8E1-F29D-4705-BD38-28C3DE80D8DB} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
{3759B206-BF8D-4E46-9B04-1C19F156D295} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
{A9AA8DC8-F463-4BB2-AD7B-59060C758862} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
+ {8E115759-87CC-4F45-9679-A9EBBD59992B} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
{8C0EE13F-701F-45EF-BADF-6B7A22AA6785} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
{2C55FABC-8C94-4104-BE05-42A477D2AD9E} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
{C9293277-90BA-4F1A-BEA7-85CE41103B8D} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
diff --git a/samples/AzureManagedIdentity/AzureManagedIdentity.csproj b/samples/AzureManagedIdentity/AzureManagedIdentity.csproj
index b47b184f..d3589d97 100644
--- a/samples/AzureManagedIdentity/AzureManagedIdentity.csproj
+++ b/samples/AzureManagedIdentity/AzureManagedIdentity.csproj
@@ -7,4 +7,5 @@
+
diff --git a/samples/ConfigSample/ConfigSample.csproj b/samples/ConfigSample/ConfigSample.csproj
new file mode 100644
index 00000000..cc1b5d4f
--- /dev/null
+++ b/samples/ConfigSample/ConfigSample.csproj
@@ -0,0 +1,16 @@
+
+
+
+ fdb14b87-4a29-455c-9912-67a1e0c64081
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ConfigSample/ImageUploaded.cs b/samples/ConfigSample/ImageUploaded.cs
new file mode 100644
index 00000000..5548ee3a
--- /dev/null
+++ b/samples/ConfigSample/ImageUploaded.cs
@@ -0,0 +1,15 @@
+namespace ConfigSample;
+
+internal class ImageUploaded
+{
+ public string? ImageId { get; set; }
+ public string? Url { get; set; }
+ public long SizeBytes { get; set; }
+}
+
+internal class VideoUploaded
+{
+ public string? VideoId { get; set; }
+ public string? Url { get; set; }
+ public long SizeBytes { get; set; }
+}
diff --git a/samples/ConfigSample/Program.cs b/samples/ConfigSample/Program.cs
new file mode 100644
index 00000000..071a266f
--- /dev/null
+++ b/samples/ConfigSample/Program.cs
@@ -0,0 +1,31 @@
+using Azure.Identity;
+using ConfigSample;
+using Tingle.EventBus.Transports.Azure.ServiceBus;
+
+var host = Host.CreateDefaultBuilder(args)
+ .ConfigureServices((hostContext, services) =>
+ {
+ var configuration = hostContext.Configuration;
+
+ services.AddEventBus(builder =>
+ {
+ builder.AddConsumer();
+ builder.AddConsumer();
+
+ // Add transports
+ builder.AddAzureServiceBusTransport();
+ builder.AddInMemoryTransport("in-memory-images");
+ builder.AddInMemoryTransport("in-memory-videos");
+
+ // Transport specific configuration
+ var credential = new DefaultAzureCredential();
+ builder.Services.PostConfigure(
+ name: AzureServiceBusDefaults.Name,
+ configureOptions: o => ((AzureServiceBusTransportCredentials)o.Credentials).TokenCredential = credential);
+ });
+
+ services.AddHostedService();
+ })
+ .Build();
+
+await host.RunAsync();
diff --git a/samples/ConfigSample/Properties/launchSettings.json b/samples/ConfigSample/Properties/launchSettings.json
new file mode 100644
index 00000000..544137c3
--- /dev/null
+++ b/samples/ConfigSample/Properties/launchSettings.json
@@ -0,0 +1,11 @@
+{
+ "profiles": {
+ "ConfigSample": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "environmentVariables": {
+ "DOTNET_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/samples/ConfigSample/VehicleDoorOpenedEvent.cs b/samples/ConfigSample/VehicleDoorOpenedEvent.cs
new file mode 100644
index 00000000..82d9fdcb
--- /dev/null
+++ b/samples/ConfigSample/VehicleDoorOpenedEvent.cs
@@ -0,0 +1,26 @@
+using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
+
+namespace ConfigSample;
+
+internal class VehicleDoorOpenedEvent
+{
+ public string? VehicleId { get; set; }
+ public VehicleDoorKind Kind { get; set; }
+ public DateTimeOffset? Opened { get; set; }
+ public DateTimeOffset? Closed { get; set; }
+}
+
+internal class VehicleTelemetryEvent
+{
+ public string? DeviceId { get; set; }
+ public DateTimeOffset Timestamp { get; set; }
+ public string? Action { get; set; }
+ public VehicleDoorKind? VehicleDoorKind { get; set; }
+ public VehicleDoorStatus? VehicleDoorStatus { get; set; }
+ [JsonExtensionData]
+ public JsonObject? Extras { get; set; }
+}
+
+internal enum VehicleDoorStatus { Unknown, Open, Closed, }
+internal enum VehicleDoorKind { FrontLeft, FrontRight, RearLeft, ReadRight, Hood, Trunk, }
diff --git a/samples/ConfigSample/VehicleTelemetryEventsConsumer.cs b/samples/ConfigSample/VehicleTelemetryEventsConsumer.cs
new file mode 100644
index 00000000..81e0b88c
--- /dev/null
+++ b/samples/ConfigSample/VehicleTelemetryEventsConsumer.cs
@@ -0,0 +1,42 @@
+namespace ConfigSample;
+
+internal class VehicleTelemetryEventsConsumer : IEventConsumer
+{
+ private readonly ILogger logger;
+
+ public VehicleTelemetryEventsConsumer(ILogger logger)
+ {
+ this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public async Task ConsumeAsync(EventContext context, CancellationToken cancellationToken)
+ {
+ var telemetry = context.Event;
+
+ var status = telemetry.VehicleDoorStatus;
+ if (status is not VehicleDoorStatus.Open and not VehicleDoorStatus.Closed)
+ {
+ logger.LogWarning("Vehicle Door status '{VehicleDoorStatus}' is not yet supported", status);
+ return;
+ }
+
+ var kind = telemetry.VehicleDoorKind;
+ if (kind is null)
+ {
+ logger.LogWarning("Vehicle Door kind '{VehicleDoorKind}' cannot be null", kind);
+ return;
+ }
+
+ var timestamp = telemetry.Timestamp;
+ var updateEvt = new VehicleDoorOpenedEvent
+ {
+ VehicleId = telemetry.DeviceId,
+ Kind = kind.Value,
+ Closed = status is VehicleDoorStatus.Closed ? timestamp : null,
+ Opened = status is VehicleDoorStatus.Open ? timestamp : null,
+ };
+
+ // the VehicleDoorOpenedEvent on a broadcast bus would notify all subscribers
+ await context.PublishAsync(updateEvt, cancellationToken: cancellationToken);
+ }
+}
diff --git a/samples/ConfigSample/VisualsProducerService.cs b/samples/ConfigSample/VisualsProducerService.cs
new file mode 100644
index 00000000..4c708d98
--- /dev/null
+++ b/samples/ConfigSample/VisualsProducerService.cs
@@ -0,0 +1,42 @@
+namespace ConfigSample;
+
+internal class VisualsProducerService : BackgroundService
+{
+ private readonly IEventPublisher publisher;
+ private readonly ILogger logger;
+
+ public VisualsProducerService(IEventPublisher publisher, ILogger logger)
+ {
+ this.publisher = publisher ?? throw new ArgumentNullException(nameof(publisher));
+ this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken); // delays a little so that the logs are better visible in a better order (only ended for sample)
+
+ logger.LogInformation("Starting production ...");
+
+ var delay = TimeSpan.FromSeconds(20);
+ var times = 10;
+
+ var rnd = new Random(DateTimeOffset.UtcNow.Millisecond);
+
+ for (var i = 0; i < times; i++)
+ {
+ var id = Convert.ToUInt32(rnd.Next()).ToString();
+ var size = Convert.ToUInt32(rnd.Next());
+ var image = (i % 2) == 0;
+ var url = $"https://localhost:8080/{(image ? "images" : "videos")}/{id}.{(image ? "png" : "flv")}";
+
+ _ = image
+ ? await DoPublishAsync(new VideoUploaded { VideoId = id, SizeBytes = size, Url = url, }, stoppingToken)
+ : await DoPublishAsync(new ImageUploaded { ImageId = id, SizeBytes = size, Url = url, }, stoppingToken);
+
+ await Task.Delay(delay, stoppingToken);
+ }
+ }
+
+ private async Task DoPublishAsync(T @event, CancellationToken cancellationToken) where T : class
+ => await publisher.PublishAsync(@event, cancellationToken: cancellationToken);
+}
diff --git a/samples/ConfigSample/VisualsUploadedConsumer.cs b/samples/ConfigSample/VisualsUploadedConsumer.cs
new file mode 100644
index 00000000..3f8a31fd
--- /dev/null
+++ b/samples/ConfigSample/VisualsUploadedConsumer.cs
@@ -0,0 +1,31 @@
+namespace ConfigSample;
+
+internal class VisualsUploadedConsumer : IEventConsumer, IEventConsumer
+{
+ private static readonly TimeSpan SimulationDuration = TimeSpan.FromSeconds(1.3f);
+
+ private readonly ILogger logger;
+
+ public VisualsUploadedConsumer(ILogger logger)
+ {
+ this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public async Task ConsumeAsync(EventContext context, CancellationToken cancellationToken)
+ {
+ var id = context.Event.ImageId;
+ var thumbnailUrl = $"https://localhost:8080/thumbnails/{id}.jpg";
+
+ await Task.Delay(SimulationDuration, cancellationToken);
+ logger.LogInformation("Generated thumbnail from image '{ImageId}' at '{ThumbnailUrl}'.", id, thumbnailUrl);
+ }
+
+ public async Task ConsumeAsync(EventContext context, CancellationToken cancellationToken = default)
+ {
+ var id = context.Event.VideoId;
+ var thumbnailUrl = $"https://localhost:8080/thumbnails/{id}.jpg";
+
+ await Task.Delay(SimulationDuration, cancellationToken);
+ logger.LogInformation("Generated thumbnail from video '{VideoId}' at '{ThumbnailUrl}'.", id, thumbnailUrl);
+ }
+}
diff --git a/samples/ConfigSample/appsettings.Development.json b/samples/ConfigSample/appsettings.Development.json
new file mode 100644
index 00000000..d4ffd6f7
--- /dev/null
+++ b/samples/ConfigSample/appsettings.Development.json
@@ -0,0 +1,16 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "Microsoft": "Information",
+ "System": "Information"
+ },
+ "Console": {
+ "FormatterName": "simple",
+ "FormatterOptions": {
+ "SingleLine": true,
+ "TimestampFormat": "HH:mm:ss "
+ }
+ }
+ }
+}
diff --git a/samples/ConfigSample/appsettings.json b/samples/ConfigSample/appsettings.json
new file mode 100644
index 00000000..bbccc5b0
--- /dev/null
+++ b/samples/ConfigSample/appsettings.json
@@ -0,0 +1,46 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+
+ "EventBus": {
+ "WaitTransportStarted": false,
+ "Naming": {
+ "Convention": "DotCase",
+ "UseFullTypeNames": false
+ },
+ "DefaultTransportName": "azure-service-bus",
+ "Transports": { // keyed by name of the transport
+ "azure-service-bus": {
+ "DefaultEntityKind": "Queue", // required if using the basic SKU (does not support topics)
+ "FullyQualifiedNamespace": "{your_namespace}.servicebus.windows.net"
+ },
+ "in-memory-images": {
+ "DefaultEventIdFormat": "DoubleLongHex"
+ },
+ "in-memory-videos": {
+ "DefaultEntityKind": "Queue"
+ }
+ },
+ "Events": {
+ "ConfigSample.ImageUploaded": { // FullName of the type
+ "TransportName": "in-memory-images"
+ },
+ "ConfigSample.VideoUploaded": { // FullName of the type
+ "TransportName": "in-memory-videos",
+ "Consumers": {
+ "ConfigSample.VisualsUploadedConsumer": { // FullName of the type
+ "UnhandledErrorBehaviour": "Discard",
+ "Metadata": {
+ "generation": "2022"
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Tingle.EventBus.Transports.Amazon.Abstractions/AmazonTransportConfigureOptions.cs b/src/Tingle.EventBus.Transports.Amazon.Abstractions/AmazonTransportConfigureOptions.cs
index 8597e2e6..67f6e375 100644
--- a/src/Tingle.EventBus.Transports.Amazon.Abstractions/AmazonTransportConfigureOptions.cs
+++ b/src/Tingle.EventBus.Transports.Amazon.Abstractions/AmazonTransportConfigureOptions.cs
@@ -1,17 +1,26 @@
using Amazon;
using Amazon.Runtime;
-using Microsoft.Extensions.Options;
+using Tingle.EventBus.Configuration;
namespace Microsoft.Extensions.DependencyInjection;
///
/// A class to finish the configuration of instances of derivatives.
///
-public abstract class AmazonTransportConfigureOptions : IPostConfigureOptions where TOptions : AmazonTransportOptions
+public abstract class AmazonTransportConfigureOptions : EventBusTransportConfigureOptions where TOptions : AmazonTransportOptions
{
+ ///
+ /// Initializes a new given the configuration
+ /// provided by the .
+ ///
+ /// An instance.\
+ public AmazonTransportConfigureOptions(IEventBusConfigurationProvider configurationProvider) : base(configurationProvider) { }
+
///
- public virtual void PostConfigure(string? name, TOptions options)
+ public override void PostConfigure(string? name, TOptions options)
{
+ base.PostConfigure(name, options);
+
// Ensure the region is provided
if (string.IsNullOrWhiteSpace(options.RegionName) && options.Region == null)
{
diff --git a/src/Tingle.EventBus.Transports.Amazon.Kinesis/AmazonKinesisConfigureOptions.cs b/src/Tingle.EventBus.Transports.Amazon.Kinesis/AmazonKinesisConfigureOptions.cs
index 31e46b28..415364f4 100644
--- a/src/Tingle.EventBus.Transports.Amazon.Kinesis/AmazonKinesisConfigureOptions.cs
+++ b/src/Tingle.EventBus.Transports.Amazon.Kinesis/AmazonKinesisConfigureOptions.cs
@@ -11,15 +11,21 @@ internal class AmazonKinesisConfigureOptions : AmazonTransportConfigureOptions busOptionsAccessor)
+ ///
+ /// Initializes a new given the configuration
+ /// provided by the .
+ ///
+ /// An instance.\
+ /// An for bus configuration.\
+ public AmazonKinesisConfigureOptions(IEventBusConfigurationProvider configurationProvider, IOptions busOptionsAccessor)
+ : base(configurationProvider)
{
busOptions = busOptionsAccessor?.Value ?? throw new ArgumentNullException(nameof(busOptionsAccessor));
}
+ ///
public override void PostConfigure(string? name, AmazonKinesisTransportOptions options)
{
- if (name is null) throw new ArgumentNullException(nameof(name));
-
base.PostConfigure(name, options);
// Ensure we have options for Kinesis and the region is set
@@ -33,7 +39,7 @@ public override void PostConfigure(string? name, AmazonKinesisTransportOptions o
}
// Ensure the entity names are not longer than the limits
- var registrations = busOptions.GetRegistrations(name);
+ var registrations = busOptions.GetRegistrations(name!);
foreach (var reg in registrations)
{
// Set the IdFormat
diff --git a/src/Tingle.EventBus.Transports.Amazon.Kinesis/AmazonKinesisEventBusBuilderExtensions.cs b/src/Tingle.EventBus.Transports.Amazon.Kinesis/AmazonKinesisEventBusBuilderExtensions.cs
index 6bb8f216..35a386f1 100644
--- a/src/Tingle.EventBus.Transports.Amazon.Kinesis/AmazonKinesisEventBusBuilderExtensions.cs
+++ b/src/Tingle.EventBus.Transports.Amazon.Kinesis/AmazonKinesisEventBusBuilderExtensions.cs
@@ -20,10 +20,5 @@ public static EventBusBuilder AddAmazonKinesisTransport(this EventBusBuilder bui
/// An to configure the transport options.
///
public static EventBusBuilder AddAmazonKinesisTransport(this EventBusBuilder builder, string name, Action? configure = null)
- {
- if (builder == null) throw new ArgumentNullException(nameof(builder));
-
- builder.Services.ConfigureOptions();
- return builder.AddTransport(name, configure);
- }
+ => builder.AddTransport(name, configure);
}
diff --git a/src/Tingle.EventBus.Transports.Amazon.Sqs/AmazonSqsConfigureOptions.cs b/src/Tingle.EventBus.Transports.Amazon.Sqs/AmazonSqsConfigureOptions.cs
index 24014172..5c350805 100644
--- a/src/Tingle.EventBus.Transports.Amazon.Sqs/AmazonSqsConfigureOptions.cs
+++ b/src/Tingle.EventBus.Transports.Amazon.Sqs/AmazonSqsConfigureOptions.cs
@@ -12,15 +12,21 @@ internal class AmazonSqsConfigureOptions : AmazonTransportConfigureOptions busOptionsAccessor)
+ ///
+ /// Initializes a new given the configuration
+ /// provided by the .
+ ///
+ /// An instance.\
+ /// An for bus configuration.\
+ public AmazonSqsConfigureOptions(IEventBusConfigurationProvider configurationProvider, IOptions busOptionsAccessor)
+ : base(configurationProvider)
{
busOptions = busOptionsAccessor?.Value ?? throw new ArgumentNullException(nameof(busOptionsAccessor));
}
+ ///
public override void PostConfigure(string? name, AmazonSqsTransportOptions options)
{
- if (name is null) throw new ArgumentNullException(nameof(name));
-
base.PostConfigure(name, options);
// Ensure we have options for SQS and SNS and their regions are set
@@ -30,7 +36,7 @@ public override void PostConfigure(string? name, AmazonSqsTransportOptions optio
options.SnsConfig.RegionEndpoint ??= options.Region;
// Ensure the entity names are not longer than the limits
- var registrations = busOptions.GetRegistrations(name);
+ var registrations = busOptions.GetRegistrations(name!);
foreach (var reg in registrations)
{
// Set the IdFormat
diff --git a/src/Tingle.EventBus.Transports.Amazon.Sqs/AmazonSqsEventBusBuilderExtensions.cs b/src/Tingle.EventBus.Transports.Amazon.Sqs/AmazonSqsEventBusBuilderExtensions.cs
index 02665976..d59bda6b 100644
--- a/src/Tingle.EventBus.Transports.Amazon.Sqs/AmazonSqsEventBusBuilderExtensions.cs
+++ b/src/Tingle.EventBus.Transports.Amazon.Sqs/AmazonSqsEventBusBuilderExtensions.cs
@@ -20,10 +20,5 @@ public static EventBusBuilder AddAmazonSqsTransport(this EventBusBuilder builder
/// An to configure the transport options.
///
public static EventBusBuilder AddAmazonSqsTransport(this EventBusBuilder builder, string name, Action? configure = null)
- {
- if (builder == null) throw new ArgumentNullException(nameof(builder));
-
- builder.Services.ConfigureOptions();
- return builder.AddTransport(name, configure);
- }
+ => builder.AddTransport(name, configure);
}
diff --git a/src/Tingle.EventBus.Transports.Azure.Abstractions/AzureTransportConfigureOptions.cs b/src/Tingle.EventBus.Transports.Azure.Abstractions/AzureTransportConfigureOptions.cs
index 83b4d285..11794be3 100644
--- a/src/Tingle.EventBus.Transports.Azure.Abstractions/AzureTransportConfigureOptions.cs
+++ b/src/Tingle.EventBus.Transports.Azure.Abstractions/AzureTransportConfigureOptions.cs
@@ -1,23 +1,28 @@
using Microsoft.Extensions.Options;
+using Tingle.EventBus.Configuration;
namespace Microsoft.Extensions.DependencyInjection;
///
/// A class to finish the configuration of instances of derivatives.
///
-public abstract class AzureTransportConfigureOptions : IPostConfigureOptions, IValidateOptions
+public abstract class AzureTransportConfigureOptions : EventBusTransportConfigureOptions
where TCredential : AzureTransportCredentials
where TOptions : AzureTransportOptions
{
- ///
- public virtual void PostConfigure(string? name, TOptions options)
- {
- // intentionally left bank for future use
- }
+ ///
+ /// Initializes a new given the configuration
+ /// provided by the .
+ ///
+ /// An instance.\
+ public AzureTransportConfigureOptions(IEventBusConfigurationProvider configurationProvider) : base(configurationProvider) { }
///
- public virtual ValidateOptionsResult Validate(string? name, TOptions options)
+ public override ValidateOptionsResult Validate(string? name, TOptions options)
{
+ var result = base.Validate(name, options);
+ if (!result.Succeeded) return result;
+
// We should either have a token credential or a connection string
if (options.Credentials == default || options.Credentials.CurrentValue is null)
{
diff --git a/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureBlobStorageCredentials.cs b/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureBlobStorageCredentials.cs
index 28d1c456..7aafbe63 100644
--- a/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureBlobStorageCredentials.cs
+++ b/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureBlobStorageCredentials.cs
@@ -11,5 +11,5 @@ public class AzureBlobStorageCredentials : AzureTransportCredentials
/// A referencing the blob service.
/// This is likely to be similar to "https://{account_name}.blob.core.windows.net".
///
- public Uri? BlobServiceUrl { get; set; }
+ public Uri? ServiceUrl { get; set; }
}
diff --git a/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsConfigureOptions.cs b/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsConfigureOptions.cs
index 5df80658..7d7175b8 100644
--- a/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsConfigureOptions.cs
+++ b/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsConfigureOptions.cs
@@ -1,4 +1,6 @@
-using Microsoft.Extensions.Options;
+using Azure.Messaging.EventHubs;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
using Tingle.EventBus.Configuration;
namespace Microsoft.Extensions.DependencyInjection;
@@ -6,20 +8,62 @@ namespace Microsoft.Extensions.DependencyInjection;
///
/// A class to finish the configuration of instances of .
///
-internal class AzureEventHubsConfigureOptions : AzureTransportConfigureOptions
+internal class AzureEventHubsConfigureOptions : AzureTransportConfigureOptions,
+ IConfigureNamedOptions
{
private readonly EventBusOptions busOptions;
- public AzureEventHubsConfigureOptions(IOptions busOptionsAccessor)
+ ///
+ /// Initializes a new given the configuration
+ /// provided by the .
+ ///
+ /// An instance.\
+ /// An for bus configuration.\
+ public AzureEventHubsConfigureOptions(IEventBusConfigurationProvider configurationProvider, IOptions busOptionsAccessor)
+ : base(configurationProvider)
{
busOptions = busOptionsAccessor?.Value ?? throw new ArgumentNullException(nameof(busOptionsAccessor));
}
///
- public override void PostConfigure(string? name, AzureEventHubsTransportOptions options)
+ protected override void Configure(IConfiguration configuration, AzureEventHubsTransportOptions options)
{
- if (name is null) throw new ArgumentNullException(nameof(name));
+ base.Configure(configuration, options);
+
+ if (options.Credentials == default || options.Credentials.CurrentValue is null)
+ {
+ var fullyQualifiedNamespace = configuration.GetValue(nameof(AzureEventHubsTransportCredentials.FullyQualifiedNamespace))
+ ?? configuration.GetValue("Namespace");
+ if (fullyQualifiedNamespace is not null)
+ {
+ options.Credentials = new AzureEventHubsTransportCredentials { FullyQualifiedNamespace = fullyQualifiedNamespace, };
+ }
+ else
+ {
+ var connectionString = configuration.GetValue("ConnectionString");
+ if (connectionString is not null) options.Credentials = connectionString;
+ }
+ }
+ if (options.BlobStorageCredentials == default || options.BlobStorageCredentials.CurrentValue is null)
+ {
+ var serviceUrl = configuration.GetValue("BlobStorageServiceUrl")
+ ?? configuration.GetValue("BlobStorageEndpoint");
+ if (serviceUrl is not null)
+ {
+ options.BlobStorageCredentials = new AzureBlobStorageCredentials { ServiceUrl = serviceUrl, };
+ }
+ else
+ {
+ var connectionString = configuration.GetValue("BlobStorageConnectionString");
+ if (connectionString is not null) options.BlobStorageCredentials = connectionString;
+ }
+ }
+ }
+
+ ///
+ public override void PostConfigure(string? name, AzureEventHubsTransportOptions options)
+ {
base.PostConfigure(name, options);
// ensure we have a FullyQualifiedNamespace when using AzureEventHubsTransportCredentials
@@ -32,7 +76,7 @@ public override void PostConfigure(string? name, AzureEventHubsTransportOptions
options.CheckpointInterval = Math.Max(options.CheckpointInterval, 1);
// If there are consumers for this transport, we must check azure blob storage
- var registrations = busOptions.GetRegistrations(name);
+ var registrations = busOptions.GetRegistrations(name!);
if (registrations.Any(r => r.Consumers.Count > 0))
{
// ensure the connection string for blob storage or token credential is provided
@@ -42,9 +86,9 @@ public override void PostConfigure(string? name, AzureEventHubsTransportOptions
}
// ensure we have a BlobServiceUrl when using AzureBlobStorageCredential
- if (options.BlobStorageCredentials.CurrentValue is AzureBlobStorageCredentials absc && absc.BlobServiceUrl is null)
+ if (options.BlobStorageCredentials.CurrentValue is AzureBlobStorageCredentials absc && absc.ServiceUrl is null)
{
- throw new InvalidOperationException($"'{nameof(AzureBlobStorageCredentials.BlobServiceUrl)}' must be provided when using '{nameof(AzureBlobStorageCredentials)}'.");
+ throw new InvalidOperationException($"'{nameof(AzureBlobStorageCredentials.ServiceUrl)}' must be provided when using '{nameof(AzureBlobStorageCredentials)}'.");
}
// ensure the blob container name is provided
diff --git a/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsEventBusBuilderExtensions.cs b/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsEventBusBuilderExtensions.cs
index 833c2dce..17ff95e8 100644
--- a/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsEventBusBuilderExtensions.cs
+++ b/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsEventBusBuilderExtensions.cs
@@ -20,10 +20,5 @@ public static EventBusBuilder AddAzureEventHubsTransport(this EventBusBuilder bu
/// An to configure the transport options.
///
public static EventBusBuilder AddAzureEventHubsTransport(this EventBusBuilder builder, string name, Action? configure = null)
- {
- if (builder == null) throw new ArgumentNullException(nameof(builder));
-
- builder.Services.ConfigureOptions();
- return builder.AddTransport(name, configure);
- }
+ => builder.AddTransport(name, configure);
}
diff --git a/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsTransport.cs b/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsTransport.cs
index e70334b7..dd35e8b6 100644
--- a/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsTransport.cs
+++ b/src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsTransport.cs
@@ -287,7 +287,7 @@ async Task creator(string key, CancellationToken ct)
// blobContainerUri has the format "https://{account_name}.blob.core.windows.net/{container_name}" which can be made using "{BlobServiceUri}/{container_name}".
var cred_bs = Options.BlobStorageCredentials.CurrentValue;
var blobContainerClient = cred_bs is AzureBlobStorageCredentials abstc
- ? new BlobContainerClient(blobContainerUri: new Uri($"{abstc.BlobServiceUrl}/{Options.BlobContainerName}"),
+ ? new BlobContainerClient(blobContainerUri: new Uri($"{abstc.ServiceUrl}/{Options.BlobContainerName}"),
credential: abstc.TokenCredential)
: new BlobContainerClient(connectionString: (string)cred_bs,
blobContainerName: Options.BlobContainerName);
diff --git a/src/Tingle.EventBus.Transports.Azure.QueueStorage/AzureQueueStorageConfigureOptions.cs b/src/Tingle.EventBus.Transports.Azure.QueueStorage/AzureQueueStorageConfigureOptions.cs
index 6279a1cf..95452e0e 100644
--- a/src/Tingle.EventBus.Transports.Azure.QueueStorage/AzureQueueStorageConfigureOptions.cs
+++ b/src/Tingle.EventBus.Transports.Azure.QueueStorage/AzureQueueStorageConfigureOptions.cs
@@ -1,4 +1,5 @@
-using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
using Tingle.EventBus.Configuration;
namespace Microsoft.Extensions.DependencyInjection;
@@ -6,20 +7,47 @@ namespace Microsoft.Extensions.DependencyInjection;
///
/// A class to finish the configuration of instances of .
///
-internal class AzureQueueStorageConfigureOptions : AzureTransportConfigureOptions
+internal class AzureQueueStorageConfigureOptions : AzureTransportConfigureOptions,
+ IConfigureNamedOptions
{
private readonly EventBusOptions busOptions;
- public AzureQueueStorageConfigureOptions(IOptions busOptionsAccessor)
+ ///
+ /// Initializes a new given the configuration
+ /// provided by the .
+ ///
+ /// An instance.\
+ /// An for bus configuration.\
+ public AzureQueueStorageConfigureOptions(IEventBusConfigurationProvider configurationProvider, IOptions busOptionsAccessor)
+ : base(configurationProvider)
{
busOptions = busOptionsAccessor?.Value ?? throw new ArgumentNullException(nameof(busOptionsAccessor));
}
///
- public override void PostConfigure(string? name, AzureQueueStorageTransportOptions options)
+ protected override void Configure(IConfiguration configuration, AzureQueueStorageTransportOptions options)
{
- if (name is null) throw new ArgumentNullException(nameof(name));
+ base.Configure(configuration, options);
+
+ if (options.Credentials == default || options.Credentials.CurrentValue is null)
+ {
+ var serviceUrl = configuration.GetValue(nameof(AzureQueueStorageTransportCredentials.ServiceUrl))
+ ?? configuration.GetValue("Endpoint");
+ if (serviceUrl is not null)
+ {
+ options.Credentials = new AzureQueueStorageTransportCredentials { ServiceUrl = serviceUrl };
+ }
+ else
+ {
+ var connectionString = configuration.GetValue("ConnectionString");
+ if (connectionString is not null) options.Credentials = connectionString;
+ }
+ }
+ }
+ ///
+ public override void PostConfigure(string? name, AzureQueueStorageTransportOptions options)
+ {
base.PostConfigure(name, options);
// ensure we have a ServiceUrl when using AzureQueueStorageTransportCredentials
@@ -29,7 +57,7 @@ public override void PostConfigure(string? name, AzureQueueStorageTransportOptio
}
// Ensure there's only one consumer per event
- var registrations = busOptions.GetRegistrations(name);
+ var registrations = busOptions.GetRegistrations(name!);
var multiple = registrations.FirstOrDefault(r => r.Consumers.Count > 1);
if (multiple is not null)
{
diff --git a/src/Tingle.EventBus.Transports.Azure.QueueStorage/AzureQueueStorageEventBusBuilderExtensions.cs b/src/Tingle.EventBus.Transports.Azure.QueueStorage/AzureQueueStorageEventBusBuilderExtensions.cs
index 7e8b41dc..968f8d19 100644
--- a/src/Tingle.EventBus.Transports.Azure.QueueStorage/AzureQueueStorageEventBusBuilderExtensions.cs
+++ b/src/Tingle.EventBus.Transports.Azure.QueueStorage/AzureQueueStorageEventBusBuilderExtensions.cs
@@ -20,10 +20,5 @@ public static EventBusBuilder AddAzureQueueStorageTransport(this EventBusBuilder
/// An to configure the transport options.
///
public static EventBusBuilder AddAzureQueueStorageTransport(this EventBusBuilder builder, string name, Action? configure = null)
- {
- if (builder == null) throw new ArgumentNullException(nameof(builder));
-
- builder.Services.ConfigureOptions();
- return builder.AddTransport(name, configure);
- }
+ => builder.AddTransport(name, configure);
}
diff --git a/src/Tingle.EventBus.Transports.Azure.ServiceBus/AzureServiceBusConfigureOptions.cs b/src/Tingle.EventBus.Transports.Azure.ServiceBus/AzureServiceBusConfigureOptions.cs
index b1bc0bb0..497fd767 100644
--- a/src/Tingle.EventBus.Transports.Azure.ServiceBus/AzureServiceBusConfigureOptions.cs
+++ b/src/Tingle.EventBus.Transports.Azure.ServiceBus/AzureServiceBusConfigureOptions.cs
@@ -1,4 +1,5 @@
-using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
using Tingle.EventBus.Configuration;
namespace Microsoft.Extensions.DependencyInjection;
@@ -6,20 +7,47 @@ namespace Microsoft.Extensions.DependencyInjection;
///
/// A class to finish the configuration of instances of .
///
-internal class AzureServiceBusConfigureOptions : AzureTransportConfigureOptions
+internal class AzureServiceBusConfigureOptions : AzureTransportConfigureOptions,
+ IConfigureNamedOptions
{
private readonly EventBusOptions busOptions;
- public AzureServiceBusConfigureOptions(IOptions busOptionsAccessor)
+ ///
+ /// Initializes a new given the configuration
+ /// provided by the .
+ ///
+ /// An instance.\
+ /// An for bus configuration.\
+ public AzureServiceBusConfigureOptions(IEventBusConfigurationProvider configurationProvider, IOptions busOptionsAccessor)
+ : base(configurationProvider)
{
busOptions = busOptionsAccessor?.Value ?? throw new ArgumentNullException(nameof(busOptionsAccessor));
}
///
- public override void PostConfigure(string? name, AzureServiceBusTransportOptions options)
+ protected override void Configure(IConfiguration configuration, AzureServiceBusTransportOptions options)
{
- if (name is null) throw new ArgumentNullException(nameof(name));
+ base.Configure(configuration, options);
+
+ if (options.Credentials == default || options.Credentials.CurrentValue is null)
+ {
+ var fullyQualifiedNamespace = configuration.GetValue(nameof(AzureServiceBusTransportCredentials.FullyQualifiedNamespace))
+ ?? configuration.GetValue("Namespace");
+ if (fullyQualifiedNamespace is not null)
+ {
+ options.Credentials = new AzureServiceBusTransportCredentials { FullyQualifiedNamespace = fullyQualifiedNamespace };
+ }
+ else
+ {
+ var connectionString = configuration.GetValue("ConnectionString");
+ if (connectionString is not null) options.Credentials = connectionString;
+ }
+ }
+ }
+ ///
+ public override void PostConfigure(string? name, AzureServiceBusTransportOptions options)
+ {
base.PostConfigure(name, options);
// ensure we have a FullyQualifiedNamespace when using AzureServiceBusTransportCredentials
@@ -30,7 +58,7 @@ public override void PostConfigure(string? name, AzureServiceBusTransportOptions
// Ensure the entity names are not longer than the limits
// See https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-quotas#messaging-quotas
- var registrations = busOptions.GetRegistrations(name);
+ var registrations = busOptions.GetRegistrations(name!);
foreach (var reg in registrations)
{
// Set the IdFormat
diff --git a/src/Tingle.EventBus.Transports.Azure.ServiceBus/AzureServiceBusEventBusBuilderExtensions.cs b/src/Tingle.EventBus.Transports.Azure.ServiceBus/AzureServiceBusEventBusBuilderExtensions.cs
index 4cd3aaba..d94ff295 100644
--- a/src/Tingle.EventBus.Transports.Azure.ServiceBus/AzureServiceBusEventBusBuilderExtensions.cs
+++ b/src/Tingle.EventBus.Transports.Azure.ServiceBus/AzureServiceBusEventBusBuilderExtensions.cs
@@ -20,10 +20,5 @@ public static EventBusBuilder AddAzureServiceBusTransport(this EventBusBuilder b
/// An to configure the transport options.
///
public static EventBusBuilder AddAzureServiceBusTransport(this EventBusBuilder builder, string name, Action? configure = null)
- {
- if (builder == null) throw new ArgumentNullException(nameof(builder));
-
- builder.Services.ConfigureOptions();
- return builder.AddTransport(name, configure);
- }
+ => builder.AddTransport(name, configure);
}
diff --git a/src/Tingle.EventBus.Transports.InMemory/InMemoryEventBusBuilderExtensions.cs b/src/Tingle.EventBus.Transports.InMemory/InMemoryEventBusBuilderExtensions.cs
index d118bdcb..9ffe0bff 100644
--- a/src/Tingle.EventBus.Transports.InMemory/InMemoryEventBusBuilderExtensions.cs
+++ b/src/Tingle.EventBus.Transports.InMemory/InMemoryEventBusBuilderExtensions.cs
@@ -24,9 +24,8 @@ public static EventBusBuilder AddInMemoryTransport(this EventBusBuilder builder,
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
- builder.Services.ConfigureOptions();
builder.Services.AddSingleton();
- return builder.AddTransport(name, configure);
+ return builder.AddTransport(name, configure);
}
///
diff --git a/src/Tingle.EventBus.Transports.InMemory/InMemoryTransportConfigureOptions.cs b/src/Tingle.EventBus.Transports.InMemory/InMemoryTransportConfigureOptions.cs
index b344d772..d8601324 100644
--- a/src/Tingle.EventBus.Transports.InMemory/InMemoryTransportConfigureOptions.cs
+++ b/src/Tingle.EventBus.Transports.InMemory/InMemoryTransportConfigureOptions.cs
@@ -6,17 +6,26 @@ namespace Microsoft.Extensions.DependencyInjection;
///
/// A class to finish the configuration of instances of .
///
-internal class InMemoryTransportConfigureOptions : IPostConfigureOptions
+internal class InMemoryTransportConfigureOptions : EventBusTransportConfigureOptions
{
private readonly EventBusOptions busOptions;
- public InMemoryTransportConfigureOptions(IOptions busOptionsAccessor)
+ ///
+ /// Initializes a new given the configuration
+ /// provided by the .
+ ///
+ /// An instance.\
+ /// An for bus configuration.\
+ public InMemoryTransportConfigureOptions(IEventBusConfigurationProvider configurationProvider, IOptions busOptionsAccessor)
+ : base(configurationProvider)
{
busOptions = busOptionsAccessor?.Value ?? throw new ArgumentNullException(nameof(busOptionsAccessor));
}
- public void PostConfigure(string? name, InMemoryTransportOptions options)
+ ///
+ public override void PostConfigure(string? name, InMemoryTransportOptions options)
{
+ base.PostConfigure(name, options);
if (name is null) throw new ArgumentNullException(nameof(name));
var registrations = busOptions.GetRegistrations(name);
diff --git a/src/Tingle.EventBus.Transports.Kafka/KafkaConfigureOptions.cs b/src/Tingle.EventBus.Transports.Kafka/KafkaConfigureOptions.cs
index 82198204..93f71492 100644
--- a/src/Tingle.EventBus.Transports.Kafka/KafkaConfigureOptions.cs
+++ b/src/Tingle.EventBus.Transports.Kafka/KafkaConfigureOptions.cs
@@ -7,18 +7,26 @@ namespace Microsoft.Extensions.DependencyInjection;
///
/// A class to finish the configuration of instances of .
///
-internal class KafkaConfigureOptions : IPostConfigureOptions
+internal class KafkaConfigureOptions : EventBusTransportConfigureOptions
{
private readonly EventBusOptions busOptions;
- public KafkaConfigureOptions(IOptions busOptionsAccessor)
+ ///
+ /// Initializes a new given the configuration
+ /// provided by the .
+ ///
+ /// An instance.
+ /// An for bus configuration.\
+ public KafkaConfigureOptions(IEventBusConfigurationProvider configurationProvider, IOptions busOptionsAccessor)
+ : base(configurationProvider)
{
busOptions = busOptionsAccessor?.Value ?? throw new ArgumentNullException(nameof(busOptionsAccessor));
}
- public void PostConfigure(string? name, KafkaTransportOptions options)
+ ///
+ public override void PostConfigure(string? name, KafkaTransportOptions options)
{
- if (name is null) throw new ArgumentNullException(nameof(name));
+ base.PostConfigure(name, options);
if (options.BootstrapServers == null && options.AdminConfig == null)
{
@@ -45,7 +53,7 @@ public void PostConfigure(string? name, KafkaTransportOptions options)
options.CheckpointInterval = Math.Max(options.CheckpointInterval, 1);
// ensure there's only one consumer per event
- var registrations = busOptions.GetRegistrations(name);
+ var registrations = busOptions.GetRegistrations(name!);
var multiple = registrations.FirstOrDefault(r => r.Consumers.Count > 1);
if (multiple is not null)
{
diff --git a/src/Tingle.EventBus.Transports.Kafka/KafkaEventBusBuilderExtensions.cs b/src/Tingle.EventBus.Transports.Kafka/KafkaEventBusBuilderExtensions.cs
index 8a214fb0..1a180d0a 100644
--- a/src/Tingle.EventBus.Transports.Kafka/KafkaEventBusBuilderExtensions.cs
+++ b/src/Tingle.EventBus.Transports.Kafka/KafkaEventBusBuilderExtensions.cs
@@ -20,10 +20,5 @@ public static EventBusBuilder AddKafkaTransport(this EventBusBuilder builder, Ac
/// An to configure the transport options.
///
public static EventBusBuilder AddKafkaTransport(this EventBusBuilder builder, string name, Action? configure = null)
- {
- if (builder == null) throw new ArgumentNullException(nameof(builder));
-
- builder.Services.ConfigureOptions();
- return builder.AddTransport(name, configure);
- }
+ => builder.AddTransport(name, configure);
}
diff --git a/src/Tingle.EventBus.Transports.RabbitMQ/RabbitMqConfigureOptions.cs b/src/Tingle.EventBus.Transports.RabbitMQ/RabbitMqConfigureOptions.cs
index 10aab357..cfc85ec7 100644
--- a/src/Tingle.EventBus.Transports.RabbitMQ/RabbitMqConfigureOptions.cs
+++ b/src/Tingle.EventBus.Transports.RabbitMQ/RabbitMqConfigureOptions.cs
@@ -7,21 +7,29 @@ namespace Microsoft.Extensions.DependencyInjection;
///
/// A class to finish the configuration of instances of .
///
-internal class RabbitMqConfigureOptions : IPostConfigureOptions
+internal class RabbitMqConfigureOptions : EventBusTransportConfigureOptions
{
private readonly EventBusOptions busOptions;
- public RabbitMqConfigureOptions(IOptions busOptionsAccessor)
+ ///
+ /// Initializes a new given the configuration
+ /// provided by the .
+ ///
+ /// An instance.
+ /// An for bus configuration.\
+ public RabbitMqConfigureOptions(IEventBusConfigurationProvider configurationProvider, IOptions busOptionsAccessor)
+ : base(configurationProvider)
{
busOptions = busOptionsAccessor?.Value ?? throw new ArgumentNullException(nameof(busOptionsAccessor));
}
- public void PostConfigure(string? name, RabbitMqTransportOptions options)
+ ///
+ public override void PostConfigure(string? name, RabbitMqTransportOptions options)
{
- if (name is null) throw new ArgumentNullException(nameof(name));
+ base.PostConfigure(name, options);
// If there are consumers for this transport, confirm the right Bus options
- var registrations = busOptions.GetRegistrations(name);
+ var registrations = busOptions.GetRegistrations(name!);
if (registrations.Any(r => r.Consumers.Count > 0))
{
// we need full type names
diff --git a/src/Tingle.EventBus.Transports.RabbitMQ/RabbitMqEventBusBuilderExtensions.cs b/src/Tingle.EventBus.Transports.RabbitMQ/RabbitMqEventBusBuilderExtensions.cs
index cd5823bd..bed2e16b 100644
--- a/src/Tingle.EventBus.Transports.RabbitMQ/RabbitMqEventBusBuilderExtensions.cs
+++ b/src/Tingle.EventBus.Transports.RabbitMQ/RabbitMqEventBusBuilderExtensions.cs
@@ -20,10 +20,5 @@ public static EventBusBuilder AddRabbitMqTransport(this EventBusBuilder builder,
/// An to configure the transport options.
///
public static EventBusBuilder AddRabbitMqTransport(this EventBusBuilder builder, string name, Action? configure = null)
- {
- if (builder == null) throw new ArgumentNullException(nameof(builder));
-
- builder.Services.ConfigureOptions();
- return builder.AddTransport(name, configure);
- }
+ => builder.AddTransport(name, configure);
}
diff --git a/src/Tingle.EventBus/Configuration/DefaultEventBusConfigurationProvider.cs b/src/Tingle.EventBus/Configuration/DefaultEventBusConfigurationProvider.cs
new file mode 100644
index 00000000..f9c3d7c4
--- /dev/null
+++ b/src/Tingle.EventBus/Configuration/DefaultEventBusConfigurationProvider.cs
@@ -0,0 +1,20 @@
+using Microsoft.Extensions.Configuration;
+
+namespace Tingle.EventBus.Configuration;
+
+///
+/// Default implementation of .
+///
+internal class DefaultEventBusConfigurationProvider : IEventBusConfigurationProvider
+{
+ private readonly IConfiguration configuration;
+ private const string EventBusKey = "EventBus";
+
+ public DefaultEventBusConfigurationProvider(IConfiguration configuration)
+ {
+ this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
+ }
+
+ ///
+ public IConfiguration Configuration => configuration.GetSection(EventBusKey);
+}
\ No newline at end of file
diff --git a/src/Tingle.EventBus/Configuration/DefaultEventConfigurator.cs b/src/Tingle.EventBus/Configuration/DefaultEventConfigurator.cs
index c0e3d703..cbd5f9cd 100644
--- a/src/Tingle.EventBus/Configuration/DefaultEventConfigurator.cs
+++ b/src/Tingle.EventBus/Configuration/DefaultEventConfigurator.cs
@@ -1,4 +1,5 @@
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Tingle.EventBus.Serialization;
@@ -10,10 +11,12 @@ namespace Tingle.EventBus.Configuration;
internal class DefaultEventConfigurator : IEventConfigurator
{
private readonly IHostEnvironment environment;
+ private readonly IEventBusConfigurationProvider configurationProvider;
- public DefaultEventConfigurator(IHostEnvironment environment)
+ public DefaultEventConfigurator(IHostEnvironment environment, IEventBusConfigurationProvider configurationProvider)
{
this.environment = environment ?? throw new ArgumentNullException(nameof(environment));
+ this.configurationProvider = configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider));
}
///
@@ -22,6 +25,14 @@ public void Configure(EventRegistration registration, EventBusOptions options)
if (registration is null) throw new ArgumentNullException(nameof(registration));
if (options is null) throw new ArgumentNullException(nameof(options));
+ // bind from IConfiguration
+ var configuration = configurationProvider.Configuration.GetSection($"Events:{registration.EventType.FullName}");
+ configuration.Bind(registration);
+ foreach (var ecr in registration.Consumers)
+ {
+ configuration.GetSection($"Consumers:{ecr.ConsumerType.FullName}").Bind(ecr);
+ }
+
// set transport name
ConfigureTransportName(registration, options);
@@ -36,7 +47,7 @@ public void Configure(EventRegistration registration, EventBusOptions options)
ConfigureSerializer(registration);
}
- internal static void ConfigureTransportName(EventRegistration reg, EventBusOptions options)
+ internal void ConfigureTransportName(EventRegistration reg, EventBusOptions options)
{
// If the event transport name has not been specified, attempt to get from the attribute
var type = reg.EventType;
@@ -60,7 +71,7 @@ internal static void ConfigureTransportName(EventRegistration reg, EventBusOptio
}
}
- internal static void ConfigureEventName(EventRegistration reg, EventBusNamingOptions options)
+ internal void ConfigureEventName(EventRegistration reg, EventBusNamingOptions options)
{
// set the event name, if not set
if (string.IsNullOrWhiteSpace(reg.EventName))
@@ -81,7 +92,7 @@ internal static void ConfigureEventName(EventRegistration reg, EventBusNamingOpt
}
}
- internal static void ConfigureEntityKind(EventRegistration reg)
+ internal void ConfigureEntityKind(EventRegistration reg)
{
// set the entity kind, if not set and there is an attribute
if (reg.EntityKind == null)
@@ -135,7 +146,7 @@ internal void ConfigureConsumerNames(EventRegistration reg, EventBusNamingOption
}
}
- internal static void ConfigureSerializer(EventRegistration reg)
+ internal void ConfigureSerializer(EventRegistration reg)
{
// If the event serializer has not been specified, attempt to get from the attribute
var attrs = reg.EventType.GetCustomAttributes(false);
diff --git a/src/Tingle.EventBus/Configuration/IEventBusConfigurationProvider.cs b/src/Tingle.EventBus/Configuration/IEventBusConfigurationProvider.cs
new file mode 100644
index 00000000..72b3ccf3
--- /dev/null
+++ b/src/Tingle.EventBus/Configuration/IEventBusConfigurationProvider.cs
@@ -0,0 +1,15 @@
+using Microsoft.Extensions.Configuration;
+
+namespace Tingle.EventBus.Configuration;
+
+///
+/// Provides an interface for implementing a construct that provides
+/// access to EventBus-related configuration sections.
+///
+public interface IEventBusConfigurationProvider
+{
+ ///
+ /// Gets the where EventBus options are stored.
+ ///
+ IConfiguration Configuration { get; }
+}
diff --git a/src/Tingle.EventBus/DependencyInjection/EventBusBuilder.cs b/src/Tingle.EventBus/DependencyInjection/EventBusBuilder.cs
index 3f57e515..8bc2e889 100644
--- a/src/Tingle.EventBus/DependencyInjection/EventBusBuilder.cs
+++ b/src/Tingle.EventBus/DependencyInjection/EventBusBuilder.cs
@@ -29,6 +29,7 @@ public EventBusBuilder(IServiceCollection services)
// Register necessary services
Services.AddSingleton();
+ Services.AddSingleton();
Services.AddSingleton();
Services.AddSingleton();
Services.AddTransient();
@@ -63,34 +64,40 @@ public EventBusBuilder ConfigureSerialization(ActionAdds a which can be used by the event bus.
- /// The type to configure the transport."/>.
/// The used to handle this transport.
+ /// The type to configure the transport."/>.
+ /// The type to configure the ."/>.
/// The name of this transport.
/// Used to configure the transport options.
- public EventBusBuilder AddTransport(string name, Action? configureOptions)
- where TOptions : EventBusTransportOptions, new()
+ public EventBusBuilder AddTransport(string name, Action? configureOptions)
where THandler : EventBusTransport
- => AddTransport(name, displayName: null, configureOptions: configureOptions);
+ where TOptions : EventBusTransportOptions, new()
+ where TConfigurator : EventBusTransportConfigureOptions
+ => AddTransport(name, displayName: null, configureOptions: configureOptions);
/// Adds a which can be used by the event bus.
- /// The type to configure the transport."/>.
/// The used to handle this transport.
+ /// The type to configure the transport."/>.
+ /// The type to configure the ."/>.
/// The name of this transport.
/// The display name of this transport.
/// Used to configure the transport options.
- public EventBusBuilder AddTransport(string name, string? displayName, Action? configureOptions)
- where TOptions : EventBusTransportOptions, new()
+ public EventBusBuilder AddTransport(string name, string? displayName, Action? configureOptions)
where THandler : EventBusTransport
- => AddTransportHelper(name, displayName, configureOptions);
-
- private EventBusBuilder AddTransportHelper(string name, string? displayName, Action? configureOptions)
where TOptions : EventBusTransportOptions, new()
+ where TConfigurator : EventBusTransportConfigureOptions
+ => AddTransportHelper(name, displayName, configureOptions);
+
+ private EventBusBuilder AddTransportHelper(string name, string? displayName, Action? configureOptions)
where TTransport : class, IEventBusTransport
+ where TOptions : EventBusTransportOptions, new()
+ where TConfigurator : EventBusTransportConfigureOptions
{
Services.Configure(o => o.AddTransport(name, displayName));
+ Services.ConfigureOptions();
+
if (configureOptions is not null) Services.Configure(name, configureOptions);
- Services.ConfigureOptions>();
// // transport is not registered because multiple separate instances are required per name
// Services.AddSingleton();
return this;
diff --git a/src/Tingle.EventBus/DependencyInjection/EventBusConfigureOptions.cs b/src/Tingle.EventBus/DependencyInjection/EventBusConfigureOptions.cs
index 713ab056..0c136432 100644
--- a/src/Tingle.EventBus/DependencyInjection/EventBusConfigureOptions.cs
+++ b/src/Tingle.EventBus/DependencyInjection/EventBusConfigureOptions.cs
@@ -1,4 +1,5 @@
-using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Tingle.EventBus;
using Tingle.EventBus.Configuration;
@@ -16,17 +17,23 @@ internal class EventBusConfigureOptions : IConfigureOptions,
IValidateOptions
{
private readonly IHostEnvironment environment;
+ private readonly IEventBusConfigurationProvider configurationProvider;
private readonly IEnumerable configurators;
- public EventBusConfigureOptions(IHostEnvironment environment, IEnumerable configurators)
+ public EventBusConfigureOptions(IHostEnvironment environment,
+ IEventBusConfigurationProvider configurationProvider,
+ IEnumerable configurators)
{
this.environment = environment ?? throw new ArgumentNullException(nameof(environment));
+ this.configurationProvider = configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider));
this.configurators = configurators ?? throw new ArgumentNullException(nameof(configurators));
}
///
public void Configure(EventBusOptions options)
{
+ configurationProvider.Configuration.Bind(options);
+
// Set the default ConsumerNamePrefix
options.Naming.ConsumerNamePrefix ??= environment.ApplicationName;
}
diff --git a/src/Tingle.EventBus/DependencyInjection/EventBusNamingOptions.cs b/src/Tingle.EventBus/DependencyInjection/EventBusNamingOptions.cs
index 45e93f2c..8d60e8a4 100644
--- a/src/Tingle.EventBus/DependencyInjection/EventBusNamingOptions.cs
+++ b/src/Tingle.EventBus/DependencyInjection/EventBusNamingOptions.cs
@@ -128,7 +128,7 @@ internal string Join(params string[] args)
NamingConvention.KebabCase => string.Join("-", args).ToLowerInvariant(),
NamingConvention.SnakeCase => string.Join("_", args).ToLowerInvariant(),
NamingConvention.DotCase => string.Join(".", args).ToLowerInvariant(),
- _ => throw new ArgumentOutOfRangeException(nameof(Convention), $"'{Convention}' does not support joining"),
+ _ => throw new InvalidOperationException($"'{nameof(NamingConvention)}.{Convention}' does not support joining"),
};
}
}
diff --git a/src/Tingle.EventBus/DependencyInjection/TransportOptionsConfigureOptions.cs b/src/Tingle.EventBus/DependencyInjection/TransportOptionsConfigureOptions.cs
deleted file mode 100644
index 953c4849..00000000
--- a/src/Tingle.EventBus/DependencyInjection/TransportOptionsConfigureOptions.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Microsoft.Extensions.Options;
-using Tingle.EventBus.Transports;
-
-namespace Microsoft.Extensions.DependencyInjection;
-
-///
-/// Implementation of
-/// for shared settings in .
-///
-///
-internal class TransportOptionsConfigureOptions : IPostConfigureOptions where T : EventBusTransportOptions
-{
- public void PostConfigure(string? name, T options)
- {
- // check bounds for empty results delay
- var ticks = options.EmptyResultsDelay.Ticks;
- ticks = Math.Max(ticks, TimeSpan.FromSeconds(30).Ticks); // must be more than 30 seconds
- ticks = Math.Min(ticks, TimeSpan.FromMinutes(10).Ticks); // must be less than 10 minutes
- options.EmptyResultsDelay = TimeSpan.FromTicks(ticks);
-
- // ensure the dead-letter suffix name has been set
- if (string.IsNullOrWhiteSpace(options.DeadLetterSuffix))
- {
- throw new InvalidOperationException($"The '{nameof(options.DeadLetterSuffix)}' must be provided");
- }
- }
-}
diff --git a/src/Tingle.EventBus/Tingle.EventBus.csproj b/src/Tingle.EventBus/Tingle.EventBus.csproj
index 73d03caa..d77efca5 100644
--- a/src/Tingle.EventBus/Tingle.EventBus.csproj
+++ b/src/Tingle.EventBus/Tingle.EventBus.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/Tingle.EventBus/Transports/EventBusTransportConfigureOptions.cs b/src/Tingle.EventBus/Transports/EventBusTransportConfigureOptions.cs
new file mode 100644
index 00000000..250409b3
--- /dev/null
+++ b/src/Tingle.EventBus/Transports/EventBusTransportConfigureOptions.cs
@@ -0,0 +1,72 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using Tingle.EventBus.Configuration;
+using Tingle.EventBus.Transports;
+
+namespace Microsoft.Extensions.DependencyInjection;
+
+///
+/// Implementation of
+/// for shared settings in .
+///
+///
+public abstract class EventBusTransportConfigureOptions : IConfigureNamedOptions, IPostConfigureOptions, IValidateOptions
+ where TOptions : EventBusTransportOptions
+{
+ private readonly IEventBusConfigurationProvider configurationProvider;
+
+ ///
+ /// Initializes a new given the configuration
+ /// provided by the .
+ ///
+ /// An instance.\
+ public EventBusTransportConfigureOptions(IEventBusConfigurationProvider configurationProvider)
+ {
+ this.configurationProvider = configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider));
+ }
+
+ ///
+ public virtual void Configure(string? name, TOptions options)
+ {
+ if (string.IsNullOrEmpty(name)) return;
+
+ var configuration = configurationProvider.Configuration.GetSection($"Transports:{name}");
+ if (configuration.GetChildren().Any()) Configure(configuration, options);
+ }
+
+ ///
+ /// Invoked to configure an instance of with the relevant configuration
+ /// provided by for the specific transport by name.
+ ///
+ ///
+ ///
+ protected virtual void Configure(IConfiguration configuration, TOptions options)
+ {
+ configuration.Bind(options);
+ }
+
+ ///
+ public virtual void Configure(TOptions options) => Configure(Options.Options.DefaultName, options);
+
+ ///
+ public virtual void PostConfigure(string? name, TOptions options)
+ {
+ // check bounds for empty results delay
+ var ticks = options.EmptyResultsDelay.Ticks;
+ ticks = Math.Max(ticks, TimeSpan.FromSeconds(30).Ticks); // must be more than 30 seconds
+ ticks = Math.Min(ticks, TimeSpan.FromMinutes(10).Ticks); // must be less than 10 minutes
+ options.EmptyResultsDelay = TimeSpan.FromTicks(ticks);
+ }
+
+ ///
+ public virtual ValidateOptionsResult Validate(string? name, TOptions options)
+ {
+ // ensure the dead-letter suffix name has been set
+ if (string.IsNullOrWhiteSpace(options.DeadLetterSuffix))
+ {
+ return ValidateOptionsResult.Fail($"'{nameof(options.DeadLetterSuffix)}' must be provided.");
+ }
+
+ return ValidateOptionsResult.Success;
+ }
+}
diff --git a/tests/Tingle.EventBus.Tests/Configurator/DefaultEventConfiguratorTests.cs b/tests/Tingle.EventBus.Tests/Configurator/DefaultEventConfiguratorTests.cs
index 9c228695..f664ad98 100644
--- a/tests/Tingle.EventBus.Tests/Configurator/DefaultEventConfiguratorTests.cs
+++ b/tests/Tingle.EventBus.Tests/Configurator/DefaultEventConfiguratorTests.cs
@@ -1,4 +1,5 @@
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using Tingle.EventBus.Configuration;
using Tingle.EventBus.Serialization;
@@ -9,35 +10,41 @@ public class DefaultEventConfiguratorTests
[Fact]
public void ConfigureSerializer_UsesDefault()
{
- var configurator = new DefaultEventConfigurator(new FakeHostEnvironment("app1"));
+ var configuration = new ConfigurationBuilder().Build();
+ var configurationProvider = new DefaultEventBusConfigurationProvider(configuration);
+ var configurator = new DefaultEventConfigurator(new FakeHostEnvironment("app1"), configurationProvider);
// when not set, use default
var registration = new EventRegistration(typeof(TestEvent1));
Assert.Null(registration.EventSerializerType);
- DefaultEventConfigurator.ConfigureSerializer(registration);
+ configurator.ConfigureSerializer(registration);
Assert.Equal(typeof(IEventSerializer), registration.EventSerializerType);
}
[Fact]
public void ConfigureSerializer_RespectsAttribute()
{
- var configurator = new DefaultEventConfigurator(new FakeHostEnvironment("app1"));
+ var configuration = new ConfigurationBuilder().Build();
+ var configurationProvider = new DefaultEventBusConfigurationProvider(configuration);
+ var configurator = new DefaultEventConfigurator(new FakeHostEnvironment("app1"), configurationProvider);
// attribute is respected
var registration = new EventRegistration(typeof(TestEvent2));
Assert.Null(registration.EventSerializerType);
- DefaultEventConfigurator.ConfigureSerializer(registration);
+ configurator.ConfigureSerializer(registration);
Assert.Equal(typeof(FakeEventSerializer1), registration.EventSerializerType);
}
[Fact]
public void ConfigureSerializer_Throws_InvalidOperationException()
{
- var configurator = new DefaultEventConfigurator(new FakeHostEnvironment("app1"));
+ var configuration = new ConfigurationBuilder().Build();
+ var configurationProvider = new DefaultEventBusConfigurationProvider(configuration);
+ var configurator = new DefaultEventConfigurator(new FakeHostEnvironment("app1"), configurationProvider);
// attribute is respected
var registration = new EventRegistration(typeof(TestEvent3));
- var ex = Assert.Throws(() => DefaultEventConfigurator.ConfigureSerializer(registration));
+ var ex = Assert.Throws(() => configurator.ConfigureSerializer(registration));
Assert.Equal("The type 'Tingle.EventBus.Tests.Configurator.FakeEventSerializer2' is used"
+ " as a serializer but does not implement 'Tingle.EventBus.Serialization.IEventSerializer'",
ex.Message);
@@ -56,14 +63,16 @@ public void ConfigureSerializer_Throws_InvalidOperationException()
[InlineData(typeof(TestEvent2), true, "dev", NamingConvention.DotCase, "sample-event")]
public void ConfigureEventName_Works(Type eventType, bool useFullTypeNames, string scope, NamingConvention namingConvention, string expected)
{
- var configurator = new DefaultEventConfigurator(new FakeHostEnvironment("app1"));
+ var configuration = new ConfigurationBuilder().Build();
+ var configurationProvider = new DefaultEventBusConfigurationProvider(configuration);
+ var configurator = new DefaultEventConfigurator(new FakeHostEnvironment("app1"), configurationProvider);
var options = new EventBusOptions { };
options.Naming.Scope = scope;
options.Naming.Convention = namingConvention;
options.Naming.UseFullTypeNames = useFullTypeNames;
var registration = new EventRegistration(eventType);
- DefaultEventConfigurator.ConfigureEventName(registration, options.Naming);
+ configurator.ConfigureEventName(registration, options.Naming);
Assert.Equal(expected, registration.EventName);
}
@@ -140,7 +149,9 @@ public void SetConsumerName_Works(Type eventType,
NamingConvention namingConvention,
string expected)
{
- var configurator = new DefaultEventConfigurator(new FakeHostEnvironment("app1"));
+ var configuration = new ConfigurationBuilder().Build();
+ var configurationProvider = new DefaultEventBusConfigurationProvider(configuration);
+ var configurator = new DefaultEventConfigurator(new FakeHostEnvironment("app1"), configurationProvider);
var options = new EventBusOptions { };
options.Naming.Convention = namingConvention;
@@ -153,7 +164,7 @@ public void SetConsumerName_Works(Type eventType,
registration.Consumers.Add(new EventConsumerRegistration(consumerType));
var creg = Assert.Single(registration.Consumers);
- DefaultEventConfigurator.ConfigureEventName(registration, options.Naming);
+ configurator.ConfigureEventName(registration, options.Naming);
configurator.ConfigureConsumerNames(registration, options.Naming);
Assert.Equal(expected, creg.ConsumerName);
}
@@ -164,10 +175,12 @@ public void SetConsumerName_Works(Type eventType,
[InlineData(typeof(TestEvent3), EntityKind.Broadcast)]
public void ConfigureEntityKind_Works(Type eventType, EntityKind? expected)
{
- var configurator = new DefaultEventConfigurator(new FakeHostEnvironment("app1"));
+ var configuration = new ConfigurationBuilder().Build();
+ var configurationProvider = new DefaultEventBusConfigurationProvider(configuration);
+ var configurator = new DefaultEventConfigurator(new FakeHostEnvironment("app1"), configurationProvider);
var registration = new EventRegistration(eventType);
- DefaultEventConfigurator.ConfigureEntityKind(registration);
+ configurator.ConfigureEntityKind(registration);
Assert.Equal(expected, registration.EntityKind);
}
}