如何在 .net core 3.1 的 Newtonsoft JsonConverter 中注入依赖

How to inject dependency in Newtonsoft JsonConverter in .net core 3.1

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

我只想在属性级别使用它,而不是在全局级别使用它。因此,只有当指定属性来自某个 class(es).

时才应该执行

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:

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

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

我试过转头和其他各种选择,但没有成功。

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));
    }
}

register them in your startup ervices.AddSingleton<IConfigureOptions, JsonOptions>() (can't remember if IHttpContextAccessor is registered by default so you may need to register that one as well)

then in your Read/WriteJson methods use _accessor.HttpContext to access the context of the request

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

更重要的是,您的 api 应该允许内容协商,其中 Accept: application/json 给您一个 json 字符串,而 Accept: application/xml 给您一个 xml 字符串.

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

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

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

TL;DR; 为了使 Thomas 的 hack 工作,我需要将 IConfigureOptions<MvcNewtonsoftJsonOptions>.Configure()'s 实现更改为:

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

以下是它对我有用的方法:

Use a ContractResolver. (I was using Converter in my case).

自定义 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);
        }
    }
}

用于注册 ContractResolver 的 ServiceCollection 扩展:

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();
    ...
}

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