mirror of https://github.com/dapr/dotnet-sdk.git
				
				
				
			Use protobuf packaging for the Any type (#358)
* Use protobuf packaging for the Any type This pull request implements two converters which help with the type conversion between arbitrary types and the protobuf `Any` type. The C# protobuf library provides a mechanism to pack or unpack protobuf messages to `Any`. It provides the methods `Any.Pack` and `Any.Unpack` to serialize/deserialize messages based on `Google.Protobuf.IMessage`. For types that are not based on `Google.Protobuf.IMessage`, the existing JSON serialization/deserialization will be used. I've also cleaned the existing codebase a little bit. Fixes #268 * Fix suggested changes
This commit is contained in:
		
							parent
							
								
									ff0ead390a
								
							
						
					
					
						commit
						fdf17b7dbb
					
				|  | @ -11,13 +11,11 @@ namespace Dapr.Client | |||
|     using System.Text.Json; | ||||
|     using System.Threading; | ||||
|     using System.Threading.Tasks; | ||||
|     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.v1; | ||||
|     using Autogenerated = Autogen.Grpc.v1; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// A client for interacting with the Dapr endpoints. | ||||
|  | @ -64,14 +62,11 @@ namespace Dapr.Client | |||
| 
 | ||||
|             if (content != null) | ||||
|             { | ||||
|                 envelope.Data = ConvertToByteStringAsync(content, this.jsonSerializerOptions); | ||||
|                 envelope.Data = TypeConverters.ToJsonByteString(content, this.jsonSerializerOptions); | ||||
|             } | ||||
| 
 | ||||
|             await this.MakeGrpcCallHandleError( | ||||
|                 (options) => | ||||
|                 { | ||||
|                     return client.PublishEventAsync(envelope, options); | ||||
|                 }, | ||||
|                 options => client.PublishEventAsync(envelope, options), | ||||
|                 cancellationToken); | ||||
|         } | ||||
|         #endregion | ||||
|  | @ -103,32 +98,32 @@ namespace Dapr.Client | |||
|             ArgumentVerifier.ThrowIfNullOrEmpty(name, nameof(name)); | ||||
|             ArgumentVerifier.ThrowIfNullOrEmpty(operation, nameof(operation)); | ||||
| 
 | ||||
|             InvokeBindingResponse response = await MakeInvokeBindingRequestAsync(name, operation, data, metadata, cancellationToken); | ||||
|             Autogenerated.InvokeBindingResponse response = await MakeInvokeBindingRequestAsync(name, operation, data, metadata, cancellationToken); | ||||
|             return ConvertFromInvokeBindingResponse<TResponse>(response, this.jsonSerializerOptions); | ||||
|         } | ||||
| 
 | ||||
|         private static T ConvertFromInvokeBindingResponse<T>(InvokeBindingResponse response, JsonSerializerOptions options = null) | ||||
|         private static T ConvertFromInvokeBindingResponse<T>(Autogenerated.InvokeBindingResponse response, JsonSerializerOptions options = null) | ||||
|         { | ||||
|             var responseData = response.Data.ToStringUtf8(); | ||||
|             return JsonSerializer.Deserialize<T>(responseData, options); | ||||
|         } | ||||
| 
 | ||||
|         private async Task<InvokeBindingResponse> MakeInvokeBindingRequestAsync<TContent>( | ||||
|         private async Task<Autogenerated.InvokeBindingResponse> MakeInvokeBindingRequestAsync<TContent>( | ||||
|            string name, | ||||
|            string operation, | ||||
|            TContent data, | ||||
|            Dictionary<string, string> metadata = default, | ||||
|            CancellationToken cancellationToken = default) | ||||
|         {           | ||||
|         { | ||||
|             var envelope = new Autogenerated.InvokeBindingRequest() | ||||
|             { | ||||
|                 Name = name, | ||||
|                 Operation = operation | ||||
|             }; | ||||
|              | ||||
| 
 | ||||
|             if (data != null) | ||||
|             { | ||||
|                 envelope.Data = ConvertToByteStringAsync(data, this.jsonSerializerOptions); | ||||
|                 envelope.Data = TypeConverters.ToJsonByteString(data, this.jsonSerializerOptions); | ||||
|             } | ||||
| 
 | ||||
|             if (metadata != null) | ||||
|  | @ -137,10 +132,7 @@ namespace Dapr.Client | |||
|             } | ||||
| 
 | ||||
|             return await this.MakeGrpcCallHandleError( | ||||
|                 (options) => | ||||
|                 { | ||||
|                     return client.InvokeBindingAsync(envelope, options); | ||||
|                 }, | ||||
|                 options => client.InvokeBindingAsync(envelope, options), | ||||
|                 cancellationToken); | ||||
|         } | ||||
|         #endregion | ||||
|  | @ -149,7 +141,7 @@ namespace Dapr.Client | |||
|         public override async Task InvokeMethodAsync( | ||||
|            string appId, | ||||
|            string methodName, | ||||
|            Http.HTTPExtension httpExtension = default, | ||||
|            HTTPExtension httpExtension = default, | ||||
|            CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId)); | ||||
|  | @ -162,7 +154,7 @@ namespace Dapr.Client | |||
|            string appId, | ||||
|            string methodName, | ||||
|            TRequest data, | ||||
|            Http.HTTPExtension httpExtension = default, | ||||
|            HTTPExtension httpExtension = default, | ||||
|            CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId)); | ||||
|  | @ -171,7 +163,7 @@ namespace Dapr.Client | |||
|             Any serializedData = null; | ||||
|             if (data != null) | ||||
|             { | ||||
|                 serializedData = ConvertToAnyAsync(data, this.jsonSerializerOptions); | ||||
|                 serializedData = TypeConverters.ToAny(data, this.jsonSerializerOptions); | ||||
|             } | ||||
| 
 | ||||
