📜 ⬆️ ⬇️

Mapping in C # on the example of the serializer for AMF

Greetings, friends. Today we will discuss the implementation of mapping in C #, as well as the use of this implementation in solving real problems on the example of sending AMF data to the server. All of the following does not pretend to any standards for the implementation of algorithms and patterns of code design, it is only a description of one of the many, not always obvious to beginners, solutions.

In the process of studying the article, you will learn how to implement your own attributes and how to apply them, get acquainted with the methods of type extensions and the application of reflection in practice, learn about the basics of MSIL in general and OpCodes in particular, as well as how you can serialize objects in AMF using threads .

Formulation of the problem


To begin with, let's make an approximate list of tasks that we will need to solve in this article. Let it look like this:


As part of this article, we will work with you with some kind of ready abstract server (in your case, this is a real working server, implemented without your participation). Suppose we know that the server-side is written in Flex, so we have already received a direct link to which we need to send an AMF object. To work directly with AMF itself, we have a wonderful FluorineFX framework, providing us with all the necessary tools to organize Flex support on both the server side and the client side. But the trouble is, when it comes to the very long-awaited moment of sending an object to the server, it turns out that FluorineFX does not provide convenient means for mapping, and all our objects sent to the server will fly with the original type metadata. In fact, this is absolutely not a problem if you are the author of the client- and server-pollack, but what about situations where you need to send data packets that have a custom structure? Repeating 1 to 1 namespaces and type names is not always convenient (and sometimes impossible at all). A little googling, we find quite a working solution nearby. Immediately consider its main advantages and disadvantages:
')
Pros:


Minuses:


For those whom the cons of this solution do not confuse, all the material described below may not be needed. For those who decided to go along with me to a detour and find a more flexible solution, the material below will be very interesting.

Work with attributes


In general, the approach described in the solution goes in the right direction. Mapping itself implies some kind of binding at the level of type metadata, for this we need attributes.

Let's first make a rough description of how our types should look like when preparing these for serialization in AMF. Provide the following features:


Let's start with the implementation of our attribute class. We can write a single attribute for object types and field types and object properties (similar to the solution ), but I recommend implementing separate attributes for types and member types, which will be useful in the future for cases when it is necessary to separate the metadata processing logic of different instance types. First, we implement the attribute class for object types:

/// <summary> ///      AMF. /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public class AmfObjectAttribute : Attribute { /// <summary> ///   . /// </summary> public string Name { get; set; } /// <summary> ///     <see cref="AmfObjectAttribute"/>. /// </summary> /// <param name="name">  .</param> public AmfObjectAttribute(string name) { Name = name; } /// <summary> ///     <see cref="AmfObjectAttribute"/>. /// </summary> public AmfObjectAttribute() : this(null) { } } 

A couple of key points in the implementation of attributes:


The AmfObjectAttribute attribute will be applied to object types. If this attribute is applied, the type name of the serialized object will match the value specified in the AmfObjectAttribute property . Name , if the property value is null - the serialized object will have the original type name. I deliberately did not make a check for the presence of an attribute on types in order to realize the possibility of serialization of type objects not marked with this attribute.

Now we implement the attribute class for the properties and fields of the types:

 /// <summary> ///          AMF. /// </summary> [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public class AmfMemberAttribute : Attribute { /// <summary> ///    . /// </summary> public string Name { get; set; } /// <summary> ///     <see cref="AmfMemberAttribute"/>. /// </summary> /// <param name="name">   .</param> public AmfMemberAttribute(string name) { Name = name; } /// <summary> ///     <see cref="AmfMemberAttribute"/>. /// </summary> public AmfMemberAttribute() : this(null) { } } 

With the implementation of the attributes figured out, now let's just write a simple class that describes a certain data model. In the future we will use it for tests:

 /// <summary> ///   AMF-  .         "namespase.of.your.object". /// </summary> [AmfObject("namespase.of.your.object")] public class CustomAmfObject { /// <summary> ///    <see cref="bool"/>.       "bit_prop". /// </summary> [AmfMember("bit_prop")] public bool BooleanProperty { get; set; } = true; /// <summary> ///    <see cref="sbyte"/>.       UnsignedByteProperty. /// </summary> [AmfMember] public sbyte UnsignedByteProperty { get; set; } = 2; /// <summary> ///    <see cref="string"/>.    . /// </summary> public string StringProperty { get; set; } = "test"; /// <summary> ///    <see cref="bool"/>.       "bit_fld". /// </summary> [AmfMember("bit_fld")] public bool booleanField = false; /// <summary> ///    <see cref="float"/>.       singleField. /// </summary> [AmfMember] public float singleField = -5.00065f; /// <summary> ///    <see cref="string"/>.    . /// </summary> public string stringField = "test2"; /// <summary> ///     <see cref="CustomAmfObject"/>. /// </summary> public CustomAmfObject() { } } 

Attributes are written, the test class is implemented. Now we need to start solving the problem of serialization itself. If we plan to support the .NET 2.0 version, then we need to implement a serialization class that will work with instances of objects and carry out various manipulations with the metadata of their types. We will write code with regard to the support of the .NET 3.5 version, because there appeared two very significant features for the C # engineer: LINQ and type extension methods. We will apply them to solve our problem.

Extension methods and reflection


To implement the type extension method, simply declare a public static class and add the this modifier to the first parameter. These conditions are mandatory, otherwise the compiler simply does not understand that the method is an extension of the type. After implementation, the method can be applied to any object whose type matches or follows from the type of the first parameter of the extension method. Create our own class of extension methods:

 /// <summary> ///       /  AMF. /// </summary> public static class Extensions { } 

First of all, we will need some auxiliary methods for code reuse. In .NET 4.5, the Attribute Type .GetCustomAttribute ( Type ) method appeared, which allows you to immediately get an attribute of a given type. In .NET 3.5 there is no such thing yet, so let's implement a couple of extension methods for convenient work with attributes:

 /// <summary> ///    . /// </summary> /// <typeparam name="T">  .</typeparam> /// <param name="sourceType">   .</param> /// <returns></returns> private static T GetAttribute<T>(this Type sourceType) where T : Attribute { object[] attributes = sourceType.GetCustomAttributes(typeof(T), true); //   . if (attributes == null || attributes.Length == 0) return default(T); //        -  null. return attributes[0] as T; } /// <summary> ///    . /// </summary> /// <typeparam name="T">  .</typeparam> /// <param name="sourceMember">   .</param> /// <returns></returns> private static T GetAttribute<T>(this MemberInfo sourceMember) where T : Attribute { object[] attributes = sourceMember.GetCustomAttributes(typeof(T), true); //   . if (attributes == null || attributes.Length == 0) return default(T); //        -  null. return attributes[0] as T; } /// <summary> /// ,     . /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="sourceType">  .</param> /// <returns></returns> private static bool IsDefinedAttribute<T>(this Type sourceType) { object[] attributes = sourceType.GetCustomAttributes(typeof(T), true); //   . return attributes != null && attributes.Length > 0; } 

At each serialization procedure, the types will be generated for the specified attributes. In order not to repeat a lot of operations with each call of the serialization method, let's immediately consider the possibility of storing metadata in a dynamic assembly after its creation:

 /// <summary> ///     . /// </summary> private static ModuleBuilder moduleBuilder; /// <summary> ///    <see cref="Extensions"/>. /// </summary> static Extensions() { AssemblyName assemblyName = new AssemblyName("AmfDynamicAssembly"); //     . AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); //   . moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll"); //      . } 

Now for all types we will have our own single dynamic assembly that will store their metadata. It's time to implement an algorithm for generating types based on our attribute data. First we will step through each step of the algorithm, then I will give a complete listing of the method.

The very concept of “reflection” (it is also “reflection”, it is also a “reflection”) is based on the manipulation of type metadata. Here, in fact, everything is simple. We need to repeat the process of creating the types described in the code by the compiler, but using our own code. For the basis, we will take the metadata of the type of source object known to us, as well as the data from the attributes, if any. We also need to implement a default constructor that initializes a reference to an instance of an object of the type being generated. We can perform all the operations we need using the ModuleBuilder.TypeBuilder class.

Determine the type using TypeBuilder :

 TypeBuilder typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public); 

Defining a field with FieldBuilder :

 FieldBuilder fieldBuilder = typeBuilder.DefineField($"m_{propertyName}", propertyType, FieldAttributes.Private); 

We define a property using PropertyBuilder . Here we need to define a private field, as well as an accessor and a mutator to access it:

 PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); //   . MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; //      . MethodBuilder methodBuilderAccessor = typeBuilder.DefineMethod($"get_{propertyName}", getSetAttr, propertyType, Type.EmptyTypes); //  . ILGenerator accessorIL = methodBuilderAccessor.GetILGenerator(); //     MSIL-  . accessorIL.Emit(OpCodes.Ldarg_0); //      . accessorIL.Emit(OpCodes.Ldfld, fieldBuilder); //           . accessorIL.Emit(OpCodes.Ret); //         . MethodBuilder methodBuilderSetter = typeBuilder.DefineMethod($"set_{propertyName}", getSetAttr, null, new Type[] { propertyType }); //  . ILGenerator setterIL = methodBuilderSetter.GetILGenerator(); //     MSIL-  . setterIL.Emit(OpCodes.Ldarg_0); //      . setterIL.Emit(OpCodes.Ldarg_1); //      . setterIL.Emit(OpCodes.Stfld, fieldBuilder); //           . setterIL.Emit(OpCodes.Ret); //         . propertyBuilder.SetGetMethod(methodBuilderAccessor); //   . propertyBuilder.SetSetMethod(methodBuilderSetter); //   . 

Define a default constructor using ConstructorBuilder :

 ConstructorBuilder ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes); //  . ILGenerator ctorIL = ctor.GetILGenerator(); //     MSIL-  . ctorIL.Emit(OpCodes.Ldarg_0); //      . ctorIL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)); //           . ctorIL.Emit(OpCodes.Ret); //         . 

Initialize a new instance of an object of the just-generated type using the Activator :

 object targetObject = Activator.CreateInstance(typeBuilder.CreateType()); 

All method metadata collectors have a method (almost pun intended ) GetILGenerator () , which returns an instance of ILGenerator , which allows us to push the required sequence of statements to the MSIL computation stack. The set of IL instructions that we transmit from OpCodes directly depends on how the logic of the described method behaves. In this article, I only superficially touch on the topic of reflection, this is a reason for a separate article, in addition, you can always read the full list of instructions in the official MSDN documentation.

Now we have everything necessary for writing the logic of dynamic generation of type metadata. At the same time, we take into account the fact that the properties and fields of the generated type can also have our serialization attribute, for implementation we use recursion. If the object does not have a type name matching attribute, we return it as is. If the dynamic assembly already has metadata of our type, we collect an object instance based on them. This is all useful for optimization. The full listing of the generation method, with all the necessary checks and procedures, is as follows:

 /// <summary> ///           <see cref="AmfObjectAttribute"/>,   ,    <see cref="AmfMemberAttribute"/>. /// </summary> /// <param name="sourceObject">  .</param> /// <returns></returns> private static object GenerateType<T>(T sourceObject) { Type sourceType = sourceObject.GetType(); //     . if (sourceType.IsDictionary()) return GenerateType(sourceObject as IEnumerable<KeyValuePair<string, object>>); if (!sourceType.IsDefinedAttribute<AmfObjectAttribute>()) return sourceObject; //        -   . string typeName = sourceType.GetAttribute<AmfObjectAttribute>().Name ?? sourceType.FullName; //    . Type definedType = moduleBuilder.GetType(typeName); //       . TypeBuilder typeBuilder = null; //     . Dictionary<string, object> properties = new Dictionary<string, object>(); //   . Dictionary<string, object> fields = new Dictionary<string, object>(); //   . //       ... if (definedType == null) { typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public); //     . ConstructorBuilder ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes); //  . ILGenerator ctorIL = ctor.GetILGenerator(); //     MSIL-  . ctorIL.Emit(OpCodes.Ldarg_0); //      . ctorIL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)); //           . ctorIL.Emit(OpCodes.Ret); //         . //     . foreach (PropertyInfo propertyInfo in sourceType.GetProperties()) { AmfMemberAttribute attribute = propertyInfo.GetAttribute<AmfMemberAttribute>(); //      AmfMemberAttribute. if (attribute == null) continue; //     -  . string propertyName = attribute.Name ?? propertyInfo.Name; //   . object propertyValue = propertyInfo.GetValue(sourceObject, null); //   . Type propertyType = propertyInfo.PropertyType; //    . //        ... if (propertyInfo.PropertyType.IsDefinedAttribute<AmfObjectAttribute>() || propertyType.IsDictionary()) { //   ,   . propertyValue = propertyType.IsDictionary() ? GenerateType(propertyValue as IEnumerable<KeyValuePair<string, object>>) : GenerateType(propertyValue); propertyType = propertyValue.GetType(); //   . } FieldBuilder fieldBuilder = typeBuilder.DefineField($"m_{propertyName}", propertyType, FieldAttributes.Private); //    . PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); //   . MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; //      . MethodBuilder methodBuilderAccessor = typeBuilder.DefineMethod($"get_{propertyName}", getSetAttr, propertyType, Type.EmptyTypes); //  . ILGenerator accessorIL = methodBuilderAccessor.GetILGenerator(); //     MSIL-  . accessorIL.Emit(OpCodes.Ldarg_0); //      . accessorIL.Emit(OpCodes.Ldfld, fieldBuilder); //           . accessorIL.Emit(OpCodes.Ret); //         . MethodBuilder methodBuilderSetter = typeBuilder.DefineMethod($"set_{propertyName}", getSetAttr, null, new Type[] { propertyType }); //  . ILGenerator setterIL = methodBuilderSetter.GetILGenerator(); //     MSIL-  . setterIL.Emit(OpCodes.Ldarg_0); //      . setterIL.Emit(OpCodes.Ldarg_1); //      . setterIL.Emit(OpCodes.Stfld, fieldBuilder); //           . setterIL.Emit(OpCodes.Ret); //         . propertyBuilder.SetGetMethod(methodBuilderAccessor); //   . propertyBuilder.SetSetMethod(methodBuilderSetter); //   . properties.Add(propertyName, propertyValue); //         . } //     . foreach (FieldInfo fieldInfo in sourceType.GetFields()) { AmfMemberAttribute attribute = fieldInfo.GetAttribute<AmfMemberAttribute>(); //      AmfMemberAttribute. if (attribute == null) continue; //     -  . string fieldName = attribute.Name ?? fieldInfo.Name; //   . object fieldValue = fieldInfo.GetValue(sourceObject); //   . Type fieldType = fieldInfo.FieldType; //    . //        ... if (fieldInfo.FieldType.IsDefinedAttribute<AmfObjectAttribute>() || fieldType.IsDictionary()) { //   ,   . fieldValue = fieldType.IsDictionary() ? GenerateType(fieldValue as IEnumerable<KeyValuePair<string, object>>) : GenerateType(fieldValue); fieldType = fieldValue.GetType(); //   . } typeBuilder.DefineField(fieldName, fieldType, FieldAttributes.Public); //   . fields.Add(fieldName, fieldValue); //         . } } else { //     . foreach (PropertyInfo propertyInfo in sourceType.GetProperties()) { AmfMemberAttribute attribute = propertyInfo.GetAttribute<AmfMemberAttribute>(); //      AmfMemberAttribute. if (attribute == null) continue; //     -  . string propertyName = attribute.Name ?? propertyInfo.Name; //   . object propertyValue = propertyInfo.GetValue(sourceObject, null); //   . Type propertyType = propertyInfo.PropertyType; //    . AmfObjectAttribute propertyAttribute = propertyInfo.PropertyType.GetAttribute<AmfObjectAttribute>(); //    . //        ... if (propertyAttribute != null || propertyType.IsDictionary()) { //   ,   . propertyValue = propertyType.IsDictionary() ? GenerateType(propertyValue as IEnumerable<KeyValuePair<string, object>>) : GenerateType(propertyValue); propertyType = propertyValue.GetType(); //   . } properties.Add(propertyName, propertyValue); //         . } //     . foreach (FieldInfo fieldInfo in sourceType.GetFields()) { AmfMemberAttribute attribute = fieldInfo.GetAttribute<AmfMemberAttribute>(); //      AmfMemberAttribute. if (attribute == null) continue; //     -  . string fieldName = attribute.Name ?? fieldInfo.Name; //   . object fieldValue = fieldInfo.GetValue(sourceObject); //   . Type fieldType = fieldInfo.FieldType; //    . AmfObjectAttribute fieldAttribute = fieldInfo.FieldType.GetAttribute<AmfObjectAttribute>(); //    . //        ... if (fieldAttribute != null || fieldType.IsDictionary()) { //   ,   . fieldValue = fieldType.IsDictionary() ? GenerateType(fieldValue as IEnumerable<KeyValuePair<string, object>>) : GenerateType(fieldValue); fieldType = fieldValue.GetType(); //   . } fields.Add(fieldName, fieldValue); //         . } } object targetObject = Activator.CreateInstance(definedType ?? typeBuilder.CreateType()); //     . //     . foreach (KeyValuePair<string, object> property in properties) targetObject.GetType().GetProperty(property.Key).SetValue(targetObject, property.Value, null); //     . foreach (KeyValuePair<string, object> field in fields) targetObject.GetType().GetField(field.Key).SetValue(targetObject, field.Value); return targetObject; } 

Now we need to take care that the metadata of the arrays of objects are correctly stored during serialization. To do this, we write an elementary overload of the method of generating the type:

 /// <summary> ///            <see cref="AmfObjectAttribute"/>,   ,    <see cref="AmfMemberAttribute"/>. /// </summary> /// <param name="sourceObjects">  .</param> /// <returns></returns> private static object[] GenerateType(object[] sourceObjects) { for (int i = 0; i < sourceObjects.Length; i++) sourceObjects[i] = GenerateType(sourceObjects[i]); //      . return sourceObjects; } 

Now we can already serialize any objects and arrays in AMF for further sending to the server. But we still need the functionality that provides the sending of associative arrays AMF, which are simply not available in C #. Associative arrays here are different implementations of IEnumerable <KeyValuePair <TKey, TValue> dictionaries , as well as Hashtable hash tables, AMF recognizes each dictionary key as a field of Array type. To implement the solution of this problem, we will write another overload that can correctly generate the AMF dictionary based on the keys and values ​​of the dictionary:

 /// <summary> /// ,        .     ,             object,        ,    AMF. /// </summary> /// <param name="sourceType"> .</param> /// <returns></returns> private static bool IsDictionary(this Type sourceType) { Type type0 = typeof(IEnumerable<KeyValuePair<string, object>>); Type type1 = typeof(IDictionary<string, object>); Type type2 = typeof(Dictionary<string, object>); return sourceType.FullName == type0.FullName || sourceType.IsSubclassOf(type0) || sourceType.FullName == type1.FullName || sourceType.IsSubclassOf(type1) || sourceType.FullName == type2.FullName || sourceType.IsSubclassOf(type2); } /// <summary> ///       "-", ,  -   . /// </summary> /// <param name="fields">     "-"</param> /// <returns></returns> private static object GenerateType(IEnumerable<KeyValuePair<string, object>> fields) { AssemblyName assemblyName = new AssemblyName("AmfDynamicAssemblyForDictionary"); //     . AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); //   . ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll"); //      . TypeBuilder typeBuilder = moduleBuilder.DefineType(typeof(Array).FullName, TypeAttributes.Public); //     . ConstructorBuilder ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes); //  . ILGenerator ctorIL = ctor.GetILGenerator(); //     MSIL-  . ctorIL.Emit(OpCodes.Ldarg_0); //      . ctorIL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)); //           . ctorIL.Emit(OpCodes.Ret); //         . //     . foreach (KeyValuePair<string, object> pair in fields) { object fieldValue = pair.Value; //   . Type fieldType = fieldValue.GetType(); //    . //     ... if (fieldType.IsDefinedAttribute<AmfObjectAttribute>()) { fieldValue = GenerateType(fieldValue); //   ,   . fieldType = fieldValue.GetType(); //   . } typeBuilder.DefineField(pair.Key, fieldType, FieldAttributes.Public); //   . } object targetObject = Activator.CreateInstance(typeBuilder.CreateType()); //     . //     . foreach (KeyValuePair<string, object> pair in fields) targetObject.GetType().GetField(pair.Key).SetValue(targetObject, pair.Value); return targetObject; } 

Notice, here I create a separate dynamic assembly, and each time I call the method I override the metadata in it. This is necessary so that all the metadata of our dictionary is generated anew, otherwise after some time the copies of our dictionaries will have a whole bunch of undefined fields that should not exist in the type and which will take up space in the memory.

AMF Serialization


Now everything is exactly ready for the serialization of objects in AMF. Let's start implementing the serializer:

 /// <summary> ///     AMF. /// </summary> /// <param name="sourceObject"> .</param> /// <param name="version"> AMF.</param> /// <returns></returns> public static byte[] SerializeToAmf(this object sourceObject, ushort version) { using (MemoryStream memoryStream = new MemoryStream()) //       . using (AMFSerializer amfSerializer = new AMFSerializer(memoryStream)) //    AMF. { AMFMessage amfMessage = new AMFMessage(version); //          AMF. AMFBody amfBody = new AMFBody(AMFBody.OnResult, null, GenerateType(sourceObject)); //     AMF. amfMessage.AddBody(amfBody); //  body   AMF. amfSerializer.WriteMessage(amfMessage); //  . return memoryStream.ToArray(); //       . } } /// <summary> ///     AMF3. /// </summary> /// <param name="sourceObject"> .</param> /// <returns></returns> public static byte[] SerializeToAmf(this object sourceObject) => sourceObject.SerializeToAmf(3); /// <summary> ///     *.amf. /// </summary> /// <param name="sourceObject"> .</param> /// <param name="path"> .</param> /// <param name="version">  AMF.</param> public static void SerializeToAmf(this object sourceObject, string path, ushort version) => File.WriteAllBytes($"{path}.amf", sourceObject.SerializeToAmf(version)); /// <summary> ///     *.amf.  AMF  3. /// </summary> /// <param name="sourceObject"> .</param> /// <param name="path"> .</param> public static void SerializeToAmf(this object sourceObject, string path) => sourceObject.SerializeToAmf(path, 3); 

The implementation is almost trivial. The only caveat is that I intentionally created an overload with the AMF version number equal to three by default. When we work with Flex, this is almost always AMF3, which means there’s no point in passing an additional argument to the calls to the serialization method.

For deserialization, it is necessary to do all the above procedures, only in the reverse order:

 /// <summary> ///      AMF. /// </summary> /// <typeparam name="T">  .</typeparam> /// <param name="sourceBuffer">   .</param> /// <returns></returns> public static T DeserializeFromAmf<T>(this byte[] sourceBuffer) where T : class { using (MemoryStream memoryStream = new MemoryStream(sourceBuffer)) //       . using (AMFDeserializer amfDeserializer = new AMFDeserializer(memoryStream)) //    AMF. { AMFMessage amfMessage = amfDeserializer.ReadAMFMessage(); //   AMF. AMFBody amfBody = amfMessage.GetBodyAt(0); //  body   AMF. object amfObject = amfBody.Content; //    body AMF. Type amfObjectType = amfObject.GetType(); //     AMF. //            . IEnumerable<Type> types = from type in Assembly.GetExecutingAssembly().GetTypes() where Attribute.IsDefined(type, typeof(AmfObjectAttribute)) select type; Type currentType = null; //       . //        . foreach (Type type in types) { AmfObjectAttribute attribute = type.GetAttribute<AmfObjectAttribute>(); //   . if (attribute == null || attribute.Name != amfObjectType.FullName) continue; //       -  . currentType = type; //     . break; } if (currentType == null) return default(T); //     -  null. object targetObject = Activator.CreateInstance(currentType); //    . //     . foreach (PropertyInfo propertyInfo in currentType.GetProperties()) { AmfMemberAttribute attribute = propertyInfo.GetAttribute<AmfMemberAttribute>(); //    . if (attribute == null) continue; //     - . propertyInfo.SetValue(targetObject, amfObjectType.GetProperty(attribute.Name).GetValue(amfObject, null), null); //             . } //     . foreach (FieldInfo fieldInfo in currentType.GetFields()) { AmfMemberAttribute attribute = fieldInfo.GetAttribute<AmfMemberAttribute>(); //    . if (attribute == null) continue; //     - . fieldInfo.SetValue(targetObject, amfObjectType.GetField(attribute.Name).GetValue(amfObject)); //             . } return targetObject as T; //    T    . } } /// <summary> ///     *.amf. /// </summary> /// <typeparam name="T">  .</typeparam> /// <param name="obj"> .</param> /// <param name="path">   .</param> /// <returns>  AMF.</returns> public static T DeserializeFromAmf<T>(this object obj, string path) where T : class => File.ReadAllBytes($"{path}.amf").DeserializeFromAmf<T>(); 

Now let's see how you can serialize an object in AMF as the fastest way and send it to the server:

 using (MemoryStream memoryStream = new MemoryStream()) //             . using (AMFWriter amfWriter = new AMFWriter(memoryStream)) //      AMF. using (WebClient client = new WebClient()) //  HTTP-      (    HttpWebRequest). { amfWriter.WriteBytes(new CustomAmfObject().SerializeToAmf()); //     . client.Headers[HttpRequestHeader.ContentType] = "application/x-amf"; //   ContentType  . byte[] buffer = client.UploadData(Host, "POST", memoryStream.ToArray()); //    . } 

In the screenshot below, you can observe the structure of the serialized object, ready to be sent to the server:

image

In case the server after sending data will swear that the object does not implement the IExternalizable interface , we need to implement the IExternalizable implementation in our custom classes :

  [AmfObject("example.game.gameObject")] public class CustomAmfObject : IExternalizable { [AmfMember("x")] public float X { get; set; } [AmfMember("y")] public float Y { get; set; } [AmfMember("z")] public float Z { get; set; } public CustomAmfObject(float x, float y, float z) { X = x; Y = y; Z = z; } public CustomAmfObject() : this(0f, 0f, 0f) { } public void ReadExternal(IDataInput input) { X = input.ReadFloat(); Y = input.ReadFloat(); Z = input.ReadFloat(); } public void WriteExternal(IDataOutput output) { output.WriteFloat(X); output.WriteFloat(Y); output.WriteFloat(Z); } } 

As tests showed in real conditions, to serialize data through such an approach, a chain of records in IDataOutput and reading from IDataInput is not necessary , all data were sent to the server in the correct form without them. For the very same "rude" servers, this solution can be very useful.

Afterword


The method of solving the problem described above is far from the only one that does not require intervention in the source code of the original framework. For example, we could do without attributes and work with metadata from the code, if implemented copies of our objects using DynamicObject , ExpandoObject and other dynamic- no amenities, while placing all the work with the metadata on the DLR.

The knowledge base outlined in the article should be enough to write a serializer for almost any task, or a simple MSIL-based interpreter / compiler and reflection, as well as a more visual understanding of the principle of AMF serialization.

Sources for the article are available here .
The NuGet package for the FluorineFx extension can be found here .
If you need only to drag and AMF serialized for this entire project in FluorineFX oh how you do not want, then here you can find the proposed ArtemA option serializer implemented by means of mapping, but on the basis DataContract-serialize .NET.

I hope the material I have outlined has been helpful to you.
Thank you for attention!

Source: https://habr.com/ru/post/320446/


All Articles