forked from dapr/dotnet-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add system text json support for actor serialization
Signed-off-by: Erik O'Leary <[email protected]>
- Loading branch information
1 parent
c99475b
commit 088eee8
Showing
19 changed files
with
506 additions
and
111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
src/Dapr.Actors/Communication/ActorMessageBodyJsonConverter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// ------------------------------------------------------------------------ | ||
// Copyright 2021 The Dapr Authors | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// ------------------------------------------------------------------------ | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace Dapr.Actors.Communication | ||
{ | ||
internal class ActorMessageBodyJsonConverter<T> : JsonConverter<T> | ||
{ | ||
private readonly List<Type> methodRequestParameterTypes; | ||
private readonly List<Type> wrappedRequestMessageTypes; | ||
private readonly Type wrapperMessageType; | ||
|
||
public ActorMessageBodyJsonConverter( | ||
List<Type> methodRequestParameterTypes, | ||
List<Type> wrappedRequestMessageTypes = null | ||
) | ||
{ | ||
this.methodRequestParameterTypes = methodRequestParameterTypes; | ||
this.wrappedRequestMessageTypes = wrappedRequestMessageTypes; | ||
|
||
if (this.wrappedRequestMessageTypes != null && this.wrappedRequestMessageTypes.Count == 1) | ||
{ | ||
this.wrapperMessageType = this.wrappedRequestMessageTypes[0]; | ||
} | ||
|
||
// If T is of WrappedMessageBody, then get the 'Value' property. | ||
if (typeof(T).IsAssignableFrom(typeof(WrappedMessageBody))) | ||
{ | ||
} | ||
} | ||
|
||
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||
{ | ||
// Ensure start-of-object, then advance | ||
if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException(); | ||
reader.Read(); | ||
|
||
// Ensure property name, then advance | ||
if (reader.TokenType != JsonTokenType.PropertyName || reader.GetString() != "value") throw new JsonException(); | ||
reader.Read(); | ||
|
||
// If the value is null, return null. | ||
if (reader.TokenType == JsonTokenType.Null) | ||
{ | ||
// Read the end object token. | ||
reader.Read(); | ||
return default; | ||
} | ||
|
||
// If the value is an object, deserialize it to wrapper message type | ||
if (this.wrapperMessageType != null) | ||
{ | ||
var value = JsonSerializer.Deserialize(ref reader, this.wrapperMessageType, options); | ||
|
||
// Construct a new WrappedMessageBody with the deserialized value. | ||
var wrapper = new WrappedMessageBody() | ||
{ | ||
Value = value, | ||
}; | ||
|
||
// Read the end object token. | ||
reader.Read(); | ||
|
||
// Coerce the type to T; required because WrappedMessageBody inherits from two separate interfaces, which | ||
// cannot both be used as generic constraints | ||
return (T)((object)wrapper); | ||
} | ||
|
||
return JsonSerializer.Deserialize<T>(ref reader, options); | ||
} | ||
|
||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) | ||
{ | ||
writer.WriteStartObject(); | ||
writer.WritePropertyName("value"); | ||
|
||
if (value is WrappedMessageBody body) | ||
{ | ||
JsonSerializer.Serialize(writer, body.Value, body.Value.GetType(), options); | ||
} | ||
else | ||
writer.WriteNullValue(); | ||
writer.WriteEndObject(); | ||
} | ||
} | ||
} |
185 changes: 185 additions & 0 deletions
185
src/Dapr.Actors/Communication/ActorMessageBodyJsonSerializationProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
// ------------------------------------------------------------------------ | ||
// Copyright 2021 The Dapr Authors | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// ------------------------------------------------------------------------ | ||
|
||
namespace Dapr.Actors.Communication | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Text.Json; | ||
using System.Threading.Tasks; | ||
using System.Xml; | ||
|
||
/// <summary> | ||
/// This is the implmentation for <see cref="IActorMessageBodySerializationProvider"/>used by remoting service and client during | ||
/// request/response serialization . It uses request Wrapping and data contract for serialization. | ||
/// </summary> | ||
internal class ActorMessageBodyJsonSerializationProvider : IActorMessageBodySerializationProvider | ||
{ | ||
public JsonSerializerOptions Options { get; } | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ActorMessageBodyJsonSerializationProvider"/> class. | ||
/// </summary> | ||
public ActorMessageBodyJsonSerializationProvider(JsonSerializerOptions options) | ||
{ | ||
Options = options; | ||
} | ||
|
||
/// <summary> | ||
/// Creates a MessageFactory for Wrapped Message Json Remoting Types. This is used to create Remoting Request/Response objects. | ||
/// </summary> | ||
/// <returns> | ||
/// <see cref="IActorMessageBodyFactory" /> that provides an instance of the factory for creating | ||
/// remoting request and response message bodies. | ||
/// </returns> | ||
public IActorMessageBodyFactory CreateMessageBodyFactory() | ||
{ | ||
return new WrappedRequestMessageFactory(); | ||
} | ||
|
||
/// <summary> | ||
/// Creates IActorRequestMessageBodySerializer for a serviceInterface using Wrapped Message Json implementation. | ||
/// </summary> | ||
/// <param name="serviceInterfaceType">The remoted service interface.</param> | ||
/// <param name="methodRequestParameterTypes">The union of parameter types of all of the methods of the specified interface.</param> | ||
/// <param name="wrappedRequestMessageTypes">Wrapped Request Types for all Methods.</param> | ||
/// <returns> | ||
/// An instance of the <see cref="IActorRequestMessageBodySerializer" /> that can serialize the service | ||
/// actor request message body to a messaging body for transferring over the transport. | ||
/// </returns> | ||
public IActorRequestMessageBodySerializer CreateRequestMessageBodySerializer( | ||
Type serviceInterfaceType, | ||
IEnumerable<Type> methodRequestParameterTypes, | ||
IEnumerable<Type> wrappedRequestMessageTypes = null) | ||
{ | ||
return new MemoryStreamMessageBodySerializer<WrappedMessageBody, WrappedMessageBody>(Options, serviceInterfaceType, methodRequestParameterTypes, wrappedRequestMessageTypes); | ||
} | ||
|
||
/// <summary> | ||
/// Creates IActorResponseMessageBodySerializer for a serviceInterface using Wrapped Message Json implementation. | ||
/// </summary> | ||
/// <param name="serviceInterfaceType">The remoted service interface.</param> | ||
/// <param name="methodReturnTypes">The return types of all of the methods of the specified interface.</param> | ||
/// <param name="wrappedResponseMessageTypes">Wrapped Response Types for all remoting methods.</param> | ||
/// <returns> | ||
/// An instance of the <see cref="IActorResponseMessageBodySerializer" /> that can serialize the service | ||
/// actor response message body to a messaging body for transferring over the transport. | ||
/// </returns> | ||
public IActorResponseMessageBodySerializer CreateResponseMessageBodySerializer( | ||
Type serviceInterfaceType, | ||
IEnumerable<Type> methodReturnTypes, | ||
IEnumerable<Type> wrappedResponseMessageTypes = null) | ||
{ | ||
return new MemoryStreamMessageBodySerializer<WrappedMessageBody, WrappedMessageBody>(Options, serviceInterfaceType, methodReturnTypes, wrappedResponseMessageTypes); | ||
} | ||
|
||
/// <summary> | ||
/// Default serializer for service remoting request and response message body that uses the | ||
/// memory stream to create outgoing message buffers. | ||
/// </summary> | ||
private class MemoryStreamMessageBodySerializer<TRequest, TResponse> : | ||
IActorRequestMessageBodySerializer, | ||
IActorResponseMessageBodySerializer | ||
where TRequest : IActorRequestMessageBody | ||
where TResponse : IActorResponseMessageBody | ||
{ | ||
private readonly JsonSerializerOptions serializerOptions; | ||
|
||
public MemoryStreamMessageBodySerializer( | ||
JsonSerializerOptions serializerOptions, | ||
Type serviceInterfaceType, | ||
IEnumerable<Type> methodRequestParameterTypes, | ||
IEnumerable<Type> wrappedRequestMessageTypes = null) | ||
{ | ||
var _methodRequestParameterTypes = new List<Type>(methodRequestParameterTypes); | ||
var _wrappedRequestMessageTypes = new List<Type>(wrappedRequestMessageTypes); | ||
|
||
this.serializerOptions = new(serializerOptions) | ||
{ | ||
// Workaround since WrappedMessageBody creates an object | ||
// with parameters as fields | ||
IncludeFields = true, | ||
}; | ||
|
||
this.serializerOptions.Converters.Add(new ActorMessageBodyJsonConverter<TRequest>(_methodRequestParameterTypes, _wrappedRequestMessageTypes)); | ||
this.serializerOptions.Converters.Add(new ActorMessageBodyJsonConverter<TResponse>(_methodRequestParameterTypes, _wrappedRequestMessageTypes)); | ||
} | ||
|
||
byte[] IActorRequestMessageBodySerializer.Serialize(IActorRequestMessageBody actorRequestMessageBody) | ||
{ | ||
if (actorRequestMessageBody == null) | ||
{ | ||
return null; | ||
} | ||
|
||
using var stream = new MemoryStream(); | ||
using var writer = new Utf8JsonWriter(stream); | ||
JsonSerializer.Serialize<object>(writer, actorRequestMessageBody, this.serializerOptions); | ||
writer.Flush(); | ||
|
||
return stream.ToArray(); | ||
} | ||
|
||
async ValueTask<IActorRequestMessageBody> IActorRequestMessageBodySerializer.DeserializeAsync(Stream stream) | ||
{ | ||
if (stream == null) | ||
{ | ||
return default; | ||
} | ||
|
||
if (stream.Length == 0) | ||
{ | ||
return default; | ||
} | ||
|
||
stream.Position = 0; | ||
return await JsonSerializer.DeserializeAsync<TRequest>(stream, this.serializerOptions); | ||
} | ||
|
||
byte[] IActorResponseMessageBodySerializer.Serialize(IActorResponseMessageBody actorResponseMessageBody) | ||
{ | ||
if (actorResponseMessageBody == null) | ||
{ | ||
return null; | ||
} | ||
|
||
using var stream = new MemoryStream(); | ||
using var writer = new Utf8JsonWriter(stream); | ||
JsonSerializer.Serialize<object>(writer, actorResponseMessageBody, this.serializerOptions); | ||
writer.Flush(); | ||
|
||
return stream.ToArray(); | ||
} | ||
|
||
async ValueTask<IActorResponseMessageBody> IActorResponseMessageBodySerializer.DeserializeAsync(Stream messageBody) | ||
{ | ||
if (messageBody == null) | ||
{ | ||
return null; | ||
} | ||
|
||
using var stream = new MemoryStream(); | ||
messageBody.CopyTo(stream); | ||
stream.Position = 0; | ||
|
||
if (stream.Capacity == 0) | ||
{ | ||
return null; | ||
} | ||
|
||
return await JsonSerializer.DeserializeAsync<TResponse>(stream, this.serializerOptions); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.