-
Notifications
You must be signed in to change notification settings - Fork 754
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
Add ISerializationManager for Dependency Injection #4087
Add ISerializationManager for Dependency Injection #4087
Conversation
I forget where we landed on naming, what's the distinction between |
I decided to call this a |
@ahoefling I added a couple of new methods to The only remaining question in my mind is whether we need to copy |
@bdukes I reviewed the changes you made and appreciate the new helper methods. I think those will be very valuable. I am not very familliar with this code in DNN and am curious about the different layers of abstractions we have. It is kind of confusing to me and was wondering if we should flatten it to make it easer for developers to consume. Right now this PR creates a new Instead of just performing a lift and shift, I propose we adjust this API to be ready for the future. We should not be using reflection to instantiate controls or use the custom DNN built reflection cache. New API RecommendationI propose we adjust the I believe this structure will work very well for our custom public interface IValueSerializer<T>
{
string Serialize(T value);
T Deserialize(string serializedObject);
}
public interface ISerializer
{
string Serialize<T>(T value);
T Deserialize<T>(string serializedObject);
} Today the logic in the Null Serializer
Valid Serializer
Currently the DNN library code only uses 1 implementation of the To get this all to work under my new proposal we would need to update the Here are some example implementations that I am thinking public class DnnSerializer : ISerializer
{
protected ISerializer Serializer { get; }
public DnnSerializer(ISerializer<DefaultSerializer> serializer)
{
this.Serializer = serializer;
}
public string Serialize<T>(T object) => this.Serializer.Serialize(object);
public T Deserialize<T>(string value) => this.Serializer.Deserialize(value);
}
public class DnnSerializer<T> : DnnSerializer, ISerializer<T>
{
public DnnSerializer(IServiceProvider container)
: base(ActivatorUtilities.GetServiceOrCreateInstance<T>(container)
{
}
}
public class DefaultSerializer : IValueSerializer<object>
{
public string Serialize<T>(T object)
{
// invokes the current serialization behavior
}
public T Deserialize<T>(string value)
{
// invokes the current deserialization behavior
}
}
// This is here to demonstrate a custom serializer
public class ComplexTypeSerializer : IValueSerializer<ComplexType>
{
public string Serialize<T>(T object)
{
// invokes the current serialization behavior
}
public T Deserialize<T>(string value)
{
// invokes the current deserialization behavior
}
} UsageBelow is an example of how this new API will be used in a public class HomeController : DnnApiController
{
protected ISerializer Serializer { get; }
public HomeController(ISerializer<ComplexTypeSerializer> serializer)
{
this.Serializer = serializer;
}
} If the code needs to just use the default settings the user can just inject the public class HomeController : DnnApiController
{
protected ISerializer Serializer { get; }
public HomeController(ISerializer serializer)
{
this.Serializer = serializer;
}
} In this proposal I do not like how we have 2 interfaces that are identical in the contract
I created the 2 different interfaces to ensure we have 1 that implements the actual logic where the other is a facade around the implementation details. I am open to discussing this proposal further to see if we can come to an agreement on something to ultimate publish to this PR. The end goal is I want to be able to implement an easy interface for serialization and just start using that. Right now as implemented this abstraction becomes very difficult to change or use a custom serializaiton. |
I definitely like the approach of adjusting how these pieces fit together. I was also uncomfortable as I was doing the minimal lift-and-shift. The current Ultimately what this means is that the type of value and type of serializer is often not known at compile time. So while having a generic API is going to work well in some of our new scenarios and will be nice and ergonomic moving forward, there are other scenarios that a generic API doesn't solve. So far as I'm aware, we'll need to be able to support passing I'm trying to design an API which supports what we support now, and somewhere there's going to be some gross reflection code. Should we only support generic overloads in the interface, and the existing APIs can access those via reflection? public interface ISerializer<T>
{
string Serialize(T value);
T Deserialize(string serializedValue);
}
// NOTE: Update DotNetNuke.Entities.Modules.Settings.ISettingsSerializer<T> to inherit from ISerializer<T>
public interface ISerializationManager
{
string SerializeValue<T>(T value);
string SerializeValue<T>(T value, ISerializer<T> serializer);
string SerializeProperty<T>(T containerObject, PropertyInfo property);
string SerializeProperty<TContainer, TProperty>(TContainer containerObject, PropertyInfo property);
T DeserializeValue<T>(string serializedValue);
T DeserializeValue<T>(string serializedValue, ISerializer<T> serializer);
void DeserializeProperty<T>(T containerObject, PropertyInfo property, string serializedValue);
void DeserializeProperty<TContainer, TProperty>(TContainer containerObject, PropertyInfo property, string serializedValue, ISerializer<TProperty> serializer);
} The big piece that helps me to keep in mind is that someone is implementing public class ModulesSettings
{
[ModuleSetting(Serializer = "DotNetNuke.Tests.Core.Entities.Modules.Settings.ComplexTypeSerializer,DotNetNuke.Tests.Core")]
public ComplexType ComplexProperty { get; set; } = new ComplexType(20, 25);
} This should be changed to this: public class ModulesSettings
{
[ModuleSetting(SerializerType = typeof(ComplexTypeSerializer)]
public ComplexType ComplexProperty { get; set; } = new ComplexType(20, 25);
} And, I suppose, we should switch to using |
Thanks for explaining how the |
What's the difference between your proposal and my proposal? The major changes that I see are the addition of an overload taking |
I would like to remove the idea of the
The complicated part is our current implementation for attribute based serialization isn't really a serializer but an I see value for all of these things to work together, but I do not think we should tightly couple the |
I like the trimmed down nature of this proposal, but I have a hard time seeing what it looks like for our current settings serialization/deserialization to make use of it. Are you proposing that it wouldn't make use of this interface? Or would use it for properties that don't define a custom serializer/value converter? Would it look something like this? foreach (var (property, attribute) in GetProperties(settingsObject))
{
object propertyValue = GetPropertyValue(settingsObject, property);
Type propertyType = property.PropertyType;
string settingValue;
var customValueConverter = GetValueConverter(attribute.Serializer);
if (customValueConverter != null)
{
settingValue = InvokeConverter(customValueConverter, nameof(IValueConverter.ToString), propertyType, propertyType);
}
else
{
settingValue = InvokeSerializer(this.serializer, nameof(ISerializer.Serialize), propertyType, propertyType);
}
this.moduleController.UpdateModuleSetting(GetSettingName(property, attribute), settingValue);
} Where the two I think you're right that this trimmed down interface is the right approach for third party devs. But we also need an implementation that serves the needs of the core, and, ideally, centralizes all of the reflection in one place. So, maybe |
@mitchelsellers @donker any thoughts? @ahoefling am I still missing what you're trying to get at? |
I'm going to study this more in depth as (module) settings (de-)serialization is a pet peeve of mine. I'd love to see serialization lifted to a higher level, but it's paramount that the settings serialization remains intact. BTW, we keep referring to module settings, but it also implements TabModule settings and Portal Settings. Ideally I'd like to add host settings as well to complete this. Sample current module settings code can be found here: https://github.com/DNN-Connect/Conference/blob/master/Server/Conference/Common/ModuleSettings.cs I'm going to do some research to see if I have made my own serializers somewhere. |
Would it be an option to leave the old Controller as is and deprecate it and instead add a "Manager" next to it? First: you'd avoid any breakages of people who have created their own serializers and second: the naming becomes consistent. I am not a big fan of the interface having a different name from the implementation as it is confusing for developers IMHO. Thoughts, @ahoefling ? |
@ahoefling we are prepping for a 9.8.0 RC next Monday, can we drive this one to ready in that time? I know that @donker has made some adjustments in his own fork per the above |
This will allow serializing a single value without requiring it to be a property of an object.
This will allow deserializing a single value without requiring it to be a property of an object.
… code to reflect this
…o updated typo in ISerializationManager registration
dfabc99
to
e185a86
Compare
I have updated this PR and rebased it on the latest changes in develop. It should be good to go, but with any Dependency Injection PR I strongly recommend we download the PR Installer and validate the happy path works. I'll comment once I run through it as my sign-off for the maintainers @mitchelsellers thanks for letting me know about the timeline here! @donker Thank you for making the contributions to the fork of this branch. I pulled them in fixed a few minor issues which I commented as PR feedback so we have the history. This was really a big help as I wouldn't have had the time to get this wrapped up for 9.8.0. |
Fixes: #4086
Related: #4071
Summary
Creates
ISerializationManager
abstraction that is then implemented by theSerializationController
. The new interface is then registered as a transient service with the Dependency Injection container. I believe transient registration makes sense because it does not require any state and meets the definition of stateless service.@bdukes suggested we add this as PR feedback in #4071