【问题标题】:In C#, using a loan pattern, TryAsync, and RestClient.ExecuteAsync(), how can I get the system to wait for the result of the second callout?在 C# 中,使用贷款模式、TryAsync 和 RestClient.ExecuteAsync(),如何让系统等待第二次标注的结果?
【发布时间】:2022-01-26 00:21:31
【问题描述】:

我目前正在重构一个使用 RestSharp 的 RestClient 调用 Personio 的微服务,以便使用最新版本的 RestSharp (v107),以及使用 ExecuteAsync 而不是 Execute

我有以下方法:

    [SuppressMessage("Style", "IDE0053:Use expression body for lambda expressions", Justification = "lambda leave Match ambiguous.")]
    public TryAsync<T> WithAuthorization<T>(Func<Token, Task<T>> doSomething, CancellationToken cancellationToken) =>
        TryAsync(async () =>
        {
            T? result = default;
            Exception? resultException = null;
            TryAsync<Token> authorizationAttempt = TryAuthorize(cancellationToken);
            _ = await apply(token => doSomething(token), authorizationAttempt)
                .Match(
                    Succ: async dataTask =>
                    {
                        result = await dataTask;
                    },
                    Fail: exception =>
                    {
                        resultException = exception;
                    }
                )
                .ConfigureAwait(false);

            // FIXME:  Does not wait!!!!

            return result
                ?? ((resultException == null)
                    ? throw new PersonioRequestException("Could not get data from Personio: Reason unknown.")
                    : throw new PersonioRequestException($"Could not get data from Personio: {resultException.Message}", resultException)
                );
        });

如上面的代码所示,该方法在返回结果之前不会等待,或者碰巧抛出原因未知的异常。

使用调试器,我已经能够确定authorizationAttempt 获得了一个值并调用了doSomething(),但是在等待响应时,抛出了错误。

使用上述方法,为doSomething()提供函数的代码是这样的:

public TryAsync<RequestResponse<T>> TryGet<T>(RequestOptions options, CancellationToken cancellationToken) =>
        _authorizationClient.WithAuthorization<RequestResponse<T>>(
            async token =>
                {
                    UriBuilder urlBuilder = new(_personioConfig.BaseUrl.AppendPathSegment(options.Endpoint))
                    {
                        // This is used to add parameters which are used for filtering and may be unique for the record type, such as "updated_from".
                        Query = options.QueryParameters.ToString()
                    };

                    RestRequest request = new(urlBuilder.Uri, Method.Get);
                    request.Timeout = -1;
                    if (options.Pagination.IsActive)
                    {
                        request = request.AddQueryParameters(options.Pagination);
                    }

                    request = request
                        .AddHeader("Accept", "application/json")
                        .AddHeader("Authorization", $"Bearer {token.Value}");

                    return await GetRecords<T>(request, cancellationToken);
                },
                cancellationToken
            );

    private async Task<RequestResponse<T>> GetRecords<T>(RestRequest request, CancellationToken cancellationToken)
    {
        RestResponse<RequestResponse<T>> requestResponse = await _restClient.ExecuteAsync<RequestResponse<T>>(request, cancellationToken);

        // FIXME: The next line is never executed.

        RequestResponse<T>? dataResponse = JsonConvert.DeserializeObject<RequestResponse<T>>(requestResponse?.Content ?? "");
        return (requestResponse?.IsSuccessful ?? false)
            ? (dataResponse != null && dataResponse.WasSuccessful)
                ? dataResponse
                : throw new PersonioRequestException("Connected to Personio, but could not get records.")
            : throw (
                (requestResponse?.ErrorException != null)
                    ? new("Could not get records from Personio.", requestResponse.ErrorException)
                    : new($"Could not get records from Personio. {dataResponse?.Error?.Message ?? UnknownProblem}."));
    }

如上面的代码所示,方法 GetRecords() 被调用,但在 ExecuteAsync() 有任何结果之前,result(回到上面的第一个方法)未填充,系统会抛出一个错误。

代码的早期形式,带有早期版本的 RestSharp(v106) 和同步执行工作正常。当时的TryGet是这样的:

    public TryAsync<RequestResponse<T>> TryGet<T>(RequestOptions options) =>
        _authorizationClient.WithAuthorization<RequestResponse<T>>(token =>
        {
            UriBuilder urlBuilder = new(_personioConfig.BaseUrl.AppendPathSegment(options.Endpoint))
            {
                // This is used to add parameters which are used for filtering and may be unique for the record type, such as "updated_from".
                Query = options.QueryParameters.ToString()
            };
            _restClient.BaseUrl = urlBuilder.Uri;
            _restClient.Timeout = -1;

            IRestRequest request = new RestRequest(Method.GET);
            if (options.Pagination.IsActive)
            {
                request = request.AddQueryParameters(options.Pagination);
            }

            request = request
                .AddHeader("Accept", "application/json")
                .AddHeader("Authorization", $"Bearer {token.Value}");

            return Task.FromResult(GetRecords<T>(request));
        });

    private RequestResponse<T> GetRecords<T>(IRestRequest request)
    {
        IRestResponse<RequestResponse<T>> requestResponse = _restClient.Execute<RequestResponse<T>>(request);
        RequestResponse<T>? dataResponse = JsonConvert.DeserializeObject<RequestResponse<T>>(requestResponse.Content);
        return requestResponse.IsSuccessful
            ? (dataResponse != null && dataResponse.WasSuccessful)
                ? dataResponse
                : throw new PersonioRequestException("Connected to Personio, but could not get records.")
            : throw (
                (requestResponse.ErrorException != null)
                    ? new("Could not get records from Personio.", requestResponse.ErrorException)
                    : new($"Could not get records from Personio. {dataResponse?.Error?.Message ?? UnknownProblem}."));
    }

我做错了什么或错过了什么? 如何使用 RestSharp 107.x 和 ExecuteAsync() 完成这项工作?

【问题讨论】:

  • 如果你想等待,那么你需要await它。 TrayAsync&lt;T&gt; 是什么?它是Task 的一些自定义实现吗?
  • 这看起来非常令人费解,我会尝试将其重构为更直接... TryAsync,Match,apply - 不清楚这些中的任何一个做什么,或者为什么需要它们,它们似乎只是让事情复杂化
  • 我会用自定义身份验证器替换 WithAuthorization。我在这里有一个例子restsharp.dev/usage.html#authenticator。您真的不想一直访问身份验证端点。此外,使用 Polly 处理重试将大大简化您的代码。最后,您提到您的代码会引发异常,但您从未提及异常是什么。
  • 好的,我明白了。但是话又说回来,我的验证器示例可以很容易地更改为每次都请求令牌。您需要删除存储的令牌属性,并且最好保留用于身份验证调用的 RestClient 实例,而不是将其包装在 using 中。
  • 顺便说一句,Accept 标头包含 application/json 无论如何,您不需要明确设置它。另外,在调用restClient.ExecuteAsync&lt;RequestResponse&lt;T&gt;&gt;(request)时,response.Data会直接给你反序列化的值,你不需要再次手动反序列化。

标签: c# async-await restsharp language-ext loan-pattern


【解决方案1】:

感谢@AlexeyZimarev,我能够让 TryGet() 方法正常工作。

现在看起来像:

    public TryAsync<RequestResponse<T>> TryGet<T>(RequestOptions options, CancellationToken cancellationToken) =>
        TryAsync(async () =>
        {
            _restClient.Authenticator = _authorizationClient;
            Uri uri = new UriBuilder(_personioConfig.BaseUrl.AppendPathSegment(options.Endpoint)).Uri;
            RestRequest request = new RestRequest(uri, Method.Get)
               .AddQueryParameters(options.QueryParameters);

            if (options.Pagination.IsActive)
            {
                request = request.AddQueryParameters(options.Pagination);
            }

            request.Timeout = -1;
            return await GetRecords<T>(request, cancellationToken);
        });

_restClient.Authenticator = _authorizationClient; 行在这里不是很安全,因为它被分配在注入而不是 RestClient 上,将来可能会在其他地方使用,但我通过使客户端瞬态而不是单例来“解决”这个问题.

我还从 PersonioAuthorizationClient 中删除了所有 TryAsync,因为无论 AuthenticatorBase 正在做什么,它似乎都不能很好地发挥作用。

【讨论】:

  • 如果你使用RestClient作为一个临时依赖,你需要给它一个外部的HttpClient实例,它是由Http客户端工厂使用处理程序池实例化的,但你可能是知道。
  • @AlexeyZimarev,你能详细说明一下吗?我刚刚使用了services.AddTransient&lt;RestClient&gt;() 之类的东西,它似乎按预期工作,尽管我怀疑它不是最理想的。
  • 如果你继续实例化RestClient,它也会每次创建一个HttpClientHttpClientHandler。检查此docs.microsoft.com/en-us/dotnet/architecture/microservices/…。我会说你需要做类似services.AddHttpClient("Personio"); services.AddTransient(sp =&gt; new RestClient(sp.GetRequiredService&lt;IHttpClientFactory&gt;().CreateClient("Personio")))
  • 有道理,谢谢!
猜你喜欢
  • 2015-10-28
  • 1970-01-01
  • 2018-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-19
相关资源
最近更新 更多