Skip to content

Commit

Permalink
make NewtonsoftJson and System.Text.Json works side by side. (#4430)
Browse files Browse the repository at this point in the history
* make NewtonsoftJson and System.Text.Json works side by side.
The idea is instead of using AddNewtonsoftJson to all controllers,make an attribute to use Newtensoft as Object result's formatter, and apply this attribute to all controllers that in Elsa.Server.Api namespace.

* update
  • Loading branch information
liuliang-wt committed Sep 14, 2023
1 parent 8a97a19 commit db847f9
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using Elsa.Server.Api.Helpers;
using Elsa.Server.Api.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Elsa.Server.Api.Attributes
{
/// <summary>
/// Mark a controller to use Newtonsoft as the formatter
/// </summary>
public class NewtonsoftJsonFormatterAttribute : ActionFilterAttribute, IControllerModelConvention, IActionModelConvention
{
public void Apply(ControllerModel controller)
{
foreach (var action in controller.Actions)
{
Apply(action);
}
}

public void Apply(ActionModel action)
{
// Set the model binder to NewtonsoftJsonBodyModelBinder for parameters that are bound to the request body.
var parameters = action.Parameters.Where(p => p.BindingInfo?.BindingSource == BindingSource.Body);
foreach (var p in parameters)
{
if (p.BindingInfo != null)
{
p.BindingInfo.BinderType = typeof(NewtonsoftJsonBodyModelBinder);
}
}
}

public override void OnActionExecuted(ActionExecutedContext context)
{
//Use default MvcNewtonsoftJsonOptions injected globally
var jsonOptions = context.HttpContext.RequestServices.GetService<IOptions<MvcNewtonsoftJsonOptions>>();
var jsonSettings = jsonOptions?.Value.SerializerSettings??new JsonSerializerSettings();
if (context.Controller.GetType().FullName?.StartsWith("Elsa.Server.Api")==true)
{
//If controller's namespace start with Elsa.Server.Api.Endpoints.WorkflowDefinitions, use GetSettingsForWorkflowDefinition get get SerializerSettings
if (context.Controller.GetType().FullName?.StartsWith("Elsa.Server.Api.Endpoints.WorkflowDefinitions")==true)
{
jsonSettings = SerializationHelper.GetSettingsForWorkflowDefinition();
}
//If controller's namespace start with Elsa.Server.Api, use GetSettingsForEndpoint get get SerializerSettings
else
{
jsonSettings = SerializationHelper.GetSettingsForEndpoint();
}
}
if (context.Result is ObjectResult objectResult)
{

objectResult.Formatters.RemoveType<SystemTextJsonOutputFormatter>();
objectResult.Formatters.Add(new NewtonsoftJsonOutputFormatter(
jsonSettings,
context.HttpContext.RequestServices.GetRequiredService<ArrayPool<char>>(),
context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value,
context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcNewtonsoftJsonOptions>>().Value));
}
//For JsonResult, there is no way to change the formatter, so change them to Object result
else if (context.Result is JsonResult jr)
{
var obj = new ObjectResult(jr.Value);

obj.Formatters.RemoveType<SystemTextJsonOutputFormatter>();
obj.Formatters.Add(new NewtonsoftJsonOutputFormatter(
jsonSettings,
context.HttpContext.RequestServices.GetRequiredService<ArrayPool<char>>(),
context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value,
context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcNewtonsoftJsonOptions>>().Value));
context.Result = obj;
}
else
{
base.OnActionExecuted(context);
}
}
}

public class NewtonsoftJsonBodyModelBinder : BodyModelBinder
{
public NewtonsoftJsonBodyModelBinder(
ILoggerFactory loggerFactory,
ArrayPool<char> charPool,
IHttpRequestStreamReaderFactory readerFactory,
ObjectPoolProvider objectPoolProvider,
IOptions<MvcOptions> mvcOptions,
IOptions<MvcNewtonsoftJsonOptions> jsonOptions)
: base(GetInputFormatters(loggerFactory, charPool, objectPoolProvider, mvcOptions, jsonOptions), readerFactory)
{
}

private static IInputFormatter[] GetInputFormatters(
ILoggerFactory loggerFactory,
ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider,
IOptions<MvcOptions> mvcOptions,
IOptions<MvcNewtonsoftJsonOptions> jsonOptions)
{
var jsonOptionsValue = jsonOptions.Value;
return new IInputFormatter[]
{
new NewtonsoftJsonInputFormatter(
loggerFactory.CreateLogger<NewtonsoftJsonBodyModelBinder>(),
jsonOptionsValue.SerializerSettings,
charPool,
objectPoolProvider,
mvcOptions.Value,
jsonOptionsValue)
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Elsa.Server.Api.Attributes;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Elsa.Server.Api.Extensions
{
/// <summary>
/// Auto apply NewtonsoftJsonFormatterAttribute to all controllers in Elsa.Server.Api
/// </summary>
public class ElsaNewtonsoftJsonConvention : IControllerModelConvention
{

public ElsaNewtonsoftJsonConvention()
{
}

public void Apply(ControllerModel controller)
{
if (ShouldApplyConvention(controller))
{
var formatterAttribute = new NewtonsoftJsonFormatterAttribute();

// The attribute itself also implements IControllerModelConvention so we have to call that one as well.
// This way, the NewtonsoftJsonBodyModelBinder will be properly connected to the controller actions.
formatterAttribute.Apply(controller);

controller.Filters.Add(formatterAttribute);
}
}

private bool ShouldApplyConvention(ControllerModel controller)
{
return controller?.ControllerType?.FullName?.StartsWith("Elsa.Server.Api")==true &&
!controller.Attributes.Any(x => x.GetType() == typeof(NewtonsoftJsonFormatterAttribute));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Elsa;
using Elsa.Models;
using Elsa.Server.Api;
using Elsa.Server.Api.Extensions;
using Elsa.Server.Api.Extensions.SchemaFilters;
using Elsa.Server.Api.Mapping;
using Elsa.Server.Api.Services;
Expand Down Expand Up @@ -30,7 +31,8 @@ public static IServiceCollection AddElsaApiEndpoints(this IServiceCollection ser

var setupNewtonsoftJson = apiOptions.SetupNewtonsoftJson ?? (_ => { });

services.AddControllers().AddNewtonsoftJson(setupNewtonsoftJson);
//Don't set Newtonsoft globally
services.AddControllers();//.AddNewtonsoftJson(setupNewtonsoftJson);
services.AddRouting(options => { options.LowercaseUrls = true; });

services.AddVersionedApiExplorer(o =>
Expand All @@ -54,7 +56,11 @@ public static IServiceCollection AddElsaApiEndpoints(this IServiceCollection ser
.AddSingleton<IEndpointContentSerializerSettingsProvider, EndpointContentSerializerSettingsProvider>()
.AddAutoMapperProfile<AutoMapperProfile>()
.AddSignalR();

services.AddMvc(options =>
{
//Use this conventions to set ElsaNewtonsoftJsonConvention to all controllers in Elsa.Server.Api
options.Conventions.Add(new ElsaNewtonsoftJsonConvention());
});
return services;
}

Expand All @@ -65,7 +71,7 @@ public static IServiceCollection AddElsaApiEndpoints(this IServiceCollection ser
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Elsa", Version = "v1" });
c.EnableAnnotations();
c.ExampleFilters();
//c.ExampleFilters(); I don't know why, this line will make swagger error
c.MapType<VersionOptions?>(() => new OpenApiSchema
{
Type = PrimitiveType.String.ToString().ToLower(),
Expand All @@ -83,7 +89,6 @@ public static IServiceCollection AddElsaApiEndpoints(this IServiceCollection ser
//Allow enums to be displayed
c.SchemaFilter<XEnumNamesSchemaFilter>();
configure?.Invoke(c);
});
}
Expand Down

0 comments on commit db847f9

Please sign in to comment.