|             _ = await this.MakeInvokeRequestAsync(appId, methodName, serializedData, httpExtension, cancellationToken); | ||||
|  | @ -180,26 +172,21 @@ namespace Dapr.Client | |||
|         public override async ValueTask<TResponse> InvokeMethodAsync<TResponse>( | ||||
|            string appId, | ||||
|            string methodName, | ||||
|            Http.HTTPExtension httpExtension = default, | ||||
|            HTTPExtension httpExtension = default, | ||||
|            CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId)); | ||||
|             ArgumentVerifier.ThrowIfNullOrEmpty(methodName, nameof(methodName)); | ||||
| 
 | ||||
|             var response = await this.MakeInvokeRequestAsync(appId, methodName, null, httpExtension, cancellationToken); | ||||
|             if (response.Data.Value.IsEmpty) | ||||
|             { | ||||
|                 return default; | ||||
|             } | ||||
| 
 | ||||
|             return ConvertFromInvokeResponse<TResponse>(response, this.jsonSerializerOptions); | ||||
|             return response.Data.Value.IsEmpty ? default : TypeConverters.FromAny<TResponse>(response.Data, this.jsonSerializerOptions); | ||||
|         } | ||||
| 
 | ||||
|         public override async ValueTask<TResponse> InvokeMethodAsync<TRequest, TResponse>( | ||||
|             string appId, | ||||
|             string methodName, | ||||
|             TRequest data, | ||||
|             Http.HTTPExtension httpExtension = default, | ||||
|             HTTPExtension httpExtension = default, | ||||
|             CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId)); | ||||
|  | @ -208,23 +195,18 @@ namespace Dapr.Client | |||
|             Any serializedData = null; | ||||
|             if (data != null) | ||||
|             { | ||||
|                 serializedData = ConvertToAnyAsync(data, this.jsonSerializerOptions); | ||||
|                 serializedData = TypeConverters.ToAny(data, this.jsonSerializerOptions); | ||||
|             } | ||||
| 
 | ||||
|             var response = await this.MakeInvokeRequestAsync(appId, methodName, serializedData, httpExtension, cancellationToken); | ||||
|             if (response.Data.Value.IsEmpty) | ||||
|             { | ||||
|                 return default; | ||||
|             } | ||||
| 
 | ||||
|             return ConvertFromInvokeResponse<TResponse>(response, this.jsonSerializerOptions); | ||||
|             return response.Data.Value.IsEmpty ? default : TypeConverters.FromAny<TResponse>(response.Data, this.jsonSerializerOptions); | ||||
|         } | ||||
| 
 | ||||
|         private async Task<InvokeResponse> MakeInvokeRequestAsync( | ||||
|         private async Task<Autogenerated.InvokeResponse> MakeInvokeRequestAsync( | ||||
|             string appId, | ||||
|             string methodName, | ||||
|             Any data, | ||||
|             Http.HTTPExtension httpExtension, | ||||
|             HTTPExtension httpExtension, | ||||
|             CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             var protoHTTPExtension = new Autogenerated.HTTPExtension(); | ||||
|  | @ -236,9 +218,9 @@ namespace Dapr.Client | |||
| 
 | ||||
|                 if (httpExtension.QueryString != null) | ||||
|                 { | ||||
|                     foreach (var kv in httpExtension.QueryString) | ||||
|                     foreach (var (key, value) in httpExtension.QueryString) | ||||
|                     { | ||||
|                         protoHTTPExtension.Querystring.Add(kv.Key, kv.Value); | ||||
|                         protoHTTPExtension.Querystring.Add(key, value); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|  | @ -250,7 +232,7 @@ namespace Dapr.Client | |||
|                 contentType = Constants.ContentTypeApplicationJson; | ||||
|             } | ||||
| 
 | ||||
|             var invokeRequest = new InvokeRequest() | ||||
|             var invokeRequest = new Autogenerated.InvokeRequest() | ||||
|             { | ||||
|                 Method = methodName, | ||||
|                 Data = data, | ||||
|  | @ -266,10 +248,7 @@ namespace Dapr.Client | |||
|             }; | ||||
| 
 | ||||
|             return await this.MakeGrpcCallHandleError( | ||||
|                  (options) => | ||||
|                  { | ||||
|                      return client.InvokeServiceAsync(request, options); | ||||
|                  }, | ||||
|                  options => client.InvokeServiceAsync(request, options), | ||||
|                  cancellationToken); | ||||
|         } | ||||
|         #endregion | ||||
|  | @ -281,7 +260,7 @@ namespace Dapr.Client | |||
|             ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName)); | ||||
|             ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key)); | ||||
| 
 | ||||
|             var getStateEnvelope = new GetStateRequest() | ||||
|             var getStateEnvelope = new Autogenerated.GetStateRequest() | ||||
|             { | ||||
|                 StoreName = storeName, | ||||
|                 Key = key, | ||||
|  | @ -293,10 +272,7 @@ namespace Dapr.Client | |||
|             } | ||||
| 
 | ||||
|             var response = await this.MakeGrpcCallHandleError( | ||||
|                 (options) => | ||||
|                 { | ||||
|                     return client.GetStateAsync(getStateEnvelope, options); | ||||
|                 }, | ||||
|                 options => client.GetStateAsync(getStateEnvelope, options), | ||||
|                 cancellationToken); | ||||
| 
 | ||||
|             if (response.Data.IsEmpty) | ||||
|  | @ -314,7 +290,7 @@ namespace Dapr.Client | |||
|             ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName)); | ||||
|             ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key)); | ||||
| 
 | ||||
|             var getStateEnvelope = new GetStateRequest() | ||||
|             var getStateEnvelope = new Autogenerated.GetStateRequest() | ||||
|             { | ||||
|                 StoreName = storeName, | ||||
|                 Key = key, | ||||
|  | @ -326,15 +302,12 @@ namespace Dapr.Client | |||
|             } | ||||
| 
 | ||||
|             var response = await this.MakeGrpcCallHandleError( | ||||
|                 (options) => | ||||
|                 { | ||||
|                     return client.GetStateAsync(getStateEnvelope, options); | ||||
|                 }, | ||||
|                 options => client.GetStateAsync(getStateEnvelope, options), | ||||
|                 cancellationToken); | ||||
| 
 | ||||
|             if (response.Data.IsEmpty) | ||||
|             { | ||||
|                 return (default(TValue), response.Etag); | ||||
|                 return (default, response.Etag); | ||||
|             } | ||||
| 
 | ||||
|             var responseData = response.Data.ToStringUtf8(); | ||||
|  | @ -354,7 +327,7 @@ namespace Dapr.Client | |||
|             ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName)); | ||||
|             ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key)); | ||||
| 
 | ||||
|             await this.MakeSaveStateCallAsync<TValue>( | ||||
|             await this.MakeSaveStateCallAsync( | ||||
|                 storeName, | ||||
|                 key, | ||||
|                 value, | ||||
|  | @ -379,16 +352,17 @@ namespace Dapr.Client | |||
| 
 | ||||
|             try | ||||
|             { | ||||
|                 await this.MakeSaveStateCallAsync<TValue>(storeName, key, value, etag, stateOptions, metadata, cancellationToken); | ||||
|                 await this.MakeSaveStateCallAsync(storeName, key, value, etag, stateOptions, metadata, cancellationToken); | ||||
|                 return true; | ||||
|             } | ||||
|             catch (RpcException) | ||||
|             { } | ||||
|             { | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         internal async ValueTask MakeSaveStateCallAsync<TValue>( | ||||
|         private async ValueTask MakeSaveStateCallAsync<TValue>( | ||||
|             string storeName, | ||||
|             string key, | ||||
|             TValue value, | ||||
|  | @ -425,16 +399,13 @@ namespace Dapr.Client | |||
| 
 | ||||
|             if (value != null) | ||||
|             { | ||||
|                 stateItem.Value = ConvertToByteStringAsync(value, this.jsonSerializerOptions); | ||||
|                 stateItem.Value = TypeConverters.ToJsonByteString(value, this.jsonSerializerOptions); | ||||
|             } | ||||
| 
 | ||||
|             saveStateEnvelope.States.Add(stateItem); | ||||
| 
 | ||||
|             await this.MakeGrpcCallHandleError( | ||||
|                 (options) => | ||||
|                 { | ||||
|                     return client.SaveStateAsync(saveStateEnvelope, options); | ||||
|                 }, | ||||
|                 options => client.SaveStateAsync(saveStateEnvelope, options), | ||||
|                 cancellationToken); | ||||
|         } | ||||
| 
 | ||||
|  | @ -448,7 +419,7 @@ namespace Dapr.Client | |||
|             ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName)); | ||||
|             ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key)); | ||||
| 
 | ||||
|             await this.MakeDeleteStateCallsync( | ||||
|             await this.MakeDeleteStateCallAsync( | ||||
|                 storeName, | ||||
|                 key, | ||||
|                 etag: null, | ||||
|  | @ -460,7 +431,7 @@ namespace Dapr.Client | |||
|         public override async ValueTask<bool> TryDeleteStateAsync( | ||||
|             string storeName, | ||||
|             string key, | ||||
|             string etag = default, | ||||
|             string etag, | ||||
|             StateOptions stateOptions = default, | ||||
|             CancellationToken cancellationToken = default) | ||||
|         { | ||||
|  | @ -469,7 +440,7 @@ namespace Dapr.Client | |||
| 
 | ||||
|             try | ||||
|             { | ||||
|                 await this.MakeDeleteStateCallsync(storeName, key, etag, stateOptions, cancellationToken); | ||||
|                 await this.MakeDeleteStateCallAsync(storeName, key, etag, stateOptions, cancellationToken); | ||||
|                 return true; | ||||
|             } | ||||
|             catch (Exception) | ||||
|  | @ -479,14 +450,14 @@ namespace Dapr.Client | |||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         private async ValueTask MakeDeleteStateCallsync( | ||||
|         private async ValueTask MakeDeleteStateCallAsync( | ||||
|            string storeName, | ||||
|            string key, | ||||
|            string etag = default, | ||||
|            StateOptions stateOptions = default, | ||||
|            CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             var deleteStateEnvelope = new DeleteStateRequest() | ||||
|             var deleteStateEnvelope = new Autogenerated.DeleteStateRequest() | ||||
|             { | ||||
|                 StoreName = storeName, | ||||
|                 Key = key, | ||||
|  | @ -503,10 +474,7 @@ namespace Dapr.Client | |||
|             } | ||||
| 
 | ||||
|             await this.MakeGrpcCallHandleError( | ||||
|                 (options) => | ||||
|                 { | ||||
|                     return client.DeleteStateAsync(deleteStateEnvelope, options); | ||||
|                 }, | ||||
|                 options => client.DeleteStateAsync(deleteStateEnvelope, options), | ||||
|                 cancellationToken); | ||||
|         } | ||||
|         #endregion | ||||
|  | @ -534,10 +502,7 @@ namespace Dapr.Client | |||
|             } | ||||
| 
 | ||||
|             var response = await this.MakeGrpcCallHandleError( | ||||
|                  (options) => | ||||
|                  { | ||||
|                      return client.GetSecretAsync(envelope, options); | ||||
|                  }, | ||||
|                  options => client.GetSecretAsync(envelope, options), | ||||
|                  cancellationToken); | ||||
| 
 | ||||
|             return response.Data.ToDictionary(kv => kv.Key, kv => kv.Value); | ||||
|  | @ -558,7 +523,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); | ||||
|         } | ||||
| 
 | ||||
|  | @ -576,60 +541,32 @@ namespace Dapr.Client | |||
|                 stateRequestOptions.Concurrency = GetStateConcurrencyForConcurrencyMode(stateOptions.Concurrency.Value); | ||||
|             } | ||||
| 
 | ||||
