Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MobileFormatter should allow custom serializers for specific types #2531

Open
rockfordlhotka opened this issue Oct 15, 2021 · 2 comments
Open
Assignees

Comments

@rockfordlhotka
Copy link
Member

What @TheCakeMonster and I have been discussing is a modification to MobileFormatter where you can provide a serializer for specific types. You'd register these serializers during app startup (via DI), and when MobileFomatter encounters one of these types, it would delegate the value to your serializer to convert it to/from a string (or byte array).

I think that if we have MobileFormatter rely on a dictionary of <targettype, serializertype> that can be defined during app startup, and the dictionary injected to MobileFormatter (or something) that we'd have a good/flexible solution.

Then, if we want to use an attribute-based model, the attribute can be used to help load the dictionary, but also, types that can't be decorated with the attribute can be directly added to the dictionary during app startup.

Inside MobileFormatter, if a type isn't normally serializable, the formatter will check the dictionary to see if a custom serializer is registered for the target type. If so, the formatter will ask DI for an instance of the custom serializer, and let it serialize/deserialize the target object.

public interface ICustomSerializer
{
  byte[] Serialize(object target);
  object Deserialize(byte[] serializedData);
}

I'm not 100% sure if this should use byte[] or string - need to review what will be simplest within MobileFormatter at the point where a custom serializer would be implemented.

@rockfordlhotka
Copy link
Member Author

There are multiple parts to making this work.

Type mapping

There needs to be a way to configure a type mapping that the MobileFormatter can use to determine what type to invoke to serialize a target type. In other words, if someone asks to serialize type X (which doesn't implement IMobileObject), there needs to be a way for MobileFormatter to know that it should invoke type Y to do the serialization.

This could be done using attributes, or via configuration during app startup - having the developer provide a type map, probably via a dictionary<type, type> or something along that line. Or both?

ISerializeObject interface

There needs to be something like an ISerializeObject interface defined in the Csla.Serialization.Mobile namespace. This is what the type Y would implement so the MobileFormatter can invoke it as needed to serialize/deserialize target type X.

This could be done as noted in the original post in this thread, or it might be better if it more closely mirrors the IMobileObject interface to integrate directly into the way MobileFormatter works.

In other words, maybe a serializer looks like this pseudocode:

  public class Y: ISerializeObject<X>
  {
    public void GetState(X obj, SerializationInfo info)
    {
      // copy values from obj into info for serialization
    }

    public void SetState(X obj, SerializationInfo info)
    {
      // copy values from info into obj for deserialization
    }

    public void GetChildren(X obj, SerializationInfo info, MobileFormatter formatter)
    {
      // serialize child objects
    }

    public void SetChildren(X obj, SerializationInfo info, MobileFormatter formatter)
    {
      // deserialize child objects
    }
  }

My thinking here, is that this allows someone to write the same implementation that they would have written if they could implement IMobileObject directly, but in a separate class so they don't have to alter the target type.

In any case, externalizing serialization probably requires the use of reflection and that the author of type Y have intimate knowledge of the internal implementation and/or fields of type X. I don't see how that can be avoided without the ability to change type X - and the goal here is to not change the target type (a type we often don't control).

I think the new C# feature where you can provide a default implementation in an interface should allow us to not require a base class too. All we really need is for the four methods to, by default, do nothing. An implementer might only override the get/set state methods, or they might override all four.

MobileFormatter enhancement

The MobileFormatter type needs to be enhanced to use the type map when appropriate.

I think this is the location where the check needs to be made to see if type X is in the type map, because otherwise an exception would be thrown because the target type can't be serialized.

https://github.com/MarimerLLC/csla/blob/dd16fdae34c55ffd15afce4aa48d86eb2dcf6fe3/Source/Csla/Serialization/Mobile/MobileFormatter.cs#L148C45-L148C45

I think this is the deserialization location for a similar change.

IMobileObject mobile = (IMobileObject)ApplicationContext.CreateInstance(type);

Precedence

I think that a type that implements IMobileObject should never use a mapped type. It seems to me that if a type implements its own serialization that any mapped serializer should be ignored, otherwise we could leave people in a place that's hard to debug, because some other developer could effectively override the normal serialization of a type.

@swegele
Copy link
Contributor

swegele commented Feb 24, 2024

My thinking here, is that this allows someone to write the same implementation that they would have written if they could implement IMobileObject directly, but in a separate class so they don't have to alter the target type.

Good idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: In Progress
Development

No branches or pull requests

3 participants