Skip to content

Commit b4c85d1

Browse files
committed
support netcore 3.0.
1 parent 341ebb7 commit b4c85d1

15 files changed

Lines changed: 744 additions & 41 deletions

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright 2018 Arjen Post and contributors
1+
Copyright 2017-2019 Arjen Post and contributors
22

33
Licensed under the Apache License, Version 2.0 (the "License");
44
you may not use this file except in compliance with the License.

NOTICE

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
Copyright (c) Arjen Post and contributors
22

3-
Portions of this software were developed by Microsoft Open
4-
Technologies, Inc. They are extracted from ASP.NET Web API
5-
(http://aspnetwebstack.codeplex.com/) and slightly modified.
3+
Portions of this software were developed by Microsoft. They are extracted from
4+
ASP.NET Core (https://github.com/aspnet/AspNetCore) and slightly modified.

samples/PartialResponse.AspNetCore.Mvc.Formatters.Json.Samples/PartialResponse.AspNetCore.Mvc.Formatters.Json.Samples.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@
44
<IsPackable>false</IsPackable>
55
<LangVersion>latest</LangVersion>
66
<OutputType>Exe</OutputType>
7-
<TargetFramework>netcoreapp2.0</TargetFramework>
7+
<TargetFramework>netcoreapp3.0</TargetFramework>
88
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<ProjectReference Include="..\..\src\PartialResponse.AspNetCore.Mvc.Formatters.Json\PartialResponse.AspNetCore.Mvc.Formatters.Json.csproj" />
12+
<ProjectReference Include="../../src/PartialResponse.AspNetCore.Mvc.Formatters.Json/PartialResponse.AspNetCore.Mvc.Formatters.Json.csproj" />
1313
</ItemGroup>
1414

1515
<ItemGroup>
16-
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.2" />
17-
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.1" />
16+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
1817
</ItemGroup>
1918

2019
<ItemGroup>
20+
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
2121
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="All" />
2222
</ItemGroup>
2323

samples/PartialResponse.AspNetCore.Mvc.Formatters.Json.Samples/Startup.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,15 @@ public void ConfigureServices(IServiceCollection services)
2323
services.Configure<MvcPartialJsonOptions>(options => options.IgnoreCase = true);
2424

2525
services
26-
.AddMvc(options => options.OutputFormatters.RemoveType<JsonOutputFormatter>())
26+
.AddMvc()
2727
.AddPartialJsonFormatters();
2828
}
2929

30-
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
30+
public void Configure(IApplicationBuilder app)
3131
{
32-
loggerFactory.AddConsole(this.configuration.GetSection("Logging"));
33-
34-
app.UseMvc(routes =>
35-
{
36-
routes.MapRoute(
37-
name: "default",
38-
template: "{controller=Home}/{action=Index}");
39-
});
32+
app
33+
.UseRouting()
34+
.UseEndpoints(endpoints => endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"));
4035
}
4136
}
4237
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright (c) Arjen Post and contributors. See LICENSE and NOTICE in the project root for license information.
2+
3+
#if !ASPNETCORE2
4+
using System;
5+
using System.Collections;
6+
using System.Collections.Concurrent;
7+
using System.Collections.Generic;
8+
using System.Diagnostics;
9+
using System.Reflection;
10+
using System.Threading.Tasks;
11+
using Microsoft.AspNetCore.Mvc;
12+
using ReaderFunc = System.Func<System.Collections.Generic.IAsyncEnumerable<object>, System.Threading.Tasks.Task<System.Collections.ICollection>>;
13+
14+
namespace PartialResponse.AspNetCore.Mvc.Formatters.Json.Internal
15+
{
16+
/// <summary>
17+
/// Type that reads an <see cref="IAsyncEnumerable{T}"/> instance into a
18+
/// generic collection instance.
19+
/// </summary>
20+
/// <remarks>
21+
/// This type is used to create a strongly typed synchronous <see cref="ICollection{T}"/> instance from
22+
/// an <see cref="IAsyncEnumerable{T}"/>. An accurate <see cref="ICollection{T}"/> is required for XML formatters to
23+
/// correctly serialize.
24+
/// </remarks>
25+
internal sealed class AsyncEnumerableReader
26+
{
27+
private readonly MethodInfo converter = typeof(AsyncEnumerableReader).GetMethod(nameof(ReadInternal), BindingFlags.NonPublic | BindingFlags.Instance);
28+
29+
private readonly ConcurrentDictionary<Type, ReaderFunc> asyncEnumerableConverters = new ConcurrentDictionary<Type, ReaderFunc>();
30+
private readonly MvcOptions mvcOptions;
31+
32+
/// <summary>
33+
/// Initializes a new instance of the <see cref="AsyncEnumerableReader"/> class.
34+
/// </summary>
35+
/// <param name="mvcOptions">Accessor to <see cref="MvcOptions"/>.</param>
36+
public AsyncEnumerableReader(MvcOptions mvcOptions)
37+
{
38+
this.mvcOptions = mvcOptions;
39+
}
40+
41+
/// <summary>
42+
/// Reads a <see cref="IAsyncEnumerable{T}"/> into an <see cref="ICollection{T}"/>.
43+
/// </summary>
44+
/// <param name="value">The <see cref="IAsyncEnumerable{T}"/> to read.</param>
45+
/// <returns>The <see cref="ICollection"/>.</returns>
46+
public Task<ICollection> ReadAsync(IAsyncEnumerable<object> value)
47+
{
48+
if (value == null)
49+
{
50+
throw new ArgumentNullException(nameof(value));
51+
}
52+
53+
var type = value.GetType();
54+
if (!this.asyncEnumerableConverters.TryGetValue(type, out var result))
55+
{
56+
var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IAsyncEnumerable<>));
57+
58+
var enumeratedObjectType = enumerableType.GetGenericArguments()[0];
59+
60+
var converter = (ReaderFunc)this.converter
61+
.MakeGenericMethod(enumeratedObjectType)
62+
.CreateDelegate(typeof(ReaderFunc), this);
63+
64+
this.asyncEnumerableConverters.TryAdd(type, converter);
65+
result = converter;
66+
}
67+
68+
return result(value);
69+
}
70+
71+
private async Task<ICollection> ReadInternal<T>(IAsyncEnumerable<object> value)
72+
{
73+
var asyncEnumerable = (IAsyncEnumerable<T>)value;
74+
var result = new List<T>();
75+
var count = 0;
76+
77+
await foreach (var item in asyncEnumerable)
78+
{
79+
if (count++ >= this.mvcOptions.MaxIAsyncEnumerableBufferLimit)
80+
{
81+
throw new InvalidOperationException("Resources.FormatObjectResultExecutor_MaxEnumerationExceeded(nameof(AsyncEnumerableReader), value.GetType())");
82+
}
83+
84+
result.Add(item);
85+
}
86+
87+
return result;
88+
}
89+
}
90+
}
91+
#endif
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) Arjen Post and contributors. See LICENSE and NOTICE in the project root for license information.
2+
3+
#if !ASPNETCORE2
4+
using System;
5+
using System.Linq;
6+
using System.Reflection;
7+
8+
namespace PartialResponse.AspNetCore.Mvc.Formatters.Json.Internal
9+
{
10+
/// <summary>
11+
/// Helper related to generic interface definitions and implementing classes.
12+
/// </summary>
13+
internal static class ClosedGenericMatcher
14+
{
15+
/// <summary>
16+
/// Determine whether <paramref name="queryType"/> is or implements a closed generic <see cref="Type"/>
17+
/// created from <paramref name="interfaceType"/>.
18+
/// </summary>
19+
/// <param name="queryType">The <see cref="Type"/> of interest.</param>
20+
/// <param name="interfaceType">The open generic <see cref="Type"/> to match. Usually an interface.</param>
21+
/// <returns>
22+
/// The closed generic <see cref="Type"/> created from <paramref name="interfaceType"/> that
23+
/// <paramref name="queryType"/> is or implements. <c>null</c> if the two <see cref="Type"/>s have no such
24+
/// relationship.
25+
/// </returns>
26+
/// <remarks>
27+
/// This method will return <paramref name="queryType"/> if <paramref name="interfaceType"/> is
28+
/// <c>typeof(KeyValuePair{,})</c>, and <paramref name="queryType"/> is
29+
/// <c>typeof(KeyValuePair{string, object})</c>.
30+
/// </remarks>
31+
public static Type ExtractGenericInterface(Type queryType, Type interfaceType)
32+
{
33+
if (queryType == null)
34+
{
35+
throw new ArgumentNullException(nameof(queryType));
36+
}
37+
38+
if (interfaceType == null)
39+
{
40+
throw new ArgumentNullException(nameof(interfaceType));
41+
}
42+
43+
if (IsGenericInstantiation(queryType, interfaceType))
44+
{
45+
// queryType matches (i.e. is a closed generic type created from) the open generic type.
46+
return queryType;
47+
}
48+
49+
// Otherwise check all interfaces the type implements for a match.
50+
// - If multiple different generic instantiations exists, we want the most derived one.
51+
// - If that doesn't break the tie, then we sort alphabetically so that it's deterministic.
52+
//
53+
// We do this by looking at interfaces on the type, and recursing to the base type
54+
// if we don't find any matches.
55+
return GetGenericInstantiation(queryType, interfaceType);
56+
}
57+
58+
private static bool IsGenericInstantiation(Type candidate, Type interfaceType)
59+
{
60+
return
61+
candidate.GetTypeInfo().IsGenericType &&
62+
candidate.GetGenericTypeDefinition() == interfaceType;
63+
}
64+
65+
private static Type GetGenericInstantiation(Type queryType, Type interfaceType)
66+
{
67+
Type bestMatch = null;
68+
var interfaces = queryType.GetInterfaces();
69+
foreach (var @interface in interfaces)
70+
{
71+
if (IsGenericInstantiation(@interface, interfaceType))
72+
{
73+
if (bestMatch == null)
74+
{
75+
bestMatch = @interface;
76+
}
77+
else if (StringComparer.Ordinal.Compare(@interface.FullName, bestMatch.FullName) < 0)
78+
{
79+
bestMatch = @interface;
80+
}
81+
else
82+
{
83+
// There are two matches at this level of the class hierarchy, but @interface is after
84+
// bestMatch in the sort order.
85+
}
86+
}
87+
}
88+
89+
if (bestMatch != null)
90+
{
91+
return bestMatch;
92+
}
93+
94+
// BaseType will be null for object and interfaces, which means we've reached 'bottom'.
95+
var baseType = queryType?.GetTypeInfo().BaseType;
96+
if (baseType == null)
97+
{
98+
return null;
99+
}
100+
else
101+
{
102+
return GetGenericInstantiation(baseType, interfaceType);
103+
}
104+
}
105+
}
106+
}
107+
#endif

