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