diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 95f9af0a..2de43af9 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -24,7 +24,28 @@ namespace Dapr.Client /// A that can be used to cancel the operation. /// The data type of the object that will be serialized. /// A that will complete when the operation has completed. - public abstract Task PublishEventAsync(string pubsubName, string topicName, TData data, CancellationToken cancellationToken = default); + public abstract Task PublishEventAsync( + string pubsubName, + string topicName, + TData data, + CancellationToken cancellationToken = default); + + /// + /// Publishes an event to the specified topic. + /// + /// The name of the pubsub component to use. + /// The name of the topic the request should be published to. + /// The event data. + /// A collection of metadata key-value pairs that will be provided to the binding. The valid metadata keys and values are determined by the type of binding used. + /// A that can be used to cancel the operation. + /// The data type of the object that will be serialized. + /// A that will complete when the operation has completed. + public abstract Task PublishEventAsync( + string pubsubName, + string topicName, + TData data, + Dictionary metadata, + CancellationToken cancellationToken = default); /// /// Publishes an event to the specified topic. @@ -33,7 +54,24 @@ namespace Dapr.Client /// The name of the topic the request should be published to. /// A that can be used to cancel the operation. /// A that will complete when the operation has completed. - public abstract Task PublishEventAsync(string pubsubName, string topicName, CancellationToken cancellationToken = default); + public abstract Task PublishEventAsync( + string pubsubName, + string topicName, + CancellationToken cancellationToken = default); + + /// + /// Publishes an event to the specified topic. + /// + /// The name of the pubsub component to use. + /// The name of the topic the request should be published to. + /// A collection of metadata key-value pairs that will be provided to the binding. The valid metadata keys and values are determined by the type of binding used. + /// A that can be used to cancel the operation. + /// A that will complete when the operation has completed. + public abstract Task PublishEventAsync( + string pubsubName, + string topicName, + Dictionary metadata, + CancellationToken cancellationToken = default); /// /// Invokes an output binding. diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 1bb15ac3..66814489 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -60,23 +60,62 @@ namespace Dapr.Client #region Publish Apis /// - public override Task PublishEventAsync(string pubsubName, string topicName, TData data, CancellationToken cancellationToken = default) + + public override Task PublishEventAsync( + string pubsubName, + string topicName, + TData data, + CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName)); ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName)); ArgumentVerifier.ThrowIfNull(data, nameof(data)); - return MakePublishRequest(pubsubName, topicName, data, cancellationToken); + return MakePublishRequest(pubsubName, topicName, data, null, cancellationToken); } - /// - public override Task PublishEventAsync(string pubsubName, string topicName, CancellationToken cancellationToken = default) + public override Task PublishEventAsync( + string pubsubName, + string topicName, + TData data, + Dictionary metadata, + CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName)); ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName)); - return MakePublishRequest(pubsubName, topicName, string.Empty, cancellationToken); + ArgumentVerifier.ThrowIfNull(data, nameof(data)); + ArgumentVerifier.ThrowIfNull(metadata, nameof(metadata)); + return MakePublishRequest(pubsubName, topicName, data, metadata, cancellationToken); } - private async Task MakePublishRequest(string pubsubName, string topicName, TContent content, CancellationToken cancellationToken) + /// + public override Task PublishEventAsync( + string pubsubName, + string topicName, + CancellationToken cancellationToken = default) + { + ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName)); + ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName)); + return MakePublishRequest(pubsubName, topicName, string.Empty, null, cancellationToken); + } + + public override Task PublishEventAsync( + string pubsubName, + string topicName, + Dictionary metadata, + CancellationToken cancellationToken = default) + { + ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName)); + ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName)); + ArgumentVerifier.ThrowIfNull(metadata, nameof(metadata)); + return MakePublishRequest(pubsubName, topicName, string.Empty, metadata, cancellationToken); + } + + private async Task MakePublishRequest( + string pubsubName, + string topicName, + TContent content, + Dictionary metadata, + CancellationToken cancellationToken) { // Create PublishEventEnvelope var envelope = new Autogenerated.PublishEventRequest() @@ -90,6 +129,11 @@ namespace Dapr.Client envelope.Data = TypeConverters.ToJsonByteString(content, this.jsonSerializerOptions); } + if (metadata != null) + { + envelope.Metadata.Add(metadata); + } + await this.MakeGrpcCallHandleError( options => client.PublishEventAsync(envelope, options), cancellationToken); diff --git a/test/Dapr.Client.Test/InvokeBindingApiTest.cs b/test/Dapr.Client.Test/InvokeBindingApiTest.cs index d27bd5ab..93a08ca7 100644 --- a/test/Dapr.Client.Test/InvokeBindingApiTest.cs +++ b/test/Dapr.Client.Test/InvokeBindingApiTest.cs @@ -19,6 +19,28 @@ namespace Dapr.Client.Test { [Fact] public async Task InvokeBindingAsync_ValidateRequest() + { + // Configure Client + var httpClient = new TestHttpClient(); + var daprClient = new DaprClientBuilder() + .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) + .Build(); + + var invokeRequest = new InvokeRequest() { RequestParameter = "Hello " }; + var task = daprClient.InvokeBindingAsync("test", "create", invokeRequest); + + // Get Request and validate + httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); + var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); + request.Name.Should().Be("test"); + request.Metadata.Count.Should().Be(0); + var json = request.Data.ToStringUtf8(); + var typeFromRequest = JsonSerializer.Deserialize(json); + typeFromRequest.RequestParameter.Should().Be("Hello "); + } + + [Fact] + public async Task InvokeBindingAsync_ValidateRequest_WithMetadata() { // Configure Client var httpClient = new TestHttpClient(); diff --git a/test/Dapr.Client.Test/PublishEventApiTest.cs b/test/Dapr.Client.Test/PublishEventApiTest.cs index d5bc8f1f..890d690a 100644 --- a/test/Dapr.Client.Test/PublishEventApiTest.cs +++ b/test/Dapr.Client.Test/PublishEventApiTest.cs @@ -6,6 +6,7 @@ namespace Dapr.Client.Test { using System; + using System.Collections.Generic; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -36,6 +37,39 @@ namespace Dapr.Client.Test request.PubsubName.Should().Be(TestPubsubName); request.Topic.Should().Be("test"); jsonFromRequest.Should().Be(JsonSerializer.Serialize(publishData)); + request.Metadata.Count.Should().Be(0); + } + + [Fact] + public async Task PublishEventAsync_CanPublishTopicWithData_WithMetadata() + { + var httpClient = new TestHttpClient(); + var daprClient = new DaprClientBuilder() + .UseGrpcChannelOptions(new GrpcChannelOptions{ HttpClient = httpClient }) + .Build(); + + var metadata = new Dictionary + { + { "key1", "value1" }, + { "key2", "value2" } + }; + + var publishData = new PublishData() { PublishObjectParameter = "testparam" }; + var task = daprClient.PublishEventAsync(TestPubsubName, "test", publishData, metadata); + + httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); + var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); + var jsonFromRequest = request.Data.ToStringUtf8(); + + request.PubsubName.Should().Be(TestPubsubName); + request.Topic.Should().Be("test"); + jsonFromRequest.Should().Be(JsonSerializer.Serialize(publishData)); + + request.Metadata.Count.Should().Be(2); + request.Metadata.Keys.Contains("key1").Should().BeTrue(); + request.Metadata.Keys.Contains("key2").Should().BeTrue(); + request.Metadata["key1"].Should().Be("value1"); + request.Metadata["key2"].Should().Be("value2"); } [Fact] @@ -46,7 +80,6 @@ namespace Dapr.Client.Test .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); - var task = daprClient.PublishEventAsync(TestPubsubName, "test"); httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); @@ -55,6 +88,38 @@ namespace Dapr.Client.Test request.PubsubName.Should().Be(TestPubsubName); request.Topic.Should().Be("test"); jsonFromRequest.Should().Be("\"\""); + + request.Metadata.Count.Should().Be(0); + } + + [Fact] + public async Task PublishEventAsync_CanPublishTopicWithNoContent_WithMetadata() + { + var httpClient = new TestHttpClient(); + var daprClient = new DaprClientBuilder() + .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) + .Build(); + + var metadata = new Dictionary + { + { "key1", "value1" }, + { "key2", "value2" } + }; + + var task = daprClient.PublishEventAsync(TestPubsubName, "test", metadata); + httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); + var request = await GrpcUtils.GetRequestFromRequestMessageAsync(entry.Request); + var jsonFromRequest = request.Data.ToStringUtf8(); + + request.PubsubName.Should().Be(TestPubsubName); + request.Topic.Should().Be("test"); + jsonFromRequest.Should().Be("\"\""); + + request.Metadata.Count.Should().Be(2); + request.Metadata.Keys.Contains("key1").Should().BeTrue(); + request.Metadata.Keys.Contains("key2").Should().BeTrue(); + request.Metadata["key1"].Should().Be("value1"); + request.Metadata["key2"].Should().Be("value2"); } [Fact]