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

Custom type resolver #451

Closed
onetocny opened this issue Sep 18, 2020 · 7 comments
Closed

Custom type resolver #451

onetocny opened this issue Sep 18, 2020 · 7 comments
Assignees
Labels
enhancement (cc: feat) New feature or request

Comments

@onetocny
Copy link
Contributor

Is it possible to control a creation of objects during deserialization? I have a class to be deserialized that does not has a default constructor because I need inject dependencies into it. Ideally I would like to find a place where ExtendedXmlSerializer tell me: "Here is a type, give me an instance of that". I think that Extend method and ISerializerExtension might be a way but I really do not know how since documentation about this topic is quite poor.

@issue-label-bot
Copy link

Issue Label Bot is not confident enough to auto-label this issue. See dashboard for more details.

@Mike-E-angelo Mike-E-angelo added the enhancement (cc: feat) New feature or request label Sep 19, 2020
@Mike-E-angelo Mike-E-angelo self-assigned this Sep 19, 2020
@Mike-E-angelo
Copy link
Member

Mike-E-angelo commented Sep 19, 2020

Thanks for writing in @netaques and for the feedback. I am in agreement that the documentation could be better. It could always be better. 😁 I have given it a few tries now and there seems to always be a few areas that could see more love. Unfortunately, my time is limited these days (#383) so we will have to lean on perhaps some contributions from the community if we can get to that point.

That stated, what you are looking for is interception, which is actually something we almost did with #264 as a first pass but then relegated it to basic monitoring. The thought was that we could introduce interception if it was something that the community requested.

So it was basically easy enough to copy/paste/modify a new feature for you. Please check out this build here:
#452 (comment)

I have added a passing test demonstrating how to use this feature here:

public sealed class Issue451Tests
{
[Fact]
public void Verify()
{
var provider = new ServiceLocator();
var serializer = new ConfigurationContainer().Type<Subject>()
.WithInterceptor(new Interceptor(provider))
.Create()
.ForTesting();
var instance = new Subject();
instance.Count.Should().Be(0);
const string content =
@"<?xml version=""1.0"" encoding=""utf-8""?><Issue451Tests-Subject xmlns=""clr-namespace:ExtendedXmlSerializer.Tests.ReportedIssues;assembly=ExtendedXmlSerializer.Tests.ReportedIssues""><Count>1</Count></Issue451Tests-Subject>";
serializer.Assert(instance, content);
instance.Count.Should().Be(1);
var cycled = serializer.Deserialize<Subject>(content);
cycled.Should().BeOfType<ActivatedSubject>();
cycled.Count.Should().Be(2);
}
sealed class ServiceLocator : IServiceProvider
{
public object GetService(Type serviceType) => typeof(Subject).IsAssignableFrom(serviceType)
? new ActivatedSubject()
: throw new InvalidOperationException();
}
sealed class Interceptor : ISerializationInterceptor<Subject>
{
readonly IServiceProvider _provider;
public Interceptor(IServiceProvider provider) => _provider = provider;
public Subject Serializing(IFormatWriter writer, Subject instance)
{
instance.Count++;
return instance;
}
public Subject Activating(Type instanceType) => (Subject)_provider.GetService(instanceType);
public Subject Deserialized(IFormatReader reader, Subject instance)
{
instance.Count++;
return instance;
}
}
sealed class ActivatedSubject : Subject {}
class Subject
{
public uint Count { get; set; }
}

Please let me know if this will allow you to do what you are aiming to achieve and/or if you have any further suggestions/additions to the interface/API and I will look into it for you. 👍

@onetocny
Copy link
Contributor Author

@Mike-E-angelo thank you very much for fast reaction. This feature is precisely what I am looking for. Maybe a non generic version would be nice (using Type instead of type variables). Overall it looks really useful for me and I hope that I am no alone with that. However, I wanted to test my simplified scenario and ran into stack overflow exception. Please check out following code. The Activating method is called but somewhere further in library code fails. The interesting fact is that when I add a default constructor into Processor class and remove WithInterceptor method call everything works fine. Can you tell me what I am missing please?

[Test]
public void TestInterceptor()
{

    var xml = @"<?xml version=""1.0"" encoding=""utf-8""?>
                <Processor>
                 <Enabled>true</Enabled>
                 <Filters>
                  <Filter>
                   <Type>ISO</Type>
                  </Filter>
                 </Filters>      
                </Processor>";

    var serializer = new ConfigurationContainer()
        .EnableImplicitlyDefinedDefaultValues()
        .EnableMemberExceptionHandling()
        .EnableImplicitTyping(typeof(Processor))
        .Type<Processor>()
        .WithInterceptor(new Interceptor())
        .Create();

    var contentStream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
    Processor result;
    using (var reader = XmlReader.Create(contentStream))
    {
        result = (Processor)serializer.Deserialize(reader);
    }
}

public class Interceptor : ISerializationInterceptor<Processor>
{
    public Processor Serializing(IFormatWriter writer, Processor instance)
    {
        return instance;
    }

    public Processor Activating(Type instanceType)
    {
        // processor should be retrieved from IoC container, but created manually for simplicity of test
        var processor = new Processor(new Service());
        return processor;
    }

    public Processor Deserialized(IFormatReader reader, Processor instance)
    {
        return instance;
    }
}


public interface IService
{
}

class Service : IService
{
}

public class Processor
{
    private readonly IService _service;

    public bool Enabled { get; set; }

    public List<Filter> Filters { get; set; }

    public Processor(IService service)
    {
        _service = service;

        Filters = new List<Filter>();
    }
}

public class Filter
{
    public string Type { get; set; }
}

@Mike-E-angelo
Copy link
Member

OK great @netaques thank you for the sample code I have reproduced it on my end and am looking into this now.

I will also add support for ISerializationInterceptor which I believe you are looking for, a non-generic, object-based implementation.

@Mike-E-angelo
Copy link
Member

Alright @netaques check this build out here:

#452 (comment)

I have also added your test to the suite. Additionally, I have added support for a generalized/non-generic interceptor by way of a SerializationActivator base class:

[Fact]
public void TestInterceptor()
{
// language=XML
const string xml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<Issue451Tests_Reported-Processor>
<Enabled>true</Enabled>
<Filters>
<Filter>
<Type>ISO</Type>
</Filter>
</Filters>
</Issue451Tests_Reported-Processor>";
var serializer = new ConfigurationContainer().EnableImplicitTyping(typeof(Processor))
.Type<Processor>()
.WithInterceptor(new Interceptor())
.Create();
var contentStream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
using var reader = XmlReader.Create(contentStream);
var processor = (Processor)serializer.Deserialize(reader);
processor.Should().NotBeNull();
processor.Enabled.Should().BeTrue();
processor.Filters.Should().NotBeEmpty();
processor.Filters.Only().Type.Should().Be("ISO");
}
public class Interceptor : SerializationActivator
{
public override object Activating(Type instanceType)
{
// processor should be retrieved from IoC container, but created manually for simplicity of test
var processor = new Processor(new Service());
return processor;
}
}
public interface IService {}
class Service : IService {}
public class Processor
{
// ReSharper disable once NotAccessedField.Local
readonly IService _service;
public bool Enabled { get; set; }
public List<Filter> Filters { [UsedImplicitly] get; set; }
public Processor(IService service)
{
_service = service;
Filters = new List<Filter>();
}
}
public class Filter
{
public string Type { get; set; }
}

Please let me know if there are any further issues you encounter. If not, I will deploy this to NuGet tomorrow. 🤞

@onetocny
Copy link
Contributor Author

Thank you very much, @Mike-E-angelo. I really appreciate so fast reaction. I can confirm that with your latest commit everything works like a charm. I believe this feature will help to other developers as well.

@Mike-E-angelo
Copy link
Member

Mike-E-angelo commented Sep 22, 2020

Great! Good to hear the good news, @netaques. I have posted this to NuGet as v3.3.0:

https://www.nuget.org/packages/ExtendedXmlSerializer/

Thank you for improving ExtendedXmlSerializer! Please do let me know of any additional issues you find around this by posting a comment here and I will look into it for you. Closing for now.

An extensible Xml Serializer for .NET that builds on the functionality of the classic XmlSerializer with a powerful and robust extension model.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement (cc: feat) New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants