diff --git a/src/Orleans/Orleans.csproj b/src/Orleans/Orleans.csproj
index c6d4da1085..6ad14110b5 100644
--- a/src/Orleans/Orleans.csproj
+++ b/src/Orleans/Orleans.csproj
@@ -156,6 +156,7 @@
+
diff --git a/src/Orleans/Runtime/GrainReference.cs b/src/Orleans/Runtime/GrainReference.cs
index 8167fb941d..3fdc1580c2 100644
--- a/src/Orleans/Runtime/GrainReference.cs
+++ b/src/Orleans/Runtime/GrainReference.cs
@@ -563,13 +563,14 @@ protected internal static object DeserializeGrainReference(Type t, IDeserializat
var genericArg = reader.ReadString();
if (string.IsNullOrEmpty(genericArg))
genericArg = null;
-
+
+ var runtimeClient = context.AdditionalContext as IRuntimeClient;
if (expectObserverId)
{
- return NewObserverGrainReference(id, observerId, context.SerializationManager.RuntimeClient);
+ return NewObserverGrainReference(id, observerId, runtimeClient);
}
- return FromGrainId(id, context.SerializationManager.RuntimeClient, genericArg, silo);
+ return FromGrainId(id, runtimeClient, genericArg, silo);
}
/// Copier function for grain reference.
@@ -734,7 +735,7 @@ protected GrainReference(SerializationInfo info, StreamingContext context)
private void OnDeserialized(StreamingContext context)
{
var serializerContext = context.Context as ISerializerContext;
- this.runtimeClient = serializerContext?.AdditionalContext as IRuntimeClient; ;
+ this.runtimeClient = serializerContext?.AdditionalContext as IRuntimeClient;
}
#endif
#endregion
diff --git a/src/Orleans/Serialization/ILSerializerGenerator.cs b/src/Orleans/Serialization/ILSerializerGenerator.cs
index 53be03e2c0..051af80b87 100644
--- a/src/Orleans/Serialization/ILSerializerGenerator.cs
+++ b/src/Orleans/Serialization/ILSerializerGenerator.cs
@@ -23,7 +23,12 @@ internal class ILSerializerGenerator
private static readonly SerializationManager.DeepCopier ImmutableTypeCopier = (obj, context) => obj;
private static readonly ILFieldBuilder FieldBuilder = new ILFieldBuilder();
-
+
+ private static readonly Type OnDeserializedLifecycleType = typeof(IOnDeserialized);
+
+ private static readonly MethodInfo OnDeserializedMethod =
+ TypeUtils.Method((IOnDeserialized i) => i.OnDeserialized(default(ISerializerContext)));
+
static ILSerializerGenerator()
{
DirectSerializers = new Dictionary
@@ -278,6 +283,14 @@ private ILDelegateBuilder EmitDeserializer(Ty
}
}
+ // If the type implements the IOnDeserialized lifecycle handler, call that method now.
+ if (OnDeserializedLifecycleType.IsAssignableFrom(type))
+ {
+ il.LoadLocal(result);
+ il.LoadArgument(1);
+ il.Call(OnDeserializedMethod);
+ }
+
il.LoadLocal(result);
il.BoxIfValueType(type);
il.Return();
diff --git a/src/Orleans/Serialization/IOnDeserialized.cs b/src/Orleans/Serialization/IOnDeserialized.cs
new file mode 100644
index 0000000000..655197f42a
--- /dev/null
+++ b/src/Orleans/Serialization/IOnDeserialized.cs
@@ -0,0 +1,14 @@
+namespace Orleans.Serialization
+{
+ ///
+ /// Indicates that a class is to be notified when it has been deserialized.
+ ///
+ public interface IOnDeserialized
+ {
+ ///
+ /// Notifies this instance that it has been fully deserialized.
+ ///
+ /// The serializer context.
+ void OnDeserialized(ISerializerContext context);
+ }
+}
\ No newline at end of file
diff --git a/src/Orleans/Serialization/SerializationContext.cs b/src/Orleans/Serialization/SerializationContext.cs
index 5874d70168..a47f07e3e5 100644
--- a/src/Orleans/Serialization/SerializationContext.cs
+++ b/src/Orleans/Serialization/SerializationContext.cs
@@ -14,7 +14,10 @@ public interface ISerializerContext
/// Gets the service provider.
///
IServiceProvider ServiceProvider { get; }
-
+
+ ///
+ /// Gets additional context associated with this instance.
+ ///
object AdditionalContext { get; }
}
diff --git a/src/OrleansAzureUtils/Providers/Streams/AzureQueue/IAzureQueueDataAdapter.cs b/src/OrleansAzureUtils/Providers/Streams/AzureQueue/IAzureQueueDataAdapter.cs
index 1b2d2c5713..e3d587fadb 100644
--- a/src/OrleansAzureUtils/Providers/Streams/AzureQueue/IAzureQueueDataAdapter.cs
+++ b/src/OrleansAzureUtils/Providers/Streams/AzureQueue/IAzureQueueDataAdapter.cs
@@ -1,10 +1,7 @@
-
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
-using System.Runtime.Serialization;
using Microsoft.WindowsAzure.Storage.Queue;
-using Orleans.CodeGeneration;
using Orleans.Providers.Streams.Common;
using Orleans.Serialization;
using Orleans.Streams;
@@ -30,7 +27,7 @@ public interface IAzureQueueDataAdapter
///
/// Original data adapter. Here to maintain backwards compatablity, but does not support json and other custom serializers
///
- public class AzureQueueDataAdapterV1 : IAzureQueueDataAdapter
+ public class AzureQueueDataAdapterV1 : IAzureQueueDataAdapter, IOnDeserialized
{
private SerializationManager serializationManager;
@@ -67,25 +64,16 @@ public IBatchContainer FromCloudQueueMessage(CloudQueueMessage cloudMsg, long se
return azureQueueBatch;
}
- [SerializerMethod]
- private static void Serialize(object obj, ISerializationContext context, Type expected)
+ void IOnDeserialized.OnDeserialized(ISerializerContext context)
{
+ this.serializationManager = context.SerializationManager;
}
-
- [DeserializerMethod]
- private static object Deserialize(Type expected, IDeserializationContext context)
- {
- return new DefaultMemoryMessageBodySerializer(context.SerializationManager);
- }
-
- [CopierMethod]
- private static object Copy(object obj, ICopyContext context) => obj;
}
///
/// Data adapter that uses types that support custom serializers (like json).
///
- public class AzureQueueDataAdapterV2 : IAzureQueueDataAdapter
+ public class AzureQueueDataAdapterV2 : IAzureQueueDataAdapter, IOnDeserialized
{
private SerializationManager serializationManager;
@@ -122,18 +110,9 @@ public IBatchContainer FromCloudQueueMessage(CloudQueueMessage cloudMsg, long se
return azureQueueBatch;
}
- [SerializerMethod]
- private static void Serialize(object obj, ISerializationContext context, Type expected)
+ void IOnDeserialized.OnDeserialized(ISerializerContext context)
{
+ this.serializationManager = context.SerializationManager;
}
-
- [DeserializerMethod]
- private static object Deserialize(Type expected, IDeserializationContext context)
- {
- return new DefaultMemoryMessageBodySerializer(context.SerializationManager);
- }
-
- [CopierMethod]
- private static object Copy(object obj, ICopyContext context) => obj;
}
}
diff --git a/src/OrleansCodeGenerator/SerializerGenerator.cs b/src/OrleansCodeGenerator/SerializerGenerator.cs
index 8ed050f640..5073af44a6 100644
--- a/src/OrleansCodeGenerator/SerializerGenerator.cs
+++ b/src/OrleansCodeGenerator/SerializerGenerator.cs
@@ -143,6 +143,17 @@ private static MemberDeclarationSyntax GenerateDeserializerMethod(Type type, Lis
SF.CastExpression(field.Type, deserialized))));
}
+ // If the type implements the internal IOnDeserialized lifecycle method, invoke it's method now.
+ if (typeof(IOnDeserialized).IsAssignableFrom(type))
+ {
+ Expression> onDeserializedMethod = _ => _.OnDeserialized(default(ISerializerContext));
+
+ // C#: ((IOnDeserialized)result).OnDeserialized(context);
+ var typedResult = SF.ParenthesizedExpression(SF.CastExpression(typeof(IOnDeserialized).GetTypeSyntax(), resultVariable));
+ var invokeOnDeserialized = onDeserializedMethod.Invoke(typedResult).AddArgumentListArguments(SF.Argument(contextParameter));
+ body.Add(SF.ExpressionStatement(invokeOnDeserialized));
+ }
+
body.Add(SF.ReturnStatement(SF.CastExpression(type.GetTypeSyntax(), resultVariable)));
return
SF.MethodDeclaration(typeof(object).GetTypeSyntax(), "Deserializer")
diff --git a/src/OrleansProviders/Streams/Memory/MemoryBatchContainer.cs b/src/OrleansProviders/Streams/Memory/MemoryBatchContainer.cs
index f35e698bfc..cb68c15df8 100644
--- a/src/OrleansProviders/Streams/Memory/MemoryBatchContainer.cs
+++ b/src/OrleansProviders/Streams/Memory/MemoryBatchContainer.cs
@@ -1,17 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Microsoft.Extensions.DependencyInjection;
using Orleans.Providers.Streams.Common;
using Orleans.Runtime;
+using Orleans.Serialization;
using Orleans.Streams;
namespace Orleans.Providers
{
[Serializable]
- internal class MemoryBatchContainer : IBatchContainer
+ internal class MemoryBatchContainer : IBatchContainer, IOnDeserialized
where TSerializer : IMemoryMessageBodySerializer
{
- private readonly IMemoryMessageBodySerializer serializer;
+ [NonSerialized]
+ private TSerializer serializer;
private readonly EventSequenceToken realToken;
public Guid StreamGuid => MessageData.StreamGuid;
public string StreamNamespace => MessageData.StreamNamespace;
@@ -54,5 +57,10 @@ public bool ShouldDeliver(IStreamIdentity stream, object filterData, StreamFilte
{
return true;
}
+
+ void IOnDeserialized.OnDeserialized(ISerializerContext context)
+ {
+ this.serializer = ActivatorUtilities.GetServiceOrCreateInstance(context.ServiceProvider);
+ }
}
}
diff --git a/src/OrleansProviders/Streams/Memory/MemoryMessageBody.cs b/src/OrleansProviders/Streams/Memory/MemoryMessageBody.cs
index 2637a6cc8f..90738c7453 100644
--- a/src/OrleansProviders/Streams/Memory/MemoryMessageBody.cs
+++ b/src/OrleansProviders/Streams/Memory/MemoryMessageBody.cs
@@ -2,8 +2,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Runtime.Serialization;
-using Orleans.CodeGeneration;
using Orleans.Serialization;
namespace Orleans.Providers
@@ -32,7 +30,7 @@ public interface IMemoryMessageBodySerializer
/// Default IMemoryMessageBodySerializer
///
[Serializable]
- public class DefaultMemoryMessageBodySerializer : IMemoryMessageBodySerializer
+ public class DefaultMemoryMessageBodySerializer : IMemoryMessageBodySerializer, IOnDeserialized
{
[NonSerialized]
private SerializationManager serializationManager;
@@ -58,26 +56,9 @@ public MemoryMessageBody Deserialize(ArraySegment bodyBytes)
return serializationManager.DeserializeFromByteArray(bodyBytes.ToArray());
}
- [SerializerMethod]
- private static void Serialize(object obj, ISerializationContext context, Type expected)
+ void IOnDeserialized.OnDeserialized(ISerializerContext context)
{
- }
-
- [DeserializerMethod]
- private static object Deserialize(Type expected, IDeserializationContext context)
- {
- return new DefaultMemoryMessageBodySerializer(context.SerializationManager);
- }
-
- [CopierMethod]
- private static object Copy(object obj, ICopyContext context) => obj;
-
- [OnDeserialized]
- private void OnDeserialized(StreamingContext streamingContext)
- {
-#if !NETSTANDARD_TODO
- this.serializationManager = (streamingContext.Context as ISerializerContext)?.SerializationManager;
-#endif
+ this.serializationManager = context.SerializationManager;
}
}
diff --git a/src/OrleansServiceBus/Providers/Streams/EventHub/EventHubBatchContainer.cs b/src/OrleansServiceBus/Providers/Streams/EventHub/EventHubBatchContainer.cs
index dea4d65191..cf519f0677 100644
--- a/src/OrleansServiceBus/Providers/Streams/EventHub/EventHubBatchContainer.cs
+++ b/src/OrleansServiceBus/Providers/Streams/EventHub/EventHubBatchContainer.cs
@@ -9,7 +9,6 @@
using Microsoft.ServiceBus.Messaging;
#endif
using Newtonsoft.Json;
-using Orleans.CodeGeneration;
using Orleans.Runtime;
using Orleans.Serialization;
using Orleans.Streams;
@@ -20,14 +19,14 @@ namespace Orleans.ServiceBus.Providers
/// Batch container that is delivers payload and stream position information for a set of events in an EventHub EventData.
///
[Serializable]
- public class EventHubBatchContainer : IBatchContainer
+ public class EventHubBatchContainer : IBatchContainer, IOnDeserialized
{
[JsonProperty]
private readonly EventHubMessage eventHubMessage;
[JsonIgnore]
[NonSerialized]
- private readonly SerializationManager serializationManager;
+ private SerializationManager serializationManager;
[JsonProperty]
private readonly EventHubSequenceToken token;
@@ -125,28 +124,10 @@ internal static EventData ToEventData(SerializationManager serializationManag
}
return eventData;
}
-
- [SerializerMethod]
- private static void Serialize(object obj, ISerializationContext context, Type expected)
- {
- var input = (EventHubBatchContainer) obj;
- SerializationManager.SerializeInner(input.eventHubMessage, context, typeof(EventHubMessage));
- }
-
- [DeserializerMethod]
- private static object Deserialize(Type expected, IDeserializationContext context)
- {
- var message = SerializationManager.DeserializeInner(context);
- return new EventHubBatchContainer(message, context.SerializationManager);
- }
-
- [CopierMethod]
- private static object Copy(object obj, ICopyContext context)
+
+ void IOnDeserialized.OnDeserialized(ISerializerContext context)
{
- var result =
- new EventHubBatchContainer(((EventHubBatchContainer) obj).eventHubMessage, context.SerializationManager);
- context.RecordCopy(obj, result);
- return result;
+ this.serializationManager = context.SerializationManager;
}
}
}
diff --git a/test/NonSiloTests/Serialization/BuiltInSerializerTests.cs b/test/NonSiloTests/Serialization/BuiltInSerializerTests.cs
index 519292ab15..9b9dd7842d 100644
--- a/test/NonSiloTests/Serialization/BuiltInSerializerTests.cs
+++ b/test/NonSiloTests/Serialization/BuiltInSerializerTests.cs
@@ -417,6 +417,28 @@ public void Serialize_Stack(SerializerToUse serializerToUse)
}
}
+ ///
+ /// Tests that the callback is invoked after deserialization.
+ ///
+ ///
+ [Theory, TestCategory("Functional"), TestCategory("Serialization")]
+ [InlineData(SerializerToUse.NoFallback)]
+ public void Serialize_TypeWithOnDeserializedHook(SerializerToUse serializerToUse)
+ {
+ var environment = InitializeSerializer(serializerToUse);
+
+ var input = new TypeWithOnDeserializedHook
+ {
+ Int = 5
+ };
+ var deserialized = OrleansSerializationLoop(environment.SerializationManager, input);
+ var result = Assert.IsType(deserialized);
+ Assert.Equal(input.Int, result.Int);
+ Assert.Null(input.Context);
+ Assert.NotNull(result.Context);
+ Assert.Equal(environment.SerializationManager, result.Context.SerializationManager);
+ }
+
[Theory, TestCategory("Functional"), TestCategory("Serialization")]
[InlineData(SerializerToUse.NoFallback)]
public void Serialize_SortedSetWithComparer(SerializerToUse serializerToUse)
diff --git a/test/NonSiloTests/Serialization/ILBasedSerializerTests.cs b/test/NonSiloTests/Serialization/ILBasedSerializerTests.cs
index a486b5b638..5593424139 100644
--- a/test/NonSiloTests/Serialization/ILBasedSerializerTests.cs
+++ b/test/NonSiloTests/Serialization/ILBasedSerializerTests.cs
@@ -1,3 +1,4 @@
+using System;
using TestExtensions;
namespace UnitTests.Serialization
@@ -56,13 +57,46 @@ public void ILSerializer_AllowCopiedFieldsToDifferFromSerializedFields()
Assert.Equal(3, deserialized.Three);
}
+ ///
+ /// Tests that supports the lifecycle hook.
+ ///
+ [Fact]
+ public void ILSerializer_LifecycleHooksAreCalled()
+ {
+ var input = new FieldTest();
+ var generator = new ILSerializerGenerator();
+ var serializers = generator.GenerateSerializer(input.GetType());
+ var writer = new SerializationContext(this.fixture.SerializationManager)
+ {
+ StreamWriter = new BinaryTokenStreamWriter()
+ };
+ serializers.Serialize(input, writer, input.GetType());
+ var reader = new DeserializationContext(this.fixture.SerializationManager)
+ {
+ StreamReader = new BinaryTokenStreamReader(writer.StreamWriter.ToByteArray())
+ };
+ var deserialized = (FieldTest)serializers.Deserialize(input.GetType(), reader);
+
+ Assert.Null(input.Context);
+ Assert.NotNull(deserialized.Context);
+ Assert.Equal(this.fixture.SerializationManager, deserialized.Context.SerializationManager);
+ }
+
[SuppressMessage("ReSharper", "StyleCop.SA1401", Justification = "This is for testing purposes.")]
- private class FieldTest
+ private class FieldTest : IOnDeserialized
{
#pragma warning disable 169
public int One;
public int Two;
public int Three;
+
+ [NonSerialized]
+ public ISerializerContext Context;
+
+ public void OnDeserialized(ISerializerContext context)
+ {
+ this.Context = context;
+ }
#pragma warning restore 169
}
}
diff --git a/test/TestGrainInterfaces/SerializerTestTypes.cs b/test/TestGrainInterfaces/SerializerTestTypes.cs
new file mode 100644
index 0000000000..2373bdd9ee
--- /dev/null
+++ b/test/TestGrainInterfaces/SerializerTestTypes.cs
@@ -0,0 +1,22 @@
+using System;
+using Orleans.Serialization;
+
+namespace UnitTests.GrainInterfaces
+{
+ ///
+ /// A type with an hook, to test that it is correctly called by the internal serializers.
+ ///
+ [Serializable]
+ public class TypeWithOnDeserializedHook : IOnDeserialized
+ {
+ [NonSerialized]
+ public ISerializerContext Context;
+
+ public int Int { get; set; }
+
+ void IOnDeserialized.OnDeserialized(ISerializerContext context)
+ {
+ this.Context = context;
+ }
+ }
+}
diff --git a/test/TestGrainInterfaces/TestGrainInterfaces.csproj b/test/TestGrainInterfaces/TestGrainInterfaces.csproj
index 8311021c56..63086560e8 100644
--- a/test/TestGrainInterfaces/TestGrainInterfaces.csproj
+++ b/test/TestGrainInterfaces/TestGrainInterfaces.csproj
@@ -129,6 +129,7 @@
+