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); } } }