diff --git a/Directory.Build.props b/Directory.Build.props
index 9addc9b9..460d53d7 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,7 +4,7 @@
preview
- $(NoWarn);CS1591
+ $(NoWarn);CS1591;NU5128
true
true
$(MSBuildThisFileDirectory)\eng\key.snk
diff --git a/Directory.Build.targets b/Directory.Build.targets
index b04e7792..495ff58c 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -12,4 +12,18 @@
$(_ProjectCopyright)
+
+
+
+
+
+ reactive
+
+
+
+
diff --git a/eng/Versions.props b/eng/Versions.props
index 3873ae16..718f9766 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -23,12 +23,16 @@
'$(TargetFramework)' == 'netcoreapp3.0' ">3.0.0
+
+ 2.1.0
+ 3.0.0
+
+
1.0.0
1.8.5
4.4.0
6.3.0
- 3.0.0
2019.1.3
12.0.2
1.0.2
diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj b/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj
index 6f59b8cb..21cfecba 100644
--- a/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj
+++ b/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj
@@ -1,7 +1,7 @@
- netstandard2.1
+ netstandard2.0;netstandard2.1
@@ -23,4 +23,8 @@
+
+ $(DefineConstants);SUPPORTS_BCL_ASYNC_ENUMERABLE
+
+
diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs
index c3f58649..ba00bb4d 100644
--- a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs
+++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs
@@ -5,8 +5,12 @@
*/
using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Query;
using OpenIddict.EntityFrameworkCore;
using OpenIddict.EntityFrameworkCore.Models;
@@ -112,5 +116,54 @@ namespace Microsoft.EntityFrameworkCore
.ApplyConfiguration(new OpenIddictScopeConfiguration())
.ApplyConfiguration(new OpenIddictTokenConfiguration());
}
+
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ ///
+ /// Converts an EF Core/IX-based async enumeration to a BCL enumeration.
+ ///
+ /// The type of the returned entities.
+ /// The EF Core/IX async enumeration.
+ /// The that can be used to abort the operation.
+ /// The non-streamed async enumeration containing the results.
+ internal static IAsyncEnumerable AsAsyncEnumerable(
+ [NotNull] this AsyncEnumerable source, CancellationToken cancellationToken = default)
+ {
+ return ExecuteAsync(cancellationToken);
+
+ async IAsyncEnumerable ExecuteAsync(CancellationToken cancellationToken)
+ {
+ foreach (var element in await source.ToListAsync(cancellationToken))
+ {
+ yield return element;
+ }
+ }
+ }
+
+ ///
+ /// Executes the query and returns the results as a non-streamed async enumeration.
+ ///
+ /// The type of the returned entities.
+ /// The query source.
+ /// The that can be used to abort the operation.
+ /// The non-streamed async enumeration containing the results.
+ internal static IAsyncEnumerable AsAsyncEnumerable(
+ [NotNull] this IQueryable source, CancellationToken cancellationToken = default)
+ {
+ if (source is null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ return ExecuteAsync(cancellationToken);
+
+ async IAsyncEnumerable ExecuteAsync(CancellationToken cancellationToken)
+ {
+ foreach (var element in await source.ToListAsync(cancellationToken))
+ {
+ yield return element;
+ }
+ }
+ }
+#endif
}
}
\ No newline at end of file
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
index ca74f0d2..c0ba3477 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
@@ -16,6 +16,7 @@ using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
@@ -327,7 +328,14 @@ namespace OpenIddict.EntityFrameworkCore
/// Exposes a compiled query allowing to retrieve all the applications
/// associated with the specified post_logout_redirect_uri.
///
- private static readonly Func> FindByPostLogoutRedirectUri =
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindByPostLogoutRedirectUri =
+
// To optimize the efficiency of the query a bit, only applications whose stringified
// PostLogoutRedirectUris contains the specified URL are returned. Once the applications
// are retrieved, a second pass is made to ensure only valid elements are returned.
@@ -353,6 +361,9 @@ namespace OpenIddict.EntityFrameworkCore
}
return FindByPostLogoutRedirectUri(Context, address)
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
.WhereAwait(async application => (await GetPostLogoutRedirectUrisAsync(application, cancellationToken))
.Contains(address, StringComparer.Ordinal));
}
@@ -361,7 +372,14 @@ namespace OpenIddict.EntityFrameworkCore
/// Exposes a compiled query allowing to retrieve all the
/// applications associated with the specified redirect_uri.
///
- private static readonly Func> FindByRedirectUri =
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindByRedirectUri =
+
// To optimize the efficiency of the query a bit, only applications whose stringified
// RedirectUris property contains the specified URL are returned. Once the applications
// are retrieved, a second pass is made to ensure only valid elements are returned.
@@ -387,6 +405,9 @@ namespace OpenIddict.EntityFrameworkCore
}
return FindByRedirectUri(Context, address)
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
.WhereAwait(async application => (await GetRedirectUrisAsync(application, cancellationToken))
.Contains(address, StringComparer.Ordinal));
}
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
index 77c5a04b..2d609471 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
@@ -16,6 +16,7 @@ using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
@@ -247,7 +248,14 @@ namespace OpenIddict.EntityFrameworkCore
/// Exposes a compiled query allowing to retrieve the authorizations corresponding
/// to the specified subject and associated with the application identifier.
///
- private static readonly Func> FindBySubjectAndClient =
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindBySubjectAndClient =
+
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using authorization.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check.
@@ -282,13 +290,24 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The client cannot be null or empty.", nameof(client));
}
- return FindBySubjectAndClient(Context, ConvertIdentifierFromString(client), subject);
+ return FindBySubjectAndClient(Context, ConvertIdentifierFromString(client), subject)
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
+ ;
}
///
/// Exposes a compiled query allowing to retrieve the authorizations matching the specified parameters.
///
- private static readonly Func> FindBySubjectClientAndStatus =
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindBySubjectClientAndStatus =
+
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using authorization.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check.
@@ -329,13 +348,24 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
- return FindBySubjectClientAndStatus(Context, ConvertIdentifierFromString(client), subject, status);
+ return FindBySubjectClientAndStatus(Context, ConvertIdentifierFromString(client), subject, status)
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
+ ;
}
///
/// Exposes a compiled query allowing to retrieve the authorizations matching the specified parameters.
///
- private static readonly Func> FindBySubjectClientStatusAndType =
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindBySubjectClientStatusAndType =
+
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using authorization.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check.
@@ -384,7 +414,11 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The type cannot be null or empty.", nameof(type));
}
- return FindBySubjectClientStatusAndType(Context, ConvertIdentifierFromString(client), subject, status, type);
+ return FindBySubjectClientStatusAndType(Context, ConvertIdentifierFromString(client), subject, status, type)
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
+ ;
}
///
@@ -409,7 +443,14 @@ namespace OpenIddict.EntityFrameworkCore
/// Exposes a compiled query allowing to retrieve the list of
/// authorizations corresponding to the specified application identifier.
///
- private static readonly Func> FindByApplicationId =
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindByApplicationId =
+
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using authorization.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check.
@@ -436,7 +477,11 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
- return FindByApplicationId(Context, ConvertIdentifierFromString(identifier));
+ return FindByApplicationId(Context, ConvertIdentifierFromString(identifier))
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
+ ;
}
///
@@ -470,7 +515,14 @@ namespace OpenIddict.EntityFrameworkCore
/// Exposes a compiled query allowing to retrieve all the
/// authorizations corresponding to the specified subject.
///
- private static readonly Func> FindBySubject =
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindBySubject =
+
EF.CompileAsyncQuery((TContext context, string subject) =>
from authorization in context.Set()
.Include(authorization => authorization.Application)
@@ -492,7 +544,11 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
- return FindBySubject(Context, subject);
+ return FindBySubject(Context, subject)
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
+ ;
}
///
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs
index c9e04c11..5a56c621 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs
@@ -14,6 +14,7 @@ using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
@@ -235,8 +236,13 @@ namespace OpenIddict.EntityFrameworkCore
///
/// Exposes a compiled query allowing to retrieve a list of scopes using their name.
///
- private static readonly Func, IAsyncEnumerable> FindByNames =
- EF.CompileAsyncQuery((TContext context, ImmutableArray names) =>
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func, IAsyncEnumerable>
+#else
+ Func, AsyncEnumerable>
+#endif
+ FindByNames = EF.CompileAsyncQuery((TContext context, ImmutableArray names) =>
from scope in context.Set().AsTracking()
where names.Contains(scope.Name)
select scope);
@@ -255,13 +261,24 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("Scope names cannot be null or empty.", nameof(names));
}
- return FindByNames(Context, names);
+ return FindByNames(Context, names)
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
+ ;
}
///
/// Exposes a compiled query allowing to retrieve all the scopes that contain the specified resource.
///
- private static readonly Func> FindByResource =
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindByResource =
+
// To optimize the efficiency of the query a bit, only scopes whose stringified
// Resources column contains the specified resource are returned. Once the scopes
// are retrieved, a second pass is made to ensure only valid elements are returned.
@@ -287,6 +304,9 @@ namespace OpenIddict.EntityFrameworkCore
}
return FindByResource(Context, resource)
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
.WhereAwait(async scope => (await GetResourcesAsync(scope, cancellationToken)).Contains(resource, StringComparer.Ordinal));
}
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
index a7a35855..f8e902ad 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
@@ -6,7 +6,6 @@
using System;
using System.Collections.Generic;
-using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Linq;
@@ -16,6 +15,7 @@ using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
@@ -201,7 +201,14 @@ namespace OpenIddict.EntityFrameworkCore
/// Exposes a compiled query allowing to retrieve the tokens corresponding
/// to the specified subject and associated with the application identifier.
///
- private static readonly Func> FindBySubjectAndClient =
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindBySubjectAndClient =
+
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using token.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check.
@@ -237,13 +244,24 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The client cannot be null or empty.", nameof(client));
}
- return FindBySubjectAndClient(Context, ConvertIdentifierFromString(client), subject);
+ return FindBySubjectAndClient(Context, ConvertIdentifierFromString(client), subject)
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
+ ;
}
///
/// Exposes a compiled query allowing to retrieve the tokens matching the specified parameters.
///
- private static readonly Func> FindBySubjectClientAndStatus =
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindBySubjectClientAndStatus =
+
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using token.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check.
@@ -285,13 +303,24 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
- return FindBySubjectClientAndStatus(Context, ConvertIdentifierFromString(client), subject, status);
+ return FindBySubjectClientAndStatus(Context, ConvertIdentifierFromString(client), subject, status)
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
+ ;
}
///
/// Exposes a compiled query allowing to retrieve the tokens matching the specified parameters.
///
- private static readonly Func> FindBySubjectClientStatusAndType =
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindBySubjectClientStatusAndType =
+
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using token.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check.
@@ -341,14 +370,25 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The type cannot be null or empty.", nameof(type));
}
- return FindBySubjectClientStatusAndType(Context, ConvertIdentifierFromString(client), subject, status, type);
+ return FindBySubjectClientStatusAndType(Context, ConvertIdentifierFromString(client), subject, status, type)
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
+ ;
}
///
/// Exposes a compiled query allowing to retrieve the list of
/// tokens corresponding to the specified application identifier.
///
- private static readonly Func> FindByApplicationId =
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindByApplicationId =
+
// Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be
// filtered using token.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check.
@@ -375,14 +415,25 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
- return FindByApplicationId(Context, ConvertIdentifierFromString(identifier));
+ return FindByApplicationId(Context, ConvertIdentifierFromString(identifier))
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
+ ;
}
///
/// Exposes a compiled query allowing to retrieve the list of
/// tokens corresponding to the specified authorization identifier.
///
- private static readonly Func> FindByAuthorizationId =
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindByAuthorizationId =
+
// Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be
// filtered using token.Authorization.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check.
@@ -409,7 +460,11 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
- return FindByAuthorizationId(Context, ConvertIdentifierFromString(identifier));
+ return FindByAuthorizationId(Context, ConvertIdentifierFromString(identifier))
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
+ ;
}
///
@@ -480,8 +535,13 @@ namespace OpenIddict.EntityFrameworkCore
/// Exposes a compiled query allowing to retrieve the
/// list of tokens corresponding to the specified subject.
///
- private static readonly Func> FindBySubject =
- EF.CompileAsyncQuery((TContext context, string subject) =>
+ private static readonly
+#if SUPPORTS_BCL_ASYNC_ENUMERABLE
+ Func>
+#else
+ Func>
+#endif
+ FindBySubject = EF.CompileAsyncQuery((TContext context, string subject) =>
from token in context.Set()
.Include(token => token.Application)
.Include(token => token.Authorization)
@@ -502,7 +562,11 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
- return FindBySubject(Context, subject);
+ return FindBySubject(Context, subject)
+#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
+ .AsAsyncEnumerable(cancellationToken)
+#endif
+ ;
}
///
diff --git a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddict.EntityFrameworkCore.Tests.csproj b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddict.EntityFrameworkCore.Tests.csproj
index 1d0e1e67..249ebab4 100644
--- a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddict.EntityFrameworkCore.Tests.csproj
+++ b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddict.EntityFrameworkCore.Tests.csproj
@@ -1,7 +1,8 @@
- netcoreapp3.0
+ netcoreapp2.1;netcoreapp3.0;net461
+ netcoreapp2.1;netcoreapp3.0