From 44ca6496d8c29e14df2122f6704d24b38a1755a4 Mon Sep 17 00:00:00 2001 From: Leon Mai Date: Thu, 30 Apr 2020 11:47:29 -0700 Subject: [PATCH] =?UTF-8?q?Move=20to=20new=20invoke=20proto,=20specify=20c?= =?UTF-8?q?ontenttype=20in=20response=20to=20the=20dapr=E2=80=A6=20(#295)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move to new invoke proto, specify contenttype in response to the dapr/config message, clarify sample * Update for additional proto changes * cr Co-authored-by: LM --- samples/AspNetCore/RoutingSample/Startup.cs | 18 ++- samples/Client/DaprClient/Program.cs | 94 +++++++---- .../RouterBuilderExtensions.cs | 3 +- src/Dapr.Client/Dapr.Client.csproj | 37 +++-- src/Dapr.Client/DaprClient.cs | 57 ++----- src/Dapr.Client/DaprClientBuilder.cs | 2 +- src/Dapr.Client/DaprClientGrpc.cs | 124 ++++++++++----- src/Dapr.Client/HTTPExtension.cs | 41 +++++ src/Dapr.Client/HTTPVerb.cs | 53 +++++++ .../Protos/dapr/proto/common/v1/common.proto | 83 ++++++++++ .../{ => dapr/proto/dapr/v1}/dapr.proto | 58 +++---- src/Dapr.Client/Protos/daprclient.proto | 76 --------- .../DaprSecretStoreConfigurationExtensions.cs | 4 +- .../DaprSecretStoreConfigurationProvider.cs | 4 +- .../DaprSecretStoreConfigurationSource.cs | 4 +- .../StateEntryModelBinderTest.cs | 12 +- test/Dapr.Client.Test/InvokeApiTest.cs | 146 ++++++++++++------ test/Dapr.Client.Test/InvokeBindingApiTest.cs | 4 +- test/Dapr.Client.Test/PublishEventApiTest.cs | 2 +- test/Dapr.Client.Test/SecretApiTest.cs | 2 +- test/Dapr.Client.Test/StateApiTest.cs | 2 +- ...aprSecretStoreConfigurationProviderTest.cs | 6 +- 22 files changed, 528 insertions(+), 304 deletions(-) create mode 100644 src/Dapr.Client/HTTPExtension.cs create mode 100644 src/Dapr.Client/HTTPVerb.cs create mode 100644 src/Dapr.Client/Protos/dapr/proto/common/v1/common.proto rename src/Dapr.Client/Protos/{ => dapr/proto/dapr/v1}/dapr.proto (61%) delete mode 100644 src/Dapr.Client/Protos/daprclient.proto diff --git a/samples/AspNetCore/RoutingSample/Startup.cs b/samples/AspNetCore/RoutingSample/Startup.cs index b9c2b073..e7207dc2 100644 --- a/samples/AspNetCore/RoutingSample/Startup.cs +++ b/samples/AspNetCore/RoutingSample/Startup.cs @@ -5,9 +5,9 @@ namespace RoutingSample { + using System; using System.Text.Json; using System.Threading.Tasks; - using Dapr; using Dapr.Client; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -83,25 +83,33 @@ namespace RoutingSample async Task Balance(HttpContext context) { + Console.WriteLine("Enter Balance"); var client = context.RequestServices.GetRequiredService(); var id = (string)context.Request.RouteValues["id"]; + Console.WriteLine("id is {0}", id); var account = await client.GetStateAsync(StoreName, id); if (account == null) { + Console.WriteLine("Account not found"); context.Response.StatusCode = 404; return; } + Console.WriteLine("Account balance is {0}", account.Balance); + context.Response.ContentType = "application/json"; await JsonSerializer.SerializeAsync(context.Response.Body, account, serializerOptions); } async Task Deposit(HttpContext context) { + Console.WriteLine("Enter Deposit"); + var client = context.RequestServices.GetRequiredService(); var transaction = await JsonSerializer.DeserializeAsync(context.Request.Body, serializerOptions); + Console.WriteLine("Id is {0}, Amount is {1}", transaction.Id, transaction.Amount); var account = await client.GetStateAsync(StoreName, transaction.Id); if (account == null) { @@ -110,12 +118,14 @@ namespace RoutingSample if (transaction.Amount < 0m) { + Console.WriteLine("Invalid amount"); context.Response.StatusCode = 400; return; } account.Balance += transaction.Amount; await client.SaveStateAsync(StoreName, transaction.Id, account); + Console.WriteLine("Balance is {0}", account.Balance); context.Response.ContentType = "application/json"; await JsonSerializer.SerializeAsync(context.Response.Body, account, serializerOptions); @@ -123,24 +133,28 @@ namespace RoutingSample async Task Withdraw(HttpContext context) { + Console.WriteLine("Enter Withdraw"); var client = context.RequestServices.GetRequiredService(); - var transaction = await JsonSerializer.DeserializeAsync(context.Request.Body, serializerOptions); + Console.WriteLine("Id is {0}", transaction.Id); var account = await client.GetStateAsync(StoreName, transaction.Id); if (account == null) { + Console.WriteLine("Account not found"); context.Response.StatusCode = 404; return; } if (transaction.Amount < 0m) { + Console.WriteLine("Invalid amount"); context.Response.StatusCode = 400; return; } account.Balance -= transaction.Amount; await client.SaveStateAsync(StoreName, transaction.Id, account); + Console.WriteLine("Balance is {0}", account.Balance); context.Response.ContentType = "application/json"; await JsonSerializer.SerializeAsync(context.Response.Body, account, serializerOptions); diff --git a/samples/Client/DaprClient/Program.cs b/samples/Client/DaprClient/Program.cs index 33acdd2a..bdb5010b 100644 --- a/samples/Client/DaprClient/Program.cs +++ b/samples/Client/DaprClient/Program.cs @@ -6,10 +6,10 @@ namespace DaprClient { using System; - using System.Collections.Generic; - using System.Threading; + using System.Text.Json; using System.Threading.Tasks; using Dapr.Client; + using Dapr.Client.Http; /// /// Shows Dapr client calls. @@ -26,7 +26,15 @@ namespace DaprClient /// A representing the asynchronous operation. public static async Task Main(string[] args) { - var client = new DaprClientBuilder().Build(); + var jsonOptions = new JsonSerializerOptions() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + }; + + var client = new DaprClientBuilder() + .UseJsonSerializationOptions(jsonOptions) + .Build(); await PublishEventAsync(client); @@ -42,26 +50,31 @@ namespace DaprClient #region Service Invoke - Required RoutingService // This provides an example of how to invoke a method on another REST service that is listening on http. // To use it run RoutingService in this solution. - // Invoke deposit operation on RoutingSample service by publishing event. + // Invoke deposit operation on RoutingSample service by publishing event. + //await PublishDepositeEventToRoutingSampleAsync(client); + + //await Task.Delay(TimeSpan.FromSeconds(1)); - // Invoke deposit operation on RoutingSample service by POST. + //await DepositUsingServiceInvocation(client); + + //Invoke deposit operation on RoutingSample service by POST. //await InvokeWithdrawServiceOperationAsync(client); - // Invoke deposit operation on RoutingSample service by GET. + //Invoke deposit operation on RoutingSample service by GET. //await InvokeBalanceServiceOperationAsync(client); #endregion + Console.WriteLine("Done"); } internal static async Task PublishDepositeEventToRoutingSampleAsync(DaprClient client) { - var eventData = new { id = "17", amount = (decimal)10, }; + var eventData = new { Id = "17", Amount = (decimal)10, }; await client.PublishEventAsync("deposit", eventData); Console.WriteLine("Published deposit event!"); } - internal static async Task PublishEventAsync(DaprClient client) { var eventData = new Widget() { Size = "small", Color = "yellow", }; @@ -95,6 +108,24 @@ namespace DaprClient Console.WriteLine("Deleted State!"); } + internal static async Task DepositUsingServiceInvocation(DaprClient client) + { + Console.WriteLine("DepositUsingServiceInvocation"); + var data = new { id = "17", amount = (decimal)99 }; + + HTTPExtension httpExtension = new HTTPExtension() + { + Verb = HTTPVerb.Post + }; + + // Invokes a POST method named "depoit" that takes input of type "Transaction" as define in the RoutingSample. + Console.WriteLine("invoking"); + + var a = await client.InvokeMethodAsync("routing", "deposit", data, httpExtension); + Console.WriteLine("Returned: id:{0} | Balance:{1}", a.Id, a.Balance); + + Console.WriteLine("Completed"); + } /// /// This example shows how to invoke a POST method on a service listening on http. @@ -107,14 +138,16 @@ namespace DaprClient /// internal static async Task InvokeWithdrawServiceOperationAsync(DaprClient client) { + Console.WriteLine("Invoking withdraw"); var data = new { id = "17", amount = (decimal)10, }; - // Add the verb to metadata if the method is other than a POST - var metaData = new Dictionary(); - metaData.Add("http.verb", "POST"); + HTTPExtension httpExtension = new HTTPExtension() + { + Verb = HTTPVerb.Post + }; - // Invokes a POST method named "Withdraw" that takes input of type "Transaction" as define in the RoutingSample. - await client.InvokeMethodAsync("routing", "Withdraw", data, metaData); + // Invokes a POST method named "Withdraw" that takes input of type "Transaction" as define in the RoutingSample. + await client.InvokeMethodAsync("routing", "Withdraw", data, httpExtension); Console.WriteLine("Completed"); } @@ -130,28 +163,16 @@ namespace DaprClient /// internal static async Task InvokeBalanceServiceOperationAsync(DaprClient client) { - // Add the verb to metadata if the method is other than a POST - var metaData = new Dictionary(); - metaData.Add("http.verb", "GET"); + Console.WriteLine("Invoking balance"); // Invokes a GET method named "hello" that takes input of type "MyData" and returns a string. - var res = await client.InvokeMethodAsync("routing", "17", metaData); - - Console.WriteLine($"Received balance {res}"); - } + HTTPExtension httpExtension = new HTTPExtension() + { + Verb = HTTPVerb.Get + }; + var res = await client.InvokeMethodAsync("routing", "17", httpExtension); - /// - /// This example shows how to invoke a method on a service listening on gRPC. - /// - /// - /// - internal static async Task InvokeMethodOnGrpcServiceAsync(DaprClient client) - { - MyData data = new MyData() { Message = "mydata" }; - - // invokes a method named "hello" that takes input of type "MyData" and returns a string. - string s = await client.InvokeMethodAsync("nodeapp", "hello", data); - Console.WriteLine("received {0}", s); + Console.WriteLine($"Received balance {res.Balance}"); } private class Widget @@ -167,5 +188,14 @@ namespace DaprClient public String Message { get; set; } } + + + internal class Account + { + public string Id { get; set; } + + public decimal Balance { get; set; } + } } } + diff --git a/src/Dapr.Actors.AspNetCore/RouterBuilderExtensions.cs b/src/Dapr.Actors.AspNetCore/RouterBuilderExtensions.cs index 48140d24..87b835d3 100644 --- a/src/Dapr.Actors.AspNetCore/RouterBuilderExtensions.cs +++ b/src/Dapr.Actors.AspNetCore/RouterBuilderExtensions.cs @@ -16,7 +16,8 @@ namespace Dapr.Actors.AspNetCore { routeBuilder.MapGet("dapr/config", async context => { - await ActorRuntime.Instance.SerializeSettingsAndRegisteredTypes(context.Response.BodyWriter); + context.Response.ContentType = "application/json"; + await ActorRuntime.Instance.SerializeSettingsAndRegisteredTypes(context.Response.BodyWriter); }); } diff --git a/src/Dapr.Client/Dapr.Client.csproj b/src/Dapr.Client/Dapr.Client.csproj index 8a1840ce..c2e121fe 100644 --- a/src/Dapr.Client/Dapr.Client.csproj +++ b/src/Dapr.Client/Dapr.Client.csproj @@ -1,21 +1,24 @@  - - netcoreapp3.1 - + + netcoreapp3.1 + + + + + + + + + This package contains the reference assemblies for developing services using Dapr. + + + + + + + + + - - - This package contains the reference assemblies for developing services using Dapr. - - - - - - - - - - - diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 865bef57..1c62a5cc 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -5,12 +5,9 @@ namespace Dapr.Client { - using System; using System.Collections.Generic; - using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; - using Dapr.Client.Autogen.Grpc; /// /// Defines client methods for interacting with Dapr endpoints. @@ -56,20 +53,13 @@ namespace Dapr.Client /// /// The Dapr application id to invoke the method on. /// The name of the method to invoke. - /// A key/value pair to pass to the method to invoke. + /// Additional fields that may be needed if the receiving app is listening on HTTP. /// A that can be used to cancel the operation. - /// A that will complete when the operation has completed. - /// If the target service is listening on http, a key value pair of http.verb and the verb must be added to the metadata parameter. - /// For example: - /// - /// ... - /// myMetadata.Add("http.verb", "POST"); - /// daprClient.InvokeMethodAsync(...., myMetadata); - /// + /// A that will complete when the operation has completed. public abstract Task InvokeMethodAsync( string appId, string methodName, - Dictionary metadata = default, + Dapr.Client.Http.HTTPExtension httpExtension = default, CancellationToken cancellationToken = default); /// @@ -79,21 +69,14 @@ namespace Dapr.Client /// The Dapr application id to invoke the method on. /// The name of the method to invoke. /// Data to pass to the method - /// A key/value pair to pass to the method to invoke. + /// Additional fields that may be needed if the receiving app is listening on HTTP. /// A that can be used to cancel the operation. - /// A that will return the value when the operation has completed. - /// If the target service is listening on http, a key value pair of http.verb and the verb must be added to the metadata parameter. - /// For example: - /// - /// ... - /// myMetadata.Add("http.verb", "POST"); - /// daprClient.InvokeMethodAsync(...., myMetadata); - /// + /// A that will return the value when the operation has completed. public abstract Task InvokeMethodAsync( string appId, string methodName, TRequest data, - Dictionary metadata = default, + Dapr.Client.Http.HTTPExtension httpExtension = default, CancellationToken cancellationToken = default); /// @@ -102,20 +85,13 @@ namespace Dapr.Client /// The type of the object in the response. /// The Dapr application id to invoke the method on. /// The name of the method to invoke. - /// A key/value pair to pass to the method to invoke. + /// Additional fields that may be needed if the receiving app is listening on HTTP. /// A that can be used to cancel the operation. - /// A that will return the value when the operation has completed. - /// If the target service is listening on http, a key value pair of http.verb and the verb must be added to the metadata parameter. - /// For example: - /// - /// ... - /// myMetadata.Add("http.verb", "POST"); - /// daprClient.InvokeMethodAsync(...., myMetadata); - /// + /// A that will return the value when the operation has completed. public abstract ValueTask InvokeMethodAsync( string appId, string methodName, - Dictionary metadata = default, + Dapr.Client.Http.HTTPExtension httpExtension = default, CancellationToken cancellationToken = default); /// @@ -125,22 +101,15 @@ namespace Dapr.Client /// The type of the object in the response. /// The Dapr application id to invoke the method on. /// The name of the method to invoke. - /// Data to pass to the method - /// A key/value pair to pass to the method to invoke. + /// Data to pass to the method + /// Additional fields that may be needed if the receiving app is listening on HTTP. /// A that can be used to cancel the operation. - /// A that will return the value when the operation has completed. - /// If the target service is listening on http, a key value pair of http.verb and the verb must be added to the metadata parameter. - /// For example: - /// - /// ... - /// myMetadata.Add("http.verb", "POST"); - /// daprClient.InvokeMethodAsync(...., myMetadata); - /// + /// A that will return the value when the operation has completed. public abstract ValueTask InvokeMethodAsync( string appId, string methodName, TRequest data, - Dictionary metadata = default, + Dapr.Client.Http.HTTPExtension httpExtension = default, CancellationToken cancellationToken = default); /// diff --git a/src/Dapr.Client/DaprClientBuilder.cs b/src/Dapr.Client/DaprClientBuilder.cs index c612a80e..ba25a037 100644 --- a/src/Dapr.Client/DaprClientBuilder.cs +++ b/src/Dapr.Client/DaprClientBuilder.cs @@ -26,7 +26,7 @@ namespace Dapr.Client public DaprClientBuilder() { var daprGrpcPort = Environment.GetEnvironmentVariable("DAPR_GRPC_PORT") ?? defaultDaprGrpcPort; - this.daprEndpoint = $"http://127.0.0.1:{daprGrpcPort}"; + this.daprEndpoint = $"http://127.0.0.1:{daprGrpcPort}"; } /// diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 8578f20d..76d6975d 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -7,17 +7,17 @@ namespace Dapr.Client { using System; using System.Collections.Generic; - using System.IO; using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; - using Dapr.Client.Autogen.Grpc; + using Dapr.Client.Autogen.Grpc.v1; + using Dapr.Client.Http; using Google.Protobuf; using Google.Protobuf.WellKnownTypes; using Grpc.Core; using Grpc.Net.Client; - using Autogenerated = Dapr.Client.Autogen.Grpc; + using Autogenerated = Dapr.Client.Autogen.Grpc.v1; /// /// A client for interacting with the Dapr endpoints. @@ -113,20 +113,20 @@ namespace Dapr.Client public override async Task InvokeMethodAsync( string appId, string methodName, - Dictionary metadata = default, + Http.HTTPExtension httpExtension = default, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId)); ArgumentVerifier.ThrowIfNullOrEmpty(methodName, nameof(methodName)); - _ = await this.MakeInvokeRequestAsync(appId, methodName, null, metadata, cancellationToken); + _ = await this.MakeInvokeRequestAsync(appId, methodName, null, httpExtension, cancellationToken); } public override async Task InvokeMethodAsync( string appId, string methodName, TRequest data, - Dictionary metadata = default, + Http.HTTPExtension httpExtension = default, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId)); @@ -138,33 +138,32 @@ namespace Dapr.Client serializedData = ConvertToAnyAsync(data, this.jsonSerializerOptions); } - _ = await this.MakeInvokeRequestAsync(appId, methodName, serializedData, metadata, cancellationToken); + _ = await this.MakeInvokeRequestAsync(appId, methodName, serializedData, httpExtension, cancellationToken); } public override async ValueTask InvokeMethodAsync( string appId, string methodName, - Dictionary metadata = default, + Http.HTTPExtension httpExtension = default, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId)); ArgumentVerifier.ThrowIfNullOrEmpty(methodName, nameof(methodName)); - var response = await this.MakeInvokeRequestAsync(appId, methodName, null, metadata, cancellationToken); + var response = await this.MakeInvokeRequestAsync(appId, methodName, null, httpExtension, cancellationToken); if (response.Data.Value.IsEmpty) { return default; } - var responseData = response.Data.Value.ToStringUtf8(); - return JsonSerializer.Deserialize(responseData, this.jsonSerializerOptions); + return ConvertFromInvokeResponse(response, this.jsonSerializerOptions); } public override async ValueTask InvokeMethodAsync( string appId, string methodName, TRequest data, - Dictionary metadata = default, + Http.HTTPExtension httpExtension = default, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId)); @@ -176,45 +175,64 @@ namespace Dapr.Client serializedData = ConvertToAnyAsync(data, this.jsonSerializerOptions); } - var response = await this.MakeInvokeRequestAsync(appId, methodName, serializedData, metadata, cancellationToken); + var response = await this.MakeInvokeRequestAsync(appId, methodName, serializedData, httpExtension, cancellationToken); if (response.Data.Value.IsEmpty) { return default; } - var responseData = response.Data.Value.ToStringUtf8(); - return JsonSerializer.Deserialize(responseData, this.jsonSerializerOptions); + return ConvertFromInvokeResponse(response, this.jsonSerializerOptions); } - private async Task MakeInvokeRequestAsync( + private async Task MakeInvokeRequestAsync( string appId, string methodName, Any data, - Dictionary metadata = default, + Http.HTTPExtension httpExtension, CancellationToken cancellationToken = default) { - var envelope = new Autogenerated.InvokeServiceEnvelope() + Autogenerated.HTTPExtension protoHTTPExtension = null; + string contentType = ""; + + if (httpExtension != null) { - Id = appId, - Method = methodName + protoHTTPExtension = new Autogenerated.HTTPExtension() + { + Verb = ConvertHTTPVerb(httpExtension.Verb) + }; + + if (httpExtension.QueryString != null) + { + foreach (var kv in httpExtension.QueryString) + { + protoHTTPExtension.Querystring.Add(kv.Key, kv.Value); + } + } + + contentType = httpExtension.ContentType == null ? "application/json" : httpExtension.ContentType; + } + + var invokeRequest = new InvokeRequest() + { + Method = methodName, + Data = data, + ContentType = contentType, + HttpExtension = protoHTTPExtension }; - if (data != null) + var request = new Autogenerated.InvokeServiceRequest() { - envelope.Data = data; - } - - if (metadata != null) - { - envelope.Metadata.Add(metadata); - } + + Id = appId, + Message = invokeRequest, + }; return await this.MakeGrpcCallHandleError( - (options) => - { - return client.InvokeServiceAsync(envelope, options); - }, - cancellationToken); + (options) => + { + return client.InvokeServiceAsync(request, options); + }, + cancellationToken); } #endregion @@ -502,7 +520,7 @@ namespace Dapr.Client { var callOptions = new CallOptions(cancellationToken: cancellationToken); - // COmmon Exception Handling logic can be added here for all calls. + // Common Exception Handling logic can be added here for all calls. return await callFunc.Invoke(callOptions); } @@ -517,7 +535,7 @@ namespace Dapr.Client if (stateOptions.Concurrency != null) { - stateRequestOptions.Concurrency = GetStringForConcurrencyMode(stateOptions.Concurrency.Value); + stateRequestOptions.Concurrency = GetStringForConcurrencyMode(stateOptions.Concurrency.Value); } if (stateOptions.RetryOptions != null) @@ -552,12 +570,43 @@ namespace Dapr.Client { var bytes = JsonSerializer.SerializeToUtf8Bytes(data, options); any.Value = ByteString.CopyFrom(bytes); - } return any; } + private static T ConvertFromInvokeResponse(InvokeResponse response, JsonSerializerOptions options = null) + { + var responseData = response.Data.Value.ToStringUtf8(); + return JsonSerializer.Deserialize(responseData, options); + } + + private static Autogenerated.HTTPExtension.Types.Verb ConvertHTTPVerb(HTTPVerb verb) + { + switch (verb) + { + case HTTPVerb.Get: + return Autogenerated.HTTPExtension.Types.Verb.Get; + case HTTPVerb.Head: + return Autogenerated.HTTPExtension.Types.Verb.Head; + case HTTPVerb.Post: + return Autogenerated.HTTPExtension.Types.Verb.Post; + case HTTPVerb.Put: + return Autogenerated.HTTPExtension.Types.Verb.Put; + case HTTPVerb.Delete: + return Autogenerated.HTTPExtension.Types.Verb.Delete; + case HTTPVerb.Connect: + return Autogenerated.HTTPExtension.Types.Verb.Connect; + case HTTPVerb.Options: + return Autogenerated.HTTPExtension.Types.Verb.Options; + case HTTPVerb.Trace: + return Autogenerated.HTTPExtension.Types.Verb.Trace; + default: + throw new NotImplementedException( + string.Format("Service invocation with verb '{0}' is not supported", verb.ToString())); + } + } + private static string GetStringForConsistencyMode(ConsistencyMode consistencyMode) { if (consistencyMode.Equals(ConsistencyMode.Eventual)) @@ -571,7 +620,7 @@ namespace Dapr.Client } throw new ArgumentException($"{consistencyMode.ToString()} Consistency Mode is not supported."); - } + } private static string GetStringForConcurrencyMode(ConcurrencyMode concurrencyMode) { @@ -605,3 +654,4 @@ namespace Dapr.Client #endregion Helper Methods } } + diff --git a/src/Dapr.Client/HTTPExtension.cs b/src/Dapr.Client/HTTPExtension.cs new file mode 100644 index 00000000..8d1b86fb --- /dev/null +++ b/src/Dapr.Client/HTTPExtension.cs @@ -0,0 +1,41 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// ------------------------------------------------------------ + +namespace Dapr.Client.Http +{ + using System.Collections.Generic; + + /// + /// This class is only needed if the app you are calling is listening on HTTP. + /// It contains propertes that represent data may be populated for an HTTP receiver. + /// + public class HTTPExtension + { + /// + /// The constructor. + /// + public HTTPExtension() + { + this.Verb = HTTPVerb.Post; + this.QueryString = new Dictionary(); + } + + /// + /// The HTTP Content-Type + /// + public string ContentType { get; set; } + + /// + /// This is the HTTP verb. + /// + public HTTPVerb Verb { get; set; } + + /// + /// This represents a collection of query strings. + /// + public Dictionary QueryString { get; set; } + + } +} diff --git a/src/Dapr.Client/HTTPVerb.cs b/src/Dapr.Client/HTTPVerb.cs new file mode 100644 index 00000000..75e3eb14 --- /dev/null +++ b/src/Dapr.Client/HTTPVerb.cs @@ -0,0 +1,53 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// ------------------------------------------------------------ + +namespace Dapr.Client.Http +{ + /// + /// The HTTP verb to use for this message. + /// + public enum HTTPVerb + { + /// + /// The HTTP verb GET + /// + Get, + + /// + /// The HTTP verb HEAD + /// + Head, + + /// + /// The HTTP verb POST + /// + Post, + + /// + /// The HTTP verb PUT + /// + Put, + + /// + /// The HTTP verb DELETE + /// + Delete, + + /// + /// The HTTP verb CONNECT + /// + Connect, + + /// + /// The HTTP verb OPTIONS + /// + Options, + + /// + /// The HTTP verb TRACE + /// + Trace + } +} diff --git a/src/Dapr.Client/Protos/dapr/proto/common/v1/common.proto b/src/Dapr.Client/Protos/dapr/proto/common/v1/common.proto new file mode 100644 index 00000000..2072e451 --- /dev/null +++ b/src/Dapr.Client/Protos/dapr/proto/common/v1/common.proto @@ -0,0 +1,83 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// ------------------------------------------------------------ + +syntax = "proto3"; + +package dapr.proto.common.v1; + +import "google/protobuf/any.proto"; + +option csharp_namespace = "Dapr.Client.Autogen.Grpc.v1"; +option java_outer_classname = "CommonProtos"; +option java_package = "io.dapr.v1"; +option go_package = "github.com/dapr/dapr/pkg/proto/common/v1"; + +// HTTPExtension includes HTTP verb and querystring +// when Dapr runtime delivers HTTP content. +// +// For example, when callers calls http invoke api +// POST http://localhost:3500/v1.0/invoke//method/?query1=value1&query2=value2 +// +// Dapr runtime will parse POST as a verb and extract querystring to quersytring map. +message HTTPExtension { + // Type of HTTP 1.1 Methods + // RFC 7231: https://tools.ietf.org/html/rfc7231#page-24 + enum Verb { + NONE = 0; + GET = 1; + HEAD = 2; + POST = 3; + PUT = 4; + DELETE = 5; + CONNECT = 6; + OPTIONS = 7; + TRACE = 8; + } + + // verb is HTTP verb. + // + // This is required. + Verb verb = 1; + + // querystring includes HTTP querystring. + map querystring = 2; +} + +message InvokeRequest { + // method is a method name which will be invoked by caller. + // + // This field is required. + string method = 1; + + // data conveys bytes value or Protobuf message which caller sent. + // Dapr treats Any.value as bytes type if Any.type_url is unset. + // + // This field is required. + google.protobuf.Any data = 2; + + // content_type is the type of data content. + // + // This field is required if data delivers http request body + // Otherwise, this is optional. + string content_type = 3; + + // http_extension includes http specific fields if request conveys + // http-compatible request. + // + // This field is optional. + HTTPExtension http_extension = 4; +} + +message InvokeResponse { + // data conveys the content body of InvokeService response. + // + // This field is required. + google.protobuf.Any data = 1; + + // content_type is the type of data content. + // + // This field is required. + string content_type = 2; +} diff --git a/src/Dapr.Client/Protos/dapr.proto b/src/Dapr.Client/Protos/dapr/proto/dapr/v1/dapr.proto similarity index 61% rename from src/Dapr.Client/Protos/dapr.proto rename to src/Dapr.Client/Protos/dapr/proto/dapr/v1/dapr.proto index 01dcfff8..a236088b 100644 --- a/src/Dapr.Client/Protos/dapr.proto +++ b/src/Dapr.Client/Protos/dapr/proto/dapr/v1/dapr.proto @@ -1,21 +1,26 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// ------------------------------------------------------------ + syntax = "proto3"; -package dapr; +package dapr.proto.dapr.v1; import "google/protobuf/any.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/duration.proto"; +import "dapr/proto/common/v1/common.proto"; +option csharp_namespace = "Dapr.Client.Autogen.Grpc.v1"; option java_outer_classname = "DaprProtos"; -option java_package = "io.dapr"; +option java_package = "io.dapr.v1"; +option go_package = "github.com/dapr/dapr/pkg/proto/dapr/v1"; -option csharp_namespace = "Dapr.Client.Autogen.Grpc"; - - -// Dapr definitions +// Dapr service provides APIs to user application to access Dapr building blocks. service Dapr { rpc PublishEvent(PublishEventEnvelope) returns (google.protobuf.Empty) {} - rpc InvokeService(InvokeServiceEnvelope) returns (InvokeServiceResponseEnvelope) {} + rpc InvokeService(InvokeServiceRequest) returns (common.v1.InvokeResponse) {} rpc InvokeBinding(InvokeBindingEnvelope) returns (google.protobuf.Empty) {} rpc GetState(GetStateEnvelope) returns (GetStateResponseEnvelope) {} rpc GetSecret(GetSecretEnvelope) returns (GetSecretResponseEnvelope) {} @@ -23,27 +28,35 @@ service Dapr { rpc DeleteState(DeleteStateEnvelope) returns (google.protobuf.Empty) {} } -message InvokeServiceResponseEnvelope { - google.protobuf.Any data = 1; - map metadata = 2; +// InvokeServiceRequest represents the request message for Service invocation. +message InvokeServiceRequest { + // id specifies callee's app id. + // + // This field is required. + string id = 1; + + // message which will be delivered to callee. + // + // This field is required. + common.v1.InvokeRequest message = 3; } message DeleteStateEnvelope { - string storeName = 1; + string store_name = 1; string key = 2; string etag = 3; StateOptions options = 4; } message SaveStateEnvelope { - string storeName = 1; + string store_name = 1; repeated StateRequest requests = 2; } message GetStateEnvelope { - string storeName = 1; - string key = 2; - string consistency = 3; + string store_name = 1; + string key = 2; + string consistency = 3; } message GetStateResponseEnvelope { @@ -52,7 +65,7 @@ message GetStateResponseEnvelope { } message GetSecretEnvelope { - string storeName = 1; + string store_name = 1; string key = 2; map metadata = 3; } @@ -67,16 +80,9 @@ message InvokeBindingEnvelope { map metadata = 3; } -message InvokeServiceEnvelope { - string id = 1; - string method = 2; - google.protobuf.Any data = 3; - map metadata = 4; -} - message PublishEventEnvelope { - string topic = 1; - google.protobuf.Any data = 2; + string topic = 1; + google.protobuf.Any data = 2; } message State { @@ -90,7 +96,7 @@ message State { message StateOptions { string concurrency = 1; string consistency = 2; - RetryPolicy retryPolicy = 3; + RetryPolicy retry_policy = 3; } message RetryPolicy { diff --git a/src/Dapr.Client/Protos/daprclient.proto b/src/Dapr.Client/Protos/daprclient.proto deleted file mode 100644 index a383ba16..00000000 --- a/src/Dapr.Client/Protos/daprclient.proto +++ /dev/null @@ -1,76 +0,0 @@ -syntax = "proto3"; - -package daprclient; - -import "google/protobuf/any.proto"; -import "google/protobuf/empty.proto"; -import "google/protobuf/duration.proto"; - -option java_outer_classname = "DaprClientProtos"; -option java_package = "io.dapr"; - -// User Code definitions -service DaprClient { - rpc OnInvoke (InvokeEnvelope) returns (google.protobuf.Any) {} - rpc GetTopicSubscriptions(google.protobuf.Empty) returns (GetTopicSubscriptionsEnvelope) {} - rpc GetBindingsSubscriptions(google.protobuf.Empty) returns (GetBindingsSubscriptionsEnvelope) {} - rpc OnBindingEvent(BindingEventEnvelope) returns (BindingResponseEnvelope) {} - rpc OnTopicEvent(CloudEventEnvelope) returns (google.protobuf.Empty) {} -} - -message CloudEventEnvelope { - string id = 1; - string source = 2; - string type = 3; - string specVersion = 4; - string dataContentType = 5; - string topic = 6; - google.protobuf.Any data = 7; -} - -message BindingEventEnvelope { - string name = 1; - google.protobuf.Any data = 2; - map metadata = 3; -} - -message BindingResponseEnvelope { - google.protobuf.Any data = 1; - repeated string to = 2; - repeated State state = 3; - string concurrency = 4; -} - -message InvokeEnvelope { - string method = 1; - google.protobuf.Any data = 2; - map metadata = 3; -} - -message GetTopicSubscriptionsEnvelope { - repeated string topics = 1; -} - -message GetBindingsSubscriptionsEnvelope { - repeated string bindings = 1; -} - -message State { - string key = 1; - google.protobuf.Any value = 2; - string etag = 3; - map metadata = 4; - StateOptions options = 5; -} - -message StateOptions { - string concurrency = 1; - string consistency = 2; - RetryPolicy retryPolicy = 3; -} - -message RetryPolicy { - int32 threshold = 1; - string pattern = 2; - google.protobuf.Duration interval = 3; -} diff --git a/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationExtensions.cs b/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationExtensions.cs index 224dcf2d..506a7dee 100644 --- a/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationExtensions.cs +++ b/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationExtensions.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------ +// ------------------------------------------------------------ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // ------------------------------------------------------------ @@ -53,4 +53,4 @@ namespace Dapr.Extensions.Configuration public static IConfigurationBuilder AddDaprSecretStore(this IConfigurationBuilder configurationBuilder, Action configureSource) => configurationBuilder.Add(configureSource); } -} \ No newline at end of file +} diff --git a/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationProvider.cs b/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationProvider.cs index 3b419ca7..1409aa79 100644 --- a/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationProvider.cs +++ b/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationProvider.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------ +// ------------------------------------------------------------ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // ------------------------------------------------------------ @@ -62,4 +62,4 @@ namespace Dapr.Extensions.Configuration.DaprSecretStore Data = data; } } -} \ No newline at end of file +} diff --git a/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationSource.cs b/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationSource.cs index e4a76ab0..771b82aa 100644 --- a/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationSource.cs +++ b/src/Dapr.Extensions.Configuration/DaprSecretStoreConfigurationSource.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------ +// ------------------------------------------------------------ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // ------------------------------------------------------------ @@ -35,4 +35,4 @@ namespace Dapr.Extensions.Configuration.DaprSecretStore return new DaprSecretStoreConfigurationProvider(Store, SecretDescriptors, Client); } } -} \ No newline at end of file +} diff --git a/test/Dapr.AspNetCore.Test/StateEntryModelBinderTest.cs b/test/Dapr.AspNetCore.Test/StateEntryModelBinderTest.cs index 8c0acddb..74b42fe3 100644 --- a/test/Dapr.AspNetCore.Test/StateEntryModelBinderTest.cs +++ b/test/Dapr.AspNetCore.Test/StateEntryModelBinderTest.cs @@ -6,19 +6,11 @@ namespace Dapr.AspNetCore.Test { using System; - using System.Buffers.Binary; - using System.IO; using System.Net; - using System.Net.Http; - using System.Net.Http.Headers; - using System.Text.Json; using System.Threading.Tasks; using Dapr.Client; - using Dapr.Client.Autogen.Grpc; + using Dapr.Client.Autogen.Grpc.v1; using FluentAssertions; - using Google.Protobuf; - using Google.Protobuf.WellKnownTypes; - using Grpc.Core; using Grpc.Net.Client; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -69,7 +61,7 @@ namespace Dapr.AspNetCore.Test context.ValidationState.Count.Should().Be(1); context.ValidationState[context.Result.Model].SuppressValidation.Should().BeTrue(); - } + } [Fact] public async Task BindAsync_CanBindStateEntry() diff --git a/test/Dapr.Client.Test/InvokeApiTest.cs b/test/Dapr.Client.Test/InvokeApiTest.cs index ac8e9d94..496f1abd 100644 --- a/test/Dapr.Client.Test/InvokeApiTest.cs +++ b/test/Dapr.Client.Test/InvokeApiTest.cs @@ -9,7 +9,8 @@ namespace Dapr.Client.Test using System.Net; using System.Text.Json; using System.Threading.Tasks; - using Dapr.Client.Autogen.Grpc; + using Dapr.Client.Autogen.Grpc.v1; + using Dapr.Client.Http; using FluentAssertions; using Grpc.Core; using Grpc.Net.Client; @@ -26,21 +27,31 @@ namespace Dapr.Client.Test .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); - var metadata = new Dictionary(); - metadata.Add("key1", "value1"); - metadata.Add("key2", "value2"); - var task = daprClient.InvokeMethodAsync("test", "test", metadata); + var queryString = new Dictionary(); + queryString.Add("key1", "value1"); + queryString.Add("key2", "value2"); + + var httpExtension = new Http.HTTPExtension() + { + Verb = HTTPVerb.Post, + QueryString = queryString + }; + + var task = daprClient.InvokeMethodAsync("app1", "mymethod", httpExtension); // Get Request and validate httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); - envelope.Id.Should().Be("test"); - envelope.Method.Should().Be("test"); - envelope.Metadata.Count.Should().Be(2); - envelope.Metadata.Keys.Contains("key1").Should().BeTrue(); - envelope.Metadata.Keys.Contains("key2").Should().BeTrue(); - envelope.Metadata["key1"].Should().Be("value1"); - envelope.Metadata["key2"].Should().Be("value2"); + var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); + + envelope.Id.Should().Be("app1"); + envelope.Message.Method.Should().Be("mymethod"); + + envelope.Message.HttpExtension.Verb.Should().Be(Autogen.Grpc.v1.HTTPExtension.Types.Verb.Post); + envelope.Message.HttpExtension.Querystring.Count.Should().Be(2); + envelope.Message.HttpExtension.Querystring.ContainsKey("key1").Should().BeTrue(); + envelope.Message.HttpExtension.Querystring.ContainsKey("key2").Should().BeTrue(); + envelope.Message.HttpExtension.Querystring["key1"].Should().Be("value1"); + envelope.Message.HttpExtension.Querystring["key2"].Should().Be("value2"); } [Fact] @@ -53,12 +64,12 @@ namespace Dapr.Client.Test .Build(); var task = daprClient.InvokeMethodAsync("test", "test"); - + // Get Request and validate httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); + var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.Id.Should().Be("test"); - envelope.Method.Should().Be("test"); + envelope.Message.Method.Should().Be("test"); // Create Response & Respond var data = new InvokedResponse() { Name = "Look, I was invoked!" }; @@ -82,10 +93,11 @@ namespace Dapr.Client.Test // Get Request and validate httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); + var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.Id.Should().Be("test"); - envelope.Method.Should().Be("test"); - var json = envelope.Data.Value.ToStringUtf8(); + envelope.Message.Method.Should().Be("test"); + + var json = envelope.Message.Data.Value.ToStringUtf8(); var typeFromRequest = JsonSerializer.Deserialize(json); typeFromRequest.RequestParameter.Should().Be("Hello "); @@ -111,10 +123,11 @@ namespace Dapr.Client.Test // Get Request and validate httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); + var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.Id.Should().Be("test"); - envelope.Method.Should().Be("test"); - var json = envelope.Data.Value.ToStringUtf8(); + envelope.Message.Method.Should().Be("test"); + + var json = envelope.Message.Data.Value.ToStringUtf8(); var typeFromRequest = JsonSerializer.Deserialize(json); typeFromRequest.RequestParameter.Should().Be("Hello "); @@ -139,9 +152,9 @@ namespace Dapr.Client.Test // Get Request and validate httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); + var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.Id.Should().Be("test"); - envelope.Method.Should().Be("test"); + envelope.Message.Method.Should().Be("test"); // Create Response & Respond var data = new InvokedResponse() { Name = "Look, I was invoked!" }; @@ -165,9 +178,9 @@ namespace Dapr.Client.Test // Get Request and validate httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); + var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.Id.Should().Be("test"); - envelope.Method.Should().Be("test"); + envelope.Message.Method.Should().Be("test"); // Create Response & Respond var response = GrpcUtils.CreateResponse(HttpStatusCode.NotAcceptable); @@ -186,20 +199,22 @@ namespace Dapr.Client.Test .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) .Build(); - var task = daprClient.InvokeMethodAsync("test", "test", new InvokeRequest() { RequestParameter = "Hello " }); + InvokeRequest invokeRequest = new InvokeRequest() { RequestParameter = "Hello " }; + var task = daprClient.InvokeMethodAsync("test", "test", invokeRequest); // Get Request and validate httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); + var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.Id.Should().Be("test"); - envelope.Method.Should().Be("test"); - var json = envelope.Data.Value.ToStringUtf8(); + envelope.Message.Method.Should().Be("test"); + + var json = envelope.Message.Data.Value.ToStringUtf8(); var typeFromRequest = JsonSerializer.Deserialize(json); typeFromRequest.RequestParameter.Should().Be("Hello "); // Create Response & Respond - var data = new InvokedResponse() { Name = "Look, I was invoked!" }; - SendResponse(data, entry); + var response = new InvokedResponse() { Name = "Look, I was invoked!" }; + SendResponse(response, entry); FluentActions.Awaiting(async () => await task).Should().NotThrow(); } @@ -217,10 +232,10 @@ namespace Dapr.Client.Test // Get Request and validate httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); + var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.Id.Should().Be("test"); - envelope.Method.Should().Be("test"); - var json = envelope.Data.Value.ToStringUtf8(); + envelope.Message.Method.Should().Be("test"); + var json = envelope.Message.Data.Value.ToStringUtf8(); var typeFromRequest = JsonSerializer.Deserialize(json); typeFromRequest.RequestParameter.Should().Be("Hello "); @@ -248,17 +263,17 @@ namespace Dapr.Client.Test // Get Request and validate httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); + var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.Id.Should().Be("test"); - envelope.Method.Should().Be("test"); - var json = envelope.Data.Value.ToStringUtf8(); + envelope.Message.Method.Should().Be("test"); + var json = envelope.Message.Data.Value.ToStringUtf8(); json.Should().Be(JsonSerializer.Serialize(invokeRequest, jsonOptions)); } [Fact] public async Task InvokeMethodAsync_WithReturnTypeAndData_UsesConfiguredJsonSerializerOptions() - { + { // Configure Client var httpClient = new TestHttpClient(); var jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; @@ -274,10 +289,55 @@ namespace Dapr.Client.Test // Get Request and validate httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); - var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); + var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); envelope.Id.Should().Be("test"); - envelope.Method.Should().Be("test"); - var json = envelope.Data.Value.ToStringUtf8(); + envelope.Message.Method.Should().Be("test"); + + var json = envelope.Message.Data.Value.ToStringUtf8(); + json.Should().Be(JsonSerializer.Serialize(invokeRequest, jsonOptions)); + + SendResponse(invokedResponse, entry, jsonOptions); + var response = await task; + + response.Name.Should().Be(invokedResponse.Name); + } + + [Fact] + public async Task InvokeMethodAsync_WithReturnTypeAndData_WithNonDefaultVerb_WithQueryString_UsesConfiguredJsonSerializerOptions() + { + // Configure Client + var httpClient = new TestHttpClient(); + var jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + var daprClient = new DaprClientBuilder() + .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) + .UseJsonSerializationOptions(jsonOptions) + .Build(); + + var invokeRequest = new InvokeRequest() { RequestParameter = "Hello " }; + var invokedResponse = new InvokedResponse { Name = "Look, I was invoked!" }; + + Dictionary queryString = new Dictionary(); + queryString.Add("key1", "value1"); + var httpExtension = new Http.HTTPExtension() + { + Verb = HTTPVerb.Put, + QueryString = queryString + }; + + var task = daprClient.InvokeMethodAsync("test", "test", invokeRequest, httpExtension); + + // Get Request and validate + httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); + var envelope = await GrpcUtils.GetEnvelopeFromRequestMessageAsync(entry.Request); + envelope.Id.Should().Be("test"); + envelope.Message.Method.Should().Be("test"); + envelope.Message.HttpExtension.Verb.Should().Be(Autogen.Grpc.v1.HTTPExtension.Types.Verb.Put); + envelope.Message.HttpExtension.Querystring.Count.Should().Be(1); + envelope.Message.HttpExtension.Querystring.ContainsKey("key1").Should().BeTrue(); + envelope.Message.HttpExtension.Querystring["key1"].Should().Be("value1"); + + + var json = envelope.Message.Data.Value.ToStringUtf8(); json.Should().Be(JsonSerializer.Serialize(invokeRequest, jsonOptions)); SendResponse(invokedResponse, entry, jsonOptions); @@ -289,7 +349,7 @@ namespace Dapr.Client.Test private async void SendResponse(T data, TestHttpClient.Entry entry, JsonSerializerOptions options = null) { var dataAny = ProtobufUtils.ConvertToAnyAsync(data, options); - var dataResponse = new InvokeServiceResponseEnvelope(); + var dataResponse = new InvokeResponse(); dataResponse.Data = dataAny; var streamContent = await GrpcUtils.CreateResponseContent(dataResponse); diff --git a/test/Dapr.Client.Test/InvokeBindingApiTest.cs b/test/Dapr.Client.Test/InvokeBindingApiTest.cs index 30ef3574..714b872d 100644 --- a/test/Dapr.Client.Test/InvokeBindingApiTest.cs +++ b/test/Dapr.Client.Test/InvokeBindingApiTest.cs @@ -6,12 +6,10 @@ namespace Dapr.Client.Test { using System.Collections.Generic; - using System.Net; using System.Text.Json; using System.Threading.Tasks; - using Dapr.Client.Autogen.Grpc; + using Dapr.Client.Autogen.Grpc.v1; using FluentAssertions; - using Grpc.Core; using Grpc.Net.Client; using Xunit; diff --git a/test/Dapr.Client.Test/PublishEventApiTest.cs b/test/Dapr.Client.Test/PublishEventApiTest.cs index 648a8a05..511dd0f4 100644 --- a/test/Dapr.Client.Test/PublishEventApiTest.cs +++ b/test/Dapr.Client.Test/PublishEventApiTest.cs @@ -7,7 +7,7 @@ namespace Dapr.Client.Test { using System.Text.Json; using System.Threading.Tasks; - using Dapr.Client.Autogen.Grpc; + using Dapr.Client.Autogen.Grpc.v1; using FluentAssertions; using Grpc.Net.Client; using Xunit; diff --git a/test/Dapr.Client.Test/SecretApiTest.cs b/test/Dapr.Client.Test/SecretApiTest.cs index cc628c0e..f2623c6f 100644 --- a/test/Dapr.Client.Test/SecretApiTest.cs +++ b/test/Dapr.Client.Test/SecretApiTest.cs @@ -13,7 +13,7 @@ namespace Dapr.Client.Test using FluentAssertions; using Grpc.Net.Client; using Xunit; - using Autogenerated = Dapr.Client.Autogen.Grpc; + using Autogenerated = Dapr.Client.Autogen.Grpc.v1; public class SecretApiTest { diff --git a/test/Dapr.Client.Test/StateApiTest.cs b/test/Dapr.Client.Test/StateApiTest.cs index 6e56b960..cf5f9618 100644 --- a/test/Dapr.Client.Test/StateApiTest.cs +++ b/test/Dapr.Client.Test/StateApiTest.cs @@ -8,7 +8,7 @@ namespace Dapr.Client.Test using System.Net; using System.Text.Json; using System.Threading.Tasks; - using Autogenerated = Dapr.Client.Autogen.Grpc; + using Autogenerated = Dapr.Client.Autogen.Grpc.v1; using FluentAssertions; using Grpc.Core; using Grpc.Net.Client; diff --git a/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs b/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs index 9faf32a2..cb0c88b1 100644 --- a/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs +++ b/test/Dapr.Extensions.Configuration.Test/DaprSecretStoreConfigurationProviderTest.cs @@ -1,11 +1,11 @@ -// ------------------------------------------------------------ +// ------------------------------------------------------------ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // ------------------------------------------------------------ using System; using System.Collections.Generic; -using Autogenerated = Dapr.Client.Autogen.Grpc; +using Autogenerated = Dapr.Client.Autogen.Grpc.v1; using FluentAssertions; using Xunit; using Microsoft.Extensions.Configuration; @@ -84,4 +84,4 @@ namespace Dapr.Extensions.Configuration.Test return new ConfigurationBuilder(); } } -} \ No newline at end of file +}