From 96aaeb18fdb22e2dd977eaca3790fa8deebed8af Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 02:50:36 -0500 Subject: [PATCH 01/16] Implemented generic Dapr client builder method to use across all Dapr packages to reduce code duplication throughout. Signed-off-by: Whit Waldo --- src/Dapr.Common/DaprGenericClientBuilder.cs | 4 +- .../Extensions/DaprClientBuilderExtensions.cs | 64 +++++++++++++++++++ src/Dapr.Common/IDaprClient.cs | 19 ++++++ src/Dapr.Common/IDaprServiceBuilder.cs | 19 ++++++ 4 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs create mode 100644 src/Dapr.Common/IDaprClient.cs create mode 100644 src/Dapr.Common/IDaprServiceBuilder.cs diff --git a/src/Dapr.Common/DaprGenericClientBuilder.cs b/src/Dapr.Common/DaprGenericClientBuilder.cs index 7a7abf025..8ff59490f 100644 --- a/src/Dapr.Common/DaprGenericClientBuilder.cs +++ b/src/Dapr.Common/DaprGenericClientBuilder.cs @@ -21,7 +21,7 @@ namespace Dapr.Common; /// /// Builder for building a generic Dapr client. /// -public abstract class DaprGenericClientBuilder where TClientBuilder : class +public abstract class DaprGenericClientBuilder where TClientBuilder : class, IDaprClient { /// /// Initializes a new instance of the class. @@ -186,7 +186,7 @@ public DaprGenericClientBuilder UseTimeout(TimeSpan timeout) /// /// The assembly the dependencies are being built for. /// - protected (GrpcChannel channel, HttpClient httpClient, Uri httpEndpoint, string daprApiToken) BuildDaprClientDependencies(Assembly assembly) + protected internal (GrpcChannel channel, HttpClient httpClient, Uri httpEndpoint, string daprApiToken) BuildDaprClientDependencies(Assembly assembly) { var grpcEndpoint = new Uri(this.GrpcEndpoint); if (grpcEndpoint.Scheme != "http" && grpcEndpoint.Scheme != "https") diff --git a/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs b/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs new file mode 100644 index 000000000..2ee0ff3f1 --- /dev/null +++ b/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs @@ -0,0 +1,64 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Reflection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Dapr.Common.Extensions; + +/// +/// Generic extension used to build out type-specific Dapr clients. +/// +internal static class DaprClientBuilderExtensions +{ + /// + /// Registers the necessary base functionality for a Dapr client. + /// + /// The type of the client builder interface. + /// The concrete Dapr client type being created. + /// The type of the static builder used to build the Dapr ot client. + /// The collection of services to which the Dapr client and associated services are being registered. + /// An optional method used to provide additional configurations to the client builder. + /// The registered lifetime of the Dapr client. + /// The collection of DI-registered services. + //internal static TBuilderInterface AddDaprClient( + internal static TBuilderInterface AddDaprClient( + this IServiceCollection services, + Action? configure = null, + ServiceLifetime lifetime = ServiceLifetime.Singleton) + where TBuilderInterface : class, IDaprServiceBuilder + where TClient : class, IDaprClient + where TClientBuilder : DaprGenericClientBuilder, new() + { + ArgumentNullException.ThrowIfNull(services, nameof(services)); + + services.AddHttpClient(); + + var registration = new Func(provider => + { + var configuration = provider.GetService(); + var builder = (TClientBuilder)Activator.CreateInstance(typeof(TClientBuilder), configuration)!; + + builder.UseDaprApiToken(DaprDefaults.GetDefaultDaprApiToken(configuration)); + configure?.Invoke(provider, builder); + var (channel, httpClient, httpEndpoint, daprApiToken) = + builder.BuildDaprClientDependencies(Assembly.GetExecutingAssembly()); + return (TClient)Activator.CreateInstance(typeof(TClient), channel, httpClient, httpEndpoint, daprApiToken)!; + }); + + services.Add(new ServiceDescriptor(typeof(TClient), registration, lifetime)); + + return (TBuilderInterface)Activator.CreateInstance(typeof(TBuilderInterface), services)!; + } +} diff --git a/src/Dapr.Common/IDaprClient.cs b/src/Dapr.Common/IDaprClient.cs new file mode 100644 index 000000000..a37a06760 --- /dev/null +++ b/src/Dapr.Common/IDaprClient.cs @@ -0,0 +1,19 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common; + +/// +/// Base interface for any of the specific Dapr clients. +/// +public interface IDaprClient; diff --git a/src/Dapr.Common/IDaprServiceBuilder.cs b/src/Dapr.Common/IDaprServiceBuilder.cs new file mode 100644 index 000000000..fe3d79220 --- /dev/null +++ b/src/Dapr.Common/IDaprServiceBuilder.cs @@ -0,0 +1,19 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Common; + +/// +/// Responsible for registering Dapr services with dependency injection. +/// +public interface IDaprServiceBuilder; From c24cccb4672490c066ae9b02437910ce5fd8ce4f Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 02:51:14 -0500 Subject: [PATCH 02/16] Implemented changes to Dapr.AI to use generic client builder extension. Signed-off-by: Whit Waldo --- .../DaprConversationClientBuilder.cs | 13 +++++- .../Extensions/DaprAiConversationBuilder.cs | 12 +---- .../DaprAiConversationBuilderExtensions.cs | 46 +++---------------- .../Extensions/IDaprAiConversationBuilder.cs | 4 +- src/Dapr.AI/DaprAIClient.cs | 3 +- .../Extensions/IDaprAiServiceBuilder.cs | 3 +- 6 files changed, 25 insertions(+), 56 deletions(-) diff --git a/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs b/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs index 5e0a0825d..58a601265 100644 --- a/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs +++ b/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs @@ -26,10 +26,19 @@ public sealed class DaprConversationClientBuilder : DaprGenericClientBuilder. /// /// - public DaprConversationClientBuilder(IConfiguration? configuration = null) : base(configuration) + public DaprConversationClientBuilder(IConfiguration? configuration) : base(configuration) { } - + + /// + /// + /// + /// + public DaprConversationClientBuilder() : base(null) + { + throw new NotImplementedException(); + } + /// /// Builds the client instance from the properties of the builder. /// diff --git a/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilder.cs b/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilder.cs index 876d223b1..4ba236259 100644 --- a/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilder.cs +++ b/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilder.cs @@ -18,18 +18,10 @@ namespace Dapr.AI.Conversation.Extensions; /// /// Used by the fluent registration builder to configure a Dapr AI conversational manager. /// -public sealed class DaprAiConversationBuilder : IDaprAiConversationBuilder +public sealed class DaprAiConversationBuilder(IServiceCollection services) : IDaprAiConversationBuilder { /// /// The registered services on the builder. /// - public IServiceCollection Services { get; } - - /// - /// Used to initialize a new . - /// - public DaprAiConversationBuilder(IServiceCollection services) - { - Services = services; - } + public IServiceCollection Services { get; } = services; } diff --git a/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs b/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs index 2f049a906..21567ded5 100644 --- a/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs +++ b/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs @@ -11,9 +11,8 @@ // limitations under the License. // ------------------------------------------------------------------------ -using Microsoft.Extensions.Configuration; +using Dapr.Common.Extensions; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; namespace Dapr.AI.Conversation.Extensions; @@ -23,42 +22,11 @@ namespace Dapr.AI.Conversation.Extensions; public static class DaprAiConversationBuilderExtensions { /// - /// Registers the necessary functionality for the Dapr AI conversation functionality. + /// Registers the necessary functionality for the Dapr AI Conversation functionality. /// - /// - public static IDaprAiConversationBuilder AddDaprConversationClient(this IServiceCollection services, Action? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton) - { - ArgumentNullException.ThrowIfNull(services, nameof(services)); - - services.AddHttpClient(); - - var registration = new Func(provider => - { - var configuration = provider.GetService(); - var builder = new DaprConversationClientBuilder(configuration); - - var httpClientFactory = provider.GetRequiredService(); - builder.UseHttpClientFactory(httpClientFactory); - - configure?.Invoke(provider, builder); - - return builder.Build(); - }); - - switch (lifetime) - { - case ServiceLifetime.Scoped: - services.TryAddScoped(registration); - break; - case ServiceLifetime.Transient: - services.TryAddTransient(registration); - break; - case ServiceLifetime.Singleton: - default: - services.TryAddSingleton(registration); - break; - } - - return new DaprAiConversationBuilder(services); - } + public static IDaprAiConversationBuilder AddDaprAiConversationClient( + this IServiceCollection services, + Action? configure = null, + ServiceLifetime lifetime = ServiceLifetime.Singleton) => services + .AddDaprClient(configure, lifetime); } diff --git a/src/Dapr.AI/Conversation/Extensions/IDaprAiConversationBuilder.cs b/src/Dapr.AI/Conversation/Extensions/IDaprAiConversationBuilder.cs index 30d3822d4..3afe63559 100644 --- a/src/Dapr.AI/Conversation/Extensions/IDaprAiConversationBuilder.cs +++ b/src/Dapr.AI/Conversation/Extensions/IDaprAiConversationBuilder.cs @@ -18,6 +18,4 @@ namespace Dapr.AI.Conversation.Extensions; /// /// Provides a root builder for the Dapr AI conversational functionality facilitating a more fluent-style registration. /// -public interface IDaprAiConversationBuilder : IDaprAiServiceBuilder -{ -} +public interface IDaprAiConversationBuilder : IDaprAiServiceBuilder; diff --git a/src/Dapr.AI/DaprAIClient.cs b/src/Dapr.AI/DaprAIClient.cs index a2fd2255f..37db73147 100644 --- a/src/Dapr.AI/DaprAIClient.cs +++ b/src/Dapr.AI/DaprAIClient.cs @@ -12,13 +12,14 @@ // ------------------------------------------------------------------------ using Dapr.AI.Conversation; +using Dapr.Common; namespace Dapr.AI; /// /// The base implementation of a Dapr AI client. /// -public abstract class DaprAIClient +public abstract class DaprAIClient : IDaprClient { /// /// Sends various inputs to the large language model via the Conversational building block on the Dapr sidecar. diff --git a/src/Dapr.AI/Extensions/IDaprAiServiceBuilder.cs b/src/Dapr.AI/Extensions/IDaprAiServiceBuilder.cs index 8a0a80c2c..98a776526 100644 --- a/src/Dapr.AI/Extensions/IDaprAiServiceBuilder.cs +++ b/src/Dapr.AI/Extensions/IDaprAiServiceBuilder.cs @@ -11,6 +11,7 @@ // limitations under the License. // ------------------------------------------------------------------------ +using Dapr.Common; using Microsoft.Extensions.DependencyInjection; namespace Dapr.AI.Extensions; @@ -18,7 +19,7 @@ namespace Dapr.AI.Extensions; /// /// Responsible for registering Dapr AI service functionality. /// -public interface IDaprAiServiceBuilder +public interface IDaprAiServiceBuilder : IDaprServiceBuilder { /// /// The registered services on the builder. From cd24dac0762d6073030ff24fc91baa09d321532c Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 02:59:37 -0500 Subject: [PATCH 03/16] Updating IDaprClient to implement IDisposable instead of putting it on each client type Signed-off-by: Whit Waldo --- src/Dapr.Common/IDaprClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dapr.Common/IDaprClient.cs b/src/Dapr.Common/IDaprClient.cs index a37a06760..001cc17c7 100644 --- a/src/Dapr.Common/IDaprClient.cs +++ b/src/Dapr.Common/IDaprClient.cs @@ -16,4 +16,4 @@ namespace Dapr.Common; /// /// Base interface for any of the specific Dapr clients. /// -public interface IDaprClient; +public interface IDaprClient : IDisposable; From 1c5a377923a344d7b64947af83ccf5d5817b80cc Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 02:59:59 -0500 Subject: [PATCH 04/16] Added IDisposable implementation Signed-off-by: Whit Waldo --- .../Conversation/DaprConversationClient.cs | 9 +++++++++ src/Dapr.AI/DaprAIClient.cs | 20 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/Dapr.AI/Conversation/DaprConversationClient.cs b/src/Dapr.AI/Conversation/DaprConversationClient.cs index ed33e6774..5895a297c 100644 --- a/src/Dapr.AI/Conversation/DaprConversationClient.cs +++ b/src/Dapr.AI/Conversation/DaprConversationClient.cs @@ -117,4 +117,13 @@ public override async Task ConverseAsync(string daprCo return new DaprConversationResponse(outputs); } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.HttpClient.Dispose(); + } + } } diff --git a/src/Dapr.AI/DaprAIClient.cs b/src/Dapr.AI/DaprAIClient.cs index 37db73147..bd3e45416 100644 --- a/src/Dapr.AI/DaprAIClient.cs +++ b/src/Dapr.AI/DaprAIClient.cs @@ -21,6 +21,8 @@ namespace Dapr.AI; /// public abstract class DaprAIClient : IDaprClient { + private bool disposed; + /// /// Sends various inputs to the large language model via the Conversational building block on the Dapr sidecar. /// @@ -32,4 +34,22 @@ public abstract class DaprAIClient : IDaprClient public abstract Task ConverseAsync(string daprConversationComponentName, IReadOnlyList inputs, ConversationOptions? options = null, CancellationToken cancellationToken = default); + + /// + public void Dispose() + { + if (!this.disposed) + { + Dispose(disposing: true); + this.disposed = true; + } + } + + /// + /// Disposes the resources associated with the object. + /// + /// true if called by a call to the Dispose method; otherwise false. + protected virtual void Dispose(bool disposing) + { + } } From 4d3cd836a52a3dfb359dd1e11e61677141fe7609 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 03:15:52 -0500 Subject: [PATCH 05/16] Tweaks to the generic AddDaprClient signature to remove unnecessary empty constructor overload in client builders. Signed-off-by: Whit Waldo --- .../Conversation/DaprConversationClientBuilder.cs | 13 ++----------- .../Extensions/DaprClientBuilderExtensions.cs | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs b/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs index 58a601265..6ed457257 100644 --- a/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs +++ b/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs @@ -25,20 +25,11 @@ public sealed class DaprConversationClientBuilder : DaprGenericClientBuilder /// Used to initialize a new instance of the . /// - /// - public DaprConversationClientBuilder(IConfiguration? configuration) : base(configuration) + /// An optional to configure the client with. + public DaprConversationClientBuilder(IConfiguration? configuration = null) : base(configuration) { } - /// - /// - /// - /// - public DaprConversationClientBuilder() : base(null) - { - throw new NotImplementedException(); - } - /// /// Builds the client instance from the properties of the builder. /// diff --git a/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs b/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs index 2ee0ff3f1..52ac97b3d 100644 --- a/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs +++ b/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs @@ -39,7 +39,7 @@ internal static TBuilderInterface AddDaprClient, new() + where TClientBuilder : DaprGenericClientBuilder { ArgumentNullException.ThrowIfNull(services, nameof(services)); From 7feb78c916ac4ed37cf4c9cdc90f12cf3eb3ca8f Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 03:16:25 -0500 Subject: [PATCH 06/16] Modified Conversation client to utilize the GrpcClient pattern of the other clients for consistency. Signed-off-by: Whit Waldo --- .../Conversation/DaprConversationClient.cs | 78 ++++----------- .../DaprConversationClientBuilder.cs | 2 +- .../DaprConversationGrpcClient.cs | 95 +++++++++++++++++++ src/Dapr.AI/DaprAIClient.cs | 13 --- 4 files changed, 112 insertions(+), 76 deletions(-) create mode 100644 src/Dapr.AI/Conversation/DaprConversationGrpcClient.cs diff --git a/src/Dapr.AI/Conversation/DaprConversationClient.cs b/src/Dapr.AI/Conversation/DaprConversationClient.cs index 5895a297c..b4f62ad01 100644 --- a/src/Dapr.AI/Conversation/DaprConversationClient.cs +++ b/src/Dapr.AI/Conversation/DaprConversationClient.cs @@ -11,16 +11,26 @@ // limitations under the License. // ------------------------------------------------------------------------ -using Dapr.Common; -using Dapr.Common.Extensions; using P = Dapr.Client.Autogen.Grpc.v1; namespace Dapr.AI.Conversation; /// +/// /// Used to interact with the Dapr conversation building block. +/// Use to create a or register +/// for use with dependency injection via +/// DaprJobsServiceCollectionExtensions.AddDaprJobsClient. +/// +/// +/// Implementations of implement because the +/// client accesses network resources. For best performance, create a single long-lived client instance +/// and share it for the lifetime of the application. This is done for you if created via the DI extensions. Avoid +/// creating a disposing a client instance for each operation that the application performs - this can lead to socket +/// exhaustion and other problems. +/// /// -public sealed class DaprConversationClient : DaprAIClient +public abstract class DaprConversationClient : DaprAIClient { /// /// The HTTP client used by the client for calling the Dapr runtime. @@ -67,63 +77,7 @@ public DaprConversationClient(P.Dapr.DaprClient client, /// Optional options used to configure the conversation. /// Cancellation token. /// The response(s) provided by the LLM provider. - public override async Task ConverseAsync(string daprConversationComponentName, IReadOnlyList inputs, ConversationOptions? options = null, - CancellationToken cancellationToken = default) - { - var request = new P.ConversationRequest - { - Name = daprConversationComponentName - }; - - if (options is not null) - { - if (options.ConversationId is not null) - { - request.ContextID = options.ConversationId; - } - - request.ScrubPII = options.ScrubPII; - - foreach (var (key, value) in options.Metadata) - { - request.Metadata.Add(key, value); - } - - foreach (var (key, value) in options.Parameters) - { - request.Parameters.Add(key, value); - } - } - - foreach (var input in inputs) - { - request.Inputs.Add(new P.ConversationInput - { - ScrubPII = input.ScrubPII, - Content = input.Content, - Role = input.Role.GetValueFromEnumMember() - }); - } - - var grpCCallOptions = - DaprClientUtilities.ConfigureGrpcCallOptions(typeof(DaprConversationClient).Assembly, this.DaprApiToken, - cancellationToken); - - var result = await Client.ConverseAlpha1Async(request, grpCCallOptions).ConfigureAwait(false); - var outputs = result.Outputs.Select(output => new DaprConversationResult(output.Result) - { - Parameters = output.Parameters.ToDictionary(kvp => kvp.Key, parameter => parameter.Value) - }).ToList(); - - return new DaprConversationResponse(outputs); - } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - this.HttpClient.Dispose(); - } - } + public abstract Task ConverseAsync(string daprConversationComponentName, + IReadOnlyList inputs, ConversationOptions? options = null, + CancellationToken cancellationToken = default); } diff --git a/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs b/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs index 6ed457257..88cf3b2a9 100644 --- a/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs +++ b/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs @@ -41,6 +41,6 @@ public override DaprConversationClient Build() { var daprClientDependencies = BuildDaprClientDependencies(typeof(DaprConversationClient).Assembly); var client = new Autogenerated.DaprClient(daprClientDependencies.channel); - return new DaprConversationClient(client, daprClientDependencies.httpClient, daprClientDependencies.daprApiToken); + return new DaprConversationGrpcClient(client, daprClientDependencies.httpClient, daprClientDependencies.daprApiToken); } } diff --git a/src/Dapr.AI/Conversation/DaprConversationGrpcClient.cs b/src/Dapr.AI/Conversation/DaprConversationGrpcClient.cs new file mode 100644 index 000000000..6a1a5f438 --- /dev/null +++ b/src/Dapr.AI/Conversation/DaprConversationGrpcClient.cs @@ -0,0 +1,95 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Common; +using Dapr.Common.Extensions; +using Autogenerated = Dapr.Client.Autogen.Grpc.v1; + +namespace Dapr.AI.Conversation; + +/// +/// Used to initialize a new instance of a . +/// +/// The Dapr client. +/// The HTTP client used by the client for calling the Dapr runtime. +/// An optional token required to send requests to the Dapr sidecar. +internal sealed class DaprConversationGrpcClient(Autogenerated.Dapr.DaprClient client, HttpClient httpClient, string? daprApiToken = null) : DaprConversationClient(client, httpClient, daprApiToken: daprApiToken) +{ + /// + /// Sends various inputs to the large language model via the Conversational building block on the Dapr sidecar. + /// + /// The name of the Dapr conversation component. + /// The input values to send. + /// Optional options used to configure the conversation. + /// Cancellation token. + /// The response(s) provided by the LLM provider. + public override async Task ConverseAsync(string daprConversationComponentName, IReadOnlyList inputs, ConversationOptions? options = null, + CancellationToken cancellationToken = default) + { + var request = new Autogenerated.ConversationRequest + { + Name = daprConversationComponentName + }; + + if (options is not null) + { + if (options.ConversationId is not null) + { + request.ContextID = options.ConversationId; + } + + request.ScrubPII = options.ScrubPII; + + foreach (var (key, value) in options.Metadata) + { + request.Metadata.Add(key, value); + } + + foreach (var (key, value) in options.Parameters) + { + request.Parameters.Add(key, value); + } + } + + foreach (var input in inputs) + { + request.Inputs.Add(new Autogenerated.ConversationInput + { + ScrubPII = input.ScrubPII, + Content = input.Content, + Role = input.Role.GetValueFromEnumMember() + }); + } + + var grpCCallOptions = + DaprClientUtilities.ConfigureGrpcCallOptions(typeof(DaprConversationClient).Assembly, this.DaprApiToken, + cancellationToken); + + var result = await Client.ConverseAlpha1Async(request, grpCCallOptions).ConfigureAwait(false); + var outputs = result.Outputs.Select(output => new DaprConversationResult(output.Result) + { + Parameters = output.Parameters.ToDictionary(kvp => kvp.Key, parameter => parameter.Value) + }).ToList(); + + return new DaprConversationResponse(outputs); + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.HttpClient.Dispose(); + } + } +} diff --git a/src/Dapr.AI/DaprAIClient.cs b/src/Dapr.AI/DaprAIClient.cs index bd3e45416..ae029ece3 100644 --- a/src/Dapr.AI/DaprAIClient.cs +++ b/src/Dapr.AI/DaprAIClient.cs @@ -11,7 +11,6 @@ // limitations under the License. // ------------------------------------------------------------------------ -using Dapr.AI.Conversation; using Dapr.Common; namespace Dapr.AI; @@ -23,18 +22,6 @@ public abstract class DaprAIClient : IDaprClient { private bool disposed; - /// - /// Sends various inputs to the large language model via the Conversational building block on the Dapr sidecar. - /// - /// The name of the Dapr conversation component. - /// The input values to send. - /// Optional options used to configure the conversation. - /// Cancellation token. - /// The response(s) provided by the LLM provider. - public abstract Task ConverseAsync(string daprConversationComponentName, - IReadOnlyList inputs, ConversationOptions? options = null, - CancellationToken cancellationToken = default); - /// public void Dispose() { From 9188fc6957582c83f131ae179ba44f11f71f0f4d Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 03:20:49 -0500 Subject: [PATCH 07/16] Updated Dapr.Jobs to use generic builder extensions Signed-off-by: Whit Waldo --- src/Dapr.Jobs/DaprJobsClient.cs | 3 +- .../DaprJobsServiceCollectionExtensions.cs | 47 +++---------------- src/Dapr.Jobs/IDaprJobsBuilder.cs | 28 +++++++++++ 3 files changed, 37 insertions(+), 41 deletions(-) create mode 100644 src/Dapr.Jobs/IDaprJobsBuilder.cs diff --git a/src/Dapr.Jobs/DaprJobsClient.cs b/src/Dapr.Jobs/DaprJobsClient.cs index 4dd4abd70..9afe631a1 100644 --- a/src/Dapr.Jobs/DaprJobsClient.cs +++ b/src/Dapr.Jobs/DaprJobsClient.cs @@ -11,6 +11,7 @@ // limitations under the License. // ------------------------------------------------------------------------ +using Dapr.Common; using Dapr.Jobs.Models; using Dapr.Jobs.Models.Responses; @@ -31,7 +32,7 @@ namespace Dapr.Jobs; /// exhaustion and other problems. /// /// -public abstract class DaprJobsClient : IDisposable +public abstract class DaprJobsClient : IDaprClient { private bool disposed; diff --git a/src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs b/src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs index 03540aae1..2383ee665 100644 --- a/src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs +++ b/src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs @@ -11,9 +11,8 @@ // limitations under the License. // ------------------------------------------------------------------------ -using Microsoft.Extensions.Configuration; +using Dapr.Common.Extensions; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; namespace Dapr.Jobs.Extensions; @@ -25,45 +24,13 @@ public static class DaprJobsServiceCollectionExtensions /// /// Adds Dapr Jobs client support to the service collection. /// - /// The . + /// The . /// Optionally allows greater configuration of the using injected services. /// The lifetime of the registered services. /// - public static IServiceCollection AddDaprJobsClient(this IServiceCollection serviceCollection, Action? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton) - - { - ArgumentNullException.ThrowIfNull(serviceCollection, nameof(serviceCollection)); - - //Register the IHttpClientFactory implementation - serviceCollection.AddHttpClient(); - - var registration = new Func(serviceProvider => - { - var httpClientFactory = serviceProvider.GetRequiredService(); - var configuration = serviceProvider.GetService(); - - var builder = new DaprJobsClientBuilder(configuration); - builder.UseHttpClientFactory(httpClientFactory); - - configure?.Invoke(serviceProvider, builder); - - return builder.Build(); - }); - - switch (lifetime) - { - case ServiceLifetime.Scoped: - serviceCollection.TryAddScoped(registration); - break; - case ServiceLifetime.Transient: - serviceCollection.TryAddTransient(registration); - break; - case ServiceLifetime.Singleton: - default: - serviceCollection.TryAddSingleton(registration); - break; - } - - return serviceCollection; - } + public static IDaprJobsBuilder AddDaprJobsClient( + this IServiceCollection services, + Action? configure = null, + ServiceLifetime lifetime = ServiceLifetime.Singleton) => + services.AddDaprClient(configure, lifetime); } diff --git a/src/Dapr.Jobs/IDaprJobsBuilder.cs b/src/Dapr.Jobs/IDaprJobsBuilder.cs new file mode 100644 index 000000000..2dada3234 --- /dev/null +++ b/src/Dapr.Jobs/IDaprJobsBuilder.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Common; +using Microsoft.Extensions.DependencyInjection; + +namespace Dapr.Jobs; + +/// +/// Responsible for registering Dapr Jobs service functionality. +/// +public interface IDaprJobsBuilder : IDaprServiceBuilder +{ + /// + /// The registered services on the builder. + /// + public IServiceCollection Services { get; } +} From d32634291354c81f9cd8d749baf7ab88a41c996e Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 03:32:52 -0500 Subject: [PATCH 08/16] Refactored Dapr.Messaging for consistency with other Dapr clients (e.g. implements IDisposable, primary constructors, use of generic service registration extension) Signed-off-by: Whit Waldo --- src/Dapr.Messaging/IDaprMessagingBuilder.cs | 21 +++++++ .../DaprPublishSubscribeClient.cs | 49 ++++++++++++++++- .../DaprPublishSubscribeGrpcClient.cs | 55 +++++++------------ ...ishSubscribeServiceCollectionExtensions.cs | 45 +++------------ .../PublishSubscribe/IDaprPubSubBuilder.cs | 19 +++++++ 5 files changed, 115 insertions(+), 74 deletions(-) create mode 100644 src/Dapr.Messaging/IDaprMessagingBuilder.cs create mode 100644 src/Dapr.Messaging/PublishSubscribe/IDaprPubSubBuilder.cs diff --git a/src/Dapr.Messaging/IDaprMessagingBuilder.cs b/src/Dapr.Messaging/IDaprMessagingBuilder.cs new file mode 100644 index 000000000..79e5bf3f5 --- /dev/null +++ b/src/Dapr.Messaging/IDaprMessagingBuilder.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Common; + +namespace Dapr.Messaging; + +/// +/// Provides a root builder for the Dapr Messaging operations facilitating a more fluent-style registration. +/// +public interface IDaprMessagingBuilder : IDaprServiceBuilder; diff --git a/src/Dapr.Messaging/PublishSubscribe/DaprPublishSubscribeClient.cs b/src/Dapr.Messaging/PublishSubscribe/DaprPublishSubscribeClient.cs index 8fbec2dfe..cbf25ea49 100644 --- a/src/Dapr.Messaging/PublishSubscribe/DaprPublishSubscribeClient.cs +++ b/src/Dapr.Messaging/PublishSubscribe/DaprPublishSubscribeClient.cs @@ -11,13 +11,42 @@ // limitations under the License. // ------------------------------------------------------------------------ +using Dapr.Common; +using P = Dapr.Client.Autogen.Grpc.v1; + namespace Dapr.Messaging.PublishSubscribe; /// /// The base implementation of a Dapr pub/sub client. /// -public abstract class DaprPublishSubscribeClient +public abstract class DaprPublishSubscribeClient(P.Dapr.DaprClient client, HttpClient httpClient, string? daprApiToken = null) : IDaprClient { + private bool disposed; + + /// + /// The HTTP client used by the client for calling the Dapr runtime. + /// + /// + /// Property exposed for testing purposes. + /// + internal protected readonly HttpClient HttpClient = httpClient; + + /// + /// The Dapr API token value. + /// + /// + /// Property exposed for testing purposes. + /// + internal protected readonly string? DaprApiToken = daprApiToken; + + /// + /// The autogenerated Dapr client. + /// + /// + /// Property exposed for testing purposes. + /// + internal protected readonly P.Dapr.DaprClient Client = client; + /// /// Dynamically subscribes to a Publish/Subscribe component and topic. /// @@ -28,4 +57,22 @@ public abstract class DaprPublishSubscribeClient /// Cancellation token. /// public abstract Task SubscribeAsync(string pubSubName, string topicName, DaprSubscriptionOptions options, TopicMessageHandler messageHandler, CancellationToken cancellationToken = default); + + /// + public void Dispose() + { + if (!this.disposed) + { + Dispose(disposing: true); + this.disposed = true; + } + } + + /// + /// Disposes the resources associated with the object. + /// + /// true if called by a call to the Dispose method; otherwise false. + protected virtual void Dispose(bool disposing) + { + } } diff --git a/src/Dapr.Messaging/PublishSubscribe/DaprPublishSubscribeGrpcClient.cs b/src/Dapr.Messaging/PublishSubscribe/DaprPublishSubscribeGrpcClient.cs index 33ef05494..ace670df3 100644 --- a/src/Dapr.Messaging/PublishSubscribe/DaprPublishSubscribeGrpcClient.cs +++ b/src/Dapr.Messaging/PublishSubscribe/DaprPublishSubscribeGrpcClient.cs @@ -18,40 +18,11 @@ namespace Dapr.Messaging.PublishSubscribe; /// /// A client for interacting with the Dapr endpoints. /// -internal sealed class DaprPublishSubscribeGrpcClient : DaprPublishSubscribeClient +internal sealed class DaprPublishSubscribeGrpcClient( + P.DaprClient client, + HttpClient httpClient, + string? daprApiToken = null) : DaprPublishSubscribeClient(client, httpClient, daprApiToken) { - /// - /// The HTTP client used by the client for calling the Dapr runtime. - /// - /// - /// Property exposed for testing purposes. - /// - internal readonly HttpClient HttpClient; - /// - /// The Dapr API token value. - /// - /// - /// Property exposed for testing purposes. - /// - internal readonly string? DaprApiToken; - /// - /// The autogenerated Dapr client. - /// - /// - /// Property exposed for testing purposes. - /// - private readonly P.DaprClient Client; - - /// - /// Creates a new instance of a - /// - public DaprPublishSubscribeGrpcClient(P.DaprClient client, HttpClient httpClient, string? daprApiToken) - { - this.Client = client; - this.HttpClient = httpClient; - this.DaprApiToken = daprApiToken; - } - /// /// Dynamically subscribes to a Publish/Subscribe component and topic. /// @@ -61,11 +32,25 @@ public DaprPublishSubscribeGrpcClient(P.DaprClient client, HttpClient httpClient /// The delegate reflecting the action to take upon messages received by the subscription. /// Cancellation token. /// - public override async Task SubscribeAsync(string pubSubName, string topicName, DaprSubscriptionOptions options, TopicMessageHandler messageHandler, CancellationToken cancellationToken = default) + public override async Task SubscribeAsync( + string pubSubName, + string topicName, + DaprSubscriptionOptions options, + TopicMessageHandler messageHandler, + CancellationToken cancellationToken = default) { - var receiver = new PublishSubscribeReceiver(pubSubName, topicName, options, messageHandler, this.Client); + var receiver = new PublishSubscribeReceiver(pubSubName, topicName, options, messageHandler, Client); await receiver.SubscribeAsync(cancellationToken); return receiver; } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.HttpClient.Dispose(); + } + } } diff --git a/src/Dapr.Messaging/PublishSubscribe/Extensions/PublishSubscribeServiceCollectionExtensions.cs b/src/Dapr.Messaging/PublishSubscribe/Extensions/PublishSubscribeServiceCollectionExtensions.cs index 3d9e3ee8d..e459d73f1 100644 --- a/src/Dapr.Messaging/PublishSubscribe/Extensions/PublishSubscribeServiceCollectionExtensions.cs +++ b/src/Dapr.Messaging/PublishSubscribe/Extensions/PublishSubscribeServiceCollectionExtensions.cs @@ -1,6 +1,5 @@ -using Microsoft.Extensions.Configuration; +using Dapr.Common.Extensions; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; namespace Dapr.Messaging.PublishSubscribe.Extensions; @@ -16,40 +15,10 @@ public static class PublishSubscribeServiceCollectionExtensions /// Optionally allows greater configuration of the using injected services. /// The lifetime of the registered services. /// - public static IServiceCollection AddDaprPubSubClient(this IServiceCollection services, Action? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton) - { - ArgumentNullException.ThrowIfNull(services, nameof(services)); - - //Register the IHttpClientFactory implementation - services.AddHttpClient(); - - var registration = new Func(serviceProvider => - { - var httpClientFactory = serviceProvider.GetRequiredService(); - var configuration = serviceProvider.GetService(); - - var builder = new DaprPublishSubscribeClientBuilder(configuration); - builder.UseHttpClientFactory(httpClientFactory); - - configure?.Invoke(serviceProvider, builder); - - return builder.Build(); - }); - - switch (lifetime) - { - case ServiceLifetime.Scoped: - services.TryAddScoped(registration); - break; - case ServiceLifetime.Transient: - services.TryAddTransient(registration); - break; - default: - case ServiceLifetime.Singleton: - services.TryAddSingleton(registration); - break; - } - - return services; - } + public static IDaprPubSubBuilder AddDaprPubSubClient( + this IServiceCollection services, + Action? configure = null, + ServiceLifetime lifetime = ServiceLifetime.Singleton) => + services.AddDaprClient( + configure, lifetime); } diff --git a/src/Dapr.Messaging/PublishSubscribe/IDaprPubSubBuilder.cs b/src/Dapr.Messaging/PublishSubscribe/IDaprPubSubBuilder.cs new file mode 100644 index 000000000..23e6df1f6 --- /dev/null +++ b/src/Dapr.Messaging/PublishSubscribe/IDaprPubSubBuilder.cs @@ -0,0 +1,19 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Messaging.PublishSubscribe; + +/// +/// Provides a Dapr client builder specific for Publish/Subscribe operations. +/// +public interface IDaprPubSubBuilder : IDaprMessagingBuilder; From 22d30e0438e533a64c7b5dc2a6fd4dd78645565f Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 03:34:17 -0500 Subject: [PATCH 09/16] Fixed Dapr.Common unit tests Signed-off-by: Whit Waldo --- test/Dapr.Common.Test/DaprGenericClientBuilderTest.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/Dapr.Common.Test/DaprGenericClientBuilderTest.cs b/test/Dapr.Common.Test/DaprGenericClientBuilderTest.cs index d28b40058..a783a127b 100644 --- a/test/Dapr.Common.Test/DaprGenericClientBuilderTest.cs +++ b/test/Dapr.Common.Test/DaprGenericClientBuilderTest.cs @@ -83,12 +83,19 @@ public void DaprGenericClientBuilder_ShouldUpdateTimeout() Assert.Equal(timeout, builder.Timeout); } - private class SampleDaprGenericClientBuilder : DaprGenericClientBuilder + private sealed class SampleDaprGenericClientBuilder : DaprGenericClientBuilder, IDaprClient { public override SampleDaprGenericClientBuilder Build() { // Implementation throw new NotImplementedException(); } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + } } } From 9137ac08aee6fbbb0f07f788cb6ebbdcc7cfb52c Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 04:53:55 -0500 Subject: [PATCH 10/16] Added and fixed unit tests for DaprClientBuilderExtensions.cs Signed-off-by: Whit Waldo --- src/Dapr.Common/Dapr.Common.csproj | 1 + .../Extensions/DaprClientBuilderExtensions.cs | 32 +++- src/Dapr.Common/IDaprServiceBuilder.cs | 10 +- test/Dapr.Common.Test/Dapr.Common.Test.csproj | 1 + .../DaprClientBuilderExtensionsTests.cs | 178 ++++++++++++++++++ 5 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 test/Dapr.Common.Test/Extensions/DaprClientBuilderExtensionsTests.cs diff --git a/src/Dapr.Common/Dapr.Common.csproj b/src/Dapr.Common/Dapr.Common.csproj index dac090d3e..03d23f9bd 100644 --- a/src/Dapr.Common/Dapr.Common.csproj +++ b/src/Dapr.Common/Dapr.Common.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs b/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs index 52ac97b3d..1070133c2 100644 --- a/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs +++ b/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs @@ -14,6 +14,7 @@ using System.Reflection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Autogenerated = Dapr.Client.Autogen.Grpc.v1.Dapr; namespace Dapr.Common.Extensions; @@ -25,23 +26,39 @@ internal static class DaprClientBuilderExtensions /// /// Registers the necessary base functionality for a Dapr client. /// - /// The type of the client builder interface. - /// The concrete Dapr client type being created. + /// The type of the client builder interface. + /// The abstract Dapr client type being created. + /// The concrete Dapr client type being created. /// The type of the static builder used to build the Dapr ot client. /// The collection of services to which the Dapr client and associated services are being registered. /// An optional method used to provide additional configurations to the client builder. /// The registered lifetime of the Dapr client. /// The collection of DI-registered services. //internal static TBuilderInterface AddDaprClient( - internal static TBuilderInterface AddDaprClient( + internal static TServiceBuilder AddDaprClient( this IServiceCollection services, Action? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton) - where TBuilderInterface : class, IDaprServiceBuilder where TClient : class, IDaprClient + where TConcreteClient : TClient + where TServiceBuilder : class, IDaprServiceBuilder where TClientBuilder : DaprGenericClientBuilder { ArgumentNullException.ThrowIfNull(services, nameof(services)); + + //Ensure that TConcreteClient is a concrete class + if (typeof(TConcreteClient).IsInterface || typeof(TConcreteClient).IsAbstract) + { + throw new ArgumentException($"{typeof(TConcreteClient).Name} must be a concrete class", + nameof(TConcreteClient)); + } + + //Ensure that TServiceBuilder is a concrete class + if (typeof(TServiceBuilder).IsInterface || typeof(TServiceBuilder).IsAbstract) + { + throw new ArgumentException($"{typeof(TServiceBuilder).Name} must be a concrete class", + nameof(TServiceBuilder)); + } services.AddHttpClient(); @@ -52,13 +69,14 @@ internal static TBuilderInterface AddDaprClient /// Responsible for registering Dapr services with dependency injection. /// -public interface IDaprServiceBuilder; +public interface IDaprServiceBuilder +{ + /// + /// The registered services on the builder. + /// + public IServiceCollection Services { get; } +} diff --git a/test/Dapr.Common.Test/Dapr.Common.Test.csproj b/test/Dapr.Common.Test/Dapr.Common.Test.csproj index 22120719c..730371053 100644 --- a/test/Dapr.Common.Test/Dapr.Common.Test.csproj +++ b/test/Dapr.Common.Test/Dapr.Common.Test.csproj @@ -7,6 +7,7 @@ + all diff --git a/test/Dapr.Common.Test/Extensions/DaprClientBuilderExtensionsTests.cs b/test/Dapr.Common.Test/Extensions/DaprClientBuilderExtensionsTests.cs new file mode 100644 index 000000000..91659df77 --- /dev/null +++ b/test/Dapr.Common.Test/Extensions/DaprClientBuilderExtensionsTests.cs @@ -0,0 +1,178 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +#nullable enable +using System; +using System.Net.Http; +using Dapr.Common.Extensions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; +using Autogenerated = Dapr.Client.Autogen.Grpc.v1; + +namespace Dapr.Common.Test.Extensions; + +public sealed class DaprClientBuilderExtensionsTests +{ + [Fact] + public void AddDaprClient_CallsConfigureAction() + { + var services = new ServiceCollection(); + var configurationMock = new Mock(); + var serviceProviderMock = new Mock(); + serviceProviderMock.Setup(sp => sp.GetService(typeof(IConfiguration))).Returns(configurationMock.Object); + + var configureCalled = false; + + services.AddDaprClient(( + _, + _) => + { + configureCalled = true; + }); + + var serviceProvider = services.BuildServiceProvider(); + var client = serviceProvider.GetRequiredService(); + + Assert.NotNull(client); + Assert.True(configureCalled); + } + + [Fact] + public void AddDaprClient_ThrowsIfServicesIsNull() + { +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. + IServiceCollection services = null; +#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type. + Assert.Throws(() => + // ReSharper disable once AssignNullToNotNullAttribute +#pragma warning disable CS8604 // Possible null reference argument. + services.AddDaprClient( +#pragma warning restore CS8604 // Possible null reference argument. + (_, _) => { }, ServiceLifetime.Singleton)); + } + + [Fact] + public void AddDaprClient_RegistersClientWithServiceCollection_Singleton() + { + var services = new ServiceCollection(); + var configurationMock = new Mock(); + var serviceProviderMock = new Mock(); + serviceProviderMock.Setup(sp => sp.GetService(typeof(IConfiguration))).Returns(configurationMock.Object); + + services.AddDaprClient(( + _, + _) => + { + }, ServiceLifetime.Singleton); + + var serviceProvider = services.BuildServiceProvider(); + var client1 = serviceProvider.GetRequiredService(); + var client2 = serviceProvider.GetRequiredService(); + + Assert.NotNull(client1); + Assert.NotNull(client2); + Assert.Same(client1, client2); //Singletons should return the same instance + } + + [Fact] + public void AddDaprClient_RegistersClientWithServiceCollection_Scoped() + { + var services = new ServiceCollection(); + var configurationMock = new Mock(); + var serviceProviderMock = new Mock(); + serviceProviderMock.Setup(sp => sp.GetService(typeof(IConfiguration))).Returns(configurationMock.Object); + + services.AddDaprClient(( + _, + _) => + { + }, ServiceLifetime.Transient); + + var serviceProvider = services.BuildServiceProvider(); + var client1 = serviceProvider.GetRequiredService(); + var client2 = serviceProvider.GetRequiredService(); + + Assert.NotNull(client1); + Assert.NotNull(client2); + Assert.NotSame(client1, client2); //Transient should return different instances + } + + [Fact] + public void AddDaprClient_RegistersClientWithServiceCollection_Transient() + { + var services = new ServiceCollection(); + var configurationMock = new Mock(); + var serviceProviderMock = new Mock(); + serviceProviderMock.Setup(sp => sp.GetService(typeof(IConfiguration))).Returns(configurationMock.Object); + + services.AddDaprClient(( + _, + _) => + { + }, ServiceLifetime.Scoped); + + var serviceProvider = services.BuildServiceProvider(); + + using var scope1 = serviceProvider.CreateScope(); + var client1 = scope1.ServiceProvider.GetRequiredService(); + var client2 = scope1.ServiceProvider.GetRequiredService(); + + Assert.NotNull(client1); + Assert.NotNull(client2); + Assert.Same(client1, client2); //Transient should return the same instance within the same scope + + using (var scope2 = serviceProvider.CreateScope()) + { + var client3 = scope2.ServiceProvider.GetRequiredService(); + Assert.NotNull(client3); + Assert.NotSame(client1, client3); //Scoped should return different instances across different scopes + } + } + + private interface IDaprTestServiceBuilder : IDaprServiceBuilder; + + private sealed class DaprTestBuilder(IServiceCollection services) : IDaprTestServiceBuilder + { + /// + /// The registered services on the builder. + /// + public IServiceCollection Services { get; } = services; + } + + private sealed class DaprTestClientBuilder(IConfiguration? configuration = null) : DaprGenericClientBuilder(configuration) + { + public override DaprTestClient Build() + { + throw new NotImplementedException(); + } + } + + private abstract class DaprTestClient(Autogenerated.Dapr.DaprClient client, HttpClient httpClient, string? daprApiToken = null) : IDaprClient + { + internal readonly HttpClient HttpClient = httpClient; + internal readonly string? DaprApiToken = daprApiToken; + internal Autogenerated.Dapr.DaprClient Client { get; } = client; + + public void Dispose() + { + // TODO release managed resources here + } + } + + private sealed class DaprTestGrpcClient( + Autogenerated.Dapr.DaprClient client, + HttpClient httpClient, + string? daprApiToken = null) : DaprTestClient(client, httpClient, daprApiToken); +} From 9e505918fffd42fa4ff2f30ed0fc40e55f166539 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 04:55:21 -0500 Subject: [PATCH 11/16] Tweak to use same import reference as other clients for consistency Signed-off-by: Whit Waldo --- src/Dapr.AI/Conversation/DaprConversationClient.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Dapr.AI/Conversation/DaprConversationClient.cs b/src/Dapr.AI/Conversation/DaprConversationClient.cs index b4f62ad01..b081427b0 100644 --- a/src/Dapr.AI/Conversation/DaprConversationClient.cs +++ b/src/Dapr.AI/Conversation/DaprConversationClient.cs @@ -11,7 +11,7 @@ // limitations under the License. // ------------------------------------------------------------------------ -using P = Dapr.Client.Autogen.Grpc.v1; +using Autogenerated = Dapr.Client.Autogen.Grpc.v1.Dapr; namespace Dapr.AI.Conversation; @@ -52,7 +52,7 @@ public abstract class DaprConversationClient : DaprAIClient /// /// Property exposed for testing purposes. /// - internal P.Dapr.DaprClient Client { get; } + internal Autogenerated.DaprClient Client { get; } /// /// Used to initialize a new instance of a . @@ -60,7 +60,7 @@ public abstract class DaprConversationClient : DaprAIClient /// The Dapr client. /// The HTTP client used by the client for calling the Dapr runtime. /// An optional token required to send requests to the Dapr sidecar. - public DaprConversationClient(P.Dapr.DaprClient client, + protected DaprConversationClient(Autogenerated.DaprClient client, HttpClient httpClient, string? daprApiToken = null) { From 892c1dac0c537a14b64c115ee7dbb66a348bf496 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 04:55:56 -0500 Subject: [PATCH 12/16] Renamed to AddDaprConversationClient to remain consistent with 1.14 naming - no need to introduce a breaking change here. Signed-off-by: Whit Waldo --- .../Extensions/DaprAiConversationBuilderExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs b/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs index 21567ded5..9af0c81cf 100644 --- a/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs +++ b/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs @@ -24,9 +24,9 @@ public static class DaprAiConversationBuilderExtensions /// /// Registers the necessary functionality for the Dapr AI Conversation functionality. /// - public static IDaprAiConversationBuilder AddDaprAiConversationClient( + public static IDaprAiConversationBuilder AddDaprConversationClient( this IServiceCollection services, Action? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton) => services - .AddDaprClient(configure, lifetime); + .AddDaprClient(configure, lifetime); } From 3c3307cf87fb3c001082ddf2e03def26b7b2e3d9 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 04:56:47 -0500 Subject: [PATCH 13/16] Tweaks made to each service collection extension method to accommodate issues identified with Dapr.Common.Tests unit tests Signed-off-by: Whit Waldo --- src/Dapr.AI/Extensions/IDaprAiServiceBuilder.cs | 8 +------- .../Extensions/DaprJobsServiceCollectionExtensions.cs | 2 +- .../PublishSubscribeServiceCollectionExtensions.cs | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Dapr.AI/Extensions/IDaprAiServiceBuilder.cs b/src/Dapr.AI/Extensions/IDaprAiServiceBuilder.cs index 98a776526..d7d84d1fb 100644 --- a/src/Dapr.AI/Extensions/IDaprAiServiceBuilder.cs +++ b/src/Dapr.AI/Extensions/IDaprAiServiceBuilder.cs @@ -19,10 +19,4 @@ namespace Dapr.AI.Extensions; /// /// Responsible for registering Dapr AI service functionality. /// -public interface IDaprAiServiceBuilder : IDaprServiceBuilder -{ - /// - /// The registered services on the builder. - /// - public IServiceCollection Services { get; } -} +public interface IDaprAiServiceBuilder : IDaprServiceBuilder; diff --git a/src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs b/src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs index 2383ee665..d68820587 100644 --- a/src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs +++ b/src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs @@ -32,5 +32,5 @@ public static IDaprJobsBuilder AddDaprJobsClient( this IServiceCollection services, Action? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton) => - services.AddDaprClient(configure, lifetime); + services.AddDaprClient(configure, lifetime); } diff --git a/src/Dapr.Messaging/PublishSubscribe/Extensions/PublishSubscribeServiceCollectionExtensions.cs b/src/Dapr.Messaging/PublishSubscribe/Extensions/PublishSubscribeServiceCollectionExtensions.cs index e459d73f1..d7c256000 100644 --- a/src/Dapr.Messaging/PublishSubscribe/Extensions/PublishSubscribeServiceCollectionExtensions.cs +++ b/src/Dapr.Messaging/PublishSubscribe/Extensions/PublishSubscribeServiceCollectionExtensions.cs @@ -19,6 +19,6 @@ public static IDaprPubSubBuilder AddDaprPubSubClient( this IServiceCollection services, Action? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton) => - services.AddDaprClient( + services.AddDaprClient( configure, lifetime); } From 908ec94e200bd4682b0a8695a69e5db58041e620 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 04:58:58 -0500 Subject: [PATCH 14/16] Applied minor change to improve unit test Signed-off-by: Whit Waldo --- .../Conversation/DaprConversationClientBuilderTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Dapr.AI.Test/Conversation/DaprConversationClientBuilderTest.cs b/test/Dapr.AI.Test/Conversation/DaprConversationClientBuilderTest.cs index 901c4b656..6546a0802 100644 --- a/test/Dapr.AI.Test/Conversation/DaprConversationClientBuilderTest.cs +++ b/test/Dapr.AI.Test/Conversation/DaprConversationClientBuilderTest.cs @@ -28,6 +28,6 @@ public void Build_WithDefaultConfiguration_ShouldReturnNewInstanceOfDaprConversa // Assert Assert.NotNull(client); - Assert.IsType(client); + Assert.IsAssignableFrom(client); } } From 7a69c1f1c18c154bc9539627503924392ee4664a Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 05:21:38 -0500 Subject: [PATCH 15/16] Fixes to Dapr.Jobs to implement prior functionality + fix unit tests Signed-off-by: Whit Waldo --- src/Dapr.Jobs/DaprJobsClient.cs | 27 ++++++++++++++- src/Dapr.Jobs/DaprJobsGrpcClient.cs | 34 +------------------ src/Dapr.Jobs/Extensions/DaprJobsBuilder.cs | 28 +++++++++++++++ .../DaprJobsServiceCollectionExtensions.cs | 2 +- src/Dapr.Jobs/IDaprJobsBuilder.cs | 8 +---- ...aprJobsServiceCollectionExtensionsTests.cs | 16 ++++----- 6 files changed, 64 insertions(+), 51 deletions(-) create mode 100644 src/Dapr.Jobs/Extensions/DaprJobsBuilder.cs diff --git a/src/Dapr.Jobs/DaprJobsClient.cs b/src/Dapr.Jobs/DaprJobsClient.cs index 9afe631a1..2aecf816a 100644 --- a/src/Dapr.Jobs/DaprJobsClient.cs +++ b/src/Dapr.Jobs/DaprJobsClient.cs @@ -14,6 +14,7 @@ using Dapr.Common; using Dapr.Jobs.Models; using Dapr.Jobs.Models.Responses; +using Autogenerated = Dapr.Client.Autogen.Grpc.v1.Dapr; namespace Dapr.Jobs; @@ -32,10 +33,34 @@ namespace Dapr.Jobs; /// exhaustion and other problems. /// /// -public abstract class DaprJobsClient : IDaprClient +public abstract class DaprJobsClient(Autogenerated.DaprClient client, HttpClient httpClient, string? daprApiToken = null) : IDaprClient { private bool disposed; + /// + /// The HTTP client used by the client for calling the Dapr runtime. + /// + /// + /// Property exposed for testing purposes. + /// + internal readonly HttpClient HttpClient = httpClient; + + /// + /// The Dapr API token value. + /// + /// + /// Property exposed for testing purposes. + /// + internal readonly string? DaprApiToken = daprApiToken; + + /// + /// The autogenerated Dapr client. + /// + /// + /// Property exposed for testing purposes. + /// + internal Autogenerated.DaprClient Client { get; } = client; + /// /// Schedules a job with Dapr. /// diff --git a/src/Dapr.Jobs/DaprJobsGrpcClient.cs b/src/Dapr.Jobs/DaprJobsGrpcClient.cs index 8743aa350..182db48cf 100644 --- a/src/Dapr.Jobs/DaprJobsGrpcClient.cs +++ b/src/Dapr.Jobs/DaprJobsGrpcClient.cs @@ -24,40 +24,8 @@ namespace Dapr.Jobs; /// /// A client for interacting with the Dapr endpoints. /// -internal sealed class DaprJobsGrpcClient : DaprJobsClient +internal sealed class DaprJobsGrpcClient(Autogenerated.Dapr.DaprClient client, HttpClient httpClient, string? daprApiToken = null) : DaprJobsClient(client, httpClient, daprApiToken: daprApiToken) { - /// - /// The HTTP client used by the client for calling the Dapr runtime. - /// - /// - /// Property exposed for testing purposes. - /// - internal readonly HttpClient HttpClient; - /// - /// The Dapr API token value. - /// - /// - /// Property exposed for testing purposes. - /// - internal readonly string? DaprApiToken; - /// - /// The autogenerated Dapr client. - /// - /// - /// Property exposed for testing purposes. - /// - internal Autogenerated.Dapr.DaprClient Client { get; } - - internal DaprJobsGrpcClient( - Autogenerated.Dapr.DaprClient innerClient, - HttpClient httpClient, - string? daprApiToken) - { - this.Client = innerClient; - this.HttpClient = httpClient; - this.DaprApiToken = daprApiToken; - } - /// /// Schedules a job with Dapr. /// diff --git a/src/Dapr.Jobs/Extensions/DaprJobsBuilder.cs b/src/Dapr.Jobs/Extensions/DaprJobsBuilder.cs new file mode 100644 index 000000000..51bdb8985 --- /dev/null +++ b/src/Dapr.Jobs/Extensions/DaprJobsBuilder.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Microsoft.Extensions.DependencyInjection; + +namespace Dapr.Jobs.Extensions; + +/// +/// Used by the fluent registration builder to configure a Dapr Jobs client. +/// +/// +public sealed class DaprJobsBuilder(IServiceCollection services) : IDaprJobsBuilder +{ + /// + /// The registered services on the builder. + /// + public IServiceCollection Services { get; } = services; +} diff --git a/src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs b/src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs index d68820587..7eed80abc 100644 --- a/src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs +++ b/src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs @@ -32,5 +32,5 @@ public static IDaprJobsBuilder AddDaprJobsClient( this IServiceCollection services, Action? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton) => - services.AddDaprClient(configure, lifetime); + services.AddDaprClient(configure, lifetime); } diff --git a/src/Dapr.Jobs/IDaprJobsBuilder.cs b/src/Dapr.Jobs/IDaprJobsBuilder.cs index 2dada3234..d5211c5e5 100644 --- a/src/Dapr.Jobs/IDaprJobsBuilder.cs +++ b/src/Dapr.Jobs/IDaprJobsBuilder.cs @@ -19,10 +19,4 @@ namespace Dapr.Jobs; /// /// Responsible for registering Dapr Jobs service functionality. /// -public interface IDaprJobsBuilder : IDaprServiceBuilder -{ - /// - /// The registered services on the builder. - /// - public IServiceCollection Services { get; } -} +public interface IDaprJobsBuilder : IDaprServiceBuilder; diff --git a/test/Dapr.Jobs.Test/Extensions/DaprJobsServiceCollectionExtensionsTests.cs b/test/Dapr.Jobs.Test/Extensions/DaprJobsServiceCollectionExtensionsTests.cs index 28a8a0681..814e52794 100644 --- a/test/Dapr.Jobs.Test/Extensions/DaprJobsServiceCollectionExtensionsTests.cs +++ b/test/Dapr.Jobs.Test/Extensions/DaprJobsServiceCollectionExtensionsTests.cs @@ -47,21 +47,19 @@ public void AddDaprJobsClient_FromIConfiguration() } [Fact] - public void AddDaprJobsClient_RegistersDaprClientOnlyOnce() + public void AddDaprJobsClient_DaprClientRegistration_UseMostRecentVersion() { var services = new ServiceCollection(); - - var clientBuilder = new Action((sp, builder) => + services.AddDaprJobsClient((_, builder) => { - builder.UseDaprApiToken("abc"); - }); - + //Sets the API token value + builder.UseDaprApiToken("abcd1234"); + }); services.AddDaprJobsClient(); //Sets a default API token value of an empty string - services.AddDaprJobsClient(clientBuilder); //Sets the API token value - + var serviceProvider = services.BuildServiceProvider(); - var daprJobClient = serviceProvider.GetService() as DaprJobsGrpcClient; + var daprJobClient = serviceProvider.GetRequiredService() as DaprJobsGrpcClient; Assert.NotNull(daprJobClient!.HttpClient); Assert.False(daprJobClient.HttpClient.DefaultRequestHeaders.TryGetValues("dapr-api-token", out var _)); From 55efb5cfebc7416cfa1887abc22342ce01adbb6a Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 1 Apr 2025 05:26:34 -0500 Subject: [PATCH 16/16] Fixed remaining unit tests across clients Signed-off-by: Whit Waldo --- .../DaprAiConversationBuilderExtensions.cs | 2 +- .../Extensions/DaprPubSubBuilder.cs | 28 +++++++++++++++++++ ...ishSubscribeServiceCollectionExtensions.cs | 2 +- ...DaprAiConversationBuilderExtensionsTest.cs | 12 ++++---- 4 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 src/Dapr.Messaging/PublishSubscribe/Extensions/DaprPubSubBuilder.cs diff --git a/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs b/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs index 9af0c81cf..42bd91804 100644 --- a/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs +++ b/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs @@ -28,5 +28,5 @@ public static IDaprAiConversationBuilder AddDaprConversationClient( this IServiceCollection services, Action? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton) => services - .AddDaprClient(configure, lifetime); + .AddDaprClient(configure, lifetime); } diff --git a/src/Dapr.Messaging/PublishSubscribe/Extensions/DaprPubSubBuilder.cs b/src/Dapr.Messaging/PublishSubscribe/Extensions/DaprPubSubBuilder.cs new file mode 100644 index 000000000..2772df2fd --- /dev/null +++ b/src/Dapr.Messaging/PublishSubscribe/Extensions/DaprPubSubBuilder.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Microsoft.Extensions.DependencyInjection; + +namespace Dapr.Messaging.PublishSubscribe.Extensions; + +/// +/// Used by the fluent registration builder to configure a Dapr Publish/Subscribe client. +/// +/// +public sealed class DaprPubSubBuilder(IServiceCollection services) : IDaprPubSubBuilder +{ + /// + /// The registered services on the builder. + /// + public IServiceCollection Services { get; } = services; +} diff --git a/src/Dapr.Messaging/PublishSubscribe/Extensions/PublishSubscribeServiceCollectionExtensions.cs b/src/Dapr.Messaging/PublishSubscribe/Extensions/PublishSubscribeServiceCollectionExtensions.cs index d7c256000..954940e53 100644 --- a/src/Dapr.Messaging/PublishSubscribe/Extensions/PublishSubscribeServiceCollectionExtensions.cs +++ b/src/Dapr.Messaging/PublishSubscribe/Extensions/PublishSubscribeServiceCollectionExtensions.cs @@ -19,6 +19,6 @@ public static IDaprPubSubBuilder AddDaprPubSubClient( this IServiceCollection services, Action? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton) => - services.AddDaprClient( + services.AddDaprClient( configure, lifetime); } diff --git a/test/Dapr.AI.Test/Conversation/Extensions/DaprAiConversationBuilderExtensionsTest.cs b/test/Dapr.AI.Test/Conversation/Extensions/DaprAiConversationBuilderExtensionsTest.cs index 2ee321895..20f550f52 100644 --- a/test/Dapr.AI.Test/Conversation/Extensions/DaprAiConversationBuilderExtensionsTest.cs +++ b/test/Dapr.AI.Test/Conversation/Extensions/DaprAiConversationBuilderExtensionsTest.cs @@ -47,18 +47,16 @@ public void AddDaprConversationClient_FromIConfiguration() } [Fact] - public void AddDaprConversationClient_RegistersDaprClientOnlyOnce() + public void AddDaprConversationClient_RegistersDaprClient_UsesMostRecentRegistration() { var services = new ServiceCollection(); - var clientBuilder = new Action((sp, builder) => + services.AddDaprConversationClient((_, builder) => { - builder.UseDaprApiToken("abc"); - }); - + builder.UseDaprApiToken("abc123"); + }); //Sets the API token value services.AddDaprConversationClient(); //Sets a default API token value of an empty string - services.AddDaprConversationClient(clientBuilder); //Sets the API token value - + var serviceProvider = services.BuildServiceProvider(); var daprConversationClient = serviceProvider.GetService();