Атрибут JsonConverter: десериализовать с помощью настраиваемого конструктора и Autofac

Используем пользовательский JsonConverter для преобразования моего объекта JSON. Это достигается с помощью атрибута JsonConverter для объекта IQuery ниже

[JsonConverter(typeof(CustomConverter<IQuery>))]
public interface IQuery
{
}

Пользовательский общий класс ниже (некоторые бит удалены для краткости)

public class CustomConverter<T> : JsonConverter
{
    // This should be created via AutoFac
    public ICustomObjectCreator<T> ObjectCreator { get; set; }

    // This default constructr always gets called
    public CustomConverter() {}

    // I want to call this constructor
    [JsonConstructor]
    public CustomConverter(ICustomObjectCreator<T> objectCreator)
    {
        Context = context;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;

        // Load JObject from stream 
        var jObject = JObject.Load(reader);

        // Create target object based on JObject 
        var target = Create(objectType, jObject);

        // Populate the object properties 
        serializer.Populate(jObject.CreateReader(), target);

        return target;
    }

    protected T Create(Type objectType, JObject jObject)
    {
        var type = jObject.GetValue("type", StringComparison.OrdinalIgnoreCase)?.Value<string>();
        return ObjectCreator.Create(type);
    }
}

Интерфейс ICustomObjectConverter прост

public interface ICustomObjectCreator<out T>
{
    T Create(string type);
}

и одна из его реализации

public class QueryObjectCreator : ICustomObjectCreator<IQuery>
{
    public IQuery Create(string type)
    {
        // ... some logic to create a concrete object
        return (IQuery)concreteObject;
    }
}

Наконец, Autofac подключен для соблюдения вышеперечисленных

builder.RegisterType<QueryObjectCreator>()
       .As<ICustomObjectCreator<IQuery>>()
       .InstancePerLifetimeScope();

Проблемы:

  • Когда вызывается CustomJsonConverter, вызывается только его конструктор по умолчанию. JsonConstructor НИКОГДА не вызван.
  • Если я удалю конструктор по умолчанию, тогда весь JsonConverter НИКОГДА не будет вызван!

У меня есть inklinkg, что AutoFac никогда не вызывается при вызове JsonConverter. Я даже попробовал вложение свойств, чтобы построить QueryObjectConstruct явно, но даже это никогда не вызывается. Как я могу заставить его работать так, чтобы мой QueryObjectCretor вводился через DI?

Я нашел эту статью о дезадаптации зависимостей и десериализации JSON.net. Однако, для ручного разрешения с помощью вызова DeserializeObject < > (), как я могу, если он работает, заставить его работать с атрибутом JsonConverter?

Спасибо

Ответы

Ответ 1

Вы можете сделать следующие шаги для достижения своей цели:

  • Создайте не общий интерфейс для вашего интерфейса ICustomObjectCreator, чтобы сделать объекты более удобными.
  • Ввести общий базовый класс ObjectCreatorBase<T>, который вызывает ваш общий метод Create.
  • Создайте и задайте настройки по умолчанию, которые используются JsonConvert.
  • Установите AutofacContractResolver как ContractResolver.

Для начала работы см. следующий пример:

void Main()
{
    var builder = new ContainerBuilder();

    builder.RegisterType<QueryObjectCreator>()
        .As<ICustomObjectCreator<IQuery>>()
        .InstancePerLifetimeScope();

    var container = builder.Build();

    Func<JsonSerializerSettings> settingsFactory = () =>
    {
        var settings = new JsonSerializerSettings();
        settings.ContractResolver = new AutofacContractResolver(container);

        return settings;
    };

    JsonConvert.DefaultSettings = settingsFactory;

    var myObject = new MyObject { Query = new Query(42) };

    var json = JsonConvert.SerializeObject(myObject);

    myObject = JsonConvert.DeserializeObject<MyObject>(json);
    Console.WriteLine(myObject.Query.MyProperty);
}

// Define other methods and classes here
public class AutofacContractResolver : DefaultContractResolver
{
    private readonly IContainer _container;

    public AutofacContractResolver(IContainer container)
    {
        _container = container;
    }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        JsonObjectContract contract = base.CreateObjectContract(objectType);

        var customObjectCreatorType = typeof(ICustomObjectCreator<>).MakeGenericType(objectType);

        if (!_container.IsRegistered(customObjectCreatorType))
            return contract;

        var customObjectCreator = (ICustomObjectCreator) _container.Resolve(customObjectCreatorType);

        // I don't know how you want to obtain the string which shall be passed to CreateObject
        contract.DefaultCreator = () => customObjectCreator.CreateObject("XYZ");
        return contract;
    }
}

public interface ICustomObjectCreator
{
    object CreateObject(string type);
}

public interface ICustomObjectCreator<out T> : ICustomObjectCreator
{
    T Create(string type);
}

public abstract class ObjectCreatorBase<T> : ICustomObjectCreator<T>
{
    public object CreateObject(string type)
    {
        return Create(type);
    }

    public abstract T Create(string type);
}

public class QueryObjectCreator : ObjectCreatorBase<IQuery>
{
    public override IQuery Create(string type)
    {
        Console.WriteLine("Create called");

        // ... some logic to create a concrete object
        var concreteObject = new Query();
        return (IQuery)concreteObject;
    }
}

public interface IQuery
{
    int MyProperty { get; set; }
}

public class Query : IQuery
{
    public int MyProperty { get; set; }

    public Query()
    {
    }

    public Query(int myProperty)
    {
        MyProperty = myProperty;
    }
}

