diff --git a/AbsInjectTypeDll/AbsInjectTypeDll.csproj b/AbsInjectTypeDll/AbsInjectTypeDll.csproj new file mode 100644 index 0000000..fc6edfa --- /dev/null +++ b/AbsInjectTypeDll/AbsInjectTypeDll.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.1 + enable + + + + + + + diff --git a/AbsInjectTypeDll/Class1.cs b/AbsInjectTypeDll/Class1.cs new file mode 100644 index 0000000..4aed4b6 --- /dev/null +++ b/AbsInjectTypeDll/Class1.cs @@ -0,0 +1,71 @@ +using ShareAttributes; +using System; + +namespace AbsInjectTypeDll.DllSubAssembly +{ + + [UnionAbsOrInterface] + public abstract class CBase1 + { + } + + public abstract class CBase2 + { + public long CTB2 { get; set; } + } + + [UnionAbsOrInterface] + public abstract class CBase3 : CBase1 + { + + } + + [UnionAbsOrInterface] + public abstract class CBase4 + { + + } + + public abstract class CBase5 : CBase4 + { + + } + [UnionAbsOrInterface] + public abstract class CBase6 : CBase4 + { + + } + + [UnionAbsOrInterface] + public interface IBase1 + { + + } + + public interface IBase2 + { + + } + + [UnionAbsOrInterface] + public interface IBase3 : IBase1 + { + + } + + [UnionAbsOrInterface] + public interface IBase4 + { + + } + + public interface IBase5 : IBase4 + { + + } + [UnionAbsOrInterface] + public interface IBase6 : IBase4 + { + + } +} diff --git a/Blah/PolymorphicDelegate.cs b/Blah/PolymorphicDelegate.cs deleted file mode 100644 index 9e851d6..0000000 --- a/Blah/PolymorphicDelegate.cs +++ /dev/null @@ -1,45 +0,0 @@ -using MessagePack; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PolymorphicMessagePack -{ - //I created this, because I have no clue how to create a delegate (MethodInfo.Invoke is too slow), that uses the generic parameter of the class (As opposed to a generic method that has it's own parameter 'T'). - //Is this the only way? - internal abstract class PolymorphicDelegate - { - public abstract void Serialize(ref MessagePackWriter writer, object value, MessagePackSerializerOptions options); - public abstract object Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options); - - } - - internal class PolymorphicDelegate : PolymorphicDelegate - { - private delegate void SerializeDelegate(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options); - private delegate T DeserializeDelegate(ref MessagePackReader reader, MessagePackSerializerOptions options); - - private SerializeDelegate _serializeDelegate; - private DeserializeDelegate _deserializeDelegate; - - public PolymorphicDelegate(IFormatterResolver resolver) - { - var formatter = resolver.GetFormatter(); - - _serializeDelegate = formatter.Serialize; - _deserializeDelegate = formatter.Deserialize; - } - - public override void Serialize(ref MessagePackWriter writer, object value, MessagePackSerializerOptions options) - { - _serializeDelegate.Invoke(ref writer, (T)value, options); - } - - public override object Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) - { - return _deserializeDelegate.Invoke(ref reader, options); - } - } -} diff --git a/Blah/PolymorphicFormatter.cs b/Blah/PolymorphicFormatter.cs deleted file mode 100644 index f5386f4..0000000 --- a/Blah/PolymorphicFormatter.cs +++ /dev/null @@ -1,85 +0,0 @@ -using MessagePack; -using MessagePack.Formatters; -using MessagePack.Resolvers; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace PolymorphicMessagePack -{ - - public class PolymorphicFormatter : IMessagePackFormatter - { - private object lockObj = new object(); - - public PolymorphicFormatter() - { - } - - public void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options) - { - - if (value == null) - { - writer.WriteNil(); - return; - } - - //Could remove this if the settings were part of the regular options - if (!(options is PolymorphicMessagePackSerializerOptions polyOptions)) - throw new ArgumentException($"You cannot use a { nameof(PolymorphicResolver) } without also using { nameof(PolymorphicMessagePackSerializerOptions) }", nameof(options)); - - var actualtype = value.GetType(); - - if (!polyOptions.PolymorphicSettings.TypeToId.TryGetValue(actualtype, out var typeId)) - throw new MessagePackSerializationException($"Type '{ actualtype.FullName }' is not registered in { nameof(PolymorphicMessagePackSerializerOptions) }"); - - writer.WriteArrayHeader(2); - writer.WriteInt32(typeId); - - //Bottleneck - polyOptions.PolymorphicResolver.InnerSerialize(actualtype, ref writer, value, options); - } - - public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) - { - - if (reader.TryReadNil()) - return default; - - - //Could remove this if the settings were part of the regular options - if (!(options is PolymorphicMessagePackSerializerOptions polyOptions)) - throw new ArgumentException($"You cannot use a { nameof(PolymorphicResolver) } without also using { nameof(PolymorphicMessagePackSerializerOptions) }", nameof(options)); - - options.Security.DepthStep(ref reader); - - try - { - var count = reader.ReadArrayHeader(); - - if (count != 2) - throw new MessagePackSerializationException("Invalid polymorphic array count"); - - var typeId = reader.ReadInt32(); - - if (!polyOptions.PolymorphicSettings.IdToType.TryGetValue(typeId, out var type)) - throw new MessagePackSerializationException($"Cannot find Type Id: { typeId } registered in { nameof(PolymorphicMessagePackSerializerOptions) }"); - - //Bottleneck - return (T)polyOptions.PolymorphicResolver.InnerDeserialize(type, ref reader, options); - } - finally - { - reader.Depth--; - } - - } - - } - -} \ No newline at end of file diff --git a/Blah/PolymorphicMessagePackSerializerOptions.cs b/Blah/PolymorphicMessagePackSerializerOptions.cs deleted file mode 100644 index 54bb189..0000000 --- a/Blah/PolymorphicMessagePackSerializerOptions.cs +++ /dev/null @@ -1,34 +0,0 @@ -using MessagePack; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PolymorphicMessagePack -{ - - - public class PolymorphicMessagePackSerializerOptions : MessagePackSerializerOptions - { - internal readonly PolymorphicMessagePackSettings PolymorphicSettings; - internal readonly PolymorphicResolver PolymorphicResolver; - - public PolymorphicMessagePackSerializerOptions(PolymorphicMessagePackSettings polymorphicSettings) : base(new PolymorphicResolver(polymorphicSettings)) - { - PolymorphicSettings = polymorphicSettings; - PolymorphicResolver = Resolver as PolymorphicResolver; - } - - protected PolymorphicMessagePackSerializerOptions(PolymorphicMessagePackSerializerOptions copyFrom) : base(copyFrom) - { - PolymorphicSettings = copyFrom.PolymorphicSettings; - } - - protected override MessagePackSerializerOptions Clone() - { - return new PolymorphicMessagePackSerializerOptions(this); - } - - } -} diff --git a/Blah/PolymorphicMessagePackSettings.cs b/Blah/PolymorphicMessagePackSettings.cs deleted file mode 100644 index 81985b8..0000000 --- a/Blah/PolymorphicMessagePackSettings.cs +++ /dev/null @@ -1,59 +0,0 @@ -using MessagePack; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PolymorphicMessagePack -{ - public class PolymorphicMessagePackSettings - { - internal readonly Dictionary TypeToId = new(); - internal readonly Dictionary IdToType = new(); - internal readonly HashSet BaseTypes = new(); - internal IFormatterResolver InnerResolver; - internal Type InnerResolverType; - - public PolymorphicMessagePackSettings(IFormatterResolver innerResolver) - { - InnerResolver = innerResolver; - InnerResolverType = InnerResolver.GetType(); - } - - public bool SerializeOnlyRegisteredTypes { get; set; } = false; - - public void RegisterType(int typeId) - where B : class - where T : class, B - { - - if (typeof(T).IsInterface || typeof(T).IsAbstract) - throw new ArgumentException($"Failed to register derived type '{ typeof(T).FullName }'. It cannot be an interface or an abstract class.", nameof(T)); - - if (typeof(T).ContainsGenericParameters) - throw new ArgumentException($"Failed to register derived type '{ typeof(T).FullName }'. It cannot have open generic parameters. You must replace the open generic parameters with specific types.", nameof(T)); - - if (TypeToId.TryGetValue(typeof(T), out var currentId) && currentId != typeId) - throw new ArgumentException($"Failed to register derived type '{ typeof(T).FullName }'. Type '{ typeof(T).FullName }' is already registered to Type Id: { currentId }", nameof(T)); - - if (IdToType.TryGetValue(typeId, out var currentType) && currentType != typeof(T)) - throw new ArgumentException($"Failed to register derived type '{ typeof(T).FullName }'. Type Id: { typeId } is already registered to another type '{ currentType.FullName }'", nameof(typeId)); - - //Use TryAdd, becasue the type could already exist and the user is simply trying to add another base class - TypeToId.TryAdd(typeof(T), typeId); - IdToType.TryAdd(typeId, typeof(T)); - BaseTypes.Add(typeof(B)); - } - - - //TODO: convenience method - //public void RegisterTypeWithAllInterfacesAndBase(int typeId, bool includeObject = false) - // where T : class - //{ - - //} - - //TODO: What is the user needs to register 10,000 types? perhaps a way to do entire namspaaces with auto-numbering, if you aren't storing messages? - } -} diff --git a/Blah/PolymorphicResolver.cs b/Blah/PolymorphicResolver.cs deleted file mode 100644 index 1eccdac..0000000 --- a/Blah/PolymorphicResolver.cs +++ /dev/null @@ -1,86 +0,0 @@ -using MessagePack; -using MessagePack.Formatters; -using MessagePack.Resolvers; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace PolymorphicMessagePack -{ - - internal sealed class PolymorphicResolver : IFormatterResolver - { - private PolymorphicMessagePackSettings _polymorphicSettings; - private readonly ConcurrentDictionary _innerFormatterCache; - public PolymorphicResolver(PolymorphicMessagePackSettings polymorphicSettings) - { - _polymorphicSettings = polymorphicSettings; - _innerFormatterCache = new(); - } - - public IMessagePackFormatter GetFormatter() - { - //Have to check the type here, because of two reasons: - //1. Deserialize will not work with non-polymorphic types, as it assumes a typeid as the first item in a two part array, the second being the object itself - //2. We need the polymorphic settings, and they are an instance and are required to be. - - //If i had the object to be serialized or its actual type, I could make this a lot more efficient and remove the need for the Polymorphic delegate. - - //Can something be optimized here? - if (_polymorphicSettings.BaseTypes.Contains(typeof(T)) || - _polymorphicSettings.TypeToId.ContainsKey(typeof(T))) - { - return FormatterCache.Formatter; - } - else if (_polymorphicSettings.SerializeOnlyRegisteredTypes) - { - throw new MessagePackSerializationException($"Type '{ typeof(T).FullName }' is not registered in the { nameof(PolymorphicMessagePackSettings) } and { nameof(PolymorphicMessagePackSettings.SerializeOnlyRegisteredTypes) } is set to true"); - } - - return _polymorphicSettings.InnerResolver.GetFormatter(); - } - - //Bottleneck - public void InnerSerialize(Type type, ref MessagePackWriter writer, object value, MessagePackSerializerOptions options) - { - GetDelegate(type).Serialize(ref writer, value, options); - } - - //Bottleneck - public object InnerDeserialize(Type type, ref MessagePackReader reader, MessagePackSerializerOptions options) - { - return GetDelegate(type).Deserialize(ref reader, options); - } - - private PolymorphicDelegate GetDelegate(Type type) - { - if (!_innerFormatterCache.TryGetValue(type, out var ploymorphicDeletegate)) - { - var constructedType = typeof(PolymorphicDelegate<>).MakeGenericType(type); - - ploymorphicDeletegate = (PolymorphicDelegate)Activator.CreateInstance(constructedType, _polymorphicSettings.InnerResolver); - - _innerFormatterCache.TryAdd(type, ploymorphicDeletegate); - } - - return ploymorphicDeletegate; - } - - private static class FormatterCache - { - public static IMessagePackFormatter Formatter; - - static FormatterCache() - { - Formatter = new PolymorphicFormatter(); - } - - } - - } - -} \ No newline at end of file diff --git a/Fody.Test/AutoKeyWeaverTests.cs b/Fody.Test/AutoKeyWeaverTests.cs new file mode 100644 index 0000000..44fdca9 --- /dev/null +++ b/Fody.Test/AutoKeyWeaverTests.cs @@ -0,0 +1,38 @@ +using MessagePack; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using PolymorphicMessagePack.Fody; +using ShareAttributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +#pragma warning disable CS8604 + +namespace Fody.Test +{ + public class AutoKeyWeaverTests + { + static TestResult testResult; + + static AutoKeyWeaverTests() + { + var xElement = XElement.Parse(""); + var weavingTask = new AutoMsgPackKeyWeaver { Config = xElement }; + testResult = weavingTask.ExecuteTestRun("MsgPackDefineForInject.dll", runPeVerify: false); + } + + [Fact] + public void WillNotInjectNonMsgPackObjAttrClass() + { + var type = testResult.Assembly.GetType("MsgPackDefineForInject.Class2"); + + var attribute = type.GetCustomAttribute(); + + Assert.NotNull(attribute); + } + } +} diff --git a/Fody.Test/AutoPolyWeaverTests.cs b/Fody.Test/AutoPolyWeaverTests.cs new file mode 100644 index 0000000..e05736b --- /dev/null +++ b/Fody.Test/AutoPolyWeaverTests.cs @@ -0,0 +1,66 @@ +using PolymorphicMessagePack.Fody; +using ShareAttributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +#pragma warning disable CS8604 + +namespace Fody.Test +{ + public class AutoPolyWeaverTests + { + static TestResult testResult; + + static AutoPolyWeaverTests() + { + var xElement = XElement.Parse(""); + var weavingTask = new AutoPolyMsgPackWeaver { Config= xElement }; + testResult = weavingTask.ExecuteTestRun("MsgPackDefineForInject.dll",runPeVerify:false); + } + + [Fact] + public void WillNotInjectNonMsgPackObjAttrClass() + { + var type = testResult.Assembly.GetType("MsgPackDefineForInject.Class2"); + + var attribute = type.GetCustomAttribute(); + + Assert.Null(attribute); + } + + [Fact] + public void InjectMsgPackObjAttrClass() + { + var type = testResult.Assembly.GetType("MsgPackDefineForInject.Class6"); + + var attribute = type.GetCustomAttribute(); + + Assert.NotNull(attribute); + } + + [Fact] + public void InjectGenericUnionType() + { + var type = testResult.Assembly.GetType("MsgPackDefineForInject.Class5`1"); + + var attribute = type.GetCustomAttributes(); + + Assert.True(attribute.Count() == 2); + } + + [Fact] + public void InjectGenericUnionWithSameType() + { + var type = testResult.Assembly.GetType("MsgPackDefineForInject.Class7`1"); + + var attribute = type.GetCustomAttributes(); + + Assert.True(attribute.Count() == 2); + } + } +} diff --git a/Fody.Test/Fody.Test.csproj b/Fody.Test/Fody.Test.csproj new file mode 100644 index 0000000..a330162 --- /dev/null +++ b/Fody.Test/Fody.Test.csproj @@ -0,0 +1,31 @@ + + + + net6.0 + enable + enable + + false + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/Fody.Test/Usings.cs b/Fody.Test/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/Fody.Test/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2072e21 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 PatchouliTC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MsgPackDefineForInject/AbsDefine.cs b/MsgPackDefineForInject/AbsDefine.cs new file mode 100644 index 0000000..b564f8f --- /dev/null +++ b/MsgPackDefineForInject/AbsDefine.cs @@ -0,0 +1,37 @@ +using ShareAttributes; + +namespace MsgPackDefineForInject +{ + + //[UnionAbsOrInterface] + //public abstract class CBase1 + //{ + //} + + //public abstract class CBase2 + //{ + // public long CTB2 { get; set; } + //} + + //[UnionAbsOrInterface] + //public abstract class CBase3 : CBase1 + //{ + + //} + + //[UnionAbsOrInterface] + //public abstract class CBase4 + //{ + + //} + + //public abstract class CBase5 : CBase4 + //{ + + //} + //[UnionAbsOrInterface] + //public abstract class CBase6 : CBase4 + //{ + + //} +} diff --git a/MsgPackDefineForInject/FodyWeavers.xml b/MsgPackDefineForInject/FodyWeavers.xml new file mode 100644 index 0000000..c064b07 --- /dev/null +++ b/MsgPackDefineForInject/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/MsgPackDefineForInject/InterfaceDefine.cs b/MsgPackDefineForInject/InterfaceDefine.cs new file mode 100644 index 0000000..6cf0cf0 --- /dev/null +++ b/MsgPackDefineForInject/InterfaceDefine.cs @@ -0,0 +1,37 @@ +using ShareAttributes; + +namespace MsgPackDefineForInject +{ + //[UnionAbsOrInterface] + //public interface IBase1 + //{ + + //} + + //public interface IBase2 + //{ + + //} + + //[UnionAbsOrInterface] + //public interface IBase3 : IBase1 + //{ + + //} + + //[UnionAbsOrInterface] + //public interface IBase4 + //{ + + //} + + //public interface IBase5 : IBase4 + //{ + + //} + //[UnionAbsOrInterface] + //public interface IBase6 : IBase4 + //{ + + //} +} diff --git a/MsgPackDefineForInject/MsgPackClass.cs b/MsgPackDefineForInject/MsgPackClass.cs new file mode 100644 index 0000000..9d1327e --- /dev/null +++ b/MsgPackDefineForInject/MsgPackClass.cs @@ -0,0 +1,109 @@ +using AbsInjectTypeDll.DllSubAssembly; +using MessagePack; +using ShareAttributes; +using System.Runtime.Serialization; + +namespace MsgPackDefineForInject +{ + [MessagePackObject] + public class Class1 : CBase1 + { + + public long CT1 { get; set; } + } + + + [MessagePackObject] + public class Class2 : CBase2 + { + + public long CT2 { get; set; } + } + + [MessagePackObject] + public class Class2_1 : Class2 + { + public long CT2_1 { get; set; } + } + [MessagePackObject] + public class Class2_2 : Class2 + { + public long CT2_2 { get; set; } + } + + [RequireUnion(114514)] + [MessagePackObject] + public class Class3 : Class1 + { + + public long CT3 { get; set; } + } + + + [MessagePackObject] + public class Class4 : CBase4 + { + + public long CT4 { get; set; } + } + + [GenericUnion(typeof(int))] + [GenericUnion(typeof(string))] + [MessagePackObject] + public class Class5 : CBase6 + { + public long CT5 { get; set; } + } + + [MessagePackObject] + public class Class6 : IBase3 + { + [Key(0)] + public long CT6 { get; set; } + + public long CT6_1 { get; set; } = 12306; + private long CT6_2; + } + + + [GenericUnion(typeof(int))] + [GenericUnion(typeof(int))] + [GenericUnion(typeof(string))] + [GenericUnion(typeof(string))] + [MessagePackObject] + public class Class7 : CBase6 + { + public long CT7 { get; set; } + } + + + [RequireUnionGeneric(1,typeof(Class8))] + [RequireUnionGeneric(2,typeof(Class8))] + [MessagePackObject] + public class Class8 : CBase6 + { + public long CT8 { get; set; } + } + + [MessagePackObject] + public struct Struct1 + { + private long ST1 { get; set; } + + [Key(1)] + public long ST2 { get; set; } + + public long ST3; + + private long ST4; + + internal long ST5; + + public long STT5 + { + get => ST5; + set => ST5 = value; + } + + } +} diff --git a/MsgPackDefineForInject/MsgPackDefineForInject.csproj b/MsgPackDefineForInject/MsgPackDefineForInject.csproj new file mode 100644 index 0000000..b630ad5 --- /dev/null +++ b/MsgPackDefineForInject/MsgPackDefineForInject.csproj @@ -0,0 +1,34 @@ + + + + netstandard2.1 + true + false + + + + 5 + + + + 5 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + diff --git a/PolyClient/PolyClient.csproj b/PolyClient/PolyClient.csproj new file mode 100644 index 0000000..44af787 --- /dev/null +++ b/PolyClient/PolyClient.csproj @@ -0,0 +1,19 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + + diff --git a/PolyClient/Program.cs b/PolyClient/Program.cs new file mode 100644 index 0000000..89f52a1 --- /dev/null +++ b/PolyClient/Program.cs @@ -0,0 +1,44 @@ + + +using AbsInjectTypeDll.DllSubAssembly; +using Grpc.Net.Client; +using MagicOnion.Client; +using MessagePack.Resolvers; +using MessagePack; +using MsgPackDefineForInject; +using Service.Shared; +using PolymorphicMessagePack; + +namespace PolyClient +{ + internal class Program + { + static async Task Main(string[] args) + { + RegisterResolvers(); + // Connect to the server using gRPC channel. + var channel = GrpcChannel.ForAddress("https://localhost:5001"); + + // NOTE: If your project targets non-.NET Standard 2.1, use `Grpc.Core.Channel` class instead. + // var channel = new Channel("localhost", 5001, new SslCredentials()); + + // Create a proxy to call the server transparently. + var client = MagicOnionClient.Create(channel); + + // Call the server-side method using the proxy. + var resultClass = await client.GetTestData(3); + Console.WriteLine($"Result: {resultClass.GetType().Name}"); + } + + static void RegisterResolvers() + { + var polySettings = new PolymorphicMessagePackSettings(StandardResolver.Instance); + + polySettings.InjectUnionRequireFromAssembly(typeof(Class1).Assembly, typeof(CBase1).Assembly); + + var _polyOptions = new PolymorphicMessagePackSerializerOptions(polySettings); + + MessagePackSerializer.DefaultOptions = _polyOptions; + } + } +} \ No newline at end of file diff --git a/PolyMsgPack.Test/AutoMsgPackTest.cs b/PolyMsgPack.Test/AutoMsgPackTest.cs new file mode 100644 index 0000000..d76fdbf --- /dev/null +++ b/PolyMsgPack.Test/AutoMsgPackTest.cs @@ -0,0 +1,29 @@ +using MessagePack; +using MsgPackDefineForInject; +using PolymorphicMessagePack; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PolyMsgPack.Test +{ + [TestClass] + public class AutoMsgPackTest + { + public AutoMsgPackTest() + { + + } + + [TestMethod] + public void AutoMarkKey() + { + var s = MessagePackSerializer.Serialize(new Class2_2() { CT2 = 2, CT2_2 = 22, CTB2 = 12 }); + + var ds= MessagePackSerializer.Deserialize(s); + Assert.IsNotNull(ds); + } + } +} diff --git a/PolyMsgPack.Test/PolyMsgPack.Test.csproj b/PolyMsgPack.Test/PolyMsgPack.Test.csproj new file mode 100644 index 0000000..5ab241d --- /dev/null +++ b/PolyMsgPack.Test/PolyMsgPack.Test.csproj @@ -0,0 +1,25 @@ + + + + net6.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + diff --git a/PolyMsgPack.Test/PolyMsgPackTest.cs b/PolyMsgPack.Test/PolyMsgPackTest.cs new file mode 100644 index 0000000..dd6eb35 --- /dev/null +++ b/PolyMsgPack.Test/PolyMsgPackTest.cs @@ -0,0 +1,83 @@ +using AbsInjectTypeDll.DllSubAssembly; +using MessagePack; +using MessagePack.Resolvers; +using MsgPackDefineForInject; +using PolymorphicMessagePack; + +namespace PolyMsgPack.Test +{ + + [TestClass] + public class PolyMsgPackTest + { + PolymorphicMessagePackSerializerOptions _polyOptions; + public PolyMsgPackTest() + { + var polySettings = new PolymorphicMessagePackSettings(StandardResolver.Instance); + + polySettings.InjectUnionRequireFromAssembly(typeof(Class1).Assembly,typeof(CBase1).Assembly); + + _polyOptions = new PolymorphicMessagePackSerializerOptions(polySettings); + + MessagePackSerializer.DefaultOptions = _polyOptions; + } + + [TestMethod] + public void Test_NonGenericToAbs() + { + var s = MessagePackSerializer.Serialize(new Class3 { CT1 = 3,CT3=4}); + var ds = MessagePackSerializer.Deserialize(s); + + Assert.IsTrue(ds is Class1 ds1 && ds1.CT1 == 3); + } + + [TestMethod] + public void Test_NotMarkClassSer() + { + var s = MessagePackSerializer.Serialize(new Class2 { CT2 = 1 }, _polyOptions); + Assert.ThrowsException(() => MessagePackSerializer.Deserialize(s, _polyOptions)); + } + + [TestMethod] + public void Test_S_DSToSubOtherSubClass() + { + var s = MessagePackSerializer.Serialize(new Class3 { CT3 = 3, CT1 = 1 }, _polyOptions); + var ds = MessagePackSerializer.Deserialize(s, _polyOptions); + Assert.IsTrue(ds.CT1 == 1); + } + + [TestMethod] + public void Test_S_DSDriveGenericClass() + { + var s = MessagePackSerializer.Serialize(new Class4 { CT4 = 4 }, _polyOptions); + var ds = MessagePackSerializer.Deserialize(s, _polyOptions); + Assert.IsTrue(ds.CT4 == 4); + } + + [TestMethod] + public void Test_S_DSGenericClass() + { + var s = MessagePackSerializer.Serialize(new Class5 { CT5 = 5 }, _polyOptions); + var ds = MessagePackSerializer.Deserialize>(s, _polyOptions); + Assert.IsTrue(ds.CT5 == 5); + } + + [TestMethod] + public void Test_S_DSWrongGenericClass() + { + var s = MessagePackSerializer.Serialize(new Class5 { CT5 = 5 }, _polyOptions); + var ds = MessagePackSerializer.Deserialize>(s, _polyOptions); + Assert.IsNull(ds); + } + + [TestMethod] + public void Test_S_DS_MultiInterface() + { + var s = MessagePackSerializer.Serialize(new Class6(), _polyOptions); + var ds1 = MessagePackSerializer.Deserialize(s, _polyOptions); + var ds2 = MessagePackSerializer.Deserialize(s, _polyOptions); + Assert.IsNotNull(ds1); + Assert.IsNotNull(ds2); + } + } +} diff --git a/PolyMsgPack.Test/Usings.cs b/PolyMsgPack.Test/Usings.cs new file mode 100644 index 0000000..ab67c7e --- /dev/null +++ b/PolyMsgPack.Test/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/PolyServer/MyFirstService.cs b/PolyServer/MyFirstService.cs new file mode 100644 index 0000000..64d4b6d --- /dev/null +++ b/PolyServer/MyFirstService.cs @@ -0,0 +1,32 @@ +using MagicOnion.Server; +using MagicOnion; +using Service.Shared; +using MsgPackDefineForInject; +using AbsInjectTypeDll.DllSubAssembly; + +namespace PolyServer +{ + // Implements RPC service in the server project. + // The implementation class must inherit `ServiceBase` and `IMyFirstService` + public class MyFirstService : ServiceBase, IMyFirstService + { + // `UnaryResult` allows the method to be treated as `async` method. + public async UnaryResult SumAsync(int x, int y) + { + Console.WriteLine($"Received:{x}, {y}"); + return x + y; + } + + public async UnaryResult DoWorkAsync() + { + // Something to do ... + } + + public async UnaryResult GetTestData(int x) + { + Console.WriteLine($"Received:{x}"); + var package=new Class3() { CT1 = x, CT3 = 3 }; + return package; + } + } +} diff --git a/PolyServer/PolyServer.csproj b/PolyServer/PolyServer.csproj new file mode 100644 index 0000000..05e01ec --- /dev/null +++ b/PolyServer/PolyServer.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + enable + 1998 + + + + + + + + + + + + + + + diff --git a/PolyServer/Program.cs b/PolyServer/Program.cs new file mode 100644 index 0000000..5812a84 --- /dev/null +++ b/PolyServer/Program.cs @@ -0,0 +1,35 @@ +using AbsInjectTypeDll.DllSubAssembly; +using MagicOnion; +using MagicOnion.Serialization; +using MagicOnion.Server; +using MessagePack; +using MessagePack.Resolvers; +using Microsoft.AspNetCore; +using MsgPackDefineForInject; +using PolymorphicMessagePack; +using Service.Shared; + +namespace PolyServer +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .ConfigureLogging( + (hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + logging.AddDebug(); + logging.AddEventSourceLogger(); + } + ) + .UseUrls() + .UseKestrel() + .UseStartup(); + } +} \ No newline at end of file diff --git a/PolyServer/Properties/launchSettings.json b/PolyServer/Properties/launchSettings.json new file mode 100644 index 0000000..dd893bc --- /dev/null +++ b/PolyServer/Properties/launchSettings.json @@ -0,0 +1,48 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:59481", + "sslPort": 44308 + } + }, + "profiles": { + "Environment-Develop": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7152;http://localhost:5105", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Environment-Staging": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7152;http://localhost:5105", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Staging", + "ASPNETCORE_DETAILEDERRORS": "1", + "ASPNETCORE_SHUTDOWNTIMEOUTSECONDS": "3" + } + }, + "Environment-Production": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7152;http://localhost:5105", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Production" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/PolyServer/Startup.cs b/PolyServer/Startup.cs new file mode 100644 index 0000000..f54a21c --- /dev/null +++ b/PolyServer/Startup.cs @@ -0,0 +1,60 @@ +using AbsInjectTypeDll.DllSubAssembly; +using Grpc.Net.Client; +using MagicOnion.Server; +using MessagePack.Resolvers; +using MsgPackDefineForInject; +using PolymorphicMessagePack; + +namespace PolyServer +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllersWithViews(); + + services.AddGrpc(); // MagicOnion depends on ASP.NET Core gRPC service. + + var polySettings = new PolymorphicMessagePackSettings(StandardResolver.Instance); + polySettings.InjectUnionRequireFromAssembly(typeof(Class1).Assembly, typeof(CBase1).Assembly); + var _polyOptions = new PolymorphicMessagePackSerializerOptions(polySettings); + + services.AddMagicOnion(options => + { + options.MessageSerializer = new MagicOnionPolyMsgPackSerializerProvider(_polyOptions); + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapMagicOnionHttpGateway("_", + app.ApplicationServices.GetService()!.MethodHandlers, + GrpcChannel.ForAddress($"https://localhost:{Configuration["GrpcChannelPort"]}")); + endpoints.MapMagicOnionSwagger("mo/swagger", + app.ApplicationServices.GetService()!.MethodHandlers, + "/_/"); + endpoints.MapMagicOnionService(); + }); + } + } +} diff --git a/PolyServer/appsettings.Development.json b/PolyServer/appsettings.Development.json new file mode 100644 index 0000000..ffed010 --- /dev/null +++ b/PolyServer/appsettings.Development.json @@ -0,0 +1,21 @@ +{ + "Logging": { + "Debug": { + "LogLevel": { + "Default": "Warning", + "Microsoft": "Warning" + } + }, + "LogLevel": { + "Default": "Warning", + "Microsoft": "Warning" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http1AndHttp2" + } + }, + "GrpcChannelPort": 5001 +} diff --git a/PolyServer/appsettings.json b/PolyServer/appsettings.json new file mode 100644 index 0000000..2bd99c2 --- /dev/null +++ b/PolyServer/appsettings.json @@ -0,0 +1,21 @@ +{ + "Logging": { + "Debug": { + "LogLevel": { + "Default": "Warning", + "Microsoft": "Warning" + } + }, + "LogLevel": { + "Default": "Warning", + "Microsoft": "Warning" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http1AndHttp2" + } + }, + "GrpcChannelPort":5001 +} diff --git a/PolymorphicMessagePack.Fody/AutoMsgPackKeyWeaver.cs b/PolymorphicMessagePack.Fody/AutoMsgPackKeyWeaver.cs new file mode 100644 index 0000000..9fc0484 --- /dev/null +++ b/PolymorphicMessagePack.Fody/AutoMsgPackKeyWeaver.cs @@ -0,0 +1,508 @@ +using Fody; +using MessagePack; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Text; + +namespace PolymorphicMessagePack.Fody +{ + public class AutoMsgPackKeyWeaver : BaseModuleWeaver + { + public override bool ShouldCleanReference => true; + + //require check + Type _msgObjAttr = typeof(MessagePackObjectAttribute); + Type _msgDataContractAttr = typeof(DataContractAttribute); + + //ignore prop + Type _msgIgnoreAttr = typeof(IgnoreMemberAttribute); + Type _msgDataIgnoreAttr = typeof(IgnoreDataMemberAttribute); + + //mark key + Type _msgKeyAttr = typeof(KeyAttribute); + Type _msgDataMemberAttr = typeof(DataMemberAttribute); + + string _msgPackAttrAsName; + + //object for stop while + TypeDefinition _objectTypeDef; + //Auto property created private field attribute + TypeDefinition _autoPropFieldAttrDef; + //key mark def + TypeDefinition _keyMarkDef; + + TypeDefinition _msgIgnoreDef; + TypeDefinition _msgDataIgnoreDef; + + MethodReference _msgIgnoreConstructor; + + MethodReference _msgKeyConstructor; + + MethodDefinition _objctor; + + internal class ReferenceTreeNode + { + internal TypeDefinition node; + internal HashSet ManualMarkedIdKeys = new HashSet(); + internal HashSet CurrentTypeOwnIdKeys= new HashSet(); + internal List RequireAddKeyFields=new List(); + internal List RequireAddKeyProperties=new List(); + internal ReferenceTreeNode Parent; + internal List Childs=new List(); + } + + public AutoMsgPackKeyWeaver() + { + _msgPackAttrAsName = _msgObjAttr.Assembly.GetName().Name; + + } + + public override void Execute() + { + var ns = GetStringFromConfig("NameSpace"); + if (ns == null) + { + WriteError("Scan Assembly name not set in config"); + return; + } + var markPrivateField = GetBoolFromConfig("AlsoMarkPrivateField"); + var markBaseTypeIgnoreMem = GetBoolFromConfig("MarkIgnoreToFieldForNonMsgPackBaseType"); + + InitBasicRequireRef(); + + //get all mark with [MessageObject] class + var requireConsiderAbsAndInterfaceTypes = ModuleDefinition.Types.Where( + x => x.HasCustomAttributes && //has attr + x.Namespace == ns && // in target namespace + !x.IsAbstract && //not abstract class + !x.IsInterface && //not interface + (x.IsClass || (x.IsValueType && !x.IsEnum && x.IsSealed)) && // is class or struct + x.CustomAttributes.Any(y => + y.AttributeType.FullName == _msgObjAttr.FullName || + y.AttributeType.FullName== _msgDataContractAttr.FullName)); //has messageobject + + //check can mark + foreach(var type in requireConsiderAbsAndInterfaceTypes) + { + if (!type.IsPublic) + { + WriteError($"{type.FullName} not public type but marked [MessageObject] or [DataContract]"); + return; + } + } + + var refTree = GetDerivedRelationprivate(ModuleDefinition, requireConsiderAbsAndInterfaceTypes); + + //scan each type,analysis each type own require marked fields,property,and check each manual set Key Ids + foreach (var typeNode in refTree) + { + //internal:IsAssembly;private:IsPrivate;public:IsPublic + var currentTypeRequireMarkCollection = GetNodeRequireMarkFieldAndProperties(typeNode.node, markPrivateField); + //Scan and record manual set keys + foreach (var field in currentTypeRequireMarkCollection.Item1) + { + var keyAttrs = field.CustomAttributes.Where(x => x.AttributeType.FullName == _msgKeyAttr.FullName || + x.AttributeType.FullName == _msgDataMemberAttr.FullName); + if (keyAttrs.Count() > 1) + { + WriteError($"{typeNode.node.FullName}->Field:{field.Name} has both [Key] and [DataMember]"); + return; + } + else if (keyAttrs.Count() == 1) + { + var checkedAttr = keyAttrs.First(); + if (!CheckAndReigsterManualSetKeys(typeNode, checkedAttr)) + return; + } + else + { + //not set attr + typeNode.RequireAddKeyFields.Add(field); + } + } + + foreach (var property in currentTypeRequireMarkCollection.Item2) + { + var keyAttrs = property.CustomAttributes.Where(x => x.AttributeType.FullName == _msgKeyAttr.FullName || + x.AttributeType.FullName == _msgDataMemberAttr.FullName); + if (keyAttrs.Count() > 1) + { + WriteError($"{typeNode.node.FullName}->Property:{property.Name} has both [Key] and [DataMember]"); + return; + } + else if (keyAttrs.Count() == 1) + { + var checkedAttr = keyAttrs.First(); + if (!CheckAndReigsterManualSetKeys(typeNode, checkedAttr)) + return; + } + else + { + //bool HasDefaultValueSet = PropHasDefaultValue(typeNode.node, property); + //not set attr + typeNode.RequireAddKeyProperties.Add(property); + } + } + + } + //each node get in that type which field and prop need mark,and get each type manual mark key ids + //get all end node,check each ancestors used ids + HashSet visitedNode= new HashSet(); + foreach(var typeNode in refTree.Where(x => x.Childs.Count == 0)) + { + if (visitedNode.Contains(typeNode)) + continue; + visitedNode.Add(typeNode); + var current = typeNode; + var next = current.Parent; + while (next != null) + { + if (next.CurrentTypeOwnIdKeys.Overlaps(current.ManualMarkedIdKeys)) + { + WriteError($"{current.node.FullName} has conflict key id with {next.node.FullName}"); + return; + } + //attach current manual keys to parent manual keys + //let parent know can't generate key ids which derive type used + next.ManualMarkedIdKeys.UnionWith(current.ManualMarkedIdKeys); + + current = next; + next=current.Parent; + } + } + + + Stack lastBaseTypeUsedId= new Stack(); + Stack> requireVisitChildNodes = new Stack>(); + //Mark Key + foreach(var typeNode in refTree.Where(x => x.Parent == null)) + { + //each base type start with key id 0 + MarkTreeNode(typeNode, 0, markBaseTypeIgnoreMem); + } + } + private bool PropHasDefaultValue(TypeDefinition type,PropertyDefinition property) + { + //must class can property set default value + if (!type.IsClass || type.IsValueType ||type.IsInterface) return false; + + var defaultCtor = type.GetConstructors().Where(x => 0 == x.Parameters.Count).FirstOrDefault(); + if (defaultCtor == null) return false; + var autoFieldName = $"<{property.Name}>k__BackingField"; + var propAutoField = type.Fields.Where(x => x.Name == autoFieldName).FirstOrDefault(); + if (propAutoField == null) return false; + + var instructions = defaultCtor.Body.Instructions; + foreach (var instruction in instructions) + { + //only check default ctor operators + if (instruction.OpCode.Code == OpCodes.Call.Code && instruction.Operand.ToString() == _objctor.FullName) + break; + + if (instruction.OpCode.Code == OpCodes.Stfld.Code && instruction.Operand.ToString() == propAutoField.FullName) + { + return true; + } + } + return false; + } + + private void MarkTreeNode(ReferenceTreeNode root,int startId,bool markBaseTypeIgnoreMem) + { + //if config choose no [MessagePackObject] type set all fields to [ignoreMumber] + if (markBaseTypeIgnoreMem && !root.node.CustomAttributes.Any(x=>x.AttributeType.FullName==_msgObjAttr.FullName)) + { + foreach(var field in root.RequireAddKeyFields) + { + var attribute = new CustomAttribute(_msgIgnoreConstructor); + field.CustomAttributes.Add(attribute); + } + foreach (var property in root.RequireAddKeyProperties) + { + var attribute = new CustomAttribute(_msgIgnoreConstructor); + property.CustomAttributes.Add(attribute); + } + } + else + { + MarkNodeFields(root, ref startId); + } + foreach(var deriveType in root.Childs) + { + MarkTreeNode(deriveType, startId, markBaseTypeIgnoreMem); + } + } + private void MarkNodeFields(ReferenceTreeNode node,ref int startId) + { + foreach(var field in node.RequireAddKeyFields) + { + while (node.ManualMarkedIdKeys.Contains(startId)) + startId++; + + var attribute = new CustomAttribute(_msgKeyConstructor); + attribute.ConstructorArguments.Add( + new CustomAttributeArgument( + _msgKeyConstructor.Parameters[0].ParameterType, + startId)); + field.CustomAttributes.Add(attribute); + startId++; + } + + foreach(var property in node.RequireAddKeyProperties) + { + while (node.ManualMarkedIdKeys.Contains(startId)) + startId++; + + var attribute = new CustomAttribute(_msgKeyConstructor); + attribute.ConstructorArguments.Add( + new CustomAttributeArgument( + _msgKeyConstructor.Parameters[0].ParameterType, + startId)); + property.CustomAttributes.Add(attribute); + startId++; + } + } + + private bool CheckAndReigsterManualSetKeys(ReferenceTreeNode node,CustomAttribute attribute) + { + int manualSetKey; + //if is KeyAttribute and use KeyAttribute(int key) + if (attribute.AttributeType.FullName == _msgKeyAttr.FullName && + attribute.ConstructorArguments[0].Type.FullName == TypeSystem.Int32Reference.FullName) + { + manualSetKey = (int)attribute.ConstructorArguments[0].Value; + } + //if is DataMemberAttribute and use DataMemberAttribute(Order=int) + else if (attribute.AttributeType.FullName == _msgDataMemberAttr.FullName && + attribute.Properties.Any(x => x.Name == "Order")) + { + manualSetKey = (int)attribute.Properties.Where(x => x.Name == "Order").First().Argument.Value; + } + //other set with name,not consider,continue + else + { + return true; + } + //check if same key in one type + if (node.ManualMarkedIdKeys.Contains(manualSetKey)) + { + WriteError($"{node.node.FullName} has marked same key {manualSetKey} for more than one field or property"); + return false; + } + node.ManualMarkedIdKeys.Add(manualSetKey); + node.CurrentTypeOwnIdKeys.Add(manualSetKey); + return true; + } + + private (List,List) GetNodeRequireMarkFieldAndProperties(TypeDefinition targetType, bool markPrivateField) + { + List considerFields = null; + //ignore mark [IgnoreMember],[IgnoreDataMember] + var query = targetType.Fields.Where(x => !x.CustomAttributes.Any(y => + y.AttributeType.FullName == _msgIgnoreDef.FullName || + y.AttributeType.FullName == _msgDataIgnoreDef.FullName)); + + //make a copy + if (markPrivateField) + considerFields = query.ToList(); + else + considerFields = query.Where(x => !(x.IsAssembly || x.IsPrivate) || + x.CustomAttributes.Any(y => y.AttributeType.FullName == _autoPropFieldAttrDef.FullName)).ToList(); + + var considerProperties = new List(); + //Auto Field ignore :{k__BackingField}+{System.Runtime.CompilerServices.CompilerGeneratedAttribute} + //Auto Field Match: k__BackingField + + //only consider auto-prop properties,not auto-prop most will be logic and not able to serialize/deserialize + foreach (var property in targetType.Properties) + { + //remove existed auto-prop field + var autoFieldName = $"<{property.Name}>k__BackingField"; + //auto field only has one field or not + var relateAutoField = considerFields.Where(x => + x.IsPrivate && + x.Name == autoFieldName && + x.HasCustomAttributes && + x.CustomAttributes.Any(y => y.AttributeType.FullName == _autoPropFieldAttrDef.FullName) + ).FirstOrDefault(); + if (relateAutoField != null) + { + // remove them from considerFields + considerFields.Remove(relateAutoField); + } + + //ignore manual mark [IgnoreMember],[IgnoreDataMember] + if (property.HasCustomAttributes && + property.CustomAttributes.Any(x => + x.AttributeType.FullName == _msgIgnoreDef.FullName || + x.AttributeType.FullName == _msgDataIgnoreDef.FullName)) + continue; + + if(relateAutoField == null) + { + //not auto-prop + no [IgnoreMember] + var attribute = new CustomAttribute(_msgIgnoreConstructor); + property.CustomAttributes.Add(attribute); + continue; + } + + // check Accessibility [ignore internal,private] + // prop won't have Accessibility mark,must check get/set method Accessibility + // for property,get/set must has at last one public + var publicGetter = property.GetMethod != null && property.GetMethod.IsPublic ? true : false; + var publicSetter = property.SetMethod != null && property.SetMethod.IsPublic ? true : false; + + if (!markPrivateField && !(publicGetter || publicSetter)) + { + //ignore this prop,but this prop not set [IgnoreMember],add it into that property + var attribute = new CustomAttribute(_msgIgnoreConstructor); + property.CustomAttributes.Add(attribute); + continue; + } + + considerProperties.Add(property); + } + return(considerFields,considerProperties); + } + + /// + /// analysis all [MessagePackObject] class derived relation tree [from base abstract to final class] + /// + /// + /// + /// + private List GetDerivedRelationprivate( + ModuleDefinition module, + IEnumerable types) + { + List referenceTree = new List(); + + + foreach (var typeDef in types) + { + //target type exist,ignore + if (referenceTree.Any(x=>x.node.FullName==typeDef.FullName)) + continue; + + //struct + //Must check first beause struct also IsClass + if (typeDef.IsValueType) + { + var newCurrentType = new ReferenceTreeNode() { node = typeDef }; + referenceTree.Add(newCurrentType); + } + else if(typeDef.IsClass) + { + var newCurrentType = new ReferenceTreeNode() { node = typeDef }; + referenceTree.Add(newCurrentType); + + var nextcheck = newCurrentType; + + while (nextcheck != null && nextcheck.node.FullName != _objectTypeDef.FullName) + { + var baseType= nextcheck.node.BaseType?.Resolve(); + if (baseType == null || baseType.FullName == _objectTypeDef.FullName) + break; + + var existType=referenceTree.Where(x=>x.node.FullName==baseType.FullName).FirstOrDefault(); + + if(existType == null) + { + var newBaseType = new ReferenceTreeNode() { node = baseType }; + referenceTree.Add(newBaseType); + newBaseType.Childs.Add(nextcheck); + nextcheck.Parent = newBaseType; + nextcheck = newBaseType; + } + else + { + existType.Childs.Add(nextcheck); + nextcheck.Parent = existType; + break; + } + } + } + } + return referenceTree; + } + + + + public override IEnumerable GetAssembliesForScanning() + { + yield return "netstandard"; + yield return "mscorlib"; + yield return "System"; + yield return "System.Runtime"; + } + + void InitBasicRequireRef() + { + _objectTypeDef = ModuleDefinition.ImportReference(TypeSystem.ObjectDefinition).Resolve(); + _objctor = _objectTypeDef.GetConstructors().Where(x => 0 == x.Parameters.Count).First(); + + _autoPropFieldAttrDef = ModuleDefinition.ImportReference(typeof(CompilerGeneratedAttribute)).Resolve(); + + _msgDataIgnoreDef= ModuleDefinition.ImportReference(_msgDataIgnoreAttr).Resolve(); + + var msgPackAssembly = new AssemblyNameReference(_msgPackAttrAsName, null); + try + { + _keyMarkDef = ModuleDefinition.AssemblyResolver.Resolve(msgPackAssembly). + MainModule.Types. + Single(type => type.Name == _msgKeyAttr.Name); + + _msgIgnoreDef = ModuleDefinition.ImportReference(_msgIgnoreAttr).Resolve(); + + var msgIgnoreConstructor = _msgIgnoreDef + .GetConstructors() + .Single(ctor => + 0 == ctor.Parameters.Count); + _msgIgnoreConstructor = ModuleDefinition.ImportReference(msgIgnoreConstructor); + + var msgKeyConstructor = _keyMarkDef + .GetConstructors() + .Single(ctor => + 1 == ctor.Parameters.Count && + TypeSystem.Int32Reference.FullName == ctor.Parameters[0].ParameterType.FullName); + _msgKeyConstructor = ModuleDefinition.ImportReference(msgKeyConstructor); + + } + catch (Exception ex) + { + WriteError($"Init Basic TypeDefine Failed ({ex.Message})"); + } + } + + #region GetAssemblyNameSpaceAttributes + string GetStringFromConfig(string name) + { + var attribute = Config?.Attribute(name); + if (attribute == null) + { + return null; + } + var value = attribute.Value; + return value; + } + + bool GetBoolFromConfig(string name) + { + var attribute = Config?.Attribute(name); + if (attribute == null) + return false; + var value = (bool?)attribute; + if (value == null) + return false; + return value.Value; + } + #endregion + } +} diff --git a/PolymorphicMessagePack.Fody/AutoMsgPackKeyWeaver.xcf b/PolymorphicMessagePack.Fody/AutoMsgPackKeyWeaver.xcf new file mode 100644 index 0000000..cf0ef52 --- /dev/null +++ b/PolymorphicMessagePack.Fody/AutoMsgPackKeyWeaver.xcf @@ -0,0 +1,23 @@ + + + + + Namespace to use for the injected type + + + + + Also scan and mark private field + + + + + + For class Base Types,if they don't have [MessagePackObject],set their field with [IgnoreMember] if not manual mark + + + + \ No newline at end of file diff --git a/PolymorphicMessagePack.Fody/AutoPolyMsgPackWeaver.cs b/PolymorphicMessagePack.Fody/AutoPolyMsgPackWeaver.cs new file mode 100644 index 0000000..1444daa --- /dev/null +++ b/PolymorphicMessagePack.Fody/AutoPolyMsgPackWeaver.cs @@ -0,0 +1,419 @@ +using Fody; +using MessagePack; +using Mono.Cecil; +using Mono.Cecil.Rocks; +using ShareAttributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace PolymorphicMessagePack.Fody +{ + public class AutoPolyMsgPackWeaver : BaseModuleWeaver + { + public override bool ShouldCleanReference => true; + + Type _absAttr = typeof(UnionAbsOrInterfaceAttribute); + Type _genericUnion = typeof(GenericUnionAttribute); + Type _reqUnionAttr = typeof(RequireUnionAttribute); + Type _reqGenericUnionAttr = typeof(RequireUnionGenericAttribute); + + Type _msgObj = typeof(MessagePackObjectAttribute); + + string _polyAttrAsName; + string _msgPackAttrAsName; + + TypeDefinition _objectTypeRef; + + TypeDefinition _requireUnionAttrRef; + MethodReference _requireUnionAttrConstructor; + + TypeDefinition _requireUnionGenericAttrRef; + MethodReference _requireUnionGenericAttrConstructor; + + + public AutoPolyMsgPackWeaver() + { + _polyAttrAsName = _absAttr.Assembly.GetName().Name; + _msgPackAttrAsName=_msgObj.Assembly.GetName().Name; + } + + public override void Execute() + { + var ns = GetValueFromConfig("NameSpace"); + var absns = GetValueFromConfig("AbsNameSpace"); + + if(ns == null) + { + WriteError("Scan Assembly name not set in config"); + return; + } + InitBasicRequireRef(); + + IEnumerable requireConsiderAbsAndInterfaceTypes; + + if (absns == null) + { + //find abs from target namespace + requireConsiderAbsAndInterfaceTypes = ModuleDefinition.Types.Where( + x => x.HasCustomAttributes && + x.Module.Assembly.Name.Name == ns && + x.CustomAttributes.Any(y => y.AttributeType.FullName == _absAttr.FullName)); + } + else + { + var absLocationAssembly = ModuleDefinition.AssemblyReferences.Where(x => x.Name == absns).FirstOrDefault(); + if (absLocationAssembly == null) + { + WriteError($"Not Find abstract type assembly {absns} in {ns} reference assemblies"); + return; + } + var resolveTypes = ModuleDefinition.AssemblyResolver.Resolve(absLocationAssembly). + MainModule.Types; + requireConsiderAbsAndInterfaceTypes = resolveTypes.Where( + x => x.HasCustomAttributes && + x.Module.Assembly.Name.Name == absns && + x.CustomAttributes.Any(y => y.AttributeType.FullName == _absAttr.FullName)); + } + + var resultsForNonGenericTypes = GetNonGenericDerivedTypes(ModuleDefinition, requireConsiderAbsAndInterfaceTypes).OrderBy(x => x.FullName); + + var resultsForGenericTypes = GetGenericDerivedTypes(ModuleDefinition, requireConsiderAbsAndInterfaceTypes).OrderBy(x => x.Item1.FullName); + + //pass nongeneric types + HashSet ignoreNonGenericTypes=new HashSet(); + //record all manual marked used static ids + Dictionary manualMarkUsedIdForNonGenericTypes = new Dictionary(); + + Dictionary manualMarkUsedIdForGenericTypes = new Dictionary(); + + //check non generic first + foreach(var derivedType in resultsForNonGenericTypes) + { + if(derivedType.HasCustomAttributes && + derivedType.CustomAttributes.Any(x => x.AttributeType.FullName == _reqGenericUnionAttr.FullName || x.AttributeType.FullName==_genericUnion.FullName)) + { + WriteError($"Error: {derivedType.FullName} set generic types(this is non generic type)"); + return; + } + //If Marked [RequireUnionAttribute] by manual + if (derivedType.HasCustomAttributes && + derivedType.CustomAttributes.Any(x=>x.AttributeType.FullName== _reqUnionAttr.FullName)) + { + var markedReqUnionAttr=derivedType.CustomAttributes.Single(x=>x.AttributeType.FullName==_reqUnionAttr.FullName); + + //public RequireUnionAttribute(uint unionUniqueId) + var manualSetId = (uint)markedReqUnionAttr.ConstructorArguments[0].Value; + + if (manualMarkUsedIdForNonGenericTypes.TryGetValue(manualSetId,out var existUsedIdType)) + { + WriteError($"Error: {manualSetId} set for diff types:{derivedType.FullName} and {existUsedIdType.FullName}"); + return; + } + manualMarkUsedIdForNonGenericTypes.Add(manualSetId, derivedType); + ignoreNonGenericTypes.Add(derivedType); + } + } + + foreach(var derivedGenericType in resultsForGenericTypes) + { + var classtyperef = derivedGenericType.Item1; + + var relateAttributes = derivedGenericType.Item2; + + if (classtyperef.HasCustomAttributes && + classtyperef.CustomAttributes.Any(x => x.AttributeType.FullName == _reqUnionAttr.FullName)) + { + WriteError($"Error: {classtyperef.FullName} set non generic types(this is generic type)"); + return; + } + + //generic type really diff to compare + //maybe mark with same attr + //e.g: + //[RequireUnionGenericAttribute(1,typeof(string))] + //[RequireUnionGenericAttribute(2,typeof(string))] + //[RequireUnionGenericAttribute(3,typeof(int))] + //[RequireUnionGenericAttribute(4,typeof(int))] + //forget it + //visit classtyperef attributes + + //public RequireUnionGenericAttribute(uint unionUniqueId, Type supportGeneric) + if (classtyperef.HasCustomAttributes && + classtyperef.CustomAttributes.Any(x => x.AttributeType.FullName == _requireUnionGenericAttrRef.FullName)) + { + //using type to group + var groupsTypeAttributes = classtyperef.CustomAttributes + .Where(x => x.AttributeType.FullName == _requireUnionGenericAttrRef.FullName) + .GroupBy(y => y.ConstructorArguments[1].Value.ToString()); + foreach(var groupAttr in groupsTypeAttributes) + { + //only one mark + if (groupAttr.Count()==1) + { + var targetAttr = groupAttr.First(); + + var manualSetId = (uint)targetAttr.ConstructorArguments[0].Value; + var typeName = targetAttr.ConstructorArguments[1].Value.ToString(); + + var setType = (TypeReference)targetAttr.ConstructorArguments[1].Value; + var setTypeDef = setType.Resolve(); + + if (!setType.IsGenericInstance || setTypeDef.FullName!=classtyperef.FullName) + { + WriteError($"{classtyperef.FullName} fixed id manaully,but target union type not {classtyperef.FullName} generic type (is {setTypeDef.FullName})"); + return; + } + + if (manualMarkUsedIdForNonGenericTypes.TryGetValue(manualSetId, out var _) + || manualMarkUsedIdForGenericTypes.TryGetValue(manualSetId, out var _)) + { + string outputString; + var typeForNonGeneric = manualMarkUsedIdForNonGenericTypes.TryGetValue(manualSetId, out var existNonType); + var typeForGeneric = manualMarkUsedIdForGenericTypes.TryGetValue(manualSetId, out var existTypeWithGeneric); + if (typeForNonGeneric) + outputString = existNonType.FullName; + else + outputString = $"{existTypeWithGeneric.Item1.FullName}-{existTypeWithGeneric.Item2.ConstructorArguments[1].Value}"; + WriteError($"Error: {manualSetId} set for diff types:{classtyperef.FullName}-{typeName} and {outputString}"); + return; + } + + manualMarkUsedIdForGenericTypes.Add(manualSetId, (classtyperef, targetAttr)); + //remove same type mark attr in attributes + //remove from class + var existOldAttributes = classtyperef.CustomAttributes.Where(x => + x.AttributeType.FullName==_genericUnion.FullName && + x.ConstructorArguments[0].Value.ToString() == typeName).ToList(); + foreach (var oldAttribute in existOldAttributes) + classtyperef.CustomAttributes.Remove(oldAttribute); + + //remove from auto generate attr + existOldAttributes = relateAttributes.Where(x => + x.AttributeType.FullName == _genericUnion.FullName && + x.ConstructorArguments[0].Value.ToString() == typeName).ToList(); + foreach (var oldAttribute in existOldAttributes) + relateAttributes.Remove(oldAttribute); + } + //multi manual set + else + { + WriteError($"Error: {classtyperef.FullName}-{groupAttr.Key} has more than one mark"); + return; + } + } + } + + } + + uint autoIncreaseIdForPoly = 1; + while (manualMarkUsedIdForNonGenericTypes.ContainsKey(autoIncreaseIdForPoly) || manualMarkUsedIdForGenericTypes.ContainsKey(autoIncreaseIdForPoly)) + autoIncreaseIdForPoly++; + + //Add [RequireUnion(uint x)] into target class which from marked abs/interface and marked [MessageObject] + foreach (var derivedType in resultsForNonGenericTypes) + { + if (ignoreNonGenericTypes.Contains(derivedType)) + continue; + + var attribute = new CustomAttribute(_requireUnionAttrConstructor); + attribute.ConstructorArguments.Add( + new CustomAttributeArgument( + _requireUnionAttrConstructor.Parameters[0].ParameterType, + autoIncreaseIdForPoly)); + derivedType.CustomAttributes.Add(attribute); + autoIncreaseIdForPoly++; + while (manualMarkUsedIdForNonGenericTypes.ContainsKey(autoIncreaseIdForPoly) || manualMarkUsedIdForGenericTypes.ContainsKey(autoIncreaseIdForPoly)) + autoIncreaseIdForPoly++; + } + + //Add [RequireUnionGeneric(uint x,Type y)] into target class which from marked abs/interface and marked [MessageObject] + foreach (var derivedType in resultsForGenericTypes) + { + var classtyperef = derivedType.Item1; + var relateAttributes = derivedType.Item2.OrderBy(x => x.ConstructorArguments[0].Value.ToString()); + foreach (var defineoldAttribute in relateAttributes) + { + //using marked class type to package generic + var newClassdefWithGeneric = new GenericInstanceType(classtyperef); + newClassdefWithGeneric.GenericArguments.Add(defineoldAttribute.ConstructorArguments[0].Value as TypeReference); + + var attribute = new CustomAttribute(_requireUnionGenericAttrConstructor); + + attribute.ConstructorArguments.Add(new CustomAttributeArgument(_requireUnionGenericAttrConstructor.Parameters[0].ParameterType, autoIncreaseIdForPoly)); + attribute.ConstructorArguments.Add(new CustomAttributeArgument(_requireUnionGenericAttrConstructor.Parameters[1].ParameterType, newClassdefWithGeneric)); + + var existOldAttributes = classtyperef.CustomAttributes.Where(x => x.ConstructorArguments[0].Value.ToString() == defineoldAttribute.ConstructorArguments[0].Value.ToString()).ToList(); + foreach (var oldAttribute in existOldAttributes) + classtyperef.CustomAttributes.Remove(oldAttribute); + + classtyperef.CustomAttributes.Add(attribute); + + autoIncreaseIdForPoly++; + while (manualMarkUsedIdForNonGenericTypes.ContainsKey(autoIncreaseIdForPoly) || manualMarkUsedIdForGenericTypes.ContainsKey(autoIncreaseIdForPoly)) + autoIncreaseIdForPoly++; + } + } + WriteInfo($"Generate Auto PolyMark Run Finished.(Used for {ns})"); + } + + private List GetNonGenericDerivedTypes( + ModuleDefinition module, + IEnumerable baseTypes) + { + //get all non generic class types + //also must has [MessagePackObject] + var classdefs = module.GetTypes(). + Where(x => + !x.IsAbstract && + x.IsClass && + !x.HasGenericParameters && + x.HasCustomAttributes && + x.CustomAttributes.Any(y => y.AttributeType.FullName == _msgObj.FullName)); + + List derivedtypes = new List(); + + foreach (var classdef in classdefs) + { + if (derivedtypes.Contains(classdef)) + continue; + + var nextcheck = classdef; + + while (nextcheck != null && nextcheck.FullName != _objectTypeRef.FullName) + { + var baseType = baseTypes.Where(x => x.IsAbstract && x.FullName == nextcheck.FullName).FirstOrDefault(); + if (baseType != null) + { + derivedtypes.Add(classdef); + break; + } + + nextcheck = nextcheck.BaseType?.Resolve(); + } + + if (!derivedtypes.Contains(classdef)) + { + var relateInterfaces = baseTypes.Where(x => x.IsInterface && classdef.Interfaces.Any(y => y.InterfaceType.FullName == x.FullName)); + if (relateInterfaces.Count() > 0) + { + derivedtypes.Add(classdef); + } + } + } + return derivedtypes; + } + + private List<(TypeDefinition, List)> GetGenericDerivedTypes( + ModuleDefinition module, + IEnumerable baseTypes) + { + var classdefs = module.GetTypes(). + Where(x => + !x.IsAbstract && + x.IsClass && + x.HasGenericParameters && + x.HasCustomAttributes && + x.CustomAttributes.Any(y => y.AttributeType.FullName == _msgObj.FullName)); + + List<(TypeDefinition, List)> derivedtypes = new List<(TypeDefinition, List)>(); + + foreach (var classdef in classdefs) + { + //avoid same generic type repeat attr + var genericUnionTypes = classdef.CustomAttributes + .Where(x => x.AttributeType.FullName == _genericUnion.FullName) + .ToLookup(y => y.ConstructorArguments[0].Value.ToString()) + .Select(z => z.First()).ToList(); + + //package + var package = (classdef, genericUnionTypes); + + var nextcheck = classdef; + bool isInAnyAbs = false; + while (nextcheck != null && nextcheck.FullName != _objectTypeRef.FullName) + { + var baseType = baseTypes.Where(x => x.IsAbstract && x.FullName == nextcheck.FullName).FirstOrDefault(); + if (baseType != null) + { + derivedtypes.Add(package); + isInAnyAbs = true; + break; + } + nextcheck = nextcheck.BaseType?.Resolve(); + } + if (!isInAnyAbs) + { + //check all interface base type + var relateInterfaces = baseTypes.Where(x => x.IsInterface && classdef.Interfaces.Any(y => (y.InterfaceType.IsGenericInstance && y.InterfaceType.GetElementType().FullName == x.FullName) || + (!y.InterfaceType.IsGenericInstance && y.InterfaceType.FullName == x.FullName))); + if (relateInterfaces.Count() > 0) + { + derivedtypes.Add(package); + } + } + + } + return derivedtypes; + } + + public override IEnumerable GetAssembliesForScanning() + { + yield return "netstandard"; + yield return "mscorlib"; + yield return "System"; + yield return "System.Runtime"; + } + + void InitBasicRequireRef() + { + _objectTypeRef = ModuleDefinition.ImportReference(TypeSystem.ObjectDefinition).Resolve(); + var msgPackAssembly = new AssemblyNameReference(_msgPackAttrAsName, null); + var polyAttrAssembly = new AssemblyNameReference(_polyAttrAsName, null); + try + { + _requireUnionAttrRef = ModuleDefinition.AssemblyResolver.Resolve(polyAttrAssembly). + MainModule.Types. + Single(type => type.Name == _reqUnionAttr.Name); + _requireUnionGenericAttrRef = ModuleDefinition.AssemblyResolver.Resolve(polyAttrAssembly). + MainModule.Types. + Single(type => type.Name == _reqGenericUnionAttr.Name); + + var requireUnionAttrConstructor= _requireUnionAttrRef + .GetConstructors() + .Single(ctor => + 1 == ctor.Parameters.Count && + TypeSystem.UInt32Reference.FullName == ctor.Parameters[0].ParameterType.FullName); + _requireUnionAttrConstructor = ModuleDefinition.ImportReference(requireUnionAttrConstructor); + + var requireUnionGenericAttrConstructor=_requireUnionGenericAttrRef + .GetConstructors() + .Single(ctor => + 2 == ctor.Parameters.Count && + TypeSystem.UInt32Reference.FullName == ctor.Parameters[0].ParameterType.FullName && + "System.Type" == ctor.Parameters[1].ParameterType.FullName); + _requireUnionGenericAttrConstructor=ModuleDefinition.ImportReference(requireUnionGenericAttrConstructor); + + } + catch (Exception ex) + { + WriteError($"Init Basic TypeDefine Failed ({ex.Message})"); + } + } + + #region GetAssemblyNameSpaceAttributes + string GetValueFromConfig(string name) + { + var attribute = Config?.Attribute(name); + if (attribute == null) + { + return null; + } + + var value = attribute.Value; + return value; + } + #endregion + } +} diff --git a/PolymorphicMessagePack.Fody/AutoPolyMsgPackWeaver.xcf b/PolymorphicMessagePack.Fody/AutoPolyMsgPackWeaver.xcf new file mode 100644 index 0000000..feee850 --- /dev/null +++ b/PolymorphicMessagePack.Fody/AutoPolyMsgPackWeaver.xcf @@ -0,0 +1,15 @@ + + + + + Namespace to use for the injected type + + + + + Abstruct Type in space + + + \ No newline at end of file diff --git a/PolymorphicMessagePack.Fody/PolymorphicMessagePack.Fody.csproj b/PolymorphicMessagePack.Fody/PolymorphicMessagePack.Fody.csproj new file mode 100644 index 0000000..1ab5aec --- /dev/null +++ b/PolymorphicMessagePack.Fody/PolymorphicMessagePack.Fody.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0 + true + + + + + + + + + + + + diff --git a/PolymorphicMessagePack.Fody/Properties/AssemblyInfo.cs b/PolymorphicMessagePack.Fody/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/PolymorphicMessagePack.Fody/Properties/AssemblyInfo.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/PolymorphicMessagePack.csproj b/PolymorphicMessagePack.csproj index bdaba76..dc02cad 100644 --- a/PolymorphicMessagePack.csproj +++ b/PolymorphicMessagePack.csproj @@ -1,11 +1,53 @@ - net5.0 + netstandard2.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PolymorphicMessagePack.sln b/PolymorphicMessagePack.sln index aa8274d..f56c37e 100644 --- a/PolymorphicMessagePack.sln +++ b/PolymorphicMessagePack.sln @@ -1,9 +1,30 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.0.32112.339 +VisualStudioVersion = 17.5.33516.290 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PolymorphicMessagePack", "PolymorphicMessagePack\PolymorphicMessagePack.csproj", "{F7E1E83F-3593-4946-8DFB-9E8E1EAEC6E7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PolymorphicMessagePack", "PolymorphicMessagePack.csproj", "{8DF39770-8DBD-4647-A73D-7B362CD07CBA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PolyMsgPack.Test", "PolyMsgPack.Test\PolyMsgPack.Test.csproj", "{A1C98719-901D-43FA-9FE8-F80441835D4A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShareAttributes", "ShareAttributes\ShareAttributes.csproj", "{D0E6414F-4F35-41E9-A10B-AF0042E9D0EB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MsgPackDefineForInject", "MsgPackDefineForInject\MsgPackDefineForInject.csproj", "{8CF34B2D-452A-4C0D-BFDC-10789FA3E145}" + ProjectSection(ProjectDependencies) = postProject + {B9E1906C-3F9D-49AB-8B9A-A7413B513BFC} = {B9E1906C-3F9D-49AB-8B9A-A7413B513BFC} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PolymorphicMessagePack.Fody", "PolymorphicMessagePack.Fody\PolymorphicMessagePack.Fody.csproj", "{B9E1906C-3F9D-49AB-8B9A-A7413B513BFC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fody.Test", "Fody.Test\Fody.Test.csproj", "{863134AE-368E-46E9-8D60-72AC9EDBA107}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AbsInjectTypeDll", "AbsInjectTypeDll\AbsInjectTypeDll.csproj", "{FFC7FCC9-C9A6-4896-8807-670D52C725AD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PolyServer", "PolyServer\PolyServer.csproj", "{1F2010FA-8685-4FB4-85FA-48A29D9EF74B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Service.Shared", "Service.Shared\Service.Shared.csproj", "{EF0D7F0D-4BB8-41D7-95BC-E3066FEF6341}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PolyClient", "PolyClient\PolyClient.csproj", "{43F43EB2-326E-40F5-9678-EC1402949286}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,15 +32,51 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F7E1E83F-3593-4946-8DFB-9E8E1EAEC6E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F7E1E83F-3593-4946-8DFB-9E8E1EAEC6E7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F7E1E83F-3593-4946-8DFB-9E8E1EAEC6E7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F7E1E83F-3593-4946-8DFB-9E8E1EAEC6E7}.Release|Any CPU.Build.0 = Release|Any CPU + {8DF39770-8DBD-4647-A73D-7B362CD07CBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DF39770-8DBD-4647-A73D-7B362CD07CBA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DF39770-8DBD-4647-A73D-7B362CD07CBA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DF39770-8DBD-4647-A73D-7B362CD07CBA}.Release|Any CPU.Build.0 = Release|Any CPU + {A1C98719-901D-43FA-9FE8-F80441835D4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1C98719-901D-43FA-9FE8-F80441835D4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1C98719-901D-43FA-9FE8-F80441835D4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1C98719-901D-43FA-9FE8-F80441835D4A}.Release|Any CPU.Build.0 = Release|Any CPU + {D0E6414F-4F35-41E9-A10B-AF0042E9D0EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0E6414F-4F35-41E9-A10B-AF0042E9D0EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0E6414F-4F35-41E9-A10B-AF0042E9D0EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0E6414F-4F35-41E9-A10B-AF0042E9D0EB}.Release|Any CPU.Build.0 = Release|Any CPU + {8CF34B2D-452A-4C0D-BFDC-10789FA3E145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CF34B2D-452A-4C0D-BFDC-10789FA3E145}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CF34B2D-452A-4C0D-BFDC-10789FA3E145}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CF34B2D-452A-4C0D-BFDC-10789FA3E145}.Release|Any CPU.Build.0 = Release|Any CPU + {B9E1906C-3F9D-49AB-8B9A-A7413B513BFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9E1906C-3F9D-49AB-8B9A-A7413B513BFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9E1906C-3F9D-49AB-8B9A-A7413B513BFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9E1906C-3F9D-49AB-8B9A-A7413B513BFC}.Release|Any CPU.Build.0 = Release|Any CPU + {863134AE-368E-46E9-8D60-72AC9EDBA107}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {863134AE-368E-46E9-8D60-72AC9EDBA107}.Debug|Any CPU.Build.0 = Debug|Any CPU + {863134AE-368E-46E9-8D60-72AC9EDBA107}.Release|Any CPU.ActiveCfg = Release|Any CPU + {863134AE-368E-46E9-8D60-72AC9EDBA107}.Release|Any CPU.Build.0 = Release|Any CPU + {FFC7FCC9-C9A6-4896-8807-670D52C725AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFC7FCC9-C9A6-4896-8807-670D52C725AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFC7FCC9-C9A6-4896-8807-670D52C725AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFC7FCC9-C9A6-4896-8807-670D52C725AD}.Release|Any CPU.Build.0 = Release|Any CPU + {1F2010FA-8685-4FB4-85FA-48A29D9EF74B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F2010FA-8685-4FB4-85FA-48A29D9EF74B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F2010FA-8685-4FB4-85FA-48A29D9EF74B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F2010FA-8685-4FB4-85FA-48A29D9EF74B}.Release|Any CPU.Build.0 = Release|Any CPU + {EF0D7F0D-4BB8-41D7-95BC-E3066FEF6341}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF0D7F0D-4BB8-41D7-95BC-E3066FEF6341}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF0D7F0D-4BB8-41D7-95BC-E3066FEF6341}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF0D7F0D-4BB8-41D7-95BC-E3066FEF6341}.Release|Any CPU.Build.0 = Release|Any CPU + {43F43EB2-326E-40F5-9678-EC1402949286}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43F43EB2-326E-40F5-9678-EC1402949286}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43F43EB2-326E-40F5-9678-EC1402949286}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43F43EB2-326E-40F5-9678-EC1402949286}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1641AF2D-0E2D-439C-9A01-0E20BAB57CA9} + SolutionGuid = {01350A72-C58D-4348-80AB-F78490469B60} EndGlobalSection EndGlobal diff --git a/PolymorphicMessagePack/MagicOnionPolyMsgPackFormatter.cs b/PolymorphicMessagePack/MagicOnionPolyMsgPackFormatter.cs new file mode 100644 index 0000000..f53c5ce --- /dev/null +++ b/PolymorphicMessagePack/MagicOnionPolyMsgPackFormatter.cs @@ -0,0 +1,66 @@ +using Grpc.Core; +using MagicOnion.Serialization; +using MessagePack; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace PolymorphicMessagePack +{ + public class MagicOnionPolyMsgPackSerializerProvider : IMagicOnionSerializerProvider + { + private class MessagePackMagicOnionSerializer : IMagicOnionSerializer + { + private readonly PolymorphicMessagePackSerializerOptions serializerOptions; + + public MessagePackMagicOnionSerializer(PolymorphicMessagePackSerializerOptions serializerOptions) + { + this.serializerOptions = serializerOptions; + } + + public T Deserialize(in ReadOnlySequence bytes) + { + return MessagePackSerializer.Deserialize(in bytes, serializerOptions); + } + + public void Serialize(IBufferWriter writer, in T value) + { + //we use origin value type instand of fact require type[maybe abstract or interface] + //ignore valueType + if (value != null && (typeof(T).IsClass||typeof(T).IsInterface)) + { + MessagePackSerializer.Serialize(value.GetType(), writer, value, serializerOptions); + } + else + { + MessagePackSerializer.Serialize(writer, value); + } + + } + + void IMagicOnionSerializer.Serialize(IBufferWriter writer, in T value) + { + Serialize(writer, in value); + } + + T IMagicOnionSerializer.Deserialize(in ReadOnlySequence bytes) + { + return Deserialize(in bytes); + } + } + + protected PolymorphicMessagePackSerializerOptions SerializerOptions { get; } + + public MagicOnionPolyMsgPackSerializerProvider(PolymorphicMessagePackSerializerOptions serializerOptions) + { + SerializerOptions = serializerOptions; + } + + public IMagicOnionSerializer Create(MethodType methodType, MethodInfo methodInfo) + { + return new MessagePackMagicOnionSerializer(SerializerOptions); + } + } +} diff --git a/PolymorphicMessagePack/PolymorphicDelegate.cs b/PolymorphicMessagePack/PolymorphicDelegate.cs deleted file mode 100644 index 9e851d6..0000000 --- a/PolymorphicMessagePack/PolymorphicDelegate.cs +++ /dev/null @@ -1,45 +0,0 @@ -using MessagePack; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PolymorphicMessagePack -{ - //I created this, because I have no clue how to create a delegate (MethodInfo.Invoke is too slow), that uses the generic parameter of the class (As opposed to a generic method that has it's own parameter 'T'). - //Is this the only way? - internal abstract class PolymorphicDelegate - { - public abstract void Serialize(ref MessagePackWriter writer, object value, MessagePackSerializerOptions options); - public abstract object Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options); - - } - - internal class PolymorphicDelegate : PolymorphicDelegate - { - private delegate void SerializeDelegate(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options); - private delegate T DeserializeDelegate(ref MessagePackReader reader, MessagePackSerializerOptions options); - - private SerializeDelegate _serializeDelegate; - private DeserializeDelegate _deserializeDelegate; - - public PolymorphicDelegate(IFormatterResolver resolver) - { - var formatter = resolver.GetFormatter(); - - _serializeDelegate = formatter.Serialize; - _deserializeDelegate = formatter.Deserialize; - } - - public override void Serialize(ref MessagePackWriter writer, object value, MessagePackSerializerOptions options) - { - _serializeDelegate.Invoke(ref writer, (T)value, options); - } - - public override object Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) - { - return _deserializeDelegate.Invoke(ref reader, options); - } - } -} diff --git a/PolymorphicMessagePack/PolymorphicFormatter.cs b/PolymorphicMessagePack/PolymorphicFormatter.cs index f5386f4..323bb30 100644 --- a/PolymorphicMessagePack/PolymorphicFormatter.cs +++ b/PolymorphicMessagePack/PolymorphicFormatter.cs @@ -1,23 +1,85 @@ using MessagePack; using MessagePack.Formatters; -using MessagePack.Resolvers; using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; namespace PolymorphicMessagePack { + internal interface IMessagePackDeserializeToObject + { + object Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options); + } - public class PolymorphicFormatter : IMessagePackFormatter + public class PolymorphicFormatter : IMessagePackFormatter, IMessagePackDeserializeToObject { - private object lockObj = new object(); - public PolymorphicFormatter() + private readonly IMessagePackFormatter _formater; + + public PolymorphicFormatter() { } + + public PolymorphicFormatter(IFormatterResolver resolver) { + _formater = resolver.GetFormatter(); + //target method fact belong to instance + //var instance = Expression.Constant(formatter); + + ////get method info+it's params + //var methodInfo = resolver.GetType().GetMethod("Serialize"); + //var methodTypeParams = methodInfo.GetParameters().Select(m => m.ParameterType); + //var delegateInfo = typeof(SerializeDelegate).GetMethod("Invoke"); + //var delegateTypeParams = delegateInfo.GetParameters().Select(m => m.ParameterType); + //var delegateArguments = delegateTypeParams.Select(Expression.Parameter).ToArray(); + + ////make a transform for delegate to Expr.parameter wrong types + //var convertedArguments = methodTypeParams.Zip( + // delegateTypeParams, delegateArguments, + // (methodType, delegateType, delegateArgument) => + // methodType != delegateType + // ? (Expression)Expression.Convert(delegateArgument, methodType) + // : delegateArgument); + ////make call + //MethodCallExpression methodCall = Expression.Call( + // instance, + // methodInfo, + // convertedArguments + // ); + ////transform delegate call to instance call-with return type convert + //Expression convertedMethodCall = delegateInfo.ReturnType == methodInfo.ReturnType + // ? (Expression)methodCall + //: Expression.Convert(methodCall, delegateInfo.ReturnType); + + + ////End + //_serializeDelegate = Expression.Lambda( + // convertedMethodCall, + // delegateArguments + // ).Compile(); + + ////For Deserialize,Do samething + //methodInfo = resolver.GetType().GetMethod("Deserialize"); + //methodTypeParams = methodInfo.GetParameters().Select(m => m.ParameterType); + //delegateInfo = typeof(DeserializeDelegate).GetMethod("Invoke"); + //delegateTypeParams = delegateInfo.GetParameters().Select(m => m.ParameterType); + //delegateArguments = delegateTypeParams.Select(Expression.Parameter).ToArray(); + + //convertedArguments = methodTypeParams.Zip( + // delegateTypeParams, delegateArguments, + // (methodType, delegateType, delegateArgument) => + // methodType != delegateType + // ? (Expression)Expression.Convert(delegateArgument, methodType) + // : delegateArgument); + //methodCall = Expression.Call( + // instance, + // methodInfo, + // convertedArguments + // ); + //convertedMethodCall = delegateInfo.ReturnType == methodInfo.ReturnType + // ? (Expression)methodCall + //: Expression.Convert(methodCall, delegateInfo.ReturnType); + + //_deserializeDelegate = Expression.Lambda( + // convertedMethodCall, + // delegateArguments + // ).Compile(); } public void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options) @@ -31,18 +93,17 @@ public void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializ //Could remove this if the settings were part of the regular options if (!(options is PolymorphicMessagePackSerializerOptions polyOptions)) - throw new ArgumentException($"You cannot use a { nameof(PolymorphicResolver) } without also using { nameof(PolymorphicMessagePackSerializerOptions) }", nameof(options)); + throw new ArgumentException($"You cannot use a {nameof(PolymorphicResolver)} without also using {nameof(PolymorphicMessagePackSerializerOptions)}", nameof(options)); var actualtype = value.GetType(); if (!polyOptions.PolymorphicSettings.TypeToId.TryGetValue(actualtype, out var typeId)) - throw new MessagePackSerializationException($"Type '{ actualtype.FullName }' is not registered in { nameof(PolymorphicMessagePackSerializerOptions) }"); + throw new MessagePackSerializationException($"Type '{actualtype.FullName}' is not registered in {nameof(PolymorphicMessagePackSerializerOptions)}"); writer.WriteArrayHeader(2); - writer.WriteInt32(typeId); + writer.WriteUInt32(typeId); - //Bottleneck - polyOptions.PolymorphicResolver.InnerSerialize(actualtype, ref writer, value, options); + _formater.Serialize(ref writer, value, options); } public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) @@ -54,7 +115,7 @@ public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions //Could remove this if the settings were part of the regular options if (!(options is PolymorphicMessagePackSerializerOptions polyOptions)) - throw new ArgumentException($"You cannot use a { nameof(PolymorphicResolver) } without also using { nameof(PolymorphicMessagePackSerializerOptions) }", nameof(options)); + throw new ArgumentException($"You cannot use a {nameof(PolymorphicResolver)} without also using {nameof(PolymorphicMessagePackSerializerOptions)}", nameof(options)); options.Security.DepthStep(ref reader); @@ -65,13 +126,13 @@ public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions if (count != 2) throw new MessagePackSerializationException("Invalid polymorphic array count"); - var typeId = reader.ReadInt32(); + var typeId = reader.ReadUInt32(); if (!polyOptions.PolymorphicSettings.IdToType.TryGetValue(typeId, out var type)) - throw new MessagePackSerializationException($"Cannot find Type Id: { typeId } registered in { nameof(PolymorphicMessagePackSerializerOptions) }"); + throw new MessagePackSerializationException($"Cannot find Type Id: {typeId} registered in {nameof(PolymorphicMessagePackSerializerOptions)}"); //Bottleneck - return (T)polyOptions.PolymorphicResolver.InnerDeserialize(type, ref reader, options); + return polyOptions.PolymorphicResolver.InnerDeserialize(type, ref reader, options); } finally { @@ -80,6 +141,13 @@ public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions } + object IMessagePackDeserializeToObject.Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) + { + var result = _formater.Deserialize(ref reader, options); + if (result is K fact) + return fact; + return default; + } } } \ No newline at end of file diff --git a/PolymorphicMessagePack/PolymorphicMessagePackSerializerOptions.cs b/PolymorphicMessagePack/PolymorphicMessagePackSerializerOptions.cs index 54bb189..cb09f0f 100644 --- a/PolymorphicMessagePack/PolymorphicMessagePackSerializerOptions.cs +++ b/PolymorphicMessagePack/PolymorphicMessagePackSerializerOptions.cs @@ -1,9 +1,4 @@ using MessagePack; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace PolymorphicMessagePack { @@ -11,16 +6,19 @@ namespace PolymorphicMessagePack public class PolymorphicMessagePackSerializerOptions : MessagePackSerializerOptions { + internal readonly PolymorphicMessagePackSettings PolymorphicSettings; internal readonly PolymorphicResolver PolymorphicResolver; - public PolymorphicMessagePackSerializerOptions(PolymorphicMessagePackSettings polymorphicSettings) : base(new PolymorphicResolver(polymorphicSettings)) + public PolymorphicMessagePackSerializerOptions(PolymorphicMessagePackSettings polymorphicSettings) + : base(new PolymorphicResolver(polymorphicSettings)) { PolymorphicSettings = polymorphicSettings; PolymorphicResolver = Resolver as PolymorphicResolver; } - protected PolymorphicMessagePackSerializerOptions(PolymorphicMessagePackSerializerOptions copyFrom) : base(copyFrom) + protected PolymorphicMessagePackSerializerOptions(PolymorphicMessagePackSerializerOptions copyFrom) + : base(copyFrom) { PolymorphicSettings = copyFrom.PolymorphicSettings; } @@ -29,6 +27,5 @@ protected override MessagePackSerializerOptions Clone() { return new PolymorphicMessagePackSerializerOptions(this); } - } } diff --git a/PolymorphicMessagePack/PolymorphicMessagePackSettings.cs b/PolymorphicMessagePack/PolymorphicMessagePackSettings.cs index 81985b8..83a3c4f 100644 --- a/PolymorphicMessagePack/PolymorphicMessagePackSettings.cs +++ b/PolymorphicMessagePack/PolymorphicMessagePackSettings.cs @@ -1,20 +1,123 @@ using MessagePack; +using MessagePack.Resolvers; +using ShareAttributes; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Reflection; namespace PolymorphicMessagePack { + + internal static class GetMarkAttributeClassListExtension + { + private static readonly Type _objType = typeof(object); + public static (Assembly, HashSet) GetMarkUnionAbsAttributeClasses(this Assembly assembly) + { + var markdata = new HashSet(assembly.GetTypes().Where(x => (x.IsAbstract || x.IsInterface) && x.GetCustomAttribute(false) != null)); + return (assembly, markdata); + } + + public static Dictionary> GetAbsDriveClassTypes(this HashSet types,Assembly target) + { + var require_search_types = target.GetTypes().Where(x => + //is abstract,is interface,not class,is object,in marked types,is generic are ignore + !x.IsAbstract && !x.IsInterface && x.IsClass && x != _objType && !types.Contains(x) && !x.IsGenericType + ); + + return AnalysisTypes(require_search_types, types); + } + + public static Dictionary> GetAbsDriveGenericClassTypes(this HashSet types, Assembly target) + { + var require_search_types = target.GetTypes().Where(x => + //is abstract,is interface,not class,is object,in marked types,not generic are ignore + !x.IsAbstract && !x.IsInterface && x.IsClass && x != _objType && !types.Contains(x) && x.IsGenericType + ); + + return AnalysisTypes(require_search_types, types); + } + + private static Dictionary> AnalysisTypes(IEnumerable types, HashSet exceptTypes) + { + Dictionary> cache = new Dictionary>(); + HashSet visitedScanInterfaceTypes = new HashSet(); + + foreach (var type in types) + { + Type temp = type; + while (temp != null && temp != _objType) + { + var factCheckType = temp.BaseType; + //if base type is marked abs + //is generic base? + if (temp.BaseType.IsGenericType) + factCheckType = temp.BaseType.GetGenericTypeDefinition(); + if (exceptTypes.Contains(factCheckType)) + { + if (!cache.TryGetValue(factCheckType, out var list)) + { + list = new List() { type }; + cache.Add(factCheckType, list); + } + else + list.Add(type); + } + + if (!temp.IsAbstract && !visitedScanInterfaceTypes.Contains(temp)) + { + visitedScanInterfaceTypes.Add(temp); + foreach (var @interface in temp.GetInterfaces()) + { + //if any interface in marked interface + //is generic base? + factCheckType = @interface; + if (@interface.IsGenericType) + factCheckType = @interface.GetGenericTypeDefinition(); + if (exceptTypes.Contains(factCheckType)) + { + if (!cache.TryGetValue(factCheckType, out var list)) + { + list = new List() { type }; + cache.Add(factCheckType, list); + } + else + { + list.Add(type); + } + } + } + } + temp = temp.BaseType; + } + } + + return cache; + } + } + public class PolymorphicMessagePackSettings { - internal readonly Dictionary TypeToId = new(); - internal readonly Dictionary IdToType = new(); - internal readonly HashSet BaseTypes = new(); + internal readonly Dictionary TypeToId = new Dictionary(); + internal readonly Dictionary IdToType = new Dictionary(); + internal readonly HashSet BaseTypes = new HashSet(); + internal readonly HashSet GenericTypes = new HashSet(); + internal readonly HashSet Assemblies = new HashSet(); internal IFormatterResolver InnerResolver; internal Type InnerResolverType; + /// + /// Use MsgPack StandardResolver + /// + public PolymorphicMessagePackSettings() + { + InnerResolver = StandardResolver.Instance; + InnerResolverType = InnerResolver.GetType(); + } + /// + /// Use your own resolver + /// + /// public PolymorphicMessagePackSettings(IFormatterResolver innerResolver) { InnerResolver = innerResolver; @@ -23,37 +126,95 @@ public PolymorphicMessagePackSettings(IFormatterResolver innerResolver) public bool SerializeOnlyRegisteredTypes { get; set; } = false; - public void RegisterType(int typeId) + /// + /// + /// + /// abstract class or interface + /// target class + /// + /// + public void RegisterType(uint typeId) where B : class where T : class, B { if (typeof(T).IsInterface || typeof(T).IsAbstract) - throw new ArgumentException($"Failed to register derived type '{ typeof(T).FullName }'. It cannot be an interface or an abstract class.", nameof(T)); + throw new ArgumentException($"Failed to register derived type '{typeof(T).FullName}'. It cannot be an interface or an abstract class.", nameof(T)); if (typeof(T).ContainsGenericParameters) - throw new ArgumentException($"Failed to register derived type '{ typeof(T).FullName }'. It cannot have open generic parameters. You must replace the open generic parameters with specific types.", nameof(T)); + throw new ArgumentException($"Failed to register derived type '{typeof(T).FullName}'. It cannot have open generic parameters. You must replace the open generic parameters with specific types.", nameof(T)); if (TypeToId.TryGetValue(typeof(T), out var currentId) && currentId != typeId) - throw new ArgumentException($"Failed to register derived type '{ typeof(T).FullName }'. Type '{ typeof(T).FullName }' is already registered to Type Id: { currentId }", nameof(T)); + throw new ArgumentException($"Failed to register derived type '{typeof(T).FullName}'. Type '{typeof(T).FullName}' is already registered to Type Id: {currentId}", nameof(T)); if (IdToType.TryGetValue(typeId, out var currentType) && currentType != typeof(T)) - throw new ArgumentException($"Failed to register derived type '{ typeof(T).FullName }'. Type Id: { typeId } is already registered to another type '{ currentType.FullName }'", nameof(typeId)); + throw new ArgumentException($"Failed to register derived type '{typeof(T).FullName}'. Type Id: {typeId} is already registered to another type '{currentType.FullName}'", nameof(typeId)); //Use TryAdd, becasue the type could already exist and the user is simply trying to add another base class - TypeToId.TryAdd(typeof(T), typeId); - IdToType.TryAdd(typeId, typeof(T)); + TypeToId.Add(typeof(T), typeId); + IdToType.Add(typeId, typeof(T)); BaseTypes.Add(typeof(B)); } + public void InjectUnionRequireFromAssembly(Assembly assembly,Assembly absClassAssembly) + { + if (Assemblies.Contains(assembly) && Assemblies.Contains(absClassAssembly)) + return; + Assemblies.Add(assembly); + Assemblies.Add(absClassAssembly); + //get all mark require union abs/interface + var markedUnionRequireAbsOrInterfaces = absClassAssembly.GetMarkUnionAbsAttributeClasses(); + + //get all drive abs/interface non generic classes + var allNeedMapNonGenericClasses = markedUnionRequireAbsOrInterfaces.Item2.GetAbsDriveClassTypes(assembly); + + //register them,record relate abs and interface + foreach (var pair in allNeedMapNonGenericClasses) + { + BaseTypes.Add(pair.Key); + foreach (var pair2 in pair.Value) + { + if (TypeToId.ContainsKey(pair2)) + continue; + var unionIdAttribute = pair2.GetCustomAttribute(false); + var pairAttr = pair2.CustomAttributes.First(); + if (unionIdAttribute == null) + throw new ArgumentException(message: $"Shouldn't Happened---{pair2.FullName} not set RequireUnionAttribute but has been scaned"); + if (IdToType.TryGetValue(unionIdAttribute.UnionUniqueId, out var existMarkType)) + throw new ArgumentException(message: $"{pair2.FullName} Set union unique Id {unionIdAttribute.UnionUniqueId},but it already been used for {existMarkType.FullName}"); - //TODO: convenience method - //public void RegisterTypeWithAllInterfacesAndBase(int typeId, bool includeObject = false) - // where T : class - //{ - - //} + TypeToId.Add(pair2, unionIdAttribute.UnionUniqueId); + IdToType.Add(unionIdAttribute.UnionUniqueId, pair2); + } + } - //TODO: What is the user needs to register 10,000 types? perhaps a way to do entire namspaaces with auto-numbering, if you aren't storing messages? + //get all drive abs/interface generic classes + var allNeedRecordGenericClasses = markedUnionRequireAbsOrInterfaces.Item2.GetAbsDriveGenericClassTypes(assembly); + //record all need prepare generic type class type + foreach (var pair in allNeedRecordGenericClasses) + { + BaseTypes.Add(pair.Key); + foreach (var pair2 in pair.Value) + { + var unionGenericAttributes = pair2.GetCustomAttributes(false); + //truth require register type + foreach (var factUsedRuntimeGenericVersion in unionGenericAttributes) + { + var fType = factUsedRuntimeGenericVersion.SupportGenericType; + if (fType.IsGenericType && fType.GetGenericTypeDefinition() == pair2) + { + if (TypeToId.ContainsKey(fType)) + continue; + if (IdToType.TryGetValue(factUsedRuntimeGenericVersion.UnionUniqueId, out var existMarkType)) + throw new ArgumentException(message: $"{fType.FullName} Set union unique Id {factUsedRuntimeGenericVersion.UnionUniqueId},but it already been used for {existMarkType.FullName}"); + TypeToId.Add(fType, factUsedRuntimeGenericVersion.UnionUniqueId); + IdToType.Add(factUsedRuntimeGenericVersion.UnionUniqueId, fType); + } + else + throw new ArgumentException(message: $"{fType.FullName} is not genericType or not generic by {pair2.FullName}"); + } + } + } + } } } diff --git a/PolymorphicMessagePack/PolymorphicResolver.cs b/PolymorphicMessagePack/PolymorphicResolver.cs index 1eccdac..4be8fff 100644 --- a/PolymorphicMessagePack/PolymorphicResolver.cs +++ b/PolymorphicMessagePack/PolymorphicResolver.cs @@ -1,25 +1,20 @@ using MessagePack; using MessagePack.Formatters; -using MessagePack.Resolvers; using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace PolymorphicMessagePack { internal sealed class PolymorphicResolver : IFormatterResolver { - private PolymorphicMessagePackSettings _polymorphicSettings; - private readonly ConcurrentDictionary _innerFormatterCache; + + private readonly PolymorphicMessagePackSettings _polymorphicSettings; + private readonly ConcurrentDictionary _innerDeserializeFormatterCache = new ConcurrentDictionary(); public PolymorphicResolver(PolymorphicMessagePackSettings polymorphicSettings) { _polymorphicSettings = polymorphicSettings; - _innerFormatterCache = new(); } public IMessagePackFormatter GetFormatter() @@ -31,56 +26,49 @@ public IMessagePackFormatter GetFormatter() //If i had the object to be serialized or its actual type, I could make this a lot more efficient and remove the need for the Polymorphic delegate. //Can something be optimized here? - if (_polymorphicSettings.BaseTypes.Contains(typeof(T)) || - _polymorphicSettings.TypeToId.ContainsKey(typeof(T))) + var inType = typeof(T); + if (_polymorphicSettings.BaseTypes.Contains(inType) || _polymorphicSettings.TypeToId.ContainsKey(inType)) { - return FormatterCache.Formatter; + if (_innerDeserializeFormatterCache.TryGetValue(inType, out var formatter)) + return (IMessagePackFormatter)formatter; + else + { + var targetTypeFormatter = new PolymorphicFormatter(_polymorphicSettings.InnerResolver); + _innerDeserializeFormatterCache.TryAdd(inType, targetTypeFormatter); + return targetTypeFormatter; + } } - else if (_polymorphicSettings.SerializeOnlyRegisteredTypes) + //generic type won't contain in settings,scan generic types + else if (inType.IsGenericType && _polymorphicSettings.GenericTypes.Contains(inType.GetGenericTypeDefinition())) { - throw new MessagePackSerializationException($"Type '{ typeof(T).FullName }' is not registered in the { nameof(PolymorphicMessagePackSettings) } and { nameof(PolymorphicMessagePackSettings.SerializeOnlyRegisteredTypes) } is set to true"); + //Nice,this generic with generic param is marked that has union require abs/interface and not registered,register it and create formatter + var targetTypeFormatter = new PolymorphicFormatter(_polymorphicSettings.InnerResolver); + _innerDeserializeFormatterCache.TryAdd(inType, targetTypeFormatter); + //get max id which current used + var avilableId = _polymorphicSettings.IdToType.Keys.Max() + 1; + _polymorphicSettings.TypeToId.Add(inType, avilableId); + _polymorphicSettings.IdToType.Add(avilableId, inType); + return targetTypeFormatter; } + else if (_polymorphicSettings.SerializeOnlyRegisteredTypes) + throw new MessagePackSerializationException($"Type '{inType.FullName}' is not registered in the {nameof(PolymorphicMessagePackSettings)} and {nameof(PolymorphicMessagePackSettings.SerializeOnlyRegisteredTypes)} is set to true"); + //Use oher formatter return _polymorphicSettings.InnerResolver.GetFormatter(); } - //Bottleneck - public void InnerSerialize(Type type, ref MessagePackWriter writer, object value, MessagePackSerializerOptions options) - { - GetDelegate(type).Serialize(ref writer, value, options); - } - - //Bottleneck - public object InnerDeserialize(Type type, ref MessagePackReader reader, MessagePackSerializerOptions options) + internal T InnerDeserialize(Type truthType, ref MessagePackReader reader, MessagePackSerializerOptions options) { - return GetDelegate(type).Deserialize(ref reader, options); - } - - private PolymorphicDelegate GetDelegate(Type type) - { - if (!_innerFormatterCache.TryGetValue(type, out var ploymorphicDeletegate)) + if (_innerDeserializeFormatterCache.TryGetValue(truthType, out var ploymorphicFormatter)) + return (T)ploymorphicFormatter.Deserialize(ref reader, options) ?? default; + else { - var constructedType = typeof(PolymorphicDelegate<>).MakeGenericType(type); - - ploymorphicDeletegate = (PolymorphicDelegate)Activator.CreateInstance(constructedType, _polymorphicSettings.InnerResolver); - - _innerFormatterCache.TryAdd(type, ploymorphicDeletegate); + var constructedType = typeof(PolymorphicFormatter<>).MakeGenericType(truthType); + var instance = (IMessagePackDeserializeToObject)Activator.CreateInstance(constructedType, _polymorphicSettings.InnerResolver); + _innerDeserializeFormatterCache.TryAdd(truthType, instance); + return (T)instance.Deserialize(ref reader, options) ?? default; } - - return ploymorphicDeletegate; } - - private static class FormatterCache - { - public static IMessagePackFormatter Formatter; - - static FormatterCache() - { - Formatter = new PolymorphicFormatter(); - } - - } - } } \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..99fb713 --- /dev/null +++ b/README.md @@ -0,0 +1,177 @@ +# AutoPolymorphicMessagePack +Auto Union Scanner & Auto Key Generator For [MessagePack-CSharp](https://github.com/neuecc/MessagePack-CSharp) + +Let you use more easy way to + + 1. union messagepack object + + 2. auto mark [Key(x)] to public or private + +## How To Use +Mark which abstract class or interface you want to union for (e.g.these class are in `Project1` project range) + +```C# + //mark abstract class + [UnionAbsOrInterface] + public abstract class CBase1 + { + + } + //mark interface + [UnionAbsOrInterface] + public interface IBase1 + { + + } + // generic interface or abstract also support + [UnionAbsOrInterface] + public interface IBase4 + { + + } +``` + +AutoPolymorphicMessagePack use Fody Plugin to weave in automatically at compile time,I haven't publish nuget package yet, +so you need add [PolymorphicMessagePack.Fody](https://github.com/PatchouliTC/PolymorphicMessagePack/tree/master/PolymorphicMessagePack.Fody) into your project + +_For more details you can see [in-solution-weaving](https://github.com/Fody/Home/blob/master/pages/in-solution-weaving.md)_ + +Then set your `Project1` follow these steps: + + 1. import [Fody Nuget Package](https://www.nuget.org/packages/Fody) into `Project1` + + **Don't worry about this package,it won't be your project reference when you compile `Project1`** + + 2. add an entry to the `Project1.csproj` file: + +```xml + + + +``` + + 3. Change the [solution build order](https://docs.microsoft.com/en-au/visualstudio/ide/how-to-create-and-remove-project-dependencies) so the `Project1` project is built after the fody projects consuming it. + 4. Compile `Project1`,Fody will generate `FodyWeavers.xml` into project when not found that file + + then write config into `FodyWeavers.xml`: + +```xml + + + +``` + 5. prepare your msgpack marked object into `Project1`: + +```C# + //if non generic type,you need do nothing,Fody will add mark and unique id attr into it + [MessagePackObject] + public class Class1 : CBase1 + { + [Key(0)] + public long CT1 { get; set; } + } + + // if generic object,all actual generic types to be used must be declared + [GenericUnion(typeof(int))] + [GenericUnion(typeof(string))] + [MessagePackObject] + public class Class2 : IBase4 + { + [Key(0)] + public long CT2 { get; set; } + } + + //if you want to fixed union id manually,mark it,fody will ignore this type and avoid use fixed id to mark other types + [RequireUnion(1)] + [MessagePackObject] + public class Class3 : CBase1 + { + [Key(0)] + public long CT1 { get; set; } + } + + //also work for generic type,but you must make sure type in [RequireUnionGeneric] is current generic type or fody will give complie error + [RequireUnionGeneric(10,typeof(Class4))] + [GenericUnion(typeof(string))] + [MessagePackObject] + public class Class4 : IBase4 + { + [Key(0)] + public long CT2 { get; set; } + } +``` + 6. using and inject `Project1` assembly into `PolymorphicMessagePackSettings` and use it + +```C# + var polySettings = new PolymorphicMessagePackSettings(); + polySettings.InjectUnionRequireFromAssembly(typeof(Project1NameSpaceWhichContainMsgPackMarkedClass).Assembly); + + //serialize fact instance + var s1 = MessagePackSerializer.Serialize(new Class1 { CT1 = 1 }, _options); + + //deserialize it to abstract + var ds1 = MessagePackSerializer.Deserialize(s1, _options); + + //serialize marked generic also allowed + var s2 = MessagePackSerializer.Serialize(new Class2 { CT2 = 2 }, _options); + + //deserialize it to generic interface + var ds2 = MessagePackSerializer.Deserialize>(s2, _options); +``` + +_You can see more in [PolyMsgPack.Test](https://github.com/PatchouliTC/PolymorphicMessagePack/tree/master/PolymorphicMessagePack.Fody),[MsgPackDefineForInject](https://github.com/PatchouliTC/PolymorphicMessagePack/tree/master/MsgPackDefineForInject) and use `ILSpy` to see how it works_ + +If you want to enable auto Key generate ,set `FodyWeavers.xml` with `AutoMsgPackKeyWeaver` + +```xml + + + +``` + +Auto Key generate will scan target assembly,find all mark [MessagePackObject] type,and get their base type relate tree,then follow config to add unique Key id ([Key(int x)]) as much as possible to every not manual marked fields/Props + + +`NameSpace` : which assembly to scan + +`AlsoMarkPrivateField` : if set with `true`,then all private/internal field/prop will be select and try to add key + +`MarkIgnoreToFieldForNonMsgPackBaseType` :if target type base type is not mark [MessagePackObject],then all of it's public(private/internal if AlsoMarkPrivateField enabled) will be mark [IgnoreMember] + +`AutoMsgPackKeyWeaver` will do these step: + + 1. only scan mark with [MessagePackObject] or [DataContract] classes + + 2. will ignore all manual mark with [IgnoreMember] or [IgnoreDataMember] or [Key(string name)] or [DataContract] fields/props + + 3. will follow config, add key to target fields/props which not manual mark with [Key(int x)] or [DataContract(Order=int)] + + 4. will check every field/prop,if it has both [Key(int)] and [DataContract[Order=int]],will cause complie error + + 5. will check target class Accessibility,only public class can mark [MessageObject] + + 6. will check type and base type any fields/props key id conflict [e.g. B->A,both use [key(1)],will cause complie error] + +also you can enable both + +```xml + + + + +``` + +## Note + + 1. This tool will automatic **give each derivedType unique id** to distinguish which type is when deserialize + + Also you can use `[RequireUnion(1)]`(For non generic) or `[RequireUnionGeneric(2,typeof(Class2))]`(For generic) to manual fixed id + + **it can Fixed target type match id and won't change id in diff version** + 2. Fody plugin also will Check each fixed id,If the same Id is pointed to a different type, **it will prevent compilation and indicate the specific conflict type** + + 3. If you want to use _Fody.Test_ to see _PolymorphicMessagePack.Fody_ works,make sure change `MsgPackDefineForInject` project property: +```xml + true +``` diff --git a/Service.Shared/IMyFirstService.cs b/Service.Shared/IMyFirstService.cs new file mode 100644 index 0000000..3051dfc --- /dev/null +++ b/Service.Shared/IMyFirstService.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using AbsInjectTypeDll.DllSubAssembly; +using MagicOnion; +using MsgPackDefineForInject; + +namespace Service.Shared +{ + // Defines .NET interface as a Server/Client IDL. + // The interface is shared between server and client. + public interface IMyFirstService : IService + { + // The return type must be `UnaryResult` or `UnaryResult`. + UnaryResult SumAsync(int x, int y); + // `UnaryResult` does not have a return value like `Task`, `ValueTask`, or `void`. + UnaryResult DoWorkAsync(); + + UnaryResult GetTestData(int x); + } +} diff --git a/Service.Shared/Service.Shared.csproj b/Service.Shared/Service.Shared.csproj new file mode 100644 index 0000000..30f1733 --- /dev/null +++ b/Service.Shared/Service.Shared.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.1 + enable + + + + + + + + + + + + diff --git a/ShareAttributes/Attributes.cs b/ShareAttributes/Attributes.cs new file mode 100644 index 0000000..9270c4c --- /dev/null +++ b/ShareAttributes/Attributes.cs @@ -0,0 +1,63 @@ +using System; + +namespace ShareAttributes +{ + /// + /// Abstract/Interface Mark + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] + public class UnionAbsOrInterfaceAttribute : Attribute + { + + } + + /// + /// GenericClassMark + /// No need current generic type in param + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class GenericUnionAttribute : Attribute + { + public Type GenericUsageType { get; set; } + + public GenericUnionAttribute(Type genericUsageType) + { + GenericUsageType = genericUsageType; + } + } + + + /// + /// For non generic class,Mark it's union unique id + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public class RequireUnionAttribute : Attribute + { + public uint UnionUniqueId { get; private set; } + + public RequireUnionAttribute(uint unionUniqueId) + { + UnionUniqueId = unionUniqueId; + } + } + + /// + /// For generic class,Mark which generic types it will be used + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class RequireUnionGenericAttribute : RequireUnionAttribute + { + public Type SupportGenericType { get; private set; } + + /// + /// + /// + /// unique Id + /// which runtime generic will be used + public RequireUnionGenericAttribute(uint unionUniqueId, Type supportGeneric) + : base(unionUniqueId) + { + SupportGenericType = supportGeneric; + } + } +} diff --git a/ShareAttributes/ShareAttributes.csproj b/ShareAttributes/ShareAttributes.csproj new file mode 100644 index 0000000..9f5c4f4 --- /dev/null +++ b/ShareAttributes/ShareAttributes.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + +