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

Posted

技术标签:

【中文标题】如何在 .net core 3.1 中的 Newtonsoft JsonConverter 中注入依赖项【英文标题】:How to inject dependency in Newtonsoft JsonConverter in .net core 3.1 【发布时间】: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:

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

Cannot replace default JSON contract resolver in ASP.Core 3 Custom JsonConverter with parameters in .NET Core https://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm .Net Core Api - Custom JSON Resolver based on Request Values http://www.dotnet-programming.com/post/2017/05/07/Aspnet-core-Deserializing-Json-with-Dependency-Injection.aspx

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

https://***.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 answer 从MvcJsonOptions 适当更改为MvcNewtonsoftJsonOptions 对您不起作用? 作为替代方案,这可能对您有用:Pass additional data to JsonConverter。 @SunnySharma 我需要将依赖项注入到我的自定义JsonConverter 实现中,它不能有非默认构造函数,否则会导致异常。你的答案反过来使用了一个自定义的ContractResolver,它显然可以有一个非默认的构造函数——所以我们的个人问题有点不同。我在下面发布了我的答案。它基于 Thomas 的 hack,但稍作修改以处理我的特定设置。 【参考方案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,但这不是我想要的。

以上是关于如何在 .net core 3.1 中的 Newtonsoft JsonConverter 中注入依赖项的主要内容,如果未能解决你的问题,请参考以下文章

如何更改 .NET Core 3.1 API 中的路由错误

如何在启动类中读取应用程序 URL,特别是在 .net core 3.1 中的“ConfigureService”方法中?

如何从 ASP.NET Core 3.1 中的存储库类创建确认电子邮件回调

如何从 Asp.NET Core 3.1 启动类访问 launchSettings.json 中的 `applicationUrl` 属性?

在 .net core 3.1 中,如何从不记名令牌返回数据?

登录 ASP.NET Core 3.1 后如何获取用户信息