public class MyObject
{
    public IQuery Query { get; set; }
}

Выход должен быть

Create called
42

Возможно, вы могли бы упростить код, удалив все экземпляры ICustomObjectCreator, просто используя Autofac для непосредственного создания ваших объектов.

Update

Первый подход работает, но он не учитывает, что вам нужно получить строку для определения того, какой тип объекта вы создаете (type).

Чтобы это получилось, вы можете сделать следующее:

  • Зарегистрируйте CustomConverter как общий.
  • Перезаписать метод ResolveContractConverter, чтобы вернуть экземпляр конвертера в случае, если для типа был зарегистрирован ICustomObjectCreator.
  • Измените DefaultSettings так, чтобы использовался AutofacContractResolver.

См. следующий пример:

void Main()
{
    var builder = new ContainerBuilder();

    builder.RegisterType<QueryObjectCreator>()
        .As<ICustomObjectCreator<IQuery>>()
        .InstancePerLifetimeScope();

    builder.RegisterGeneric(typeof(CustomConverter<>)).AsSelf().InstancePerLifetimeScope();

    var container = builder.Build();

    Func<JsonSerializerSettings> settingsFactory = () =>
    {
        var settings = new JsonSerializerSettings();
        settings.ContractResolver = new AutofacContractResolver(container);

        return settings;
    };

    JsonConvert.DefaultSettings = settingsFactory;

    var myObject = new MyObject { Query = new Query(42) };

    var json = JsonConvert.SerializeObject(myObject);

    myObject = JsonConvert.DeserializeObject<MyObject>(json);
    Console.WriteLine(myObject.Query.MyProperty);
}

// Define other methods and classes here
public class AutofacContractResolver : DefaultContractResolver
{
    private readonly IContainer _container;

    public AutofacContractResolver(IContainer container)
    {
        _container = container;
    }

    protected override JsonConverter ResolveContractConverter(Type objectType)
    {
        var customObjectCreatorType = typeof(ICustomObjectCreator<>).MakeGenericType(objectType);
        if (!_container.IsRegistered(customObjectCreatorType))
            return base.ResolveContractConverter(objectType);

        var customConverterType = typeof(CustomConverter<>).MakeGenericType(objectType);
        return (JsonConverter) _container.Resolve(customConverterType);
    }
}

public class CustomConverter<T> : JsonConverter
{
    // This should be created via AutoFac
    public ICustomObjectCreator<T> ObjectCreator { get; }

    // This default constructr always gets called
    public CustomConverter() { }

    // I want to call this constructor
    public CustomConverter(ICustomObjectCreator<T> objectCreator)
    {
        Console.WriteLine("Constructor called");
        ObjectCreator = objectCreator;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;

        // Load JObject from stream 
        var jObject = JObject.Load(reader);

        // Create target object based on JObject 
        var target = Create(objectType, jObject);

        // Populate the object properties 
        serializer.Populate(jObject.CreateReader(), target);

        return target;
    }

    protected T Create(Type objectType, JObject jObject)
    {
        var type = jObject.GetValue("type", StringComparison.OrdinalIgnoreCase)?.Value<string>();
        return ObjectCreator.Create(type);
    }
}

public interface ICustomObjectCreator<out T> 
{
    T Create(string type);
}

public class QueryObjectCreator : ICustomObjectCreator<IQuery>
{
    public IQuery Create(string type)
    {
        Console.WriteLine("Create called");

        // ... some logic to create a concrete object
        var concreteObject = new Query();
        return (IQuery)concreteObject;
    }
}

public interface IQuery
{
    int MyProperty { get; set; }
}

public class Query : IQuery
{
    public int MyProperty { get; set; }

    public Query()
    {
    }

    public Query(int myProperty)
    {
        MyProperty = myProperty;
    }
}

public class MyObject
{
    public IQuery Query { get; set; }
}

Выход должен быть

Constructor called
Create called
42

Вот ссылка .NET Fiddle для образца.

Ответ 2

Предположим, что параметр autofac выглядит следующим образом:

public class AutofacContractResolver : DefaultContractResolver
{
    private readonly IContainer _container;

    public AutofacContractResolver(IContainer container)
    {
        _container = container;
    }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        JsonObjectContract contract = base.CreateObjectContract(objectType);

        // use Autofac to create types that have been registered with it
        if (_container.IsRegistered(objectType))
        {
           contract.DefaultCreator = () => _container.Resolve(objectType);
        }  

        return contract;
    }
}

Затем предположим, что ваш класс выглядит следующим образом:

public class TaskController
{
    private readonly ITaskRepository _repository;
    private readonly ILogger _logger;

    public TaskController(ITaskRepository repository, ILogger logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public ITaskRepository Repository
    {
        get { return _repository; }
    }

    public ILogger Logger
    {
        get { return _logger; }
    }
}

Следовательно, использование резольвера в десериализации может быть следующим:

ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<TaskRepository>().As<ITaskRepository>();
builder.RegisterType<TaskController>();
builder.Register(c => new LogService(new DateTime(2000, 12, 12))).As<ILogger>();

IContainer container = builder.Build();

AutofacContractResolver contractResolver = new AutofacContractResolver(container);

string json = @"{
      'Logger': {
        'Level':'Debug'
      }
}";

// ITaskRespository and ILogger constructor parameters are injected by Autofac 
TaskController controller = JsonConvert.DeserializeObject<TaskController>(json, new JsonSerializerSettings
{
    ContractResolver = contractResolver
});

Console.WriteLine(controller.Repository.GetType().Name);

Более подробную информацию вы можете найти в http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm