【问题标题】:How to inject dependency in Newtonsoft JsonConverter in .net core 3.1如何在 .net core 3.1 中的 Newtonsoft JsonConverter 中注入依赖项
【发布时间】:2021-10-11 02:02:14
【问题描述】:

我无法让依赖注入在 .net core 3.1 中为以下 Newtonsoft JsonConverter 工作。

我只想在属性级别使用它,而不是在全局级别。因此,它应该只在某个类的指定属性时才执行。

JsonConverter:

public class HelloWorldCustomConverter : JsonConverter<string>
{
    private readonly IMyService _myService;

    public HelloWorldCustomConverter(IMyService myService)
    {
        _myService = myService;
    }
    public override bool CanRead => false;
            
    public override string ReadJson(JsonReader reader, Type objectType, string existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, string value, JsonSerializer serializer)
    {
        // append a value using the injected service
        writer.WriteValue($"{value}-{myService.GetValue()}");
    }
}

用法:

public class MyClass
{   
    public string Title {  get; set; }

    [JsonConverter(typeof(HelloWorldCustomConverter))]
    public string Details {  get; set; }
}

它是 .NET Core 3.1 和 Newtonsoft.json 版本 13.0.1。

感谢任何帮助,谢谢。

编辑 1:

我从 StackOverflow 查了很多答案,但到目前为止没有一个对我有用。他们中的大多数都已经过时了,或者缺少一些东西来使其正常工作。其中很少有我已经检查过但对我不起作用:

编辑 2:我尝试了建议作为重复参考的帖子,但它在我的情况下不起作用。

https://stackoverflow.com/questions/53288633/net-core-api-custom-json-resolver-based-on-request-values

我尝试过转头和其他各种选择,但没有运气。

James(日期:2108)建议的解决方法之一没有奏效。

参考:https://github.com/JamesNK/Newtonsoft.Json/issues/1910

你可以试试

public class JsonOptions : IConfigureOptions<MvcJsonOptions>
   {
    IHttpContextAccessor _accessor;

    public JsonOptions(IHttpContextAccessor accessor)
    {
        _accessor = accessor;
    }
    public virtual void Configure(MvcJsonOptions options)
    {
        options.SerializerSettings.Converters.Add(new MyCustomConverter(_accessor));
    }
}

在您的启动中注册它们 ervices.AddSingleton() (不记得是否默认注册了 IHttpContextAccessor 所以 您可能还需要注册那个)

然后在您的 Read/WriteJson 方法中使用 _accessor.HttpContext 来 访问请求的上下文

【问题讨论】:

  • 这方面有很多问题和答案,没有一个对你有用吗?
  • @TheGeneral - 是的,我做到了。用一些参考更新了我的问题。
  • This answerMvcJsonOptions 适当更改为MvcNewtonsoftJsonOptions 对您不起作用?
  • 作为替代方案,这可能对您有用:Pass additional data to JsonConverter
  • @SunnySharma 我需要将依赖项注入到我的自定义JsonConverter 实现中,它不能有非默认构造函数,否则会导致异常。你的答案反过来使用了一个自定义的ContractResolver,它显然可以有一个非默认的构造函数——所以我们的个人问题有点不同。我在下面发布了我的答案。它基于 Thomas 的 hack,但稍作修改以处理我的特定设置。

标签: c# asp.net-core json.net


【解决方案1】:

Thomas' blog post 中的 cmets 来看,您已经尝试过他的方法。无论您是否设法使其正常工作,当我尝试实施 Thomas 的 hack 时,我都会发布我遇到的问题的解决方案 - 也许这会对其他人有所帮助。

在我的设置中,自定义 JsonConverter 实际上不是由 MVC 框架直接实例化,而是通过 Newtonsoft 的 JToken.ToObject() 间接实例化,它创建了一个默认为 JsonSerializerSettingsJsonSerializer。在ToObject() 的调用链下方,我的自定义JsonConverter 使用这些默认设置进行实例化。

TL;DR; 为了让 Thomas 的 hack 发挥作用,我需要将 IConfigureOptions&lt;MvcNewtonsoftJsonOptions&gt;.Configure()'s 的实现更改为:

public void Configure(MvcNewtonsoftJsonOptions options)
{
    JsonConvert.DefaultSettings = () =>
    {
        var settings = new JsonSerializerSettings();
        settings.Converters.Add(new ServiceProviderDummyConverter(_httpContextAccessor, _serviceProvider));
        return settings;
    };
}

【讨论】:

    【解决方案2】:

    这对我来说是这样的:

    使用 ContractResolver。 (就我而言,我使用的是 Converter)。

    自定义 ContractResolver。根据需要更改逻辑。

    using HelloWorld.Attributes;
    using HelloWorld.Helpers;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Serialization;
    using System;
    using System.Reflection;
    
    namespace HelloWorld.Serializers
    {
        public class MyCustomContractResolver : CamelCasePropertyNamesContractResolver
        {       
            private readonly IServiceProvider _serviceProvider;
            public MyCustomContractResolver(IServiceProvider serviceProvider)
            {           
                _serviceProvider = serviceProvider;
            }
            protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
            {
                var property = base.CreateProperty(member, memberSerialization);
    
                // this condition is specific to my case, just to showcase how I'm accessing value from HTTP Context
                if (Attribute.IsDefined(member, typeof(MyCustomAttribute),true))
                {
                    if (property.PropertyType == typeof(string))
                    {
                        PropertyInfo propertyInfo = member as PropertyInfo;
                        // access required services here
                        var contextAccessor = _serviceProvider.GetRequiredService<IHttpContextAccessor>();
                        var customHelper = _serviceProvider.GetRequiredService<ICustomHelper>();
                        var attribute = (MyCustomAttribute)member.GetCustomAttribute(typeof(MyCustomAttributeAttribute));
                        property.ValueProvider = new StringValueProvider(propertyInfo, customHelper, contextAccessor, attribute);
                    }
                }
    
                return property;
            }
    
            public class StringValueProvider : IValueProvider
            {
                private PropertyInfo _targetProperty;
                private readonly ICustomHelper _customHelper;
                private readonly IHttpContextAccessor _httpContextAccessor;
                private readonly MyCustomAttribute _attribute;
                public StringValueProvider(
                    PropertyInfo targetProperty, 
                    ICustomHelper customHelper, 
                    IHttpContextAccessor httpContextAccessor,
                    MyCustomAttribute attribute)
                {
                    _targetProperty = targetProperty;
                    _customHelper = customHelper;
                    _httpContextAccessor = httpContextAccessor;
                    _attribute = attribute;
                }
    
                // SetValue gets called by Json.Net during deserialization.
                // The value parameter has the original value read from the JSON;
                // target is the object on which to set the value.
                public void SetValue(object target, object value)
                {
                    _targetProperty.SetValue(target, value);
                }
    
                // GetValue is called by Json.Net during serialization.
                // The target parameter has the object from which to read the value;
                // the return value is what gets written to the JSON
                public object GetValue(object target)
                {
                    object value = _targetProperty.GetValue(target);
                    var userId = _httpContextAccessor.HttpContext.Request.Headers["UserId"].ToString();
                    return value == null ? value : _customHelper.SetGreetingsTextForUser(value.ToString(),userId, _attribute.UserRole);
                }
            }
        }
    }
    

    带有 serviceProvider 注入的 MvcNewtonsoftJsonOptionsWrapper

    using HelloWorld.Serializers;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Options;
    using System;
    
    namespace HelloWorld.Extensions
    {
        public class MvcNewtonsoftJsonOptionsWrapper : IConfigureOptions<MvcNewtonsoftJsonOptions>
        {
            IServiceProvider ServiceProvider;
            public MvcNewtonsoftJsonOptionsWrapper(IServiceProvider serviceProvider)
            {
                this.ServiceProvider = serviceProvider;
            }
            public void Configure(MvcNewtonsoftJsonOptions options)
            {
                options.SerializerSettings.ContractResolver = new MyCustomContractResolver(ServiceProvider);
            }
        }
    }
    

    ServiceCollection Extension 注册 ContractResolver:

    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection MyCustomContractResolver(this IServiceCollection services)
        {
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.AddTransient<IConfigureOptions<MvcNewtonsoftJsonOptions>, MvcNewtonsoftJsonOptionsWrapper>();
            return services;
        }
    }
    

    在 Startup.cs 文件中注册 DI 中的 ContractResolver:

    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddAppendSasTokenContractResolver();
        ...
    }
    

    就是这样。让我知道这是否适合你!

    【讨论】:

      【解决方案3】:

      恕我直言,您要实现的目标存在重大问题。序列化控制器方法的结果不应包含任何逻辑。

      更重要的是,你的 api 应该允许内容协商,Accept: application/json 给你一个 json 字符串,Accept: application/xml 给你一个 xml 字符串。

      相反,您应该利用依赖注入,在您的其他服务中注入 MyService 并在那里调用 myResult.whatever = myService.GetValue()

      【讨论】:

      • 感谢@Pieterjan,但这不是我想要的。
      猜你喜欢
      • 2022-01-23
      • 2020-06-04
      • 2020-12-20
      • 2021-07-15
      • 1970-01-01
      • 2016-05-22
      • 1970-01-01
      • 1970-01-01
      • 2020-05-20
      相关资源
      最近更新 更多