【问题标题】:How can Json.NET perform dependency injection during deserialization?Json.NET在反序列化过程中如何进行依赖注入?
【发布时间】:2014-02-18 16:33:52
【问题描述】:

当我有一个没有默认构造函数的类,即使用依赖注入来传递它的依赖关系时,Newtonsoft.Json 可以创建这样的对象吗?

例如:

public class SomeFoo
{
    private readonly IFooDependency _dependency;

    public SomeFoo(IFooDependency dependency){
        if(dependency == null)
            throw new ArgumentNullException("dependency");

        _dependency = dependency;
    }

    public string Data { get; set; }
    public int MoreData { get; set; }

    public void DoFoo(){
        Data = _dependency.GetFooData();
        MoreData = _dependency.GetMoreFooDate();
    }
}

在序列化过程中,我只关心存储 Data 和 MoreData(以及对象的类型,但暂时不要让事情复杂化)。现在,要反序列化,我可以调用类似

var obj = JsonConvert.DeserializeObject<SomeFoo>(jsonText);

如何让 JsonConvert 知道我的 DI 容器?

(注意:解决方法是在我的类中始终使用默认构造函数,并在其中调用 Service Locator 以获取我需要的任何依赖项。我只是在寻找一些更干净的解决方案,而不用这样污染我的类构造函数)。

【问题讨论】:

标签: c# json dependency-injection json.net ninject


【解决方案1】:

您不应该让JsonConvert 知道有关您的 DI 容器的任何信息。您遇到的问题是由应用程序设计中的缺陷引起的。这里的缺陷是你混合数据和行为

如果您将数据与行为分开,您的问题(以及许多其他问题)就会消失。您可以通过创建两个类来做到这一点:一个用于数据,另一个用于行为:

public class SomeFoo
{
    public string Data { get; set; }
    public int MoreData { get; set; }
}

public class SomeFooHandler
{
    private readonly IFooDependency _dependency;

    public SomeFooHandler(IFooDependency dependency) {
        _dependency = dependency;
    }

    public void Handle(SomeFoo foo) {
        foo.Data = _dependency.GetFooData();
        foo.MoreData = _dependency.GetMoreFooDate();
    }
}

由于现在数据和行为分离,SomeFoo 可以毫无问题地序列化,SomeFooHandler 可以简单地注入。 SomeFoo 变成了Parameter Object

【讨论】:

  • visitor pattern敲钟,这对我想要实现的目标很有帮助,谢谢!
  • 呃!呃,我不敢相信我没有意识到这一点。我正在制作一个任务执行运行器,并且我让任务接受它们的依赖项以及和选项。我会把它们分开,这样更干净。谢谢提醒。
  • 您为什么总是想要分离数据和行为?
  • @MarkT:这个论点表达得更清楚了here
  • 该参数仅在行为不需要序列化时才成立。如果确实如此,则此点无效。
【解决方案2】:

我同意 Steven 发布的关注点分离,以及 Mark Seemann 发布的here 的答案。 但是,如果您仍然想走这条路,这里有一个可能会有所帮助的解决方案:

继承CustomCreationConverter&lt;T&gt;:

internal class NinjectCustomConverter<T> : CustomCreationConverter<T> where T : class
{
    private readonly IResolutionRoot _serviceLocator;

    public NinjectCustomConverter(IResolutionRoot serviceLocator)
    {
        _serviceLocator = serviceLocator;
    }

    public override T Create(Type objectType)
    {
        return _serviceLocator.Get(objectType) as T;
    }
}

然后确保您也通过 DI 容器检索此转换器实例。下面的代码将反序列化对您的对象执行 DI:

var ninjectConverter = kernel.Get<NinjectCustomConverter<SerializedObject>>();
var settings = new JsonSerializerSettings();
settings.Converters.Add(ninjectConverter);

var instance = JsonConvert.DeserializeObject<SerializedObject>(json, settings);

Here is a complete working example.

【讨论】:

  • 我个人的选择是创建一个单独的 IContractResolver,它要么维护自己的依赖关系映射,要么直接使用 DI 解析器。这样,您可以简单地将 IContractResolver 注入执行反序列化的类型中,并且 IoC 仍然得到严格维护。这是一种非常相似的方法,但消除了对特定转换器的“依赖”。
  • 这只有在您可以访问SerializedObject时才有意义
  • @AndrewHanlon - 愿意分享您的 IContractResolver 实施,这听起来像是我在寻找的东西
  • @ElFik 抱歉,我手头没有很好的例子。但本质上,我已经覆盖了 DefaultContractResolver 中的 Create Parameter/Property 方法来注入依赖项。
  • 没有问题,我们通过在自定义 DefaultContractResolver 中覆盖 CreateObjectContract,从基本方法中获取 JsonObjectContract,然后使用 func 覆盖 contract.DefaultCreator 来调用构造函数。注入值的类型。
【解决方案3】:

如果您的目标是使用注入的依赖项来修改数据,那么您可以创建自定义Converter

有了这个,你应该能够注入你的依赖。类似于下面的代码:

 var settings = new JsonSerializerSettings
            {
                Converters = { new FooConverter<T>(injectedDependency) }
            };
 return JsonConvert.DeserializeObject<Dto>(json, settings);

关于如何创建自定义Converters的示例有很多,大家可以参考一下。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多