|             if (stateOptions.RetryOptions != null) | ||||
|             if (stateOptions.RetryOptions == null) | ||||
|             { | ||||
|                 var retryPolicy = new Autogenerated.StateRetryPolicy(); | ||||
|                 if (stateOptions.RetryOptions.RetryMode != null) | ||||
|                 { | ||||
|                     retryPolicy.Pattern = GetRetryPatternForRetryMode(stateOptions.RetryOptions.RetryMode.Value); | ||||
|                 } | ||||
| 
 | ||||
|                 if (stateOptions.RetryOptions.RetryInterval != null) | ||||
|                 { | ||||
|                     retryPolicy.Interval = Duration.FromTimeSpan(stateOptions.RetryOptions.RetryInterval.Value); | ||||
|                 } | ||||
| 
 | ||||
|                 if (stateOptions.RetryOptions.RetryThreshold != null) | ||||
|                 { | ||||
|                     retryPolicy.Threshold = stateOptions.RetryOptions.RetryThreshold.Value; | ||||
|                 } | ||||
| 
 | ||||
|                 stateRequestOptions.RetryPolicy = retryPolicy; | ||||
|                 return stateRequestOptions; | ||||
|             } | ||||
| 
 | ||||
|             var retryPolicy = new Autogenerated.StateRetryPolicy(); | ||||
|             if (stateOptions.RetryOptions.RetryMode != null) | ||||
|             { | ||||
|                 retryPolicy.Pattern = GetRetryPatternForRetryMode(stateOptions.RetryOptions.RetryMode.Value); | ||||
|             } | ||||
| 
 | ||||
|             if (stateOptions.RetryOptions.RetryInterval != null) | ||||
|             { | ||||
|                 retryPolicy.Interval = Duration.FromTimeSpan(stateOptions.RetryOptions.RetryInterval.Value); | ||||
|             } | ||||
| 
 | ||||
|             if (stateOptions.RetryOptions.RetryThreshold != null) | ||||
|             { | ||||
|                 retryPolicy.Threshold = stateOptions.RetryOptions.RetryThreshold.Value; | ||||
|             } | ||||
| 
 | ||||
|             stateRequestOptions.RetryPolicy = retryPolicy; | ||||
| 
 | ||||
|             return stateRequestOptions; | ||||
|         } | ||||
| 
 | ||||
|         private static Any ConvertToAnyAsync<T>(T data, JsonSerializerOptions options = null) | ||||
|         { | ||||
|             var any = new Any(); | ||||
| 
 | ||||
|             if (data != null) | ||||
|             { | ||||
|                 var bytes = JsonSerializer.SerializeToUtf8Bytes(data, options); | ||||
|                 any.Value = ByteString.CopyFrom(bytes); | ||||
|             } | ||||
| 
 | ||||
|             return any; | ||||
|         } | ||||
| 
 | ||||
|         private static ByteString ConvertToByteStringAsync<T>(T data, JsonSerializerOptions options = null) | ||||
|         { | ||||
|             if (data != null) | ||||
|             { | ||||
|                 var bytes = JsonSerializer.SerializeToUtf8Bytes(data, options);                                 | ||||
|                 return ByteString.CopyFrom(bytes); | ||||
|             } | ||||
| 
 | ||||
|             return ByteString.Empty; | ||||
|         } | ||||
| 
 | ||||
|         private static T ConvertFromInvokeResponse<T>(InvokeResponse response, JsonSerializerOptions options = null) | ||||
|         { | ||||
|             var responseData = response.Data.Value.ToStringUtf8(); | ||||
|             return JsonSerializer.Deserialize<T>(responseData, options); | ||||
|         } | ||||
| 
 | ||||
|         private static Autogenerated.HTTPExtension.Types.Verb ConvertHTTPVerb(HTTPVerb verb) | ||||
|         { | ||||
|             return verb switch | ||||
|  | @ -648,49 +585,33 @@ namespace Dapr.Client | |||
| 
 | ||||
|         private static Autogenerated.StateOptions.Types.StateConsistency GetStateConsistencyForConsistencyMode(ConsistencyMode consistencyMode) | ||||
|         { | ||||
|             if (consistencyMode.Equals(ConsistencyMode.Eventual)) | ||||
|             return consistencyMode switch | ||||
|             { | ||||
|                 return Autogenerated.StateOptions.Types.StateConsistency.ConsistencyEventual; | ||||
|             } | ||||
| 
 | ||||
|             if (consistencyMode.Equals(ConsistencyMode.Strong)) | ||||
|             { | ||||
|                 return Autogenerated.StateOptions.Types.StateConsistency.ConsistencyStrong; | ||||
|             } | ||||
| 
 | ||||
|             throw new ArgumentException($"{consistencyMode} Consistency Mode is not supported."); | ||||
|                 ConsistencyMode.Eventual => Autogenerated.StateOptions.Types.StateConsistency.ConsistencyEventual, | ||||
|                 ConsistencyMode.Strong => Autogenerated.StateOptions.Types.StateConsistency.ConsistencyStrong, | ||||
|                 _ => throw new ArgumentException($"{consistencyMode} Consistency Mode is not supported.") | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         private static Autogenerated.StateOptions.Types.StateConcurrency GetStateConcurrencyForConcurrencyMode(ConcurrencyMode concurrencyMode) | ||||
|         { | ||||
|             if (concurrencyMode.Equals(ConcurrencyMode.FirstWrite)) | ||||
|             return concurrencyMode switch | ||||
|             { | ||||
|                 return Autogenerated.StateOptions.Types.StateConcurrency.ConcurrencyFirstWrite; | ||||
|             } | ||||
| 
 | ||||
|             if (concurrencyMode.Equals(ConcurrencyMode.LastWrite)) | ||||
|             { | ||||
|                 return Autogenerated.StateOptions.Types.StateConcurrency.ConcurrencyLastWrite; | ||||
|             } | ||||
| 
 | ||||
|             throw new ArgumentException($"{concurrencyMode} Concurrency Mode is not supported."); | ||||
|                 ConcurrencyMode.FirstWrite => Autogenerated.StateOptions.Types.StateConcurrency.ConcurrencyFirstWrite, | ||||
|                 ConcurrencyMode.LastWrite => Autogenerated.StateOptions.Types.StateConcurrency.ConcurrencyLastWrite, | ||||
|                 _ => throw new ArgumentException($"{concurrencyMode} Concurrency Mode is not supported.") | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         private static Autogenerated.StateRetryPolicy.Types.RetryPattern GetRetryPatternForRetryMode(RetryMode retryMode) | ||||
|         { | ||||
|             if (retryMode.Equals(RetryMode.Exponential)) | ||||
|             return retryMode switch | ||||
|             { | ||||
|                 return Autogenerated.StateRetryPolicy.Types.RetryPattern.RetryExponential; | ||||
|             } | ||||
| 
 | ||||
|             if (retryMode.Equals(RetryMode.Linear)) | ||||
|             { | ||||
|                 return Autogenerated.StateRetryPolicy.Types.RetryPattern.RetryLinear; | ||||
|             } | ||||
| 
 | ||||
|             throw new ArgumentException($"{retryMode} Retry Mode is not supported."); | ||||
|                 RetryMode.Exponential => Autogenerated.StateRetryPolicy.Types.RetryPattern.RetryExponential, | ||||
|                 RetryMode.Linear => Autogenerated.StateRetryPolicy.Types.RetryPattern.RetryLinear, | ||||
|                 _ => throw new ArgumentException($"{retryMode} Retry Mode is not supported.") | ||||
|             }; | ||||
|         } | ||||
|         #endregion Helper Methods | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,85 @@ | |||
| // ------------------------------------------------------------ | ||||
| // Copyright (c) Microsoft Corporation. | ||||
| // Licensed under the MIT License. | ||||
| // ------------------------------------------------------------ | ||||
| 
 | ||||
| namespace Dapr.Client | ||||
| { | ||||
|     using System.Text.Json; | ||||
|     using Google.Protobuf; | ||||
|     using Google.Protobuf.WellKnownTypes; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Some type converters. | ||||
|     /// </summary> | ||||
|     internal static class TypeConverters | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Converts an arbitrary type to a <see cref="System.Text.Json"/> based <see cref="ByteString"/>. | ||||
|         /// </summary> | ||||
|         /// <param name="data">The data to convert.</param> | ||||
|         /// <param name="options">The JSON serialization options.</param> | ||||
|         /// <typeparam name="T">The type of the given data.</typeparam> | ||||
|         /// <returns>The given data as JSON based byte string.</returns> | ||||
|         public static ByteString ToJsonByteString<T>(T data, JsonSerializerOptions options = null) | ||||
|         { | ||||
|             if (data == null) | ||||
|             { | ||||
|                 return ByteString.Empty; | ||||
|             } | ||||
| 
 | ||||
|             var bytes = JsonSerializer.SerializeToUtf8Bytes(data, options); | ||||
|             return ByteString.CopyFrom(bytes); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Converts an arbitrary type to the <see cref="Any"/> Protocol Buffer type. | ||||
|         /// | ||||
|         /// If the given type is a subtype of <see cref="IMessage"/>, then it's save to use the Protocol Buffer | ||||
|         /// packaging provided for the <see cref="Any"/> type with the <see cref="Any.Pack(Google.Protobuf.IMessage)"/>. | ||||
|         /// For all other types, we use JSON serialization based on <see cref="System.Text.Json"/>. | ||||
|         /// </summary> | ||||
|         /// <param name="data">The data to convert.</param> | ||||
|         /// <param name="options">The JSON serialization options.</param> | ||||
|         /// <typeparam name="T">The type of the given data.</typeparam> | ||||
|         /// <returns>The <see cref="Any"/> type representation of the given data.</returns> | ||||
|         public static Any ToAny<T>(T data, JsonSerializerOptions options = null) | ||||
|         { | ||||
|             if (data == null) | ||||
|             { | ||||
|                 return new Any(); | ||||
|             } | ||||
| 
 | ||||
|             var t = typeof(T); | ||||
| 
 | ||||
|             return typeof(IMessage).IsAssignableFrom(t) | ||||
|                 ? Any.Pack((IMessage) data) | ||||
|                 : new Any {Value = ToJsonByteString(data, options), TypeUrl = t.FullName}; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Converts the Protocol Buffer <see cref="Any"/> type to an arbitrary type. | ||||
|         /// | ||||
|         /// If the type to convert to is a subtype of <see cref="IMessage"/> and if the type has an empty default | ||||
|         /// constructor, then we use the <see cref="Any.Unpack{T}"/> method to deserialize the given <see cref="Any"/> | ||||
|         /// instance. For all other types, we use JSON deserialization based on <see cref="System.Text.Json"/>. | ||||
|         /// </summary> | ||||
|         /// <param name="any">The any instance to convert.</param> | ||||
|         /// <param name="options">The JSON serialization options.</param> | ||||
|         /// <typeparam name="T">The type to convert to.</typeparam> | ||||
|         /// <returns>An instance of T.</returns> | ||||
|         public static T FromAny<T>(Any any, JsonSerializerOptions options = null) | ||||
|         { | ||||
|             var t = typeof(T); | ||||
| 
 | ||||
|             if (typeof(IMessage).IsAssignableFrom(t) && t.GetConstructor(System.Type.EmptyTypes) != null) | ||||
|             { | ||||
|                 var method = any.GetType().GetMethod("Unpack"); | ||||
|                 var generic = method.MakeGenericMethod(t); | ||||
|                 return (T) generic.Invoke(any, null); | ||||
|             } | ||||
| 
 | ||||
|             return JsonSerializer.Deserialize<T>(any.Value.ToStringUtf8(), options); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -16,7 +16,6 @@ | |||
|   <ItemGroup> | ||||
|     <Compile Include="..\Shared\TestHttpClient.cs" /> | ||||
|     <Compile Include="..\Shared\GrpcUtils.cs" /> | ||||
|     <Compile Include="..\Shared\ProtobufUtils.cs" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|  |  | |||
|  | @ -109,7 +109,7 @@ namespace Dapr.AspNetCore.Test | |||
| 
 | ||||
|         private async void SendResponseWithState<T>(T state, TestHttpClient.Entry entry) | ||||
|         { | ||||
|             var stateData = ProtobufUtils.ConvertToByteStringAsync(state); | ||||
|             var stateData = TypeConverters.ToJsonByteString(state); | ||||
|             var stateResponse = new GetStateResponse(); | ||||
|             stateResponse.Data = stateData; | ||||
|             stateResponse.Etag = "test"; | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ | |||
|     <PackageReference Include="Google.Protobuf" Version="3.11.4" /> | ||||
|     <PackageReference Include="Grpc.Core.Testing" Version="2.31.0-pre1" /> | ||||
|     <PackageReference Include="Grpc.Net.Client" Version="2.27.0" /> | ||||
|     <PackageReference Include="Grpc.Tools" Version="2.27.0" PrivateAssets="All" /> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" /> | ||||
|     <PackageReference Include="xunit" Version="2.4.1" /> | ||||
|     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1"> | ||||
|  | @ -16,11 +17,14 @@ | |||
|     </PackageReference> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <Protobuf Include="Protos\test.proto" ProtoRoot="Protos" GrpcServices="Client" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <Compile Include="..\Shared\AppCallbackClient.cs" /> | ||||
|     <Compile Include="..\Shared\TestHttpClient.cs" /> | ||||
|     <Compile Include="..\Shared\GrpcUtils.cs" /> | ||||
|     <Compile Include="..\Shared\ProtobufUtils.cs" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ namespace Dapr.Client.Test | |||
|     using System.Text.Json; | ||||
|     using System.Threading.Tasks; | ||||
|     using Dapr.Client.Autogen.Grpc.v1; | ||||
|     using Dapr.Client.Autogen.Test.Grpc.v1; | ||||
|     using Dapr.AppCallback.Autogen.Grpc.v1; | ||||
|     using Dapr.Client.Http; | ||||
|     using FluentAssertions; | ||||
|  | @ -395,11 +396,35 @@ namespace Dapr.Client.Test | |||
| 
 | ||||
|             var request = new Request() { RequestParameter = "Look, I was invoked!" }; | ||||
| 
 | ||||
|             var response = await daprClient.InvokeMethodAsync<Request, Response>("test1", "sayHello", request); | ||||
|             var response = await daprClient.InvokeMethodAsync<Request, Response>("test", "SayHello", request); | ||||
| 
 | ||||
|             response.Name.Should().Be("Hello Look, I was invoked!"); | ||||
|         } | ||||
| 
 | ||||
|         [Fact] | ||||
|         public async Task InvokeMethodAsync_AppCallback_RepeatedField() | ||||
|         { | ||||
|             // Configure Client | ||||
|             var jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; | ||||
|             var httpClient = new AppCallbackClient(new DaprAppCallbackService(jsonOptions)); | ||||
|             var daprClient = new DaprClientBuilder() | ||||
|                 .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) | ||||
|                 .UseJsonSerializationOptions(jsonOptions) | ||||
|                 .Build(); | ||||
| 
 | ||||
|             var testRun = new TestRun(); | ||||
|             testRun.Tests.Add(new TestCase() { Name = "test1" }); | ||||
|             testRun.Tests.Add(new TestCase() { Name = "test2" }); | ||||
|             testRun.Tests.Add(new TestCase() { Name = "test3" }); | ||||
| 
 | ||||
|             var response = await daprClient.InvokeMethodAsync<TestRun, TestRun>("test", "TestRun", testRun); | ||||
| 
 | ||||
|             response.Tests.Count.Should().Be(3); | ||||
|             response.Tests[0].Name.Should().Be("test1"); | ||||
|             response.Tests[1].Name.Should().Be("test2"); | ||||
|             response.Tests[2].Name.Should().Be("test3"); | ||||
|         } | ||||
| 
 | ||||
|         [Fact] | ||||
|         public async Task InvokeMethodAsync_AppCallback_UnexpectedMethod() | ||||
|         { | ||||
|  | @ -413,14 +438,14 @@ namespace Dapr.Client.Test | |||
| 
 | ||||
|             var request = new Request() { RequestParameter = "Look, I was invoked!" }; | ||||
| 
 | ||||
|             var response = await daprClient.InvokeMethodAsync<Request, Response>("test1", "not-existing", request); | ||||
|             var response = await daprClient.InvokeMethodAsync<Request, Response>("test", "not-existing", request); | ||||
| 
 | ||||
|             response.Name.Should().Be("unexpected"); | ||||
|         } | ||||
| 
 | ||||
|         private async void SendResponse<T>(T data, TestHttpClient.Entry entry, JsonSerializerOptions options = null) | ||||
|         { | ||||
|             var dataAny = ProtobufUtils.ConvertToAnyAsync(data, options); | ||||
|             var dataAny = TypeConverters.ToAny(data, options); | ||||
|             var dataResponse = new InvokeResponse(); | ||||
|             dataResponse.Data = dataAny; | ||||
| 
 | ||||
|  | @ -453,22 +478,33 @@ namespace Dapr.Client.Test | |||
|             { | ||||
|                 return request.Method switch | ||||
|                 { | ||||
|                     "sayHello" => SayHello(request), | ||||
|                     "SayHello" => SayHello(request), | ||||
|                     "TestRun" => TestRun(request), | ||||
|                     _ => Task.FromResult(new InvokeResponse() | ||||
|                     { | ||||
|                         Data = ProtobufUtils.ConvertToAnyAsync(new Response() { Name = $"unexpected" }, this.jsonOptions) | ||||
|                         Data = TypeConverters.ToAny(new Response() { Name = $"unexpected" }, this.jsonOptions) | ||||
|                     }) | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             private Task<InvokeResponse> SayHello(InvokeRequest request) | ||||
|             { | ||||
|                 var helloRequest = ProtobufUtils.ConvertFromAnyAsync<Request>(request.Data, this.jsonOptions); | ||||
|                 var helloRequest = TypeConverters.FromAny<Request>(request.Data, this.jsonOptions); | ||||
|                 var helloResponse = new Response() { Name = $"Hello {helloRequest.RequestParameter}" }; | ||||
| 
 | ||||
|                 return Task.FromResult(new InvokeResponse() | ||||
|                 { | ||||
|                     Data = ProtobufUtils.ConvertToAnyAsync(helloResponse, this.jsonOptions) | ||||
|                     Data = TypeConverters.ToAny(helloResponse, this.jsonOptions) | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             private Task<InvokeResponse> TestRun(InvokeRequest request) | ||||
|             { | ||||
|                 var echoRequest = TypeConverters.FromAny<TestRun>(request.Data, this.jsonOptions); | ||||
| 
 | ||||
|                 return Task.FromResult(new InvokeResponse() | ||||
|                 { | ||||
|                     Data = TypeConverters.ToAny(echoRequest, this.jsonOptions) | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -0,0 +1,16 @@ | |||
| // ------------------------------------------------------------ | ||||
| // Copyright (c) Microsoft Corporation. | ||||
| // Licensed under the MIT License. | ||||
| // ------------------------------------------------------------ | ||||
| 
 | ||||
| syntax = "proto3"; | ||||
| 
 | ||||
| option csharp_namespace = "Dapr.Client.Autogen.Test.Grpc.v1"; | ||||
| 
 | ||||
| message TestRun { | ||||
|   repeated TestCase tests = 1; | ||||
| } | ||||
| 
 | ||||
| message TestCase { | ||||
|   string name = 1; | ||||
| } | ||||
|  | @ -13,7 +13,6 @@ namespace Dapr.Client.Test | |||
|     using Grpc.Core; | ||||
|     using Grpc.Net.Client; | ||||
|     using Xunit; | ||||
|     using Google.Protobuf.WellKnownTypes; | ||||
|     using System; | ||||
|     using System.Collections.Generic; | ||||
|     using StateConsistency = Dapr.Client.Autogen.Grpc.v1.StateOptions.Types.StateConsistency; | ||||
|  | @ -145,11 +144,11 @@ namespace Dapr.Client.Test | |||
| 
 | ||||
|             var widget = new Widget() { Size = "small", Color = "yellow", }; | ||||
|             var task = daprClient.SaveStateAsync("testStore", "test", widget); | ||||
|              | ||||
| 
 | ||||
|             // Get Request and validate | ||||
|             httpClient.Requests.TryDequeue(out var entry).Should().BeTrue(); | ||||
|             var request = await GrpcUtils.GetRequestFromRequestMessageAsync<Autogenerated.SaveStateRequest>(entry.Request); | ||||
|              | ||||
| 
 | ||||
|             request.StoreName.Should().Be("testStore"); | ||||
|             request.States.Count.Should().Be(1); | ||||
|             var state = request.States[0]; | ||||
|  | @ -192,7 +191,7 @@ namespace Dapr.Client.Test | |||
|             var daprClient = new DaprClientBuilder() | ||||
|                 .UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient }) | ||||
|                 .Build(); | ||||
|              | ||||
| 
 | ||||
|             var widget = new Widget() { Size = "small", Color = "yellow", }; | ||||
|             var task = daprClient.SaveStateAsync("testStore", "test", widget); | ||||
| 
 | ||||
|  | @ -347,7 +346,7 @@ namespace Dapr.Client.Test | |||
| 
 | ||||
|             // Get Request and validate | ||||
|             httpClient.Requests.TryDequeue(out entry).Should().BeTrue(); | ||||
|             var request = await GrpcUtils.GetRequestFromRequestMessageAsync<Autogenerated.DeleteStateRequest>(entry.Request);  | ||||
|             var request = await GrpcUtils.GetRequestFromRequestMessageAsync<Autogenerated.DeleteStateRequest>(entry.Request); | ||||
|             request.StoreName.Should().Be("testStore"); | ||||
|             request.Key.Should().Be("test"); | ||||
|         } | ||||
|  | @ -584,7 +583,7 @@ namespace Dapr.Client.Test | |||
| 
 | ||||
|         private async void SendResponseWithState<T>(T state, TestHttpClient.Entry entry, string etag = null) | ||||
|         { | ||||
|             var stateDate = ProtobufUtils.ConvertToByteStringAsync(state); | ||||
|             var stateDate = TypeConverters.ToJsonByteString(state); | ||||
|             var stateResponse = new Autogenerated.GetStateResponse(); | ||||
|             stateResponse.Data = stateDate; | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,54 @@ | |||
| // ------------------------------------------------------------ | ||||
| // Copyright (c) Microsoft Corporation. | ||||
| // Licensed under the MIT License. | ||||
| // ------------------------------------------------------------ | ||||
| 
 | ||||
| namespace Dapr.Client.Test | ||||
| { | ||||
|     using Dapr.Client.Autogen.Test.Grpc.v1; | ||||
|     using FluentAssertions; | ||||
|     using Xunit; | ||||
| 
 | ||||
|     public class TypeConvertersTest | ||||
|     { | ||||
|         [Fact] | ||||
|         public void AnyConversion_GRPC_Pack_Unpack() | ||||
|         { | ||||
|             var testRun = new TestRun(); | ||||
|             testRun.Tests.Add(new TestCase() { Name = "test1" }); | ||||
|             testRun.Tests.Add(new TestCase() { Name = "test2" }); | ||||
|             testRun.Tests.Add(new TestCase() { Name = "test3" }); | ||||
| 
 | ||||
|             var any = TypeConverters.ToAny(testRun); | ||||
|             var type = TypeConverters.FromAny<TestRun>(any); | ||||
| 
 | ||||
|             type.Should().BeEquivalentTo(testRun); | ||||
|             any.TypeUrl.Should().Be("type.googleapis.com/TestRun"); | ||||
|             type.Tests.Count.Should().Be(3); | ||||
|             type.Tests[0].Name.Should().Be("test1"); | ||||
|             type.Tests[1].Name.Should().Be("test2"); | ||||
|             type.Tests[2].Name.Should().Be("test3"); | ||||
|         } | ||||
| 
 | ||||
|         [Fact] | ||||
|         public void AnyConversion_JSON_Serialization_Deserialization() | ||||
|         { | ||||
|             var response = new Response() | ||||
|             { | ||||
|                 Name = "test" | ||||
|             }; | ||||
| 
 | ||||
|             var any = TypeConverters.ToAny(response); | ||||
|             var type = TypeConverters.FromAny<Response>(any); | ||||
| 
 | ||||
|             type.Should().BeEquivalentTo(response); | ||||
|             any.TypeUrl.Should().Be("Dapr.Client.Test.TypeConvertersTest+Response"); | ||||
|             type.Name.Should().Be("test"); | ||||
|         } | ||||
| 
 | ||||
|         private class Response | ||||
|         { | ||||
|             public string Name { get; set; } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,45 +0,0 @@ | |||
| // ------------------------------------------------------------ | ||||
| // Copyright (c) Microsoft Corporation. | ||||
| // Licensed under the MIT License. | ||||
| // ------------------------------------------------------------ | ||||
| 
 | ||||
| namespace Dapr | ||||
| { | ||||
|     using System.Text.Json; | ||||
|     using Google.Protobuf; | ||||
|     using Google.Protobuf.WellKnownTypes; | ||||
| 
 | ||||
|     public class ProtobufUtils | ||||
|     { | ||||
|         public static Any ConvertToAnyAsync<T>(T data, JsonSerializerOptions options = null) | ||||
|         { | ||||
|             var any = new Any(); | ||||
| 
 | ||||
|             if (data != null) | ||||
|             { | ||||
|                 var bytes = JsonSerializer.SerializeToUtf8Bytes(data, options); | ||||
|                 any.Value = ByteString.CopyFrom(bytes); | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             return any; | ||||
|         } | ||||
| 
 | ||||
|         public static ByteString ConvertToByteStringAsync<T>(T data, JsonSerializerOptions options = null) | ||||
|         { | ||||
|             if (data != null) | ||||
|             { | ||||
|                 var bytes = JsonSerializer.SerializeToUtf8Bytes(data, options); | ||||
|                 return ByteString.CopyFrom(bytes); | ||||
|             } | ||||
| 
 | ||||
|             return ByteString.Empty; | ||||
|         } | ||||
| 
 | ||||
|         public static T ConvertFromAnyAsync<T>(Any any, JsonSerializerOptions options = null) | ||||
|         { | ||||
|             var utf8String = any.Value.ToStringUtf8(); | ||||
|             return JsonSerializer.Deserialize<T>(utf8String, options); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue