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
{
///
/// Тонкий fluent-builder HTTP-вызова поверх — порт
/// RiskProf.Helpers.FluentHttpClient. Стиль вызова:
/// uri.PrepareHttpPost().WithSecurityKey(..).WithParameter(..)
/// .ExecuteRequestJsonResultModel().JsonDeserialize<T>().
///
/// POST-параметры () уходят JSON-объектом (контроллеры на [FromBody]).
/// Два способа получить ответ:
///
/// - — разворачивает конверт
/// : Header.IsSuccess=false → исключение с Header.Message,
/// иначе наружу отдаётся JSON поля Result.
/// - — отдаёт СЫРОЕ тело без разворота конверта.
///
///
///
public class FluentHttpClient
{
private readonly Uri _requestUrl;
private readonly HttpMethod _httpMethod;
private readonly List> _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;
}
/// Ключ межсервисного доступа (заголовок X-Requested-Token).
public FluentHttpClient WithSecurityKey(string securityKey)
{
_securityKey = securityKey;
return this;
}
/// Параметр запроса: для GET — query-строка, для POST — поле JSON-тела.
public FluentHttpClient WithParameter(string name, TValue value)
{
_parameters.Add(new KeyValuePair(name, value?.ToString() ?? string.Empty));
return this;
}
/// Готовое JSON-тело POST (приоритетнее набора ).
public FluentHttpClient WithJsonContent(TObject obj)
{
if (_httpMethod != HttpMethod.Post)
throw new ArgumentOutOfRangeException(nameof(obj), "JSON content only supported for POST");
_jsonContent = obj.JsonSerialize();
return this;
}
/// Отправить запрос и вернуть JSON поля Result развёрнутого конверта
/// (Header.IsSuccess=false → исключение). Для эндпоинтов,
/// помеченных JsonResultModelWrapFilter (отдают конверт).
public Task ExecuteRequestJsonResultModel()
{
return HttpHelper.ExecuteRequest(BuildRequest(), Unwrap, errorHandler: null, securityKey: _securityKey);
}
/// Отправить запрос и вернуть СЫРОЕ тело ответа БЕЗ разворота конверта. Для эндпоинтов,
/// которые не проходят через JsonResultModelWrapFilter и отдают значение напрямую (без
/// ). Бизнес-ошибки тут не распознаются — на не-2xx
/// бросит HttpRequestException.
public Task ExecuteRequest()
{
return HttpHelper.ExecuteRequest(BuildRequest(), responseHandler: null, errorHandler: null, securityKey: _securityKey);
}
// Сборка HTTP-запроса (общая для обёрнутого и сырого вариантов).
private Func> 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 Unwrap(HttpResponseMessage response, string body)
{
JsonResultModel envelope = body.JsonDeserialize>();
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> parameters)
{
var obj = new JObject();
foreach (KeyValuePair p in parameters) obj[p.Key] = p.Value;
return obj.ToString(Formatting.None);
}
private static Uri AppendQuery(Uri uri, List> 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;
}
}
/// Точки входа в fluent-цепочку (как RiskProf.Helpers.FluentHttpClient_Extensions).
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);
}
}
}