src/PartialResponse.AspNetCore.Mvc.Formatters.Json/Internal/MediaTypeHeaderValues.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ internal static class MediaTypeHeaderValues
88
{
99
public static readonly MediaTypeHeaderValue ApplicationJson = MediaTypeHeaderValue.Parse("application/json").CopyAsReadOnly();
1010

11-
public static readonly MediaTypeHeaderValue ApplicationWildcardJson = MediaTypeHeaderValue.Parse("application/*+json").CopyAsReadOnly();
12-
1311
public static readonly MediaTypeHeaderValue TextJson = MediaTypeHeaderValue.Parse("text/json").CopyAsReadOnly();
12+
13+
public static readonly MediaTypeHeaderValue ApplicationAnyJsonSyntax = MediaTypeHeaderValue.Parse("application/*+json").CopyAsReadOnly();
1414
}
1515
}

src/PartialResponse.AspNetCore.Mvc.Formatters.Json/Internal/MvcPartialJsonMvcOptionsSetup.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System;
44
using System.Buffers;
55
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.AspNetCore.Mvc.Formatters;
67
using Microsoft.AspNetCore.Mvc.ModelBinding;
78
using Microsoft.Extensions.Logging;
89
using Microsoft.Extensions.ObjectPool;
@@ -55,7 +56,12 @@ public MvcPartialJsonMvcOptionsSetup(IFieldsParser fieldsParser, IOptions<MvcPar
5556
/// <param name="options">The MVC options.</param>
5657
public void Configure(MvcOptions options)
5758
{
59+
#if ASPNETCORE2
5860
options.OutputFormatters.Add(new PartialJsonOutputFormatter(this.partialJsonOptions.SerializerSettings, this.fieldsParser, this.charPool, this.partialJsonOptions));
61+
#else
62+
options.OutputFormatters.RemoveType<SystemTextJsonOutputFormatter>();
63+
options.OutputFormatters.Add(new PartialJsonOutputFormatter(this.partialJsonOptions.SerializerSettings, this.fieldsParser, this.charPool, this.partialJsonOptions, options));
64+
#endif
5965
options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("application/json"));
6066
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(JToken)));
6167
}

0 commit comments

Comments
 (0)