MFR client: align with interface doc; remove VB-era files

Per MFR_RESTClient/Docs/mfr_interface_description.md (added):
- MFRClientConfig: configurable TimeoutMs (default 30000), UserAgent, MaxRetries,
  and a derived RestRoot (/mfr) alongside the OData BaseUrl.
- MFRClient: apply request Timeout + UserAgent, send Accept: application/json,
  and retry idempotent GETs on transient failures (HTTP 429/5xx and
  network/timeout) with exponential backoff + jitter, honouring Retry-After.
- Added ReadODataAllPages to follow @odata.nextLink pagination.

Cleanup: removed legacy VB project files (MFR_RESTClient.vbproj, .vbproj.user,
app.config My.MySettings) and stopped tracking the generated MFR_RESTClient.xml
(now git-ignored). The active project is MFR_RESTClient.csproj.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 15:04:19 +02:00
parent a00ec1da3b
commit 27becf7c68
8 changed files with 1380 additions and 243 deletions
+3
View File
@@ -11,6 +11,9 @@ obj/
*.dbmdl
*.jfm
# Generated XML documentation output (regenerated on build)
MFR_RESTClient/MFR_RESTClient.xml
# NuGet
packages/
*.nupkg
File diff suppressed because it is too large Load Diff
+81 -9
View File
@@ -3,6 +3,7 @@ using System.Text.RegularExpressions;
using System.Web;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json.Linq;
using RestSharp;
using RestSharp.Authenticators;
@@ -34,7 +35,9 @@ public class MFRClient : IDisposable
_clientCredentials = credentials;
_clientOptions = new RestClientOptions
{
Authenticator = new HttpBasicAuthenticator(_clientCredentials.Username, _clientCredentials.Password)
Authenticator = new HttpBasicAuthenticator(_clientCredentials.Username, _clientCredentials.Password),
Timeout = TimeSpan.FromMilliseconds(config.TimeoutMs),
UserAgent = config.UserAgent
};
_client = new RestClient(_clientOptions);
}
@@ -42,6 +45,7 @@ public class MFRClient : IDisposable
public async Task<string> ReadAnything(string address, bool throwErrorIfNotOk = true)
{
var request = new RestRequest(resource: address, method: Method.Get);
request.AddHeader("Accept", "application/json");
var response = await Execute(request, "ReadAnything", throwErrorIfNotOk);
return response.Content ?? "";
}
@@ -49,10 +53,37 @@ public class MFRClient : IDisposable
public async Task<ODataEnvelope> ReadOData(string address, bool throwErrorIfNotOk = true)
{
var request = new RestRequest(resource: address, method: Method.Get);
request.AddHeader("Accept", "application/json");
var response = await Execute(request, "ReadOData", throwErrorIfNotOk);
return new ODataEnvelope(response.Content, url: address);
}
/// <summary>
/// Reads an OData collection and follows <c>@odata.nextLink</c> pages, aggregating
/// all <c>value</c> items into a single envelope (see interface doc §6.4 pagination).
/// </summary>
public async Task<ODataEnvelope> ReadODataAllPages(string address, int maxPages = 100, bool throwErrorIfNotOk = true)
{
var first = await ReadOData(address, throwErrorIfNotOk);
first.ConvertToArray();
var all = first.Value as JArray ?? new JArray();
var next = first.NextLink;
int pages = 1;
while (next != null && pages < maxPages)
{
var page = await ReadOData(next.ToString(), throwErrorIfNotOk);
page.ConvertToArray();
if (page.Value is JArray arr)
foreach (var item in arr) all.Add(item);
next = page.NextLink;
pages++;
}
first.Value = all;
first.NextLink = null;
_logger.LogDebug("ReadODataAllPages aggregated {Count} items over {Pages} page(s) — {Address}", all.Count, pages, address);
return first;
}
public byte[]? GetFile(string address, bool throwErrorIfNotOk = true)
{
byte[]? data = null;
@@ -84,19 +115,34 @@ public class MFRClient : IDisposable
}
/// <summary>
/// Executes a query against MFR.
/// Executes a request against MFR. Idempotent GET requests are retried on
/// transient failures (HTTP 429 / 5xx and network/timeout errors) using
/// exponential backoff with jitter, honouring <c>Retry-After</c> when present
/// (see interface doc §12.3). Non-GET requests are never auto-retried.
/// </summary>
private async Task<RestResponse> Execute(RestRequest request, string message, bool throwErrorIfNotOk)
{
var response = await _client.ExecuteAsync(request);
bool retryable = request.Method == Method.Get;
int maxAttempts = retryable ? Math.Max(1, ClientConfig.MaxRetries + 1) : 1;
RestResponse response = null!;
bool notFound = response.StatusCode == HttpStatusCode.InternalServerError
&& (response.Content?.IndexOf("Sequence contains no elements", 0, StringComparison.InvariantCultureIgnoreCase) ?? -1) > -1;
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
response = await _client.ExecuteAsync(request);
if (throwErrorIfNotOk
&& response.StatusCode != HttpStatusCode.OK
&& response.StatusCode != HttpStatusCode.Created
&& !notFound)
bool notFound = IsNotFound(response);
bool transient = !notFound && IsTransient(response);
if (!transient || attempt == maxAttempts) break;
var delay = BackoffDelay(attempt, response);
_logger.LogWarning("MFR transient failure: {Message} — status={Status}/{ResponseStatus}, retry {Attempt}/{Max} in {DelayMs}ms",
message, (int)response.StatusCode, response.ResponseStatus, attempt, maxAttempts - 1, (int)delay.TotalMilliseconds);
await Task.Delay(delay);
}
bool ok = response.StatusCode is HttpStatusCode.OK or HttpStatusCode.Created or HttpStatusCode.NoContent;
if (throwErrorIfNotOk && !ok && !IsNotFound(response))
{
_logger.LogWarning("Execute rest issue: {Message} — status={Status}, resource={Resource}",
message, response.StatusDescription, request.Resource);
@@ -106,6 +152,32 @@ public class MFRClient : IDisposable
return response;
}
/// <summary>MFR returns HTTP 500 "Sequence contains no elements" for empty single-entity reads; treat as not-found, not an error.</summary>
private static bool IsNotFound(RestResponse response) =>
response.StatusCode == HttpStatusCode.InternalServerError
&& (response.Content?.IndexOf("Sequence contains no elements", 0, StringComparison.InvariantCultureIgnoreCase) ?? -1) > -1;
private static bool IsTransient(RestResponse response)
{
if (response.ResponseStatus is ResponseStatus.TimedOut or ResponseStatus.Error) return true;
int code = (int)response.StatusCode;
return code == 429 || (code >= 500 && code <= 599);
}
private static TimeSpan BackoffDelay(int attempt, RestResponse response)
{
// Honour Retry-After (seconds) when the server provides it.
var retryAfter = response.Headers?
.FirstOrDefault(h => string.Equals(h.Name, "Retry-After", StringComparison.OrdinalIgnoreCase))?.Value?.ToString();
if (int.TryParse(retryAfter, out var seconds) && seconds is > 0 and <= 120)
return TimeSpan.FromSeconds(seconds);
// Exponential backoff (0.5s, 1s, 2s, …) capped at 10s, plus jitter.
double baseMs = Math.Min(10000, 500 * Math.Pow(2, attempt - 1));
double jitter = Random.Shared.Next(0, 250);
return TimeSpan.FromMilliseconds(baseMs + jitter);
}
private void ThrowExceptionIfNecessary(RestResponse response, string location, string? customMessage = null)
{
if (!HideCustomExceptions)
+20
View File
@@ -4,12 +4,32 @@ namespace MFR_RESTClient;
public class MFRClientConfig
{
/// <summary>Default mfr host (see MFR_RESTClient/Docs/mfr_interface_description.md §4).</summary>
public const string DefaultHost = "https://portal.mobilefieldreport.com";
/// <summary>OData resource root (e.g. https://host/odata/). Kept for backward compatibility.</summary>
public string BaseUrl { get; } = "";
/// <summary>REST operation root (e.g. https://host/mfr/) for mfr-specific operations
/// such as deep-create and document upload (see interface doc §8, §9).</summary>
public string RestRoot { get; } = "";
/// <summary>HTTP timeout in milliseconds (interface doc §4.3 default: 30000).</summary>
public int TimeoutMs { get; set; } = 30000;
/// <summary>User-Agent sent with requests.</summary>
public string UserAgent { get; set; } = "FuchsIntranet-MFRClient";
/// <summary>Transient-retry attempts for idempotent GET requests (429 / 5xx). 1 = no retry.</summary>
public int MaxRetries { get; set; } = 3;
public MFRClientConfig(string url)
{
BaseUrl = url.StartsWith("http") ? url : $"https://{url.Trim()}/odata/";
if (!BaseUrl.EndsWith("/")) BaseUrl += "/";
// Derive the REST root (/mfr/) from the same scheme+host as the OData root.
try { var u = new Uri(BaseUrl); RestRoot = $"{u.Scheme}://{u.Authority}/mfr/"; }
catch { RestRoot = DefaultHost + "/mfr/"; }
}
public bool IsLogoutRequired { get; set; } = false;
-79
View File
@@ -1,79 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Library</OutputType>
<MyType>Empty</MyType>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Configurations>db-dev.processweb.de;Debug;Release;server02.processweb.de</Configurations>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DocumentationFile>MFR_RESTClient.xml</DocumentationFile>
<NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DefineDebug>false</DefineDebug>
<DocumentationFile>MFR_RESTClient.xml</DocumentationFile>
<NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Update="My Project\Application.Designer.vb">
<AutoGen>True</AutoGen>
<DependentUpon>Application.myapp</DependentUpon>
</Compile>
<Compile Update="My Project\Resources.Designer.vb">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="My Project\Settings.Designer.vb">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="My Project\Application.myapp">
<Generator>MyApplicationCodeGenerator</Generator>
<LastGenOutput>Application.Designer.vb</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup Label="Compile items now included by globbing that were not in the original project file">
<Compile Remove="MFRClientSessionManagement.vb" />
</ItemGroup>
<ItemGroup>
<None Include="MFR_RESTClient.csproj" />
</ItemGroup>
<ItemGroup>
<!-- Compatible packages (kept) -->
<PackageReference Include="Microsoft.AspNet.SignalR.Client" Version="2.4.3" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
<PackageReference Include="Microsoft.Azure.Services.AppAuthentication" Version="1.6.2" />
<PackageReference Include="Microsoft.Data.Edm" Version="5.8.5" />
<PackageReference Include="Microsoft.Data.OData" Version="5.8.5" />
<PackageReference Include="Microsoft.Data.Services.Client" Version="5.8.5" />
<PackageReference Include="Microsoft.NETCore.Targets" Version="5.0.0" />
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.24" />
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
<PackageReference Include="System.Spatial" Version="5.8.5" />
<!-- Updated packages -->
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.5" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.17.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="RestSharp" Version="114.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" />
<PackageReference Include="System.Text.Encodings.Web" Version="10.0.5" />
<PackageReference Include="System.Text.Json" Version="10.0.5" />
<!-- New packages (replacements) -->
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.20.1" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0-preview.5.25277.114" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0-preview.5.25277.114" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.5" />
<!-- Deprecated but kept for compatibility (review in follow-up) -->
<PackageReference Include="Microsoft.IdentityModel.Abstractions" Version="8.17.0" />
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="5.3.0" />
<PackageReference Include="Microsoft.IdentityModel.Logging" Version="8.17.0" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.17.0" />
</ItemGroup>
</Project>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectView>ShowAllFiles</ProjectView>
</PropertyGroup>
</Project>
-28
View File
@@ -1,28 +0,0 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>MFR_RESTClient</name>
</assembly>
<members>
<member name="P:MFR_RESTClient.MFRClient.HideCustomExceptions">
<summary>
Get or set a value indicating the exceptions thrown by the client should be hidden.
</summary>
</member>
<member name="M:MFR_RESTClient.MFRClient.#ctor(MFR_RESTClient.MFRClientConfig,MFR_RESTClient.MFRClientCredentials,Microsoft.Extensions.Logging.ILogger{MFR_RESTClient.MFRClient})">
<summary>
Construct with typed config and credentials.
</summary>
</member>
<member name="M:MFR_RESTClient.MFRClient.Execute(RestSharp.RestRequest,System.String,System.Boolean)">
<summary>
Executes a query against MFR.
</summary>
</member>
<member name="M:MFR_RESTClient.HtmlCleanUp.Clean(System.String)">
<summary>
Cleanse HTML tags and other detritus from the string to make it plaintext.
</summary>
</member>
</members>
</doc>
-121
View File
@@ -1,121 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="MFR_RESTClient.My.MySettings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<appSettings>
<add key="ClientSettingsProvider.ServiceUri" value="" />
</appSettings>
<system.web>
<membership defaultProvider="ClientAuthenticationMembershipProvider">
<providers>
<add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" />
</providers>
</membership>
<roleManager defaultProvider="ClientRoleProvider" enabled="true">
<providers>
<add name="ClientRoleProvider" type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" cacheTimeout="86400" />
</providers>
</roleManager>
</system.web>
<userSettings>
<MFR_RESTClient.My.MySettings>
</MFR_RESTClient.My.MySettings>
</userSettings>
<system.serviceModel>
<extensions>
<!-- In this extension section we are introducing all known service bus extensions. User can remove the ones they don't need. -->
<behaviorExtensions>
<add name="connectionStatusBehavior" type="Microsoft.ServiceBus.Configuration.ConnectionStatusElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="transportClientEndpointBehavior" type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="serviceRegistrySettings" type="Microsoft.ServiceBus.Configuration.ServiceRegistrySettingsElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</behaviorExtensions>
<bindingElementExtensions>
<add name="netMessagingTransport" type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingTransportExtensionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="tcpRelayTransport" type="Microsoft.ServiceBus.Configuration.TcpRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="httpRelayTransport" type="Microsoft.ServiceBus.Configuration.HttpRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="httpsRelayTransport" type="Microsoft.ServiceBus.Configuration.HttpsRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="onewayRelayTransport" type="Microsoft.ServiceBus.Configuration.RelayedOnewayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</bindingElementExtensions>
<bindingExtensions>
<add name="basicHttpRelayBinding" type="Microsoft.ServiceBus.Configuration.BasicHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="webHttpRelayBinding" type="Microsoft.ServiceBus.Configuration.WebHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="ws2007HttpRelayBinding" type="Microsoft.ServiceBus.Configuration.WS2007HttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="netTcpRelayBinding" type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="netOnewayRelayBinding" type="Microsoft.ServiceBus.Configuration.NetOnewayRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="netEventRelayBinding" type="Microsoft.ServiceBus.Configuration.NetEventRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="netMessagingBinding" type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</bindingExtensions>
</extensions>
</system.serviceModel>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Azure.Services.AppAuthentication" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.6.2.0" newVersion="1.6.2.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-7.0.2.0" newVersion="7.0.2.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.IdentityModel.Clients.ActiveDirectory" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.3.0.0" newVersion="5.3.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.4.0" newVersion="4.1.4.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.IdentityModel.Tokens" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-7.0.2.0" newVersion="7.0.2.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.IdentityModel.Logging" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.11.0.0" newVersion="6.11.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.IdentityModel.JsonWebTokens" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.11.0.0" newVersion="6.11.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-7.0.0.3" newVersion="7.0.0.3" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Bcl.AsyncInterfaces" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Text.Encodings.Web" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>