Skip to content

Commit d334275

Browse files
committed
test json converter
1 parent 373a4dd commit d334275

6 files changed

Lines changed: 286 additions & 1 deletion

File tree

demo/YS.Knife.Query.Demo.AspnetCore/Controllers/MaterialController.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public partial class MaterialController : ControllerBase, IMaterialService
1313
[HttpGet]
1414
public Task<PagedList<IMaterialService.MaterialInfo>> QueryMaterials([FromQuery] LimitQueryInfo queryInfo)
1515
{
16+
HttpContext.Items["__query_select"] = queryInfo.Select;
17+
1618
return materialService.QueryMaterials(queryInfo);
1719
}
1820
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
using System.Runtime.ExceptionServices;
2+
using System.Text;
3+
using System.Text.Encodings.Web;
4+
using System.Text.Json;
5+
using System.Text.Json.Serialization;
6+
using Microsoft.AspNetCore.Mvc;
7+
using Microsoft.AspNetCore.Mvc.Formatters;
8+
using Microsoft.Net.Http.Headers;
9+
10+
namespace YS.Knife.Query.Demo.AspnetCore
11+
{
12+
internal static class MediaTypeHeaderValues
13+
{
14+
public static readonly MediaTypeHeaderValue ApplicationJson
15+
= MediaTypeHeaderValue.Parse("application/json").CopyAsReadOnly();
16+
17+
public static readonly MediaTypeHeaderValue TextJson
18+
= MediaTypeHeaderValue.Parse("text/json").CopyAsReadOnly();
19+
20+
public static readonly MediaTypeHeaderValue ApplicationAnyJsonSyntax
21+
= MediaTypeHeaderValue.Parse("application/*+json").CopyAsReadOnly();
22+
23+
public static readonly MediaTypeHeaderValue ApplicationXml
24+
= MediaTypeHeaderValue.Parse("application/xml").CopyAsReadOnly();
25+
26+
public static readonly MediaTypeHeaderValue TextXml
27+
= MediaTypeHeaderValue.Parse("text/xml").CopyAsReadOnly();
28+
29+
public static readonly MediaTypeHeaderValue ApplicationAnyXmlSyntax
30+
= MediaTypeHeaderValue.Parse("application/*+xml").CopyAsReadOnly();
31+
}
32+
33+
/// <summary>
34+
/// A <see cref="TextOutputFormatter"/> for JSON content that uses <see cref="JsonSerializer"/>.
35+
/// </summary>
36+
public class SystemTextJsonOutputFormatter2 : TextOutputFormatter
37+
{
38+
/// <summary>
39+
/// Initializes a new <see cref="SystemTextJsonOutputFormatter"/> instance.
40+
/// </summary>
41+
/// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/>.</param>
42+
public SystemTextJsonOutputFormatter2(JsonSerializerOptions jsonSerializerOptions)
43+
{
44+
SerializerOptions = jsonSerializerOptions;
45+
46+
SupportedEncodings.Add(Encoding.UTF8);
47+
SupportedEncodings.Add(Encoding.Unicode);
48+
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson);
49+
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson);
50+
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax);
51+
}
52+
53+
internal static SystemTextJsonOutputFormatter CreateFormatter(JsonOptions jsonOptions)
54+
{
55+
var jsonSerializerOptions = jsonOptions.JsonSerializerOptions;
56+
57+
//jsonSerializerOptions.
58+
if (jsonSerializerOptions.Encoder is null)
59+
{
60+
// If the user hasn't explicitly configured the encoder, use the less strict encoder that does not encode all non-ASCII characters.
61+
jsonSerializerOptions = new JsonSerializerOptions(jsonSerializerOptions)
62+
{
63+
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
64+
};
65+
}
66+
67+
return new SystemTextJsonOutputFormatter(jsonSerializerOptions);
68+
}
69+
70+
/// <summary>
71+
/// Gets the <see cref="JsonSerializerOptions"/> used to configure the <see cref="JsonSerializer"/>.
72+
/// </summary>
73+
/// <remarks>
74+
/// A single instance of <see cref="SystemTextJsonOutputFormatter"/> is used for all JSON formatting. Any
75+
/// changes to the options will affect all output formatting.
76+
/// </remarks>
77+
public JsonSerializerOptions SerializerOptions { get; }
78+
79+
/// <inheritdoc />
80+
public sealed override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
81+
{
82+
if (context == null)
83+
{
84+
throw new ArgumentNullException(nameof(context));
85+
}
86+
87+
if (selectedEncoding == null)
88+
{
89+
throw new ArgumentNullException(nameof(selectedEncoding));
90+
}
91+
92+
var httpContext = context.HttpContext;
93+
94+
// context.ObjectType reflects the declared model type when specified.
95+
// For polymorphic scenarios where the user declares a return type, but returns a derived type,
96+
// we want to serialize all the properties on the derived type. This keeps parity with
97+
// the behavior you get when the user does not declare the return type and with Json.Net at least at the top level.
98+
var objectType = context.Object?.GetType() ?? context.ObjectType ?? typeof(object);
99+
100+
var responseStream = httpContext.Response.Body;
101+
if (selectedEncoding.CodePage == Encoding.UTF8.CodePage)
102+
{
103+
await JsonSerializer.SerializeAsync(responseStream, context.Object, objectType, GetJsonSerializerOptions(context));
104+
await responseStream.FlushAsync();
105+
}
106+
else
107+
{
108+
// JsonSerializer only emits UTF8 encoded output, but we need to write the response in the encoding specified by
109+
// selectedEncoding
110+
var transcodingStream = Encoding.CreateTranscodingStream(httpContext.Response.Body, selectedEncoding, Encoding.UTF8, leaveOpen: true);
111+
112+
ExceptionDispatchInfo exceptionDispatchInfo = null;
113+
try
114+
{
115+
await JsonSerializer.SerializeAsync(transcodingStream, context.Object, objectType, GetJsonSerializerOptions(context));
116+
await transcodingStream.FlushAsync();
117+
}
118+
catch (Exception ex)
119+
{
120+
// TranscodingStream may write to the inner stream as part of it's disposal.
121+
// We do not want this exception "ex" to be eclipsed by any exception encountered during the write. We will stash it and
122+
// explicitly rethrow it during the finally block.
123+
exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
124+
}
125+
finally
126+
{
127+
try
128+
{
129+
await transcodingStream.DisposeAsync();
130+
}
131+
catch when (exceptionDispatchInfo != null)
132+
{
133+
}
134+
135+
exceptionDispatchInfo?.Throw();
136+
}
137+
}
138+
}
139+
140+
protected virtual JsonSerializerOptions GetJsonSerializerOptions(OutputFormatterWriteContext context)
141+
{
142+
var converters = GetDynamicConverters(context.HttpContext);
143+
if (converters == null || converters.Length == 0)
144+
{
145+
return this.SerializerOptions;
146+
}
147+
else
148+
{
149+
var newSerializerOptions = new JsonSerializerOptions(this.SerializerOptions);
150+
Array.ForEach(converters, p => newSerializerOptions.Converters.Add(p));
151+
152+
return newSerializerOptions;
153+
}
154+
}
155+
private JsonConverter[] GetDynamicConverters(HttpContext context)
156+
{
157+
//TODO
158+
return null;
159+
}
160+
161+
private SelectInfo GetSelectInfo(HttpContext context)
162+
{
163+
var text = context.Items["__query_select"] as string;
164+
if (string.IsNullOrEmpty(text))
165+
{
166+
return null;
167+
}
168+
else
169+
{
170+
return SelectInfo.Parse(text);
171+
}
172+
}
173+
}
174+
175+
public class FilterColumnJsonFormatter : SystemTextJsonOutputFormatter
176+
{
177+
public FilterColumnJsonFormatter(JsonSerializerOptions jsonSerializerOptions) : base(jsonSerializerOptions)
178+
{
179+
}
180+
public override bool CanWriteResult(OutputFormatterCanWriteContext context)
181+
{
182+
return base.CanWriteResult(context);
183+
}
184+
protected override bool CanWriteType(Type type)
185+
{
186+
return base.CanWriteType(type);
187+
}
188+
public override Task WriteAsync(OutputFormatterWriteContext context)
189+
{
190+
var selectInfo = GetSelectInfo(context.HttpContext);
191+
return base.WriteAsync(context);
192+
}
193+
private SelectInfo GetSelectInfo(HttpContext context)
194+
{
195+
var text = context.Items["__query_select"] as string;
196+
if (string.IsNullOrEmpty(text))
197+
{
198+
return null;
199+
}
200+
else
201+
{
202+
return SelectInfo.Parse(text);
203+
}
204+
}
205+
}
206+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.Extensions.Options;
3+
4+
namespace YS.Knife.Query.Demo.AspnetCore
5+
{
6+
public static class MvcBuilderExtensions
7+
{
8+
public static IMvcBuilder AddFilterJson(this IMvcBuilder mvcBuilder)
9+
{
10+
mvcBuilder.Services.AddSingleton<IConfigureOptions<MvcOptions>>(sp =>
11+
{
12+
var originalJsonOptions = sp.GetRequiredService<IOptionsMonitor<JsonOptions>>();
13+
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
14+
return new ConfigMvcJsonOptions(loggerFactory, originalJsonOptions);
15+
});
16+
return mvcBuilder;
17+
}
18+
}
19+
public class ConfigMvcJsonOptions : IConfigureOptions<MvcOptions>
20+
{
21+
private readonly ILoggerFactory loggerFactory;
22+
private readonly IOptionsMonitor<JsonOptions> jsonOptions;
23+
24+
public ConfigMvcJsonOptions(ILoggerFactory loggerFactory,IOptionsMonitor<JsonOptions> jsonOptions)
25+
{
26+
this.loggerFactory = loggerFactory;
27+
this.jsonOptions = jsonOptions;
28+
}
29+
public void Configure(MvcOptions options)
30+
{
31+
var optionValue = jsonOptions.CurrentValue;
32+
options.OutputFormatters.Insert(0, new FilterColumnJsonFormatter(optionValue.JsonSerializerOptions));
33+
}
34+
}
35+
}

demo/YS.Knife.Query.Demo.AspnetCore/Program.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
using YS.Knife.Query.Demo.Models;
44
using Microsoft.EntityFrameworkCore;
55
using Microsoft.EntityFrameworkCore.Sqlite;
6+
using Microsoft.AspNetCore.Http.Json;
7+
using Microsoft.Extensions.Options;
8+
using YS.Knife.Query.Demo.AspnetCore;
69

710
var builder = WebApplication.CreateBuilder(args);
811

@@ -18,8 +21,19 @@
1821
op.UseSqlite("Data Source=demo.db");
1922
op.LogTo(Console.WriteLine);
2023
});
24+
builder.Services.AddControllers()
25+
.AddFilterJson();
26+
//builder.Services.AddControllers()
27+
// .AddJsonOptions(t =>
28+
// {
29+
// builder.Services.AddSingleton<IConfigureOptions<JsonOptions>, CustomJsonOptionsSetup>();
2130

31+
// });
2232

33+
//builder.Services.PostConfigure<System.Text.Json.JsonSerializerOptions>((t) =>
34+
//{
35+
// t.AllowTrailingCommas = true;
36+
//});
2337
var app = builder.Build();
2438

2539
// Configure the HTTP request pipeline.

demo/YS.Knife.Query.Demo.Models/BaseEntity.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,16 @@ public class Material : BaseEntity
1313
{
1414
[StringLength(32)]
1515
public string Name { get; set; }
16-
1716

17+
public MaterialType Type { get; set; }
18+
19+
20+
}
21+
public enum MaterialType
22+
{
23+
Common,
24+
Meat,
25+
Fruit,
26+
Vegetable
1827
}
1928
}

test/YS.Knife.Query.IntegrationTest/Operators/InTest.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class InTest
1111
{
1212
[Theory]
1313
[MemberData(nameof(GetTestData))]
14+
[MemberData(nameof(GetTestData2))]
1415
public void ConstantAndConstant(Type leftType, object left, Type rightType, object right, bool result)
1516
{
1617
CompareConstantAndConstant(Operator.In, leftType, left, rightType, right, result);
@@ -19,6 +20,7 @@ public void ConstantAndConstant(Type leftType, object left, Type rightType, obje
1920

2021
[Theory]
2122
[MemberData(nameof(GetTestData))]
23+
[MemberData(nameof(GetTestData2))]
2224
public void PathAndConstant(Type leftType, object left, Type rightType, object right, bool result)
2325
{
2426
ComparePathAndConstant(Operator.In, leftType, left, rightType, right, result);
@@ -49,6 +51,7 @@ public static IEnumerable<object[]> GetTestData()
4951
p.Item5
5052
});
5153
}
54+
5255
private static IEnumerable<(Type, object, Type, object, bool)> GetTestDataInternal()
5356
{
5457
yield return new(typeof(int), 1, typeof(int[]), new int[] { 1 }, true);
@@ -68,6 +71,22 @@ public static IEnumerable<object[]> GetTestData()
6871

6972
}
7073

74+
public static IEnumerable<object[]> GetTestData2()
75+
{
76+
return GetTestDataInternal2().Select(p => new object[]
77+
{
78+
p.Item1,
79+
p.Item2,
80+
p.Item3,
81+
p.Item4,
82+
p.Item5
83+
});
84+
}
85+
private static IEnumerable<(Type, object, Type, object, bool)> GetTestDataInternal2()
86+
{
87+
yield return new(typeof(int), 1, typeof(object[]), new object[] { 1 }, true);
88+
7189

90+
}
7291
}
7392
}

0 commit comments

Comments
 (0)