jasonbock / cslageneratorserialization Goto Github PK
View Code? Open in Web Editor NEWA custom CSLA serialization formatter that uses C# source generators.
License: MIT License
A custom CSLA serialization formatter that uses C# source generators.
License: MIT License
If a user adds a type to a PropertyInfo<>
that CSLA's MobileFormatter
doesn't support (e.g. PropertyInfo<MyCustomStruct>
), it'll fail. Same with this SG serializer. There may be a way to do this though with IoC support. The idea is that a user would register (probably as a singleton) key-value pairs to a SerializationTypes<TType, (TSerializer, TDeserializer)>
instance, and the SG would gen code to reach into the IoC, grab that instance, and call the serializer or deserializer (the values are basically delegates that get a reference to the context).
There's a discussion debating the future usage of [Serializable]
. While it makes sense, SGs work well if you can target an attribute. I noticed there's an [AutoSerializable]
attribute in CSLA for ... some reason. Maybe the generator targets that instead? Or should the library just define its own?
These types are currently not supported by the generator. It's unclear if they really need it, as they're pretty specialized and may not have much usage in modern .NET applications:
ReadOnlyBindingListBase
BusinessBindingListBase
DynamicListBase
DynamicBindingListBase
MobileDictionary
MobileList
Right now I'm generating the code to handle serialization for BOs. It's not much, but it's repeated code that I could move into the reader and writer contexts. Then, I could change the gen'd code to call these methods in one line, which should hopefully simplify things.
In GeneratorFormatterWriterContext
:
public void Write(IGeneratorSerializable value, bool isNotSealed = false)
{
if (value is not null)
{
(var isReferenceDuplicate, var referenceId) = this.GetReference(value);
if (isReferenceDuplicate)
{
this.Writer.Write((byte)SerializationState.Duplicate);
this.Writer.Write(referenceId);
}
else
{
this.Writer.Write((byte)SerializationState.Value);
if (isNotSealed)
{
var valueTypeName = value.GetType().AssemblyQualifiedName!;
(var isTypeNameDuplicate, var typeNameId) = this.GetTypeName(valueTypeName);
if (isTypeNameDuplicate)
{
this.Writer.Write((byte)SerializationState.Duplicate);
this.Writer.Write(typeNameId);
}
else
{
this.Writer.Write((byte)SerializationState.Value);
this.Writer.Write(valueTypeName);
}
}
value.SetState(this);
}
}
else
{
this.Writer.Write((byte)SerializationState.Null);
}
}
This would be called like this:
context.Write(this.ReadProperty<ChildPropertiesData>(ParentPropertiesData.ChildContentsProperty));
In GeneratorFormatterReaderContext
:
public void Read<T>(Action<T> propertyLoader, bool isNotSealed = false)
{
switch (this.Reader.ReadStateValue())
{
case SerializationState.Duplicate:
propertyLoader((T)this.GetReference(this.Reader.ReadInt32()));
break;
case SerializationState.Value:
T newValue;
if (isNotSealed)
{
if (this.Reader.ReadStateValue() == SerializationState.Duplicate)
{
newValue = this.CreateInstance<T>(this.GetTypeName(this.Reader.ReadInt32()))!;
}
else
{
var newValueTypeName = this.Reader.ReadString();
this.AddTypeName(newValueTypeName);
newValue = this.CreateInstance<T>(newValueTypeName)!;
}
}
else
{
newValue = this.CreateInstance<T>()!;
}
((IGeneratorSerializable)newValue).GetState(this);
propertyLoader(newValue);
this.AddReference(newValue);
break;
case SerializationState.Null:
break;
}
}
This would be called like this:
context.Read<ChildPropertiesData>(_ => this.LoadProperty(ParentPropertiesData.ChildContentsProperty, _));
I'm not entirely thrilled with the delegate usage here, but, it does make the call to LoadProperty()
optimal. Right now, calling GetReference()
always returns an object
(because it has to), but then passing that into LoadProperty()
as an object
causes LoadPropertyByReflection()
to be called in CSLA, and that's not idea.
This may allow the methods around the "duplicate" dictionaries in the contexts to be private
. Though I need to be careful with that, especially with the deleted list field for lists.
Right now, there's no way for a BO to add custom state to the serialization process. One idea would be to add two methods with DIMs to IGeneratorSerializable
, but the problem is that DIMs are not supported in .NET Standard. So, I either create another package that this one references, or I generate the interface (and anything relying on it) during SG initialization.
This idea is sketched out in sharplab.io here
using System;
using System.IO;
Roundtrip(new Normal());
Console.WriteLine();
Roundtrip(new Custom());
static void Roundtrip(ISerialize target)
{
Console.WriteLine(target.GetType().FullName);
target.Deserialize(null!);
target.CustomDeserialize(null!);
target.Serialize(null!);
target.CustomSerialize(null!);
}
public interface ISerialize
{
void Deserialize(BinaryReader reader);
void CustomDeserialize(BinaryReader reader) { Console.WriteLine("ISerialize.CustomDeserialize()"); }
void Serialize(BinaryWriter writer);
void CustomSerialize(BinaryWriter writer) { Console.WriteLine("ISerialize.CustomSerialize()"); }
}
public class Normal
: ISerialize
{
void ISerialize.Deserialize(BinaryReader reader) { Console.WriteLine("Normal.Deserialize()"); }
void ISerialize.Serialize(BinaryWriter writer) { Console.WriteLine("Normal.Serialize()"); }
}
public class Custom
: ISerialize
{
void ISerialize.Deserialize(BinaryReader reader) { Console.WriteLine("Custom.Deserialize()"); }
void ISerialize.CustomDeserialize(BinaryReader reader) { Console.WriteLine("Custom.CustomDeserialize()"); }
void ISerialize.Serialize(BinaryWriter writer) { Console.WriteLine("Custom.Serialize()"); }
void ISerialize.CustomSerialize(BinaryWriter writer) { Console.WriteLine("Custom.CustomSerialize()"); }
}
For custom serialization, if the target type is a reference type, then check to see if it's a duplicate first, and then call the user's custom implementation if it's not. This will give the same behavior for handling duplicate references within a graph that currently exists in this serializer and MobileFormatter
as well.
I may want to pass in the contexts to the custom handlers, rather than the readers and writers. Reason is, it may be possible that a custom handler needs to invoke another custom handler.
Change isNotSealed
to isSealed
in both GeneratorFormatterWriterContext
and GeneratorFormatterReaderContext
, and flip the bit when calling it. It just looks weird to have it be "negative" :)
I have a decent amount of tests, but I could always add more. In particular, I should look at current CSLA serialization tests and see how to add them to my test suite.
In CSLA 9, there will be a change that will break my serializer. Shouldn't be too difficult to address.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.