Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove the Newtonsoft.Json package and migrate to System.Text.Json #2124

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5d279a1
Merge pull request #9 from ThreeMammals/develop
EngRajabi Oct 12, 2023
6d95a53
Merge branch 'ThreeMammals:develop' into develop
EngRajabi Oct 13, 2023
c36da21
Merge branch 'ThreeMammals:develop' into develop
EngRajabi Oct 19, 2023
acbc6c6
Merge branch 'ThreeMammals:develop' into develop
EngRajabi Nov 30, 2023
2de7574
Merge pull request #11 from ThreeMammals/develop
EngRajabi Dec 21, 2023
9d73831
Merge branch 'ThreeMammals:develop' into develop
EngRajabi Jan 22, 2024
a0b930e
Merge branch 'ThreeMammals:develop' into develop
EngRajabi Feb 22, 2024
9513d0f
Merge branch 'ThreeMammals:develop' into develop
EngRajabi Feb 22, 2024
ad42213
Merge branch 'ThreeMammals:develop' into develop
EngRajabi Mar 28, 2024
4cdfcfb
Merge branch 'ThreeMammals:develop' into develop
EngRajabi Apr 23, 2024
f5f449c
Merge branch 'ThreeMammals:develop' into develop
EngRajabi Jun 4, 2024
75270dc
Merge branch 'ThreeMammals:develop' into develop
EngRajabi Jul 15, 2024
4ca1413
feat: add prometheus
EngRajabi Jul 15, 2024
e9b7c57
Merge remote-tracking branch 'origin/develop' into feat_metric
EngRajabi Jul 15, 2024
610ddf8
feat: rm newtonsoft
EngRajabi Jul 15, 2024
8241193
Merge branch 'ThreeMammals:develop' into develop
EngRajabi Jul 19, 2024
afc261a
fix: merge from develop
EngRajabi Jul 19, 2024
a7f85da
fix: rm un use
EngRajabi Jul 19, 2024
864f4f1
feat: add system text json config
EngRajabi Jul 19, 2024
c36a460
fix: AcceptanceTest ConsulServiceDiscovery
MohammadAminPourmoradian Jul 19, 2024
8115326
fix: fix indent
EngRajabi Jul 19, 2024
96f893d
feat: change un use
EngRajabi Jul 19, 2024
3cb49ba
Merge branch 'feat_metric' of https://github.com/EngRajabi/Ocelot int…
MohammadAminPourmoradian Jul 19, 2024
9bdae33
feat: add json benchmark
EngRajabi Jul 19, 2024
ce0c11e
feat: add json serialize result json
EngRajabi Jul 19, 2024
40302cf
fix
EngRajabi Jul 19, 2024
ee6ea24
update doc
EngRajabi Jul 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Clean.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FOR /d /r . %%d in (bin,obj) do @if exist "%%d" rd /s/q "%%d"
FOR /F "tokens=*" %%G IN ('DIR /B /AD /S Packages') DO RMDIR /S /Q "%%G"
FOR /F "tokens=*" %%G IN ('DIR /B /AD /S .vs') DO RMDIR /S /Q "%%G"
FOR /F "tokens=*" %%G IN ('DIR /B /AD /S .vscode') DO RMDIR /S /Q "%%G"
FOR /F "tokens=*" %%G IN ('DIR /B /AD /S TestResults') DO RMDIR /S /Q "%%G"
FOR /F "tokens=*" %%G IN ('DIR /B /AD /S AppPackages') DO RMDIR /S /Q "%%G"
DEL /Q /F /S "*.csproj.user"
DEL /Q /F /S "*.sln.DotSettings.user"
77 changes: 8 additions & 69 deletions docs/features/dependencyinjection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,14 @@ Current `implementation <https://github.com/search?q=repo%3AThreeMammals%2FOcelo
.AddApplicationPart(assembly)
.AddControllersAsServices()
.AddAuthorization()
.AddNewtonsoftJson();
.AddJsonOptions(op =>
{
op.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
op.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
op.JsonSerializerOptions.WriteIndented = false;
op.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
op.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
});
}

The method cannot be overridden. It is not virtual, and there is no way to override current behavior by inheritance.
Expand All @@ -154,74 +161,6 @@ The next section shows you an example of designing custom Ocelot pipeline by cus

.. _di-custom-builder:

Custom Builder
--------------

**Goal**: Replace ``Newtonsoft.Json`` services with ``System.Text.Json`` services.

Problem
^^^^^^^

The main `AddOcelot`_ method adds
`Newtonsoft JSON <https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.newtonsoftjsonmvccorebuilderextensions.addnewtonsoftjson>`_ services
by the ``AddNewtonsoftJson`` extension method in default builder (`AddDefaultAspNetServices`_ method).
The ``AddNewtonsoftJson`` method calling was introduced in old .NET and Ocelot releases which was necessary when Microsoft did not launch the ``System.Text.Json`` library,
but now it affects normal use, so we have an intention to solve the problem.

Modern `JSON services <https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.mvccoremvccorebuilderextensions.addjsonoptions>`_
out of `the box <https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.mvccoremvccorebuilderextensions>`_
will help to configure JSON settings by the ``JsonSerializerOptions`` property for JSON formatters during (de)serialization.

Solution
^^^^^^^^

We have the following methods in `ServiceCollectionExtensions`_ class:

.. code-block:: csharp

IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, Func<IMvcCoreBuilder, Assembly, IMvcCoreBuilder> customBuilder);
IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, IConfiguration configuration, Func<IMvcCoreBuilder, Assembly, IMvcCoreBuilder> customBuilder);

These methods with custom builder allow you to use your any desired JSON library for (de)serialization.
But we are going to create custom ``MvcCoreBuilder`` with support of JSON services, such as ``System.Text.Json``.
To do that we need to call ``AddJsonOptions`` extension of the ``MvcCoreMvcCoreBuilderExtensions`` class
(NuGet package: `Microsoft.AspNetCore.Mvc.Core <https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core/>`_) in **Startup.cs**:

.. code-block:: csharp

using Microsoft.Extensions.DependencyInjection;
using Ocelot.DependencyInjection;
using System.Reflection;

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddLogging()
.AddMiddlewareAnalysis()
.AddWebEncoders()
// Add your custom builder
.AddOcelotUsingBuilder(MyCustomBuilder);
}

private static IMvcCoreBuilder MyCustomBuilder(IMvcCoreBuilder builder, Assembly assembly)
{
return builder
.AddApplicationPart(assembly)
.AddControllersAsServices()
.AddAuthorization()

// Replace AddNewtonsoftJson() by AddJsonOptions()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.WriteIndented = true; // use System.Text.Json
});
}
}

The sample code provides settings to render JSON as indented text rather than compressed plain JSON text without spaces.
This is just one common use case, and you can add additional services to the builder.

------------------------------------------------------------------

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Ocelot.Cache;
using Ocelot.Configuration;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Repository;
using Ocelot.Infrastructure;
using Ocelot.Logging;
using Ocelot.Provider.Consul.Interfaces;
using Ocelot.Responses;
using System.Text;
using System.Text.Json;

namespace Ocelot.Provider.Consul;

Expand Down Expand Up @@ -53,14 +54,14 @@ public async Task<Response<FileConfiguration>> Get()

var bytes = queryResult.Response.Value;
var json = Encoding.UTF8.GetString(bytes);
var consulConfig = JsonConvert.DeserializeObject<FileConfiguration>(json);
var consulConfig = JsonSerializer.Deserialize<FileConfiguration>(json, JsonSerializerOptionsExtensions.Web);

return new OkResponse<FileConfiguration>(consulConfig);
}

public async Task<Response> Set(FileConfiguration ocelotConfiguration)
{
var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented);
var json = JsonSerializer.Serialize(ocelotConfiguration, JsonSerializerOptionsExtensions.WebWriteIndented);
var bytes = Encoding.UTF8.GetBytes(json);
var kvPair = new KVPair(_configurationKey)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Microsoft.AspNetCore.Hosting;
using Newtonsoft.Json;
using Ocelot.Configuration.ChangeTracking;
using Ocelot.Configuration.File;
using Ocelot.DependencyInjection;
using Ocelot.Infrastructure;
using Ocelot.Responses;
using System.Text.Json;
using FileSys = System.IO.File;

namespace Ocelot.Configuration.Repository
Expand Down Expand Up @@ -49,14 +50,14 @@ public Task<Response<FileConfiguration>> Get()
jsonConfiguration = FileSys.ReadAllText(_environmentFile.FullName);
}

var fileConfiguration = JsonConvert.DeserializeObject<FileConfiguration>(jsonConfiguration);
var fileConfiguration = JsonSerializer.Deserialize<FileConfiguration>(jsonConfiguration, JsonSerializerOptionsExtensions.Web);

return Task.FromResult<Response<FileConfiguration>>(new OkResponse<FileConfiguration>(fileConfiguration));
}

public Task<Response> Set(FileConfiguration fileConfiguration)
{
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented);
var jsonConfiguration = JsonSerializer.Serialize(fileConfiguration, JsonSerializerOptionsExtensions.WebWriteIndented);

lock (_lock)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Ocelot.Infrastructure;
using Ocelot.Logging;
using System.Text.Json;

namespace Ocelot.Configuration.Repository
{
Expand Down Expand Up @@ -68,7 +69,7 @@ private async Task Poll()

if (fileConfig.IsError)
{
_logger.LogWarning(() =>$"error geting file config, errors are {string.Join(',', fileConfig.Errors.Select(x => x.Message))}");
_logger.LogWarning(() => $"error geting file config, errors are {string.Join(',', fileConfig.Errors.Select(x => x.Message))}");
return;
}

Expand All @@ -95,7 +96,7 @@ private async Task Poll()
/// <returns>hash of the config.</returns>
private static string ToJson(FileConfiguration config)
{
var currentHash = JsonConvert.SerializeObject(config);
var currentHash = JsonSerializer.Serialize(config, JsonSerializerOptionsExtensions.Web);
return currentHash;
}

Expand Down
17 changes: 9 additions & 8 deletions src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Memory;
using Newtonsoft.Json;
using Microsoft.Extensions.Configuration.Memory;
using Ocelot.Configuration.File;

using Ocelot.Infrastructure;
using System.Text.Json;

namespace Ocelot.DependencyInjection
{
/// <summary>
Expand Down Expand Up @@ -98,8 +99,8 @@ public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder
private static IConfigurationBuilder ApplyMergeOcelotJsonOption(IConfigurationBuilder builder, MergeOcelotJson mergeTo, string json,
string primaryConfigFile, bool? optional, bool? reloadOnChange)
{
return mergeTo == MergeOcelotJson.ToMemory ?
builder.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) :
return mergeTo == MergeOcelotJson.ToMemory ?
builder.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) :
AddOcelotJsonFile(builder, json, primaryConfigFile, optional, reloadOnChange);
}

Expand Down Expand Up @@ -132,7 +133,7 @@ private static string GetMergedOcelotJson(string folder, IWebHostEnvironment env
}

var lines = File.ReadAllText(file.FullName);
var config = JsonConvert.DeserializeObject<FileConfiguration>(lines);
var config = JsonSerializer.Deserialize<FileConfiguration>(lines, JsonSerializerOptionsExtensions.Web);
if (file.Name.Equals(globalFileInfo.Name, StringComparison.OrdinalIgnoreCase) &&
file.FullName.Equals(globalFileInfo.FullName, StringComparison.OrdinalIgnoreCase))
{
Expand All @@ -143,7 +144,7 @@ private static string GetMergedOcelotJson(string folder, IWebHostEnvironment env
fileConfiguration.Routes.AddRange(config.Routes);
}

return JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented);
return JsonSerializer.Serialize(fileConfiguration, JsonSerializerOptionsExtensions.WebWriteIndented);
}

/// <summary>
Expand All @@ -160,7 +161,7 @@ private static string GetMergedOcelotJson(string folder, IWebHostEnvironment env
public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, FileConfiguration fileConfiguration,
string primaryConfigFile = null, bool? optional = null, bool? reloadOnChange = null) // optional injections
{
var json = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented);
var json = JsonSerializer.Serialize(fileConfiguration, JsonSerializerOptionsExtensions.WebWriteIndented);
return AddOcelotJsonFile(builder, json, primaryConfigFile, optional, reloadOnChange);
}

Expand Down
20 changes: 13 additions & 7 deletions src/Ocelot/DependencyInjection/OcelotBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Ocelot.Authorization;
using Ocelot.Cache;
using Ocelot.Claims;
using Ocelot.Configuration;
using Ocelot.Configuration.ChangeTracking;
Expand All @@ -27,7 +26,6 @@
using Ocelot.Multiplexer;
using Ocelot.PathManipulation;
using Ocelot.QueryStrings;
using Ocelot.RateLimiting;
using Ocelot.Request.Creator;
using Ocelot.Request.Mapper;
using Ocelot.Requester;
Expand All @@ -39,6 +37,9 @@
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.WebSockets;
using System.Reflection;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization;
using System.Text.Unicode;

namespace Ocelot.DependencyInjection
{
Expand Down Expand Up @@ -111,7 +112,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo
Services.TryAddSingleton<IHttpHandlerOptionsCreator, HttpHandlerOptionsCreator>();
Services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
Services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>();

Services.TryAddSingleton<IOcelotConfigurationChangeTokenSource, OcelotConfigurationChangeTokenSource>();
Services.TryAddSingleton<IOptionsMonitor<IInternalConfiguration>, OcelotConfigurationMonitor>();

Expand Down Expand Up @@ -157,7 +158,6 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo
/// <summary>
/// Adds default ASP.NET services which are the minimal part of the gateway core.
/// <para>
/// Finally the builder adds Newtonsoft.Json services via the <see cref="NewtonsoftJsonMvcCoreBuilderExtensions.AddNewtonsoftJson(IMvcCoreBuilder)"/> extension-method.<br/>
/// To remove these services, use custom builder in the <see cref="ServiceCollectionExtensions.AddOcelotUsingBuilder(IServiceCollection, Func{IMvcCoreBuilder, Assembly, IMvcCoreBuilder})"/> extension-method.
/// </para>
/// </summary>
Expand All @@ -171,8 +171,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo
/// Warning! The following <see cref="IMvcCoreBuilder"/> extensions being called:<br/>
/// - <see cref="MvcCoreMvcCoreBuilderExtensions.AddApplicationPart(IMvcCoreBuilder, Assembly)"/><br/>
/// - <see cref="MvcCoreMvcCoreBuilderExtensions.AddControllersAsServices(IMvcCoreBuilder)"/><br/>
/// - <see cref="MvcCoreMvcCoreBuilderExtensions.AddAuthorization(IMvcCoreBuilder)"/><br/>
/// - <see cref="NewtonsoftJsonMvcCoreBuilderExtensions.AddNewtonsoftJson(IMvcCoreBuilder)"/>, removable.
/// - <see cref="MvcCoreMvcCoreBuilderExtensions.AddAuthorization(IMvcCoreBuilder)"/>
/// </para>
/// </remarks>
/// <param name="builder">The default builder being returned by <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/> extension-method.</param>
Expand All @@ -189,7 +188,14 @@ protected IMvcCoreBuilder AddDefaultAspNetServices(IMvcCoreBuilder builder, Asse
.AddApplicationPart(assembly)
.AddControllersAsServices()
.AddAuthorization()
.AddNewtonsoftJson();
.AddJsonOptions(op =>
{
op.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
op.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
op.JsonSerializerOptions.WriteIndented = false;
op.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
op.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
});
}

public IOcelotBuilder AddSingletonDefinedAggregator<T>()
Expand Down
29 changes: 29 additions & 0 deletions src/Ocelot/Infrastructure/JsonSerializerOptionsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Unicode;

namespace Ocelot.Infrastructure
{
public static class JsonSerializerOptionsExtensions
{
public static readonly JsonSerializerOptions Web = new()
{
PropertyNameCaseInsensitive = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString,
ReferenceHandler = ReferenceHandler.IgnoreCycles,
WriteIndented = false,
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),

};

public static readonly JsonSerializerOptions WebWriteIndented = new()
{
PropertyNameCaseInsensitive = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString,
ReferenceHandler = ReferenceHandler.IgnoreCycles,
WriteIndented = true,
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
};
}
}
4 changes: 2 additions & 2 deletions src/Ocelot/Metadata/DownstreamRouteExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Ocelot.Configuration;
using Ocelot.Infrastructure;
using System.Globalization;
using System.Reflection;
using System.Text.Json;

namespace Ocelot.Metadata;
Expand Down Expand Up @@ -74,7 +74,7 @@ public static T GetMetadata<T>(this DownstreamRoute downstreamRoute, string key,
}

return (T)ConvertTo(typeof(T), metadataValue, downstreamRoute.MetadataOptions,
jsonSerializerOptions ?? new JsonSerializerOptions(JsonSerializerDefaults.Web));
jsonSerializerOptions ?? JsonSerializerOptionsExtensions.Web);
}

/// <summary>
Expand Down
Loading