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