150 lines
7.3 KiB
C#
150 lines
7.3 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Net.Http;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using Kit.Helpers.JsonResult;
|
||
using Newtonsoft.Json;
|
||
using Newtonsoft.Json.Linq;
|
||
|
||
namespace Kit.Helpers
|
||
{
|
||
/// <summary>
|
||
/// Тонкий fluent-builder HTTP-вызова поверх <see cref="HttpHelper"/> — порт
|
||
/// <c>RiskProf.Helpers.FluentHttpClient</c>. Стиль вызова:
|
||
/// <c>uri.PrepareHttpPost().WithSecurityKey(..).WithParameter(..)
|
||
/// .ExecuteRequestJsonResultModel().JsonDeserialize<T>()</c>.
|
||
/// <para>
|
||
/// POST-параметры (<see cref="WithParameter"/>) уходят JSON-объектом (контроллеры на <c>[FromBody]</c>).
|
||
/// Два способа получить ответ:
|
||
/// <list type="bullet">
|
||
/// <item><see cref="ExecuteRequestJsonResultModel"/> — разворачивает конверт
|
||
/// <see cref="JsonResultModel{T}"/>: <c>Header.IsSuccess=false</c> → исключение с <c>Header.Message</c>,
|
||
/// иначе наружу отдаётся JSON поля <c>Result</c>.</item>
|
||
/// <item><see cref="ExecuteRequest"/> — отдаёт СЫРОЕ тело без разворота конверта.</item>
|
||
/// </list>
|
||
/// </para>
|
||
/// </summary>
|
||
public class FluentHttpClient
|
||
{
|
||
private readonly Uri _requestUrl;
|
||
private readonly HttpMethod _httpMethod;
|
||
private readonly List<KeyValuePair<string, string>> _parameters = new();
|
||
private string _securityKey;
|
||
private string _jsonContent;
|
||
|
||
public FluentHttpClient(Uri requestUrl, HttpMethod httpMethod)
|
||
{
|
||
_requestUrl = requestUrl;
|
||
if (httpMethod != HttpMethod.Get && httpMethod != HttpMethod.Post)
|
||
throw new ArgumentOutOfRangeException(nameof(httpMethod), "only POST and GET methods supported");
|
||
_httpMethod = httpMethod;
|
||
}
|
||
|
||
/// <summary>Ключ межсервисного доступа (заголовок <c>X-Requested-Token</c>).</summary>
|
||
public FluentHttpClient WithSecurityKey(string securityKey)
|
||
{
|
||
_securityKey = securityKey;
|
||
return this;
|
||
}
|
||
|
||
/// <summary>Параметр запроса: для GET — query-строка, для POST — поле JSON-тела.</summary>
|
||
public FluentHttpClient WithParameter<TValue>(string name, TValue value)
|
||
{
|
||
_parameters.Add(new KeyValuePair<string, string>(name, value?.ToString() ?? string.Empty));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>Готовое JSON-тело POST (приоритетнее набора <see cref="WithParameter"/>).</summary>
|
||
public FluentHttpClient WithJsonContent<TObject>(TObject obj)
|
||
{
|
||
if (_httpMethod != HttpMethod.Post)
|
||
throw new ArgumentOutOfRangeException(nameof(obj), "JSON content only supported for POST");
|
||
_jsonContent = obj.JsonSerialize();
|
||
return this;
|
||
}
|
||
|
||
/// <summary>Отправить запрос и вернуть JSON поля <c>Result</c> развёрнутого конверта
|
||
/// <see cref="JsonResultModel{T}"/> (<c>Header.IsSuccess=false</c> → исключение). Для эндпоинтов,
|
||
/// помеченных <c>JsonResultModelWrapFilter</c> (отдают конверт).</summary>
|
||
public Task<string> ExecuteRequestJsonResultModel()
|
||
{
|
||
return HttpHelper.ExecuteRequest(BuildRequest(), Unwrap, errorHandler: null, securityKey: _securityKey);
|
||
}
|
||
|
||
/// <summary>Отправить запрос и вернуть СЫРОЕ тело ответа БЕЗ разворота конверта. Для эндпоинтов,
|
||
/// которые не проходят через <c>JsonResultModelWrapFilter</c> и отдают значение напрямую (без
|
||
/// <see cref="JsonResultModel{T}"/>). Бизнес-ошибки тут не распознаются — на не-2xx
|
||
/// <see cref="HttpHelper"/> бросит <c>HttpRequestException</c>.</summary>
|
||
public Task<string> ExecuteRequest()
|
||
{
|
||
return HttpHelper.ExecuteRequest(BuildRequest(), responseHandler: null, errorHandler: null, securityKey: _securityKey);
|
||
}
|
||
|
||
// Сборка HTTP-запроса (общая для обёрнутого и сырого вариантов).
|
||
private Func<HttpClient, Task<HttpResponseMessage>> BuildRequest()
|
||
{
|
||
if (_httpMethod == HttpMethod.Get)
|
||
{
|
||
Uri uri = AppendQuery(_requestUrl, _parameters);
|
||
return client => client.GetAsync(uri);
|
||
}
|
||
|
||
string json = _jsonContent ?? ParametersToJson(_parameters);
|
||
return client => client.PostAsync(_requestUrl,
|
||
new StringContent(json, Encoding.UTF8, "application/json"));
|
||
}
|
||
|
||
// ---- разворот конверта JsonResultModel: Header.IsSuccess=false → исключение, иначе поле Body ----
|
||
private static Task<string> Unwrap(HttpResponseMessage response, string body)
|
||
{
|
||
JsonResultModel<JToken> envelope = body.JsonDeserialize<JsonResultModel<JToken>>();
|
||
|
||
JsonResultHeader header = envelope?.Header;
|
||
if (header == null || !header.IsSuccess)
|
||
throw new InvalidOperationException(
|
||
string.IsNullOrWhiteSpace(header?.Message) ? "сервис вернул ошибку" : header.Message);
|
||
|
||
// SerializeObject возвращает валидный JSON и для примитивов ("token"/true/5), и для объектов.
|
||
return Task.FromResult(envelope.Body == null ? "null" : JsonConvert.SerializeObject(envelope.Body));
|
||
}
|
||
|
||
// POST-параметры → JSON-объект (контроллеры принимают [FromBody]).
|
||
private static string ParametersToJson(IEnumerable<KeyValuePair<string, string>> parameters)
|
||
{
|
||
var obj = new JObject();
|
||
foreach (KeyValuePair<string, string> p in parameters) obj[p.Key] = p.Value;
|
||
return obj.ToString(Formatting.None);
|
||
}
|
||
|
||
private static Uri AppendQuery(Uri uri, List<KeyValuePair<string, string>> parameters)
|
||
{
|
||
if (parameters.Count == 0) return uri;
|
||
|
||
string query = string.Join("&", parameters.Select(p =>
|
||
$"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value ?? string.Empty)}"));
|
||
|
||
var builder = new UriBuilder(uri);
|
||
builder.Query = string.IsNullOrEmpty(builder.Query)
|
||
? query
|
||
: builder.Query.TrimStart('?') + "&" + query;
|
||
return builder.Uri;
|
||
}
|
||
}
|
||
|
||
/// <summary>Точки входа в fluent-цепочку (как <c>RiskProf.Helpers.FluentHttpClient_Extensions</c>).</summary>
|
||
public static class FluentHttpClientExtensions
|
||
{
|
||
public static FluentHttpClient PrepareHttpGet(this Uri requestUri)
|
||
{
|
||
return new FluentHttpClient(requestUri, HttpMethod.Get);
|
||
}
|
||
|
||
public static FluentHttpClient PrepareHttpPost(this Uri requestUri)
|
||
{
|
||
return new FluentHttpClient(requestUri, HttpMethod.Post);
|
||
}
|
||
}
|
||